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