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