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