001    /*
002     * Copyright 2010-2015 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.kotlin.psi;
018    
019    import com.google.common.base.Function;
020    import com.google.common.base.Predicate;
021    import com.google.common.collect.Lists;
022    import com.intellij.lang.ASTNode;
023    import com.intellij.psi.PsiComment;
024    import com.intellij.psi.PsiElement;
025    import com.intellij.psi.PsiFile;
026    import com.intellij.psi.PsiWhiteSpace;
027    import com.intellij.psi.tree.IElementType;
028    import com.intellij.psi.util.PsiTreeUtil;
029    import com.intellij.util.codeInsight.CommentUtilCore;
030    import com.intellij.util.containers.ContainerUtil;
031    import org.jetbrains.annotations.Contract;
032    import org.jetbrains.annotations.NotNull;
033    import org.jetbrains.annotations.Nullable;
034    import org.jetbrains.kotlin.JetNodeTypes;
035    import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
036    import org.jetbrains.kotlin.kdoc.psi.api.KDocElement;
037    import org.jetbrains.kotlin.lexer.JetToken;
038    import org.jetbrains.kotlin.lexer.JetTokens;
039    import org.jetbrains.kotlin.name.FqName;
040    import org.jetbrains.kotlin.name.Name;
041    import org.jetbrains.kotlin.name.SpecialNames;
042    import org.jetbrains.kotlin.parsing.JetExpressionParsing;
043    import org.jetbrains.kotlin.psi.psiUtil.PsiUtilPackage;
044    import org.jetbrains.kotlin.types.expressions.OperatorConventions;
045    
046    import java.util.Collection;
047    import java.util.HashSet;
048    import java.util.List;
049    import java.util.Set;
050    
051    public class JetPsiUtil {
052        private JetPsiUtil() {
053        }
054    
055        public interface JetExpressionWrapper {
056            JetExpression getBaseExpression();
057        }
058    
059        public static <D> void visitChildren(@NotNull JetElement element, @NotNull JetVisitor<Void, D> visitor, D data) {
060            PsiElement child = element.getFirstChild();
061            while (child != null) {
062                if (child instanceof JetElement) {
063                    ((JetElement) child).accept(visitor, data);
064                }
065                child = child.getNextSibling();
066            }
067        }
068    
069        @NotNull
070        public static JetExpression safeDeparenthesize(@NotNull JetExpression expression) {
071            return safeDeparenthesize(expression, true);
072        }
073    
074        @NotNull
075        public static JetExpression safeDeparenthesize(@NotNull JetExpression expression, boolean deparenthesizeBinaryExpressionWithTypeRHS) {
076            JetExpression deparenthesized = deparenthesize(expression, deparenthesizeBinaryExpressionWithTypeRHS);
077            return deparenthesized != null ? deparenthesized : expression;
078        }
079    
080        @Nullable
081        public static JetExpression deparenthesize(@Nullable JetExpression expression) {
082            return deparenthesize(expression, /* deparenthesizeBinaryExpressionWithTypeRHS = */ true);
083        }
084    
085        @Nullable
086        public static JetExpression deparenthesize(
087                @Nullable JetExpression expression,
088                boolean deparenthesizeBinaryExpressionWithTypeRHS
089        ) {
090            return deparenthesizeWithResolutionStrategy(
091                    expression, deparenthesizeBinaryExpressionWithTypeRHS, /* deparenthesizeRecursively = */  null);
092        }
093    
094        @Nullable
095        public static JetExpression deparenthesizeOnce(
096                @Nullable JetExpression expression,
097                boolean deparenthesizeBinaryExpressionWithTypeRHS
098        ) {
099            return deparenthesizeOnce(expression, deparenthesizeBinaryExpressionWithTypeRHS, null);
100        }
101    
102        @Nullable
103        public static JetExpression deparenthesizeWithResolutionStrategy(
104                @Nullable JetExpression expression,
105                @Nullable Function<JetTypeReference, Void> typeResolutionStrategy
106        ) {
107            return deparenthesizeWithResolutionStrategy(expression, true, typeResolutionStrategy);
108        }
109    
110        @Nullable
111        private static JetExpression deparenthesizeWithResolutionStrategy(
112                @Nullable JetExpression expression,
113                boolean deparenthesizeBinaryExpressionWithTypeRHS,
114                @Nullable Function<JetTypeReference, Void> typeResolutionStrategy
115        ) {
116            while (true) {
117                JetExpression baseExpression =
118                        deparenthesizeOnce(expression, deparenthesizeBinaryExpressionWithTypeRHS, typeResolutionStrategy);
119    
120                if (baseExpression == expression) return baseExpression;
121                expression = baseExpression;
122            }
123        }
124    
125        @Nullable
126        private static JetExpression deparenthesizeOnce(
127                @Nullable JetExpression expression,
128                boolean deparenthesizeBinaryExpressionWithTypeRHS,
129                @Nullable Function<JetTypeReference, Void> typeResolutionStrategy
130        ) {
131            if (deparenthesizeBinaryExpressionWithTypeRHS && expression instanceof JetBinaryExpressionWithTypeRHS) {
132                JetBinaryExpressionWithTypeRHS binaryExpression = (JetBinaryExpressionWithTypeRHS) expression;
133                JetSimpleNameExpression operationSign = binaryExpression.getOperationReference();
134                if (JetTokens.COLON.equals(operationSign.getReferencedNameElementType())) {
135                    JetTypeReference typeReference = binaryExpression.getRight();
136                    if (typeResolutionStrategy != null && typeReference != null) {
137                        typeResolutionStrategy.apply(typeReference);
138                    }
139                    return binaryExpression.getLeft();
140                }
141                return expression;
142            }
143            else if (expression instanceof JetAnnotatedExpression) {
144                return ((JetAnnotatedExpression) expression).getBaseExpression();
145            }
146            else if (expression instanceof JetLabeledExpression) {
147                return ((JetLabeledExpression) expression).getBaseExpression();
148            }
149            else if (expression instanceof JetExpressionWrapper) {
150                return ((JetExpressionWrapper) expression).getBaseExpression();
151            }
152            else if (expression instanceof JetParenthesizedExpression) {
153                return ((JetParenthesizedExpression) expression).getExpression();
154            }
155            return expression;
156        }
157    
158        @NotNull
159        public static Name safeName(@Nullable String name) {
160            return name == null ? SpecialNames.NO_NAME_PROVIDED : Name.identifier(name);
161        }
162    
163        @NotNull
164        public static Set<JetElement> findRootExpressions(@NotNull Collection<JetElement> unreachableElements) {
165            Set<JetElement> rootElements = new HashSet<JetElement>();
166            final Set<JetElement> shadowedElements = new HashSet<JetElement>();
167            JetVisitorVoid shadowAllChildren = new JetVisitorVoid() {
168                @Override
169                public void visitJetElement(@NotNull JetElement element) {
170                    if (shadowedElements.add(element)) {
171                        element.acceptChildren(this);
172                    }
173                }
174            };
175    
176            for (JetElement element : unreachableElements) {
177                if (shadowedElements.contains(element)) continue;
178                element.acceptChildren(shadowAllChildren);
179    
180                rootElements.removeAll(shadowedElements);
181                rootElements.add(element);
182            }
183            return rootElements;
184        }
185    
186        @NotNull
187        public static String unquoteIdentifier(@NotNull String quoted) {
188            if (quoted.indexOf('`') < 0) {
189                return quoted;
190            }
191    
192            if (quoted.startsWith("`") && quoted.endsWith("`") && quoted.length() >= 2) {
193                return quoted.substring(1, quoted.length() - 1);
194            }
195            else {
196                return quoted;
197            }
198        }
199    
200        @NotNull
201        public static String unquoteIdentifierOrFieldReference(@NotNull String quoted) {
202            if (quoted.indexOf('`') < 0) {
203                return quoted;
204            }
205    
206            if (quoted.startsWith("$")) {
207                return "$" + unquoteIdentifier(quoted.substring(1));
208            }
209            else {
210                return unquoteIdentifier(quoted);
211            }
212        }
213    
214        /** @return <code>null</code> iff the tye has syntactic errors */
215        @Nullable
216        public static FqName toQualifiedName(@NotNull JetUserType userType) {
217            List<String> reversedNames = Lists.newArrayList();
218    
219            JetUserType current = userType;
220            while (current != null) {
221                String name = current.getReferencedName();
222                if (name == null) return null;
223    
224                reversedNames.add(name);
225                current = current.getQualifier();
226            }
227    
228            return FqName.fromSegments(ContainerUtil.reverse(reversedNames));
229        }
230    
231        @Nullable
232        public static Name getShortName(@NotNull JetAnnotationEntry annotation) {
233            JetTypeReference typeReference = annotation.getTypeReference();
234            assert typeReference != null : "Annotation entry hasn't typeReference " + annotation.getText();
235            JetTypeElement typeElement = typeReference.getTypeElement();
236            if (typeElement instanceof JetUserType) {
237                JetUserType userType = (JetUserType) typeElement;
238                String shortName = userType.getReferencedName();
239                if (shortName != null) {
240                    return Name.identifier(shortName);
241                }
242            }
243            return null;
244        }
245    
246        public static boolean isDeprecated(@NotNull JetModifierListOwner owner) {
247            JetModifierList modifierList = owner.getModifierList();
248            if (modifierList != null) {
249                List<JetAnnotationEntry> annotationEntries = modifierList.getAnnotationEntries();
250                for (JetAnnotationEntry annotation : annotationEntries) {
251                    Name shortName = getShortName(annotation);
252                    if (KotlinBuiltIns.FQ_NAMES.deprecated.shortName().equals(shortName)) {
253                        return true;
254                    }
255                }
256            }
257            return false;
258        }
259    
260        @Nullable
261        public static <T extends PsiElement> T getDirectParentOfTypeForBlock(@NotNull JetBlockExpression block, @NotNull Class<T> aClass) {
262            T parent = PsiTreeUtil.getParentOfType(block, aClass);
263            if (parent instanceof JetIfExpression) {
264                JetIfExpression ifExpression = (JetIfExpression) parent;
265                if (ifExpression.getElse() == block || ifExpression.getThen() == block) {
266                    return parent;
267                }
268            }
269            if (parent instanceof JetWhenExpression) {
270                JetWhenExpression whenExpression = (JetWhenExpression) parent;
271                for (JetWhenEntry whenEntry : whenExpression.getEntries()) {
272                    if (whenEntry.getExpression() == block) {
273                        return parent;
274                    }
275                }
276            }
277            if (parent instanceof JetFunctionLiteral) {
278                JetFunctionLiteral functionLiteral = (JetFunctionLiteral) parent;
279                if (functionLiteral.getBodyExpression() == block) {
280                    return parent;
281                }
282            }
283            if (parent instanceof JetTryExpression) {
284                JetTryExpression tryExpression = (JetTryExpression) parent;
285                if (tryExpression.getTryBlock() == block) {
286                    return parent;
287                }
288                for (JetCatchClause clause : tryExpression.getCatchClauses()) {
289                    if (clause.getCatchBody() == block) {
290                        return parent;
291                    }
292                }
293            }
294            return null;
295        }
296    
297        @Nullable
298        public static Name getAliasName(@NotNull JetImportDirective importDirective) {
299            String aliasName = importDirective.getAliasName();
300            JetExpression importedReference = importDirective.getImportedReference();
301            if (importedReference == null) {
302                return null;
303            }
304            JetSimpleNameExpression referenceExpression = getLastReference(importedReference);
305            if (aliasName == null) {
306                aliasName = referenceExpression != null ? referenceExpression.getReferencedName() : null;
307            }
308    
309            return aliasName != null && !aliasName.isEmpty() ? Name.identifier(aliasName) : null;
310        }
311    
312        @Nullable
313        public static JetSimpleNameExpression getLastReference(@NotNull JetExpression importedReference) {
314            JetElement selector = PsiUtilPackage.getQualifiedElementSelector(importedReference);
315            return selector instanceof JetSimpleNameExpression ? (JetSimpleNameExpression) selector : null;
316        }
317    
318        public static boolean isSelectorInQualified(@NotNull JetSimpleNameExpression nameExpression) {
319            JetElement qualifiedElement = PsiUtilPackage.getQualifiedElement(nameExpression);
320            return qualifiedElement instanceof JetQualifiedExpression
321                   || ((qualifiedElement instanceof JetUserType) && ((JetUserType) qualifiedElement).getQualifier() != null);
322        }
323    
324        public static boolean isLHSOfDot(@NotNull JetExpression expression) {
325            PsiElement parent = expression.getParent();
326            if (!(parent instanceof JetQualifiedExpression)) return false;
327            JetQualifiedExpression qualifiedParent = (JetQualifiedExpression) parent;
328            return qualifiedParent.getReceiverExpression() == expression || isLHSOfDot(qualifiedParent);
329        }
330    
331        // SCRIPT: is declaration in script?
332        public static boolean isScriptDeclaration(@NotNull JetDeclaration namedDeclaration) {
333            return getScript(namedDeclaration) != null;
334        }
335    
336        // SCRIPT: get script from top-level declaration
337        @Nullable
338        public static JetScript getScript(@NotNull JetDeclaration namedDeclaration) {
339            PsiElement parent = namedDeclaration.getParent();
340            if (parent != null && parent.getParent() instanceof JetScript) {
341                return (JetScript) parent.getParent();
342            }
343            else {
344                return null;
345            }
346        }
347    
348        public static boolean isVariableNotParameterDeclaration(@NotNull JetDeclaration declaration) {
349            if (!(declaration instanceof JetVariableDeclaration)) return false;
350            if (declaration instanceof JetProperty) return true;
351            assert declaration instanceof JetMultiDeclarationEntry;
352            JetMultiDeclarationEntry multiDeclarationEntry = (JetMultiDeclarationEntry) declaration;
353            return !(multiDeclarationEntry.getParent().getParent() instanceof JetForExpression);
354        }
355    
356        @Nullable
357        public static Name getConventionName(@NotNull JetSimpleNameExpression simpleNameExpression) {
358            if (simpleNameExpression.getIdentifier() != null) {
359                return simpleNameExpression.getReferencedNameAsName();
360            }
361    
362            PsiElement firstChild = simpleNameExpression.getFirstChild();
363            if (firstChild != null) {
364                IElementType elementType = firstChild.getNode().getElementType();
365                if (elementType instanceof JetToken) {
366                    JetToken jetToken = (JetToken) elementType;
367                    return OperatorConventions.getNameForOperationSymbol(jetToken);
368                }
369            }
370    
371            return null;
372        }
373    
374        @Nullable
375        @Contract("null, _ -> null")
376        public static PsiElement getTopmostParentOfTypes(@Nullable PsiElement element, @NotNull Class<? extends PsiElement>... parentTypes) {
377            PsiElement answer = PsiTreeUtil.getParentOfType(element, parentTypes);
378    
379            do {
380                PsiElement next = PsiTreeUtil.getParentOfType(answer, parentTypes);
381                if (next == null) break;
382                answer = next;
383            }
384            while (true);
385    
386            return answer;
387        }
388    
389        public static boolean isNullConstant(@NotNull JetExpression expression) {
390            JetExpression deparenthesized = deparenthesize(expression);
391            return deparenthesized instanceof JetConstantExpression && deparenthesized.getNode().getElementType() == JetNodeTypes.NULL;
392        }
393    
394        public static boolean isTrueConstant(@Nullable JetExpression condition) {
395            return (condition != null && condition.getNode().getElementType() == JetNodeTypes.BOOLEAN_CONSTANT &&
396                    condition.getNode().findChildByType(JetTokens.TRUE_KEYWORD) != null);
397        }
398    
399        public static boolean isAbstract(@NotNull JetDeclarationWithBody declaration) {
400            return declaration.getBodyExpression() == null;
401        }
402    
403        public static boolean isBackingFieldReference(@NotNull JetSimpleNameExpression expression) {
404            return expression.getReferencedNameElementType() == JetTokens.FIELD_IDENTIFIER;
405        }
406    
407        public static boolean isBackingFieldReference(@Nullable JetElement element) {
408            return element instanceof JetSimpleNameExpression && isBackingFieldReference((JetSimpleNameExpression)element);
409        }
410    
411        @Nullable
412        public static JetElement getExpressionOrLastStatementInBlock(@Nullable JetExpression expression) {
413            if (expression instanceof JetBlockExpression) {
414                return getLastStatementInABlock((JetBlockExpression) expression);
415            }
416            return expression;
417        }
418    
419        @Nullable
420        public static JetElement getLastStatementInABlock(@Nullable JetBlockExpression blockExpression) {
421            if (blockExpression == null) return null;
422            List<JetElement> statements = blockExpression.getStatements();
423            return statements.isEmpty() ? null : statements.get(statements.size() - 1);
424        }
425    
426        public static boolean isTrait(@NotNull JetClassOrObject classOrObject) {
427            return classOrObject instanceof JetClass && ((JetClass) classOrObject).isInterface();
428        }
429    
430        @Nullable
431        public static JetClassOrObject getOutermostClassOrObject(@NotNull JetClassOrObject classOrObject) {
432            JetClassOrObject current = classOrObject;
433            while (true) {
434                PsiElement parent = current.getParent();
435                assert classOrObject.getParent() != null : "Class with no parent: " + classOrObject.getText();
436    
437                if (parent instanceof PsiFile) {
438                    return current;
439                }
440                if (!(parent instanceof JetClassBody)) {
441                    // It is a local class, no legitimate outer
442                    return current;
443                }
444    
445                current = (JetClassOrObject) parent.getParent();
446            }
447        }
448    
449        @Nullable
450        public static JetClassOrObject getClassIfParameterIsProperty(@NotNull JetParameter jetParameter) {
451            if (jetParameter.hasValOrVarNode()) {
452                PsiElement grandParent = jetParameter.getParent().getParent();
453                if (grandParent instanceof JetPrimaryConstructor) {
454                    return ((JetPrimaryConstructor) grandParent).getContainingClassOrObject();
455                }
456            }
457    
458            return null;
459        }
460    
461        @Nullable
462        private static IElementType getOperation(@NotNull JetExpression expression) {
463            if (expression instanceof JetQualifiedExpression) {
464                return ((JetQualifiedExpression) expression).getOperationSign();
465            }
466            else if (expression instanceof JetOperationExpression) {
467                return ((JetOperationExpression) expression).getOperationReference().getReferencedNameElementType();
468            }
469            return null;
470        }
471    
472    
473        private static int getPriority(@NotNull JetExpression expression) {
474            int maxPriority = JetExpressionParsing.Precedence.values().length + 1;
475    
476            // same as postfix operations
477            if (expression instanceof JetPostfixExpression ||
478                expression instanceof JetQualifiedExpression ||
479                expression instanceof JetCallExpression ||
480                expression instanceof JetArrayAccessExpression) {
481                return maxPriority - 1;
482            }
483    
484            if (expression instanceof JetPrefixExpression || expression instanceof JetLabeledExpression) return maxPriority - 2;
485    
486            if (expression instanceof JetIfExpression) {
487                return JetExpressionParsing.Precedence.ASSIGNMENT.ordinal();
488            }
489    
490            if (expression instanceof JetDeclaration || expression instanceof JetStatementExpression) {
491                return 0;
492            }
493    
494            IElementType operation = getOperation(expression);
495            for (JetExpressionParsing.Precedence precedence : JetExpressionParsing.Precedence.values()) {
496                if (precedence != JetExpressionParsing.Precedence.PREFIX && precedence != JetExpressionParsing.Precedence.POSTFIX &&
497                    precedence.getOperations().contains(operation)) {
498                    return maxPriority - precedence.ordinal() - 1;
499                }
500            }
501    
502            return maxPriority;
503        }
504    
505        public static boolean areParenthesesUseless(@NotNull JetParenthesizedExpression expression) {
506            JetExpression innerExpression = expression.getExpression();
507            if (innerExpression == null) return true;
508    
509            PsiElement parent = expression.getParent();
510            if (!(parent instanceof JetExpression)) return true;
511    
512            return !areParenthesesNecessary(innerExpression, expression, (JetExpression) parent);
513        }
514    
515        public static boolean areParenthesesNecessary(@NotNull JetExpression innerExpression, @NotNull JetExpression currentInner, @NotNull JetExpression parentExpression) {
516            if (parentExpression instanceof JetParenthesizedExpression || innerExpression instanceof JetParenthesizedExpression) {
517                return false;
518            }
519    
520            if (parentExpression instanceof JetPackageDirective) return false;
521    
522            if (parentExpression instanceof JetWhenExpression || innerExpression instanceof JetWhenExpression) {
523                return false;
524            }
525    
526            if (innerExpression instanceof JetIfExpression) {
527                PsiElement current = parentExpression;
528    
529                while (!(current instanceof JetBlockExpression || current instanceof JetDeclaration || current instanceof JetStatementExpression)) {
530                    if (current.getTextRange().getEndOffset() != currentInner.getTextRange().getEndOffset()) {
531                        return current.getText().charAt(current.getTextLength() - 1) != ')'; // if current expression is "guarded" by parenthesis, no extra parenthesis is necessary
532                    }
533    
534                    current = current.getParent();
535                }
536            }
537    
538            IElementType innerOperation = getOperation(innerExpression);
539            IElementType parentOperation = getOperation(parentExpression);
540    
541            // 'return (@label{...})' case
542            if (parentExpression instanceof JetReturnExpression && innerExpression instanceof JetLabeledExpression) {
543                return true;
544            }
545    
546            // '(x: Int) < y' case
547            if (innerExpression instanceof JetBinaryExpressionWithTypeRHS && parentOperation == JetTokens.LT) {
548                return true;
549            }
550    
551            if (parentExpression instanceof JetLabeledExpression) return false;
552    
553            // 'x ?: ...' case
554            if (parentExpression instanceof JetBinaryExpression && parentOperation == JetTokens.ELVIS && currentInner == ((JetBinaryExpression) parentExpression).getRight()) {
555                return false;
556            }
557    
558            int innerPriority = getPriority(innerExpression);
559            int parentPriority = getPriority(parentExpression);
560    
561            if (innerPriority == parentPriority) {
562                if (parentExpression instanceof JetBinaryExpression) {
563                    if (innerOperation == JetTokens.ANDAND || innerOperation == JetTokens.OROR) {
564                        return false;
565                    }
566                    return ((JetBinaryExpression) parentExpression).getRight() == currentInner;
567                }
568    
569                //'-(-x)' case
570                if (parentExpression instanceof JetPrefixExpression && innerExpression instanceof JetPrefixExpression) {
571                    return innerOperation == parentOperation && (innerOperation == JetTokens.PLUS || innerOperation == JetTokens.MINUS);
572                }
573                return false;
574            }
575    
576            return innerPriority < parentPriority;
577        }
578    
579        public static boolean isAssignment(@NotNull PsiElement element) {
580            return element instanceof JetBinaryExpression &&
581                   JetTokens.ALL_ASSIGNMENTS.contains(((JetBinaryExpression) element).getOperationToken());
582        }
583    
584        public static boolean isOrdinaryAssignment(@NotNull PsiElement element) {
585            return element instanceof JetBinaryExpression &&
586                   ((JetBinaryExpression) element).getOperationToken().equals(JetTokens.EQ);
587        }
588    
589        public static boolean checkVariableDeclarationInBlock(@NotNull JetBlockExpression block, @NotNull String varName) {
590            for (JetElement element : block.getStatements()) {
591                if (element instanceof JetVariableDeclaration) {
592                    if (((JetVariableDeclaration) element).getNameAsSafeName().asString().equals(varName)) {
593                        return true;
594                    }
595                }
596            }
597    
598            return false;
599        }
600    
601        public static boolean checkWhenExpressionHasSingleElse(@NotNull JetWhenExpression whenExpression) {
602            int elseCount = 0;
603            for (JetWhenEntry entry : whenExpression.getEntries()) {
604                if (entry.isElse()) {
605                    elseCount++;
606                }
607            }
608            return (elseCount == 1);
609        }
610    
611        @Nullable
612        public static PsiElement skipTrailingWhitespacesAndComments(@Nullable PsiElement element)  {
613            return PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace.class, PsiComment.class);
614        }
615    
616        public static final Predicate<JetElement> ANY_JET_ELEMENT = new Predicate<JetElement>() {
617            @Override
618            public boolean apply(@Nullable JetElement input) {
619                return true;
620            }
621        };
622    
623        @NotNull
624        public static String getText(@Nullable PsiElement element) {
625            return element != null ? element.getText() : "";
626        }
627    
628        @Nullable
629        public static String getNullableText(@Nullable PsiElement element) {
630            return element != null ? element.getText() : null;
631        }
632    
633        /**
634         * CommentUtilCore.isComment fails if element <strong>inside</strong> comment.
635         *
636         * Also, we can not add KDocTokens to COMMENTS TokenSet, because it is used in JetParserDefinition.getCommentTokens(),
637         * and therefor all COMMENTS tokens will be ignored by PsiBuilder.
638         *
639         * @param element
640         * @return
641         */
642        public static boolean isInComment(PsiElement element) {
643            return CommentUtilCore.isComment(element) || element instanceof KDocElement;
644        }
645    
646        @Nullable
647        public static PsiElement getOutermostParent(@NotNull PsiElement element, @NotNull PsiElement upperBound, boolean strict) {
648            PsiElement parent = strict ? element.getParent() : element;
649            while (parent != null && parent.getParent() != upperBound) {
650                parent = parent.getParent();
651            }
652    
653            return parent;
654        }
655    
656        public static <T extends PsiElement> T getLastChildByType(@NotNull PsiElement root, @NotNull Class<? extends T>... elementTypes) {
657            PsiElement[] children = root.getChildren();
658    
659            for (int i = children.length - 1; i >= 0; i--) {
660                if (PsiTreeUtil.instanceOf(children[i], elementTypes)) {
661                    //noinspection unchecked
662                    return (T) children[i];
663                }
664            }
665    
666            return null;
667        }
668    
669        @Nullable
670        public static JetElement getOutermostDescendantElement(
671                @Nullable PsiElement root,
672                boolean first,
673                final @NotNull Predicate<JetElement> predicate
674        ) {
675            if (!(root instanceof JetElement)) return null;
676    
677            final List<JetElement> results = Lists.newArrayList();
678    
679            root.accept(
680                    new JetVisitorVoid() {
681                        @Override
682                        public void visitJetElement(@NotNull JetElement element) {
683                            if (predicate.apply(element)) {
684                                //noinspection unchecked
685                                results.add(element);
686                            }
687                            else {
688                                element.acceptChildren(this);
689                            }
690                        }
691                    }
692            );
693    
694            if (results.isEmpty()) return null;
695    
696            return first ? results.get(0) : results.get(results.size() - 1);
697        }
698    
699        @Nullable
700        public static PsiElement findChildByType(@NotNull PsiElement element, @NotNull IElementType type) {
701            ASTNode node = element.getNode().findChildByType(type);
702            return node == null ? null : node.getPsi();
703        }
704    
705        @Nullable
706        public static PsiElement skipSiblingsBackwardByPredicate(@Nullable PsiElement element, Predicate<PsiElement> elementsToSkip) {
707            if (element == null) return null;
708            for (PsiElement e = element.getPrevSibling(); e != null; e = e.getPrevSibling()) {
709                if (elementsToSkip.apply(e)) continue;
710                return e;
711            }
712            return null;
713        }
714    
715        public static PsiElement ascendIfPropertyAccessor(PsiElement element) {
716            if (element instanceof JetPropertyAccessor) {
717                return element.getParent();
718            }
719            return element;
720        }
721    
722        @Nullable
723        public static JetModifierList replaceModifierList(@NotNull JetModifierListOwner owner, @Nullable JetModifierList modifierList) {
724            JetModifierList oldModifierList = owner.getModifierList();
725            if (modifierList == null) {
726                if (oldModifierList != null) oldModifierList.delete();
727                return null;
728            }
729            else {
730                if (oldModifierList == null) {
731                    PsiElement firstChild = owner.getFirstChild();
732                    return (JetModifierList) owner.addBefore(modifierList, firstChild);
733                }
734                else {
735                    return (JetModifierList) oldModifierList.replace(modifierList);
736                }
737            }
738        }
739    
740        @Nullable
741        public static String getPackageName(@NotNull JetElement element) {
742            JetFile file = element.getContainingJetFile();
743            JetPackageDirective header = PsiTreeUtil.findChildOfType(file, JetPackageDirective.class);
744    
745            return header != null ? header.getQualifiedName() : null;
746        }
747    
748        @Nullable
749        public static JetElement getEnclosingElementForLocalDeclaration(@NotNull JetDeclaration declaration) {
750            return getEnclosingElementForLocalDeclaration(declaration, true);
751        }
752    
753        private static boolean isMemberOfObjectExpression(@NotNull JetCallableDeclaration propertyOrFunction) {
754            PsiElement parent = PsiTreeUtil.getStubOrPsiParent(propertyOrFunction);
755            if (!(parent instanceof JetClassBody)) return false;
756            PsiElement grandparent = PsiTreeUtil.getStubOrPsiParent(parent);
757            if (!(grandparent instanceof JetObjectDeclaration)) return false;
758            return PsiTreeUtil.getStubOrPsiParent(grandparent) instanceof JetObjectLiteralExpression;
759        }
760    
761        @Nullable
762        public static JetElement getEnclosingElementForLocalDeclaration(@NotNull JetDeclaration declaration, boolean skipParameters) {
763            if (declaration instanceof JetTypeParameter && skipParameters) {
764                declaration = PsiTreeUtil.getParentOfType(declaration, JetNamedDeclaration.class);
765            }
766            else if (declaration instanceof JetParameter) {
767                PsiElement parent = declaration.getParent();
768    
769                // val/var parameter of primary constructor should not be considered as local
770                if (((JetParameter) declaration).getValOrVarNode() != null && parent != null && parent.getParent() instanceof JetPrimaryConstructor) {
771                    return null;
772                }
773    
774                else if (skipParameters && parent != null && parent.getParent() instanceof JetNamedFunction) {
775                    declaration = (JetNamedFunction) parent.getParent();
776                }
777            }
778    
779            // No appropriate stub-tolerant method in PsiTreeUtil, nor JetStubbedPsiUtil, writing manually
780            PsiElement current = PsiTreeUtil.getStubOrPsiParent(declaration);
781            while (current != null) {
782                PsiElement parent = PsiTreeUtil.getStubOrPsiParent(current);
783                if (parent instanceof JetScript) return null;
784                if (current instanceof JetClassInitializer) {
785                    return ((JetClassInitializer) current).getBody();
786                }
787                if (current instanceof JetProperty || current instanceof JetFunction) {
788                    if (parent instanceof JetFile || (parent instanceof JetClassBody && !isMemberOfObjectExpression((JetCallableDeclaration) current))) {
789                        return (JetElement) current;
790                    }
791                }
792                if (current instanceof JetBlockExpression || current instanceof JetParameter) {
793                    return (JetElement) current;
794                }
795    
796                current = parent;
797            }
798            return null;
799        }
800    
801        public static boolean isLocal(@NotNull JetDeclaration declaration) {
802            return getEnclosingElementForLocalDeclaration(declaration) != null;
803        }
804    
805        @Nullable
806        public static JetToken getOperationToken(@NotNull JetOperationExpression expression) {
807            JetSimpleNameExpression operationExpression = expression.getOperationReference();
808            IElementType elementType = operationExpression.getReferencedNameElementType();
809            assert elementType == null || elementType instanceof JetToken :
810                    "JetOperationExpression should have operation token of type JetToken: " +
811                    expression;
812            return (JetToken) elementType;
813        }
814    
815        public static boolean isLabelIdentifierExpression(PsiElement element) {
816            return element instanceof JetLabelReferenceExpression;
817        }
818    
819        @Nullable
820        public static JetExpression getParentCallIfPresent(@NotNull JetExpression expression) {
821            PsiElement parent = expression.getParent();
822            while (parent != null) {
823                if (parent instanceof JetBinaryExpression ||
824                    parent instanceof JetUnaryExpression ||
825                    parent instanceof JetLabeledExpression ||
826                    parent instanceof JetDotQualifiedExpression ||
827                    parent instanceof JetCallExpression ||
828                    parent instanceof JetArrayAccessExpression ||
829                    parent instanceof JetMultiDeclaration) {
830    
831                    if (parent instanceof JetLabeledExpression) {
832                        parent = parent.getParent();
833                        continue;
834                    }
835    
836                    //check that it's in inlineable call would be in resolve call of parent
837                    return (JetExpression) parent;
838                }
839                else if (parent instanceof JetParenthesizedExpression || parent instanceof JetBinaryExpressionWithTypeRHS) {
840                    parent = parent.getParent();
841                }
842                else if (parent instanceof JetValueArgument || parent instanceof JetValueArgumentList) {
843                    parent = parent.getParent();
844                }
845                else if (parent instanceof JetFunctionLiteralExpression) {
846                    parent = parent.getParent();
847                }
848                else {
849                    return null;
850                }
851            }
852            return null;
853        }
854    
855        public static boolean isDeprecatedLambdaSyntax(@NotNull JetFunctionLiteralExpression functionLiteralExpression) {
856            JetFunctionLiteral functionLiteral = functionLiteralExpression.getFunctionLiteral();
857            if (functionLiteral.hasDeclaredReturnType() || functionLiteral.getReceiverTypeReference() != null) return true;
858    
859            JetParameterList valueParameterList = functionLiteral.getValueParameterList();
860            if (valueParameterList != null && valueParameterList.isParenthesized()) return true;
861    
862            return false;
863        }
864    }