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