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