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 }