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