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 if (importedReference instanceof JetDotQualifiedExpression) {
403 JetExpression selectorExpression = ((JetDotQualifiedExpression) importedReference).getSelectorExpression();
404 return (selectorExpression instanceof JetSimpleNameExpression) ? (JetSimpleNameExpression) selectorExpression : null;
405 }
406 if (importedReference instanceof JetSimpleNameExpression) {
407 return (JetSimpleNameExpression) importedReference;
408 }
409 return null;
410 }
411
412 public static boolean isSelectorInQualified(@NotNull JetSimpleNameExpression nameExpression) {
413 JetElement qualifiedElement = PsiUtilPackage.getQualifiedElement(nameExpression);
414 return qualifiedElement instanceof JetQualifiedExpression
415 || ((qualifiedElement instanceof JetUserType) && ((JetUserType) qualifiedElement).getQualifier() != null);
416 }
417
418 public static boolean isLHSOfDot(@NotNull JetExpression expression) {
419 PsiElement parent = expression.getParent();
420 if (!(parent instanceof JetQualifiedExpression)) return false;
421 JetQualifiedExpression qualifiedParent = (JetQualifiedExpression) parent;
422 return qualifiedParent.getReceiverExpression() == expression || isLHSOfDot(qualifiedParent);
423 }
424
425 public static boolean isVoidType(@Nullable JetTypeReference typeReference) {
426 if (typeReference == null) {
427 return false;
428 }
429
430 return KotlinBuiltIns.getInstance().getUnit().getName().asString().equals(typeReference.getText());
431 }
432
433 public static boolean isSafeCall(@NotNull Call call) {
434 ASTNode callOperationNode = call.getCallOperationNode();
435 return callOperationNode != null && callOperationNode.getElementType() == JetTokens.SAFE_ACCESS;
436 }
437
438 public static boolean isScriptDeclaration(@NotNull JetDeclaration namedDeclaration) {
439 return getScript(namedDeclaration) != null;
440 }
441
442 @Nullable
443 public static JetScript getScript(@NotNull JetDeclaration namedDeclaration) {
444 PsiElement parent = namedDeclaration.getParent();
445 if (parent != null && parent.getParent() instanceof JetScript) {
446 return (JetScript) parent.getParent();
447 }
448 else {
449 return null;
450 }
451 }
452
453 public static boolean isVariableNotParameterDeclaration(@NotNull JetDeclaration declaration) {
454 if (!(declaration instanceof JetVariableDeclaration)) return false;
455 if (declaration instanceof JetProperty) return true;
456 assert declaration instanceof JetMultiDeclarationEntry;
457 JetMultiDeclarationEntry multiDeclarationEntry = (JetMultiDeclarationEntry) declaration;
458 return !(multiDeclarationEntry.getParent().getParent() instanceof JetForExpression);
459 }
460
461 @Nullable
462 public static Name getConventionName(@NotNull JetSimpleNameExpression simpleNameExpression) {
463 if (simpleNameExpression.getIdentifier() != null) {
464 return simpleNameExpression.getReferencedNameAsName();
465 }
466
467 PsiElement firstChild = simpleNameExpression.getFirstChild();
468 if (firstChild != null) {
469 IElementType elementType = firstChild.getNode().getElementType();
470 if (elementType instanceof JetToken) {
471 JetToken jetToken = (JetToken) elementType;
472 return OperatorConventions.getNameForOperationSymbol(jetToken);
473 }
474 }
475
476 return null;
477 }
478
479 @Nullable
480 public static PsiElement getTopmostParentOfTypes(@Nullable PsiElement element, @NotNull Class<? extends PsiElement>... parentTypes) {
481 if (element == null) {
482 return null;
483 }
484
485 PsiElement result = null;
486 PsiElement parent = element.getParent();
487 while (parent != null) {
488 if (PsiTreeUtil.instanceOf(parent, parentTypes)) {
489 result = parent;
490 }
491
492 parent = parent.getParent();
493 }
494
495 return result;
496 }
497
498 public static boolean isNullConstant(@NotNull JetExpression expression) {
499 JetExpression deparenthesized = deparenthesize(expression);
500 return deparenthesized instanceof JetConstantExpression && deparenthesized.getNode().getElementType() == JetNodeTypes.NULL;
501 }
502
503 public static boolean isAbstract(@NotNull JetDeclarationWithBody declaration) {
504 return declaration.getBodyExpression() == null;
505 }
506
507 public static boolean isBackingFieldReference(@NotNull JetSimpleNameExpression expression) {
508 return expression.getReferencedNameElementType() == JetTokens.FIELD_IDENTIFIER;
509 }
510
511 public static boolean isBackingFieldReference(@Nullable JetElement element) {
512 return element instanceof JetSimpleNameExpression && isBackingFieldReference((JetSimpleNameExpression)element);
513 }
514
515 @Nullable
516 private static FqName getFQName(@Nullable JetExpression expression) {
517 if (expression == null) {
518 return null;
519 }
520
521 if (expression instanceof JetDotQualifiedExpression) {
522 JetDotQualifiedExpression dotQualifiedExpression = (JetDotQualifiedExpression) expression;
523 FqName parentFqn = getFQName(dotQualifiedExpression.getReceiverExpression());
524 Name child = getName(dotQualifiedExpression.getSelectorExpression());
525
526 return parentFqn != null && child != null ? parentFqn.child(child) : null;
527 }
528 else if (expression instanceof JetSimpleNameExpression) {
529 JetSimpleNameExpression simpleNameExpression = (JetSimpleNameExpression) expression;
530 return FqName.topLevel(simpleNameExpression.getReferencedNameAsName());
531 }
532 else {
533 throw new IllegalArgumentException("Can't construct fqn for: " + expression.getClass().toString());
534 }
535 }
536
537 @Nullable
538 private static Name getName(@Nullable JetExpression expression) {
539 if (expression == null) {
540 return null;
541 }
542
543 if (expression instanceof JetSimpleNameExpression) {
544 return ((JetSimpleNameExpression) expression).getReferencedNameAsName();
545 }
546 else {
547 throw new IllegalArgumentException("Can't construct name for: " + expression.getClass().toString());
548 }
549 }
550
551 @Nullable
552 public static JetElement getLastStatementInABlock(@Nullable JetBlockExpression blockExpression) {
553 if (blockExpression == null) return null;
554 List<JetElement> statements = blockExpression.getStatements();
555 return statements.isEmpty() ? null : statements.get(statements.size() - 1);
556 }
557
558 public static boolean isTrait(@NotNull JetClassOrObject classOrObject) {
559 return classOrObject instanceof JetClass && ((JetClass) classOrObject).isTrait();
560 }
561
562 @Nullable
563 public static JetClassOrObject getOutermostClassOrObject(@NotNull JetClassOrObject classOrObject) {
564 JetClassOrObject current = classOrObject;
565 while (true) {
566 PsiElement parent = current.getParent();
567 assert classOrObject.getParent() != null : "Class with no parent: " + classOrObject.getText();
568
569 if (parent instanceof PsiFile) {
570 return current;
571 }
572 if (parent instanceof JetClassObject) {
573 // current class IS the class object declaration
574 parent = parent.getParent();
575 assert parent instanceof JetClassBody : "Parent of class object is not a class body: " + parent;
576 }
577 if (!(parent instanceof JetClassBody)) {
578 // It is a local class, no legitimate outer
579 return current;
580 }
581
582 current = (JetClassOrObject) parent.getParent();
583 }
584 }
585
586 @Nullable
587 public static JetClass getClassIfParameterIsProperty(@NotNull JetParameter jetParameter) {
588 if (jetParameter.getValOrVarNode() != null) {
589 PsiElement parent = jetParameter.getParent();
590 if (parent instanceof JetParameterList && parent.getParent() instanceof JetClass) {
591 return (JetClass) parent.getParent();
592 }
593 }
594
595 return null;
596 }
597
598 @Nullable
599 private static IElementType getOperation(@NotNull JetExpression expression) {
600 if (expression instanceof JetQualifiedExpression) {
601 return ((JetQualifiedExpression) expression).getOperationSign();
602 }
603 else if (expression instanceof JetOperationExpression) {
604 return ((JetOperationExpression) expression).getOperationReference().getReferencedNameElementType();
605 }
606 return null;
607 }
608
609
610 private static int getPriority(@NotNull JetExpression expression) {
611 int maxPriority = JetExpressionParsing.Precedence.values().length + 1;
612
613 // same as postfix operations
614 if (expression instanceof JetPostfixExpression ||
615 expression instanceof JetQualifiedExpression ||
616 expression instanceof JetCallExpression ||
617 expression instanceof JetArrayAccessExpression) {
618 return maxPriority - 1;
619 }
620
621 if (expression instanceof JetPrefixExpression) return maxPriority - 2;
622
623 if (expression instanceof JetDeclaration || expression instanceof JetStatementExpression || expression instanceof JetIfExpression) {
624 return 0;
625 }
626
627 IElementType operation = getOperation(expression);
628 for (JetExpressionParsing.Precedence precedence : JetExpressionParsing.Precedence.values()) {
629 if (precedence != JetExpressionParsing.Precedence.PREFIX && precedence != JetExpressionParsing.Precedence.POSTFIX &&
630 precedence.getOperations().contains(operation)) {
631 return maxPriority - precedence.ordinal() - 1;
632 }
633 }
634
635 return maxPriority;
636 }
637
638 public static boolean areParenthesesUseless(@NotNull JetParenthesizedExpression expression) {
639 JetExpression innerExpression = expression.getExpression();
640 if (innerExpression == null) return true;
641
642 PsiElement parent = expression.getParent();
643 if (!(parent instanceof JetExpression)) return true;
644
645 return !areParenthesesNecessary(innerExpression, expression, (JetExpression) parent);
646 }
647
648 public static boolean areParenthesesNecessary(@NotNull JetExpression innerExpression, @NotNull JetExpression currentInner, @NotNull JetExpression parentExpression) {
649 if (parentExpression instanceof JetParenthesizedExpression || innerExpression instanceof JetParenthesizedExpression) {
650 return false;
651 }
652
653 if (parentExpression instanceof JetWhenExpression || innerExpression instanceof JetWhenExpression) {
654 return false;
655 }
656
657 if (innerExpression instanceof JetIfExpression) {
658 PsiElement current = parentExpression;
659
660 while (!(current instanceof JetBlockExpression || current instanceof JetDeclaration || current instanceof JetStatementExpression)) {
661 if (current.getTextRange().getEndOffset() != currentInner.getTextRange().getEndOffset()) {
662 return current.getText().charAt(current.getTextLength() - 1) != ')'; // if current expression is "guarded" by parenthesis, no extra parenthesis is necessary
663 }
664
665 current = current.getParent();
666 }
667 }
668
669 IElementType innerOperation = getOperation(innerExpression);
670 IElementType parentOperation = getOperation(parentExpression);
671
672 // 'return (@label{...})' case
673 if (parentExpression instanceof JetReturnExpression && innerOperation == JetTokens.LABEL_IDENTIFIER) {
674 return true;
675 }
676
677 // '(x: Int) < y' case
678 if (innerExpression instanceof JetBinaryExpressionWithTypeRHS && parentOperation == JetTokens.LT) {
679 return true;
680 }
681
682 int innerPriority = getPriority(innerExpression);
683 int parentPriority = getPriority(parentExpression);
684
685 if (innerPriority == parentPriority) {
686 if (parentExpression instanceof JetBinaryExpression) {
687 if (innerOperation == JetTokens.ANDAND || innerOperation == JetTokens.OROR) {
688 return false;
689 }
690 return ((JetBinaryExpression) parentExpression).getRight() == currentInner;
691 }
692
693 //'-(-x)' case
694 if (parentExpression instanceof JetPrefixExpression && innerExpression instanceof JetPrefixExpression) {
695 return innerOperation == parentOperation && (innerOperation == JetTokens.PLUS || innerOperation == JetTokens.MINUS);
696 }
697 return false;
698 }
699
700 return innerPriority < parentPriority;
701 }
702
703 public static boolean isAssignment(@NotNull PsiElement element) {
704 return element instanceof JetBinaryExpression &&
705 JetTokens.ALL_ASSIGNMENTS.contains(((JetBinaryExpression) element).getOperationToken());
706 }
707
708 public static boolean isOrdinaryAssignment(@NotNull PsiElement element) {
709 return element instanceof JetBinaryExpression &&
710 ((JetBinaryExpression) element).getOperationToken().equals(JetTokens.EQ);
711 }
712
713 @Nullable
714 public static JetElement getOutermostLastBlockElement(@Nullable JetElement element, @NotNull Predicate<JetElement> checkElement) {
715 if (element == null) return null;
716
717 if (!(element instanceof JetBlockExpression)) return checkElement.apply(element) ? element : null;
718
719 JetBlockExpression block = (JetBlockExpression)element;
720 int n = block.getStatements().size();
721
722 if (n == 0) return null;
723
724 JetElement lastElement = block.getStatements().get(n - 1);
725 return checkElement.apply(lastElement) ? lastElement : null;
726 }
727
728 public static boolean checkVariableDeclarationInBlock(@NotNull JetBlockExpression block, @NotNull String varName) {
729 for (JetElement element : block.getStatements()) {
730 if (element instanceof JetVariableDeclaration) {
731 if (((JetVariableDeclaration) element).getNameAsSafeName().asString().equals(varName)) {
732 return true;
733 }
734 }
735 }
736
737 return false;
738 }
739
740 public static boolean checkWhenExpressionHasSingleElse(@NotNull JetWhenExpression whenExpression) {
741 int elseCount = 0;
742 for (JetWhenEntry entry : whenExpression.getEntries()) {
743 if (entry.isElse()) {
744 elseCount++;
745 }
746 }
747 return (elseCount == 1);
748 }
749
750 @Nullable
751 public static PsiElement skipTrailingWhitespacesAndComments(@Nullable PsiElement element) {
752 return PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace.class, PsiComment.class);
753 }
754
755 public static final Predicate<JetElement> ANY_JET_ELEMENT = new Predicate<JetElement>() {
756 @Override
757 public boolean apply(@Nullable JetElement input) {
758 return true;
759 }
760 };
761
762 @NotNull
763 public static String getText(@Nullable PsiElement element) {
764 return element != null ? element.getText() : "";
765 }
766
767 @Nullable
768 public static String getNullableText(@Nullable PsiElement element) {
769 return element != null ? element.getText() : null;
770 }
771
772 /**
773 * CommentUtilCore.isComment fails if element <strong>inside</strong> comment.
774 *
775 * Also, we can not add KDocTokens to COMMENTS TokenSet, because it is used in JetParserDefinition.getCommentTokens(),
776 * and therefor all COMMENTS tokens will be ignored by PsiBuilder.
777 *
778 * @param element
779 * @return
780 */
781 public static boolean isInComment(PsiElement element) {
782 return CommentUtilCore.isComment(element) || element instanceof KDocElement;
783 }
784
785 @Nullable
786 public static PsiElement getOutermostParent(@NotNull PsiElement element, @NotNull PsiElement upperBound, boolean strict) {
787 PsiElement parent = strict ? element.getParent() : element;
788 while (parent != null && parent.getParent() != upperBound) {
789 parent = parent.getParent();
790 }
791
792 return parent;
793 }
794
795 public static <T extends PsiElement> T getLastChildByType(@NotNull PsiElement root, @NotNull Class<? extends T>... elementTypes) {
796 PsiElement[] children = root.getChildren();
797
798 for (int i = children.length - 1; i >= 0; i--) {
799 if (PsiTreeUtil.instanceOf(children[i], elementTypes)) {
800 //noinspection unchecked
801 return (T) children[i];
802 }
803 }
804
805 return null;
806 }
807
808 @Nullable
809 public static JetElement getOutermostDescendantElement(
810 @Nullable PsiElement root,
811 boolean first,
812 final @NotNull Predicate<JetElement> predicate
813 ) {
814 if (!(root instanceof JetElement)) return null;
815
816 final List<JetElement> results = Lists.newArrayList();
817
818 ((JetElement) root).accept(
819 new JetVisitorVoid() {
820 @Override
821 public void visitJetElement(@NotNull JetElement element) {
822 if (predicate.apply(element)) {
823 //noinspection unchecked
824 results.add(element);
825 }
826 else {
827 element.acceptChildren(this);
828 }
829 }
830 }
831 );
832
833 if (results.isEmpty()) return null;
834
835 return first ? results.get(0) : results.get(results.size() - 1);
836 }
837
838 @Nullable
839 public static PsiElement findChildByType(@NotNull PsiElement element, @NotNull IElementType type) {
840 ASTNode node = element.getNode().findChildByType(type);
841 return node == null ? null : node.getPsi();
842 }
843
844 @Nullable
845 public static JetExpression getCalleeExpressionIfAny(@NotNull JetExpression expression) {
846 if (expression instanceof JetSimpleNameExpression) {
847 return expression;
848 }
849 if (expression instanceof JetCallElement) {
850 JetCallElement callExpression = (JetCallElement) expression;
851 return callExpression.getCalleeExpression();
852 }
853 if (expression instanceof JetQualifiedExpression) {
854 JetExpression selectorExpression = ((JetQualifiedExpression) expression).getSelectorExpression();
855 if (selectorExpression != null) {
856 return getCalleeExpressionIfAny(selectorExpression);
857 }
858 }
859 if (expression instanceof JetUnaryExpression) {
860 return ((JetUnaryExpression) expression).getOperationReference();
861 }
862 if (expression instanceof JetBinaryExpression) {
863 return ((JetBinaryExpression) expression).getOperationReference();
864 }
865 return null;
866 }
867
868 @Nullable
869 public static PsiElement skipSiblingsBackwardByPredicate(@Nullable PsiElement element, Predicate<PsiElement> elementsToSkip) {
870 if (element == null) return null;
871 for (PsiElement e = element.getPrevSibling(); e != null; e = e.getPrevSibling()) {
872 if (elementsToSkip.apply(e)) continue;
873 return e;
874 }
875 return null;
876 }
877
878 @Nullable
879 public static PsiElement skipSiblingsForwardByPredicate(@Nullable PsiElement element, Predicate<PsiElement> elementsToSkip) {
880 if (element == null) return null;
881 for (PsiElement e = element.getNextSibling(); e != null; e = e.getNextSibling()) {
882 if (elementsToSkip.apply(e)) continue;
883 return e;
884 }
885 return null;
886 }
887
888 public static NavigatablePsiElement getPackageReference(@NotNull JetFile file, int partIndex) {
889 JetPackageDirective directive = file.getPackageDirective();
890 if (directive == null) {
891 throw new IllegalArgumentException("Should be called only for files with package directive: " + file);
892 }
893
894 List<JetSimpleNameExpression> names = directive.getPackageNames();
895 if (!(0 <= partIndex && partIndex < names.size())) {
896 throw new IndexOutOfBoundsException(String.format("%s index for file with directive %s is out of range", partIndex,
897 directive.getText()));
898 }
899
900 return names.get(partIndex);
901 }
902
903 // Delete given element and all the elements separating it from the neighboring elements of the same class
904 public static void deleteElementWithDelimiters(@NotNull PsiElement element) {
905 PsiElement paramBefore = PsiTreeUtil.getPrevSiblingOfType(element, element.getClass());
906
907 PsiElement from;
908 PsiElement to;
909 if (paramBefore != null) {
910 from = paramBefore.getNextSibling();
911 to = element;
912 }
913 else {
914 PsiElement paramAfter = PsiTreeUtil.getNextSiblingOfType(element, element.getClass());
915
916 from = element;
917 to = paramAfter != null ? paramAfter.getPrevSibling() : element;
918 }
919
920 PsiElement parent = element.getParent();
921
922 parent.deleteChildRange(from, to);
923 }
924
925 // Delete element if it doesn't contain children of a given type
926 public static <T extends PsiElement> void deleteChildlessElement(PsiElement element, Class<T> childClass) {
927 if (PsiTreeUtil.getChildrenOfType(element, childClass) == null) {
928 element.delete();
929 }
930 }
931
932 public static PsiElement ascendIfPropertyAccessor(PsiElement element) {
933 if (element instanceof JetPropertyAccessor) {
934 return element.getParent();
935 }
936 return element;
937 }
938
939 @NotNull
940 public static String getElementTextWithContext(@NotNull JetElement element) {
941 if (element instanceof JetFile) {
942 return element.getContainingFile().getText();
943 }
944
945 // Find parent for element among file children
946 PsiElement inFileParent = PsiTreeUtil.findFirstParent(element, new Condition<PsiElement>() {
947 @Override
948 public boolean value(PsiElement parentCandidate) {
949 return parentCandidate != null && parentCandidate.getParent() instanceof JetFile;
950 }
951 });
952
953 assert inFileParent != null : "For non-file element we should always be able to find parent in file children";
954
955 int startContextOffset = inFileParent.getTextRange().getStartOffset();
956 int elementContextOffset = element.getTextRange().getStartOffset();
957
958 int inFileParentOffset = elementContextOffset - startContextOffset;
959
960 return new StringBuilder(inFileParent.getText()).insert(inFileParentOffset, "<caret>").toString();
961 }
962
963 @Nullable
964 public static JetModifierList replaceModifierList(@NotNull JetModifierListOwner owner, @Nullable JetModifierList modifierList) {
965 JetModifierList oldModifierList = owner.getModifierList();
966 if (modifierList == null) {
967 if (oldModifierList != null) oldModifierList.delete();
968 return null;
969 }
970 else {
971 if (oldModifierList == null) {
972 PsiElement firstChild = owner.getFirstChild();
973 return (JetModifierList) owner.addBefore(modifierList, firstChild);
974 }
975 else {
976 return (JetModifierList) oldModifierList.replace(modifierList);
977 }
978 }
979 }
980
981 @Nullable
982 public static String getPackageName(@NotNull JetElement element) {
983 JetFile file = (JetFile) element.getContainingFile();
984 JetPackageDirective header = PsiTreeUtil.findChildOfType(file, JetPackageDirective.class);
985
986 return header != null ? header.getQualifiedName() : null;
987 }
988
989 @Nullable
990 public static JetElement getEnclosingElementForLocalDeclaration(@Nullable JetDeclaration declaration) {
991 if (declaration instanceof JetTypeParameter) {
992 declaration = PsiTreeUtil.getParentOfType(declaration, JetNamedDeclaration.class);
993 }
994 else if (declaration instanceof JetParameter) {
995 PsiElement parent = declaration.getParent();
996 if (parent != null && parent.getParent() instanceof JetNamedFunction) {
997 declaration = (JetNamedFunction) parent.getParent();
998 }
999 }
1000
1001 //noinspection unchecked
1002 JetElement container = PsiTreeUtil.getParentOfType(
1003 declaration,
1004 JetBlockExpression.class, JetClassInitializer.class, JetProperty.class, JetFunction.class, JetParameter.class
1005 );
1006 if (container == null) return null;
1007
1008 return (container instanceof JetClassInitializer) ? ((JetClassInitializer) container).getBody() : container;
1009 }
1010
1011 public static boolean isLocal(@NotNull JetDeclaration declaration) {
1012 return getEnclosingElementForLocalDeclaration(declaration) != null;
1013 }
1014
1015 @Nullable
1016 public static JetToken getOperationToken(@NotNull JetOperationExpression expression) {
1017 JetSimpleNameExpression operationExpression = expression.getOperationReference();
1018 IElementType elementType = operationExpression.getReferencedNameElementType();
1019 assert elementType == null || elementType instanceof JetToken :
1020 "JetOperationExpression should have operation token of type JetToken: " +
1021 expression;
1022 return (JetToken) elementType;
1023 }
1024
1025 public static boolean isLabelIdentifierExpression(PsiElement element) {
1026 return element instanceof JetSimpleNameExpression &&
1027 ((JetSimpleNameExpression) element).getReferencedNameElementType() == JetTokens.LABEL_IDENTIFIER;
1028 }
1029 }