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 }