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