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