001    /*
002     * Copyright 2010-2013 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.jet.lang.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.impl.CheckUtil;
028    import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
029    import com.intellij.psi.tree.IElementType;
030    import com.intellij.psi.util.PsiTreeUtil;
031    import com.intellij.util.codeInsight.CommentUtilCore;
032    import com.intellij.util.containers.ContainerUtil;
033    import org.jetbrains.annotations.NotNull;
034    import org.jetbrains.annotations.Nullable;
035    import org.jetbrains.jet.JetNodeTypes;
036    import org.jetbrains.jet.kdoc.psi.api.KDocElement;
037    import org.jetbrains.jet.lang.parsing.JetExpressionParsing;
038    import org.jetbrains.jet.lang.resolve.ImportPath;
039    import org.jetbrains.jet.lang.resolve.name.FqName;
040    import org.jetbrains.jet.lang.resolve.name.Name;
041    import org.jetbrains.jet.lang.types.expressions.OperatorConventions;
042    import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
043    import org.jetbrains.jet.lexer.JetToken;
044    import org.jetbrains.jet.lexer.JetTokens;
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    
053        public static final Name NO_NAME_PROVIDED = Name.special("<no name provided>");
054        public static final Name ROOT_NAMESPACE_NAME = Name.special("<root namespace>");
055    
056        private JetPsiUtil() {
057        }
058    
059        public static <D> void visitChildren(@NotNull JetElement element, @NotNull JetTreeVisitor<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        @Nullable
070        public static JetExpression deparenthesizeWithNoTypeResolution(@NotNull JetExpression expression) {
071            return deparenthesizeWithResolutionStrategy(expression, null);
072        }
073    
074        @Nullable
075        @Deprecated //Use JetPsiUtil.deparenthesizeWithNoTypeResolution() or ExpressionTypingServices.deparenthesize()
076        public static JetExpression deparenthesizeWithResolutionStrategy(
077                @NotNull JetExpression expression,
078                @Nullable Function<JetTypeReference, Void> typeResolutionStrategy
079        ) {
080            if (expression instanceof JetBinaryExpressionWithTypeRHS) {
081                JetBinaryExpressionWithTypeRHS binaryExpression = (JetBinaryExpressionWithTypeRHS) expression;
082                JetSimpleNameExpression operationSign = binaryExpression.getOperationReference();
083                if (JetTokens.COLON.equals(operationSign.getReferencedNameElementType())) {
084                    expression = binaryExpression.getLeft();
085                    JetTypeReference typeReference = binaryExpression.getRight();
086                    if (typeResolutionStrategy != null && typeReference != null) {
087                        typeResolutionStrategy.apply(typeReference);
088                    }
089                }
090            }
091            else if (expression instanceof JetPrefixExpression) {
092                if (JetTokens.LABELS.contains(((JetPrefixExpression) expression).getOperationReference().getReferencedNameElementType())) {
093                    JetExpression baseExpression = ((JetPrefixExpression) expression).getBaseExpression();
094                    if (baseExpression != null) {
095                        expression = baseExpression;
096                    }
097                }
098            }
099            if (expression instanceof JetParenthesizedExpression) {
100                JetExpression innerExpression = ((JetParenthesizedExpression) expression).getExpression();
101                return innerExpression != null ? deparenthesizeWithResolutionStrategy(innerExpression, typeResolutionStrategy) : null;
102            }
103            return expression;
104        }
105    
106        @NotNull
107        public static Name safeName(@Nullable String name) {
108            return name == null ? NO_NAME_PROVIDED : Name.identifier(name);
109        }
110    
111        @NotNull
112        public static Set<JetElement> findRootExpressions(@NotNull Collection<JetElement> unreachableElements) {
113            Set<JetElement> rootElements = new HashSet<JetElement>();
114            final Set<JetElement> shadowedElements = new HashSet<JetElement>();
115            JetVisitorVoid shadowAllChildren = new JetVisitorVoid() {
116                @Override
117                public void visitJetElement(JetElement element) {
118                    if (shadowedElements.add(element)) {
119                        element.acceptChildren(this);
120                    }
121                }
122            };
123    
124            for (JetElement element : unreachableElements) {
125                if (shadowedElements.contains(element)) continue;
126                element.acceptChildren(shadowAllChildren);
127    
128                rootElements.removeAll(shadowedElements);
129                rootElements.add(element);
130            }
131            return rootElements;
132        }
133    
134        @NotNull
135        public static String unquoteIdentifier(@NotNull String quoted) {
136            if (quoted.indexOf('`') < 0) {
137                return quoted;
138            }
139    
140            if (quoted.startsWith("`") && quoted.endsWith("`") && quoted.length() >= 2) {
141                return quoted.substring(1, quoted.length() - 1);
142            }
143            else {
144                return quoted;
145            }
146        }
147    
148        @NotNull
149        public static String unquoteIdentifierOrFieldReference(@NotNull String quoted) {
150            if (quoted.indexOf('`') < 0) {
151                return quoted;
152            }
153    
154            if (quoted.startsWith("$")) {
155                return "$" + unquoteIdentifier(quoted.substring(1));
156            }
157            else {
158                return unquoteIdentifier(quoted);
159            }
160        }
161    
162        @NotNull
163        public static FqName getFQName(@NotNull JetFile file) {
164            JetNamespaceHeader header = file.getNamespaceHeader();
165            return header != null ? header.getFqName() : FqName.ROOT;
166        }
167    
168        @Nullable
169        public static FqName getFQName(@NotNull JetNamedDeclaration namedDeclaration) {
170            if (namedDeclaration instanceof JetObjectDeclarationName) {
171                JetNamedDeclaration objectDeclaration = PsiTreeUtil.getParentOfType(namedDeclaration, JetObjectDeclaration.class);
172                if (objectDeclaration == null) {
173                    objectDeclaration = PsiTreeUtil.getParentOfType(namedDeclaration, JetEnumEntry.class);
174                }
175    
176                if (objectDeclaration == null) {
177                    return null;
178                }
179    
180                return getFQName(objectDeclaration);
181            }
182    
183            Name name = namedDeclaration.getNameAsName();
184            if (name == null) {
185                return null;
186            }
187    
188            PsiElement parent = namedDeclaration.getParent();
189            if (parent instanceof JetClassBody) {
190                // One nesting to JetClassBody doesn't affect to qualified name
191                parent = parent.getParent();
192            }
193    
194            FqName firstPart = null;
195            if (parent instanceof JetFile) {
196                firstPart = getFQName((JetFile) parent);
197            }
198            else if (parent instanceof JetNamedFunction || parent instanceof JetClass) {
199                firstPart = getFQName((JetNamedDeclaration) parent);
200            }
201            else if (namedDeclaration instanceof JetParameter) {
202                JetClass constructorClass = getClassIfParameterIsProperty((JetParameter) namedDeclaration);
203                if (constructorClass != null) {
204                    firstPart = getFQName(constructorClass);
205                }
206            }
207            else if (parent instanceof JetObjectDeclaration) {
208                if (parent.getParent() instanceof JetClassObject) {
209                    JetClassOrObject classOrObject = PsiTreeUtil.getParentOfType(parent, JetClassOrObject.class);
210                    if (classOrObject != null) {
211                        firstPart = getFQName(classOrObject);
212                    }
213                }
214                else {
215                    firstPart = getFQName((JetNamedDeclaration) parent);
216                }
217            }
218    
219            if (firstPart == null) {
220                return null;
221            }
222    
223            return firstPart.child(name);
224        }
225    
226        /** @return <code>null</code> iff the tye has syntactic errors */
227        @Nullable
228        public static FqName toQualifiedName(@NotNull JetUserType userType) {
229            List<String> reversedNames = Lists.newArrayList();
230    
231            JetUserType current = userType;
232            while (current != null) {
233                String name = current.getReferencedName();
234                if (name == null) return null;
235    
236                reversedNames.add(name);
237                current = current.getQualifier();
238            }
239    
240            return FqName.fromSegments(ContainerUtil.reverse(reversedNames));
241        }
242    
243        @Nullable
244        public static Name getShortName(@NotNull JetAnnotationEntry annotation) {
245            JetTypeReference typeReference = annotation.getTypeReference();
246            assert typeReference != null : "Annotation entry hasn't typeReference " + annotation.getText();
247            JetTypeElement typeElement = typeReference.getTypeElement();
248            if (typeElement instanceof JetUserType) {
249                JetUserType userType = (JetUserType) typeElement;
250                String shortName = userType.getReferencedName();
251                if (shortName != null) {
252                    return Name.identifier(shortName);
253                }
254            }
255            return null;
256        }
257    
258        public static boolean isDeprecated(@NotNull JetModifierListOwner owner) {
259            JetModifierList modifierList = owner.getModifierList();
260            if (modifierList != null) {
261                List<JetAnnotationEntry> annotationEntries = modifierList.getAnnotationEntries();
262                for (JetAnnotationEntry annotation : annotationEntries) {
263                    Name shortName = getShortName(annotation);
264                    if (KotlinBuiltIns.getInstance().getDeprecatedAnnotation().getName().equals(shortName)) {
265                        return true;
266                    }
267                }
268            }
269            return false;
270        }
271    
272        @Nullable
273        @IfNotParsed
274        public static ImportPath getImportPath(@NotNull JetImportDirective importDirective) {
275            if (PsiTreeUtil.hasErrorElements(importDirective)) {
276                return null;
277            }
278    
279            FqName importFqn = getFQName(importDirective.getImportedReference());
280            if (importFqn == null) {
281                return null;
282            }
283    
284            Name alias = null;
285            String aliasName = importDirective.getAliasName();
286            if (aliasName != null) {
287                alias = Name.identifier(aliasName);
288            }
289    
290            return new ImportPath(importFqn, importDirective.isAllUnder(), alias);
291        }
292    
293        @Nullable
294        public static <T extends PsiElement> T getDirectParentOfTypeForBlock(@NotNull JetBlockExpression block, @NotNull Class<T> aClass) {
295            T parent = PsiTreeUtil.getParentOfType(block, aClass);
296            if (parent instanceof JetIfExpression) {
297                JetIfExpression ifExpression = (JetIfExpression) parent;
298                if (ifExpression.getElse() == block || ifExpression.getThen() == block) {
299                    return parent;
300                }
301            }
302            if (parent instanceof JetWhenExpression) {
303                JetWhenExpression whenExpression = (JetWhenExpression) parent;
304                for (JetWhenEntry whenEntry : whenExpression.getEntries()) {
305                    if (whenEntry.getExpression() == block) {
306                        return parent;
307                    }
308                }
309            }
310            if (parent instanceof JetFunctionLiteral) {
311                JetFunctionLiteral functionLiteral = (JetFunctionLiteral) parent;
312                if (functionLiteral.getBodyExpression() == block) {
313                    return parent;
314                }
315            }
316            if (parent instanceof JetTryExpression) {
317                JetTryExpression tryExpression = (JetTryExpression) parent;
318                if (tryExpression.getTryBlock() == block) {
319                    return parent;
320                }
321                for (JetCatchClause clause : tryExpression.getCatchClauses()) {
322                    if (clause.getCatchBody() == block) {
323                        return parent;
324                    }
325                }
326            }
327            return null;
328        }
329    
330        public static boolean isImplicitlyUsed(@NotNull JetElement element) {
331            PsiElement parent = element.getParent();
332            if (!(parent instanceof JetBlockExpression)) return true;
333            JetBlockExpression block = (JetBlockExpression) parent;
334            List<JetElement> statements = block.getStatements();
335            if (statements.get(statements.size() - 1) == element) {
336                JetExpression expression = getDirectParentOfTypeForBlock(block, JetIfExpression.class);
337                if (expression == null) {
338                    expression = getDirectParentOfTypeForBlock(block, JetWhenExpression.class);
339                }
340                if (expression == null) {
341                    expression = getDirectParentOfTypeForBlock(block, JetFunctionLiteral.class);
342                }
343                if (expression == null) {
344                    expression = getDirectParentOfTypeForBlock(block, JetTryExpression.class);
345                }
346                if (expression != null) {
347                    return isImplicitlyUsed(expression);
348                }
349            }
350            return false;
351        }
352    
353        public static void deleteClass(@NotNull JetClassOrObject clazz) {
354            CheckUtil.checkWritable(clazz);
355            JetFile file = (JetFile) clazz.getContainingFile();
356            List<JetDeclaration> declarations = file.getDeclarations();
357            if (declarations.size() == 1) {
358                file.delete();
359            }
360            else {
361                PsiElement parent = clazz.getParent();
362                CodeEditUtil.removeChild(parent.getNode(), clazz.getNode());
363            }
364        }
365    
366        @Nullable
367        public static Name getAliasName(@NotNull JetImportDirective importDirective) {
368            String aliasName = importDirective.getAliasName();
369            JetExpression importedReference = importDirective.getImportedReference();
370            if (importedReference == null) {
371                return null;
372            }
373            JetSimpleNameExpression referenceExpression = getLastReference(importedReference);
374            if (aliasName == null) {
375                aliasName = referenceExpression != null ? referenceExpression.getReferencedName() : null;
376            }
377    
378            return aliasName != null && !aliasName.isEmpty() ? Name.identifier(aliasName) : null;
379        }
380    
381        @Nullable
382        public static JetSimpleNameExpression getLastReference(@NotNull JetExpression importedReference) {
383            if (importedReference instanceof JetDotQualifiedExpression) {
384                JetExpression selectorExpression = ((JetDotQualifiedExpression) importedReference).getSelectorExpression();
385                return (selectorExpression instanceof JetSimpleNameExpression) ? (JetSimpleNameExpression) selectorExpression : null;
386            }
387            if (importedReference instanceof JetSimpleNameExpression) {
388                return (JetSimpleNameExpression) importedReference;
389            }
390            return null;
391        }
392    
393        public static boolean isFirstPartInQualified(@NotNull JetSimpleNameExpression nameExpression) {
394            @SuppressWarnings("unchecked") JetUserType userType = PsiTreeUtil.getParentOfType(nameExpression, JetUserType.class, true,
395                                                                                              JetDeclaration.class);
396            if (userType != null) {
397                return PsiTreeUtil.isAncestor(userType.getFirstChild(), nameExpression, false);
398            }
399    
400            @SuppressWarnings("unchecked") JetQualifiedExpression qualifiedExpression = PsiTreeUtil.getParentOfType(nameExpression, JetQualifiedExpression.class, true, JetDeclaration.class);
401            if (qualifiedExpression != null) {
402                return PsiTreeUtil.isAncestor(qualifiedExpression.getFirstChild(), nameExpression, false);
403            }
404    
405            return true;
406        }
407    
408        public static boolean isVoidType(@Nullable JetTypeReference typeReference) {
409            if (typeReference == null) {
410                return false;
411            }
412    
413            return KotlinBuiltIns.getInstance().getUnit().getName().asString().equals(typeReference.getText());
414        }
415    
416        public static boolean isSafeCall(@NotNull Call call) {
417            ASTNode callOperationNode = call.getCallOperationNode();
418            return callOperationNode != null && callOperationNode.getElementType() == JetTokens.SAFE_ACCESS;
419        }
420    
421        public static boolean isFunctionLiteralWithoutDeclaredParameterTypes(@Nullable JetExpression expression) {
422            if (!(expression instanceof JetFunctionLiteralExpression)) return false;
423            JetFunctionLiteralExpression functionLiteral = (JetFunctionLiteralExpression) expression;
424            for (JetParameter parameter : functionLiteral.getValueParameters()) {
425                if (parameter.getTypeReference() != null) {
426                    return false;
427                }
428            }
429            return true;
430        }
431    
432        public static boolean isScriptDeclaration(@NotNull JetDeclaration namedDeclaration) {
433            return getScript(namedDeclaration) != null;
434        }
435    
436        @Nullable
437        public static JetScript getScript(@NotNull JetDeclaration namedDeclaration) {
438            PsiElement parent = namedDeclaration.getParent();
439            if (parent != null && parent.getParent() instanceof JetScript) {
440                return (JetScript) parent.getParent();
441            }
442            else {
443                return null;
444            }
445        }
446    
447        public static boolean isVariableNotParameterDeclaration(@NotNull JetDeclaration declaration) {
448            if (!(declaration instanceof JetVariableDeclaration)) return false;
449            if (declaration instanceof JetProperty) return true;
450            assert declaration instanceof JetMultiDeclarationEntry;
451            JetMultiDeclarationEntry multiDeclarationEntry = (JetMultiDeclarationEntry) declaration;
452            return !(multiDeclarationEntry.getParent().getParent() instanceof JetForExpression);
453        }
454    
455        @Nullable
456        public static Name getConventionName(@NotNull JetSimpleNameExpression simpleNameExpression) {
457            if (simpleNameExpression.getIdentifier() != null) {
458                return simpleNameExpression.getReferencedNameAsName();
459            }
460    
461            PsiElement firstChild = simpleNameExpression.getFirstChild();
462            if (firstChild != null) {
463                IElementType elementType = firstChild.getNode().getElementType();
464                if (elementType instanceof JetToken) {
465                    JetToken jetToken = (JetToken) elementType;
466                    return OperatorConventions.getNameForOperationSymbol(jetToken);
467                }
468            }
469    
470            return null;
471        }
472    
473        @Nullable
474        public static PsiElement getTopmostParentOfTypes(@Nullable PsiElement element, @NotNull Class<? extends PsiElement>... parentTypes) {
475            if (element == null) {
476                return null;
477            }
478    
479            PsiElement result = null;
480            PsiElement parent = element.getParent();
481            while (parent != null) {
482                if (PsiTreeUtil.instanceOf(parent, parentTypes)) {
483                    result = parent;
484                }
485    
486                parent = parent.getParent();
487            }
488    
489            return result;
490        }
491    
492        public static boolean isNullConstant(@NotNull JetExpression expression) {
493            JetExpression deparenthesized = deparenthesizeWithNoTypeResolution(expression);
494            return deparenthesized instanceof JetConstantExpression && deparenthesized.getNode().getElementType() == JetNodeTypes.NULL;
495        }
496    
497        public static boolean isAbstract(@NotNull JetDeclarationWithBody declaration) {
498            return declaration.getBodyExpression() == null;
499        }
500    
501        public static boolean isBackingFieldReference(@NotNull JetSimpleNameExpression expression) {
502            return expression.getReferencedNameElementType() == JetTokens.FIELD_IDENTIFIER;
503        }
504    
505        public static boolean isBackingFieldReference(@Nullable JetElement element) {
506            return element instanceof JetSimpleNameExpression && isBackingFieldReference((JetSimpleNameExpression)element);
507        }
508    
509        @Nullable
510        private static FqName getFQName(@Nullable JetExpression expression) {
511            if (expression == null) {
512                return null;
513            }
514    
515            if (expression instanceof JetDotQualifiedExpression) {
516                JetDotQualifiedExpression dotQualifiedExpression = (JetDotQualifiedExpression) expression;
517                FqName parentFqn = getFQName(dotQualifiedExpression.getReceiverExpression());
518                Name child = getName(dotQualifiedExpression.getSelectorExpression());
519    
520                return parentFqn != null && child != null ? parentFqn.child(child) : null;
521            }
522            else if (expression instanceof JetSimpleNameExpression) {
523                JetSimpleNameExpression simpleNameExpression = (JetSimpleNameExpression) expression;
524                return FqName.topLevel(simpleNameExpression.getReferencedNameAsName());
525            }
526            else {
527                throw new IllegalArgumentException("Can't construct fqn for: " + expression.getClass().toString());
528            }
529        }
530    
531        @Nullable
532        private static Name getName(@Nullable JetExpression expression) {
533            if (expression == null) {
534                return null;
535            }
536    
537            if (expression instanceof JetSimpleNameExpression) {
538                return ((JetSimpleNameExpression) expression).getReferencedNameAsName();
539            }
540            else {
541                throw new IllegalArgumentException("Can't construct name for: " + expression.getClass().toString());
542            }
543        }
544    
545        @Nullable
546        public static JetElement getLastStatementInABlock(@Nullable JetBlockExpression blockExpression) {
547            if (blockExpression == null) return null;
548            List<JetElement> statements = blockExpression.getStatements();
549            return statements.isEmpty() ? null : statements.get(statements.size() - 1);
550        }
551    
552        public static boolean isLocalClass(@NotNull JetClassOrObject classOrObject) {
553            return getOutermostClassOrObject(classOrObject) == null;
554        }
555    
556        public static boolean isTrait(@NotNull JetClassOrObject classOrObject) {
557            return classOrObject instanceof JetClass && ((JetClass) classOrObject).isTrait();
558        }
559    
560        @Nullable
561        public static JetClassOrObject getOutermostClassOrObject(@NotNull JetClassOrObject classOrObject) {
562            JetClassOrObject current = classOrObject;
563            while (true) {
564                PsiElement parent = current.getParent();
565                assert classOrObject.getParent() != null : "Class with no parent: " + classOrObject.getText();
566    
567                if (parent instanceof PsiFile) {
568                    return current;
569                }
570                if (parent instanceof JetClassObject) {
571                    // current class IS the class object declaration
572                    parent = parent.getParent();
573                    assert parent instanceof JetClassBody : "Parent of class object is not a class body: " + parent;
574                }
575                if (!(parent instanceof JetClassBody)) {
576                    // It is a local class, no legitimate outer
577                    return null;
578                }
579    
580                current = (JetClassOrObject) parent.getParent();
581            }
582        }
583    
584        @Nullable
585        public static JetClass getClassIfParameterIsProperty(@NotNull JetParameter jetParameter) {
586            if (jetParameter.getValOrVarNode() != null) {
587                PsiElement parent = jetParameter.getParent();
588                if (parent instanceof JetParameterList && parent.getParent() instanceof JetClass) {
589                    return (JetClass) parent.getParent();
590                }
591            }
592    
593            return null;
594        }
595    
596        @Nullable
597        private static IElementType getOperation(@NotNull JetExpression expression) {
598            if (expression instanceof JetQualifiedExpression) {
599                return ((JetQualifiedExpression) expression).getOperationSign();
600            }
601            else if (expression instanceof JetOperationExpression) {
602                return ((JetOperationExpression) expression).getOperationReference().getReferencedNameElementType();
603            }
604            return null;
605        }
606    
607        private static int getPrecedenceOfOperation(@NotNull JetExpression expression, @NotNull IElementType operation) {
608            if (expression instanceof JetPostfixExpression) return 0;
609            if (expression instanceof JetQualifiedExpression) return 0;
610            if (expression instanceof JetPrefixExpression) return 1;
611    
612            for (JetExpressionParsing.Precedence precedence : JetExpressionParsing.Precedence.values()) {
613                if (precedence != JetExpressionParsing.Precedence.PREFIX && precedence != JetExpressionParsing.Precedence.POSTFIX &&
614                    precedence.getOperations().contains(operation)) {
615                    return precedence.ordinal();
616                }
617            }
618            throw new IllegalStateException("Unknown operation");
619        }
620    
621        public static boolean areParenthesesUseless(@NotNull JetParenthesizedExpression expression) {
622            JetExpression innerExpression = expression.getExpression();
623            JetExpression parentExpression = PsiTreeUtil.getParentOfType(expression, JetExpression.class, true);
624            if (innerExpression == null || parentExpression == null) return true;
625    
626            IElementType innerOperation = getOperation(innerExpression);
627            IElementType parentOperation = getOperation(parentExpression);
628    
629            // 'return (@label{...})' case
630            if (parentExpression instanceof JetReturnExpression && innerOperation == JetTokens.LABEL_IDENTIFIER) {
631                return false;
632            }
633    
634            // '(x: Int) < y' case
635            if (innerExpression instanceof JetBinaryExpressionWithTypeRHS && parentOperation == JetTokens.LT) {
636                return false;
637            }
638    
639            // associative operations
640            if (innerOperation == parentOperation && (innerOperation == JetTokens.OROR || innerOperation == JetTokens.ANDAND)) {
641                return true;
642            }
643    
644            if (innerOperation == null) return true;
645            if (parentExpression instanceof JetArrayAccessExpression) {
646                return ((JetArrayAccessExpression) parentExpression).getArrayExpression() != expression;
647            }
648            if (parentOperation == null) return true;
649    
650            int innerPrecedence = getPrecedenceOfOperation(innerExpression, innerOperation);
651            int parentPrecedence = getPrecedenceOfOperation(parentExpression, parentOperation);
652            return innerPrecedence < parentPrecedence;
653        }
654    
655        public static boolean isAssignment(@NotNull PsiElement element) {
656            return element instanceof JetBinaryExpression &&
657                   JetTokens.ALL_ASSIGNMENTS.contains(((JetBinaryExpression) element).getOperationToken());
658        }
659    
660        public static boolean isOrdinaryAssignment(@NotNull PsiElement element) {
661            return element instanceof JetBinaryExpression &&
662                   ((JetBinaryExpression) element).getOperationToken().equals(JetTokens.EQ);
663        }
664    
665        @Nullable
666        public static JetElement getOutermostLastBlockElement(@Nullable JetElement element, @NotNull Predicate<JetElement> checkElement) {
667            if (element == null) return null;
668    
669            if (!(element instanceof JetBlockExpression)) return checkElement.apply(element) ? element : null;
670    
671            JetBlockExpression block = (JetBlockExpression)element;
672            int n = block.getStatements().size();
673    
674            if (n == 0) return null;
675    
676            JetElement lastElement = block.getStatements().get(n - 1);
677            return checkElement.apply(lastElement) ? lastElement : null;
678        }
679    
680        @Nullable
681        public static PsiElement getParentByTypeAndPredicate(
682                @Nullable PsiElement element, @NotNull Class<? extends PsiElement> aClass, @NotNull Predicate<PsiElement> predicate, boolean strict) {
683            if (element == null) return null;
684            if (strict) {
685                element = element.getParent();
686            }
687    
688            while (element != null) {
689                //noinspection unchecked
690                if (aClass.isInstance(element) && predicate.apply(element)) {
691                    //noinspection unchecked
692                    return element;
693                }
694                if (element instanceof PsiFile) return null;
695                element = element.getParent();
696            }
697    
698            return null;
699        }
700    
701        public static boolean checkVariableDeclarationInBlock(@NotNull JetBlockExpression block, @NotNull String varName) {
702            for (JetElement element : block.getStatements()) {
703                if (element instanceof JetVariableDeclaration) {
704                    if (((JetVariableDeclaration) element).getNameAsSafeName().asString().equals(varName)) {
705                        return true;
706                    }
707                }
708            }
709    
710            return false;
711        }
712    
713        public static boolean checkWhenExpressionHasSingleElse(@NotNull JetWhenExpression whenExpression) {
714            int elseCount = 0;
715            for (JetWhenEntry entry : whenExpression.getEntries()) {
716                if (entry.isElse()) {
717                    elseCount++;
718                }
719            }
720            return (elseCount == 1);
721        }
722    
723        @Nullable
724        public static PsiElement skipTrailingWhitespacesAndComments(@Nullable PsiElement element)  {
725            return PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace.class, PsiComment.class);
726        }
727    
728        public static final Predicate<JetElement> ANY_JET_ELEMENT = new Predicate<JetElement>() {
729            @Override
730            public boolean apply(@Nullable JetElement input) {
731                return true;
732            }
733        };
734    
735        @NotNull
736        public static String getText(@Nullable PsiElement element) {
737            return element != null ? element.getText() : "";
738        }
739    
740        @Nullable
741        public static String getNullableText(@Nullable PsiElement element) {
742            return element != null ? element.getText() : null;
743        }
744    
745        /**
746         * CommentUtilCore.isComment fails if element <strong>inside</strong> comment.
747         *
748         * Also, we can not add KDocTokens to COMMENTS TokenSet, because it is used in JetParserDefinition.getCommentTokens(),
749         * and therefor all COMMENTS tokens will be ignored by PsiBuilder.
750         *
751         * @param element
752         * @return
753         */
754        public static boolean isInComment(PsiElement element) {
755            return CommentUtilCore.isComment(element) || element instanceof KDocElement;
756        }
757    
758        @Nullable
759        public static PsiElement getOutermostParent(@NotNull PsiElement element, @NotNull PsiElement upperBound, boolean strict) {
760            PsiElement parent = strict ? element.getParent() : element;
761            while (parent != null && parent.getParent() != upperBound) {
762                parent = parent.getParent();
763            }
764    
765            return parent;
766        }
767    
768        public static <T extends PsiElement> T getLastChildByType(@NotNull PsiElement root, @NotNull Class<? extends T>... elementTypes) {
769            PsiElement[] children = root.getChildren();
770    
771            for (int i = children.length - 1; i >= 0; i--) {
772                if (PsiTreeUtil.instanceOf(children[i], elementTypes)) {
773                    //noinspection unchecked
774                    return (T) children[i];
775                }
776            }
777    
778            return null;
779        }
780    
781        @Nullable
782        public static JetElement getOutermostDescendantElement(
783                @Nullable PsiElement root,
784                boolean first,
785                final @NotNull Predicate<JetElement> predicate
786        ) {
787            if (!(root instanceof JetElement)) return null;
788    
789            final List<JetElement> results = Lists.newArrayList();
790    
791            ((JetElement) root).accept(
792                    new JetVisitorVoid() {
793                        @Override
794                        public void visitJetElement(JetElement element) {
795                            if (predicate.apply(element)) {
796                                //noinspection unchecked
797                                results.add(element);
798                            }
799                            else {
800                                element.acceptChildren(this);
801                            }
802                        }
803                    }
804            );
805    
806            if (results.isEmpty()) return null;
807    
808            return first ? results.get(0) : results.get(results.size() - 1);
809        }
810    
811        @Nullable
812        public static PsiElement findChildByType(@NotNull PsiElement element, @NotNull IElementType type) {
813            ASTNode node = element.getNode().findChildByType(type);
814            return node == null ? null : node.getPsi();
815        }
816    
817        @Nullable
818        public static JetExpression getCalleeExpressionIfAny(@NotNull JetExpression expression) {
819            if (expression instanceof JetCallElement) {
820                JetCallElement callExpression = (JetCallElement) expression;
821                return callExpression.getCalleeExpression();
822            }
823            if (expression instanceof JetQualifiedExpression) {
824                JetExpression selectorExpression = ((JetQualifiedExpression) expression).getSelectorExpression();
825                if (selectorExpression != null) {
826                    return getCalleeExpressionIfAny(selectorExpression);
827                }
828            }
829            if (expression instanceof JetUnaryExpression) {
830                return ((JetUnaryExpression) expression).getOperationReference();
831            }
832            if (expression instanceof JetBinaryExpression) {
833                return ((JetBinaryExpression) expression).getOperationReference();
834            }
835            return null;
836        }
837    
838        @Nullable
839        public static PsiElement skipSiblingsBackwardByPredicate(@Nullable PsiElement element, Predicate<PsiElement> elementsToSkip) {
840            if (element == null) return null;
841            for (PsiElement e = element.getPrevSibling(); e != null; e = e.getPrevSibling()) {
842                if (elementsToSkip.apply(e)) continue;
843                return e;
844            }
845            return null;
846        }
847    
848        @Nullable
849        public static PsiElement skipSiblingsForwardByPredicate(@Nullable PsiElement element, Predicate<PsiElement> elementsToSkip) {
850            if (element == null) return null;
851            for (PsiElement e = element.getNextSibling(); e != null; e = e.getNextSibling()) {
852                if (elementsToSkip.apply(e)) continue;
853                return e;
854            }
855            return null;
856        }
857    }