001 /*
002 * Copyright 2010-2015 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.jetbrains.kotlin.psi;
018
019 import com.google.common.base.Function;
020 import com.google.common.base.Predicate;
021 import com.google.common.collect.Lists;
022 import com.intellij.lang.ASTNode;
023 import com.intellij.psi.PsiComment;
024 import com.intellij.psi.PsiElement;
025 import com.intellij.psi.PsiFile;
026 import com.intellij.psi.PsiWhiteSpace;
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.Contract;
032 import org.jetbrains.annotations.NotNull;
033 import org.jetbrains.annotations.Nullable;
034 import org.jetbrains.kotlin.JetNodeTypes;
035 import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
036 import org.jetbrains.kotlin.kdoc.psi.api.KDocElement;
037 import org.jetbrains.kotlin.lexer.JetToken;
038 import org.jetbrains.kotlin.lexer.JetTokens;
039 import org.jetbrains.kotlin.name.FqName;
040 import org.jetbrains.kotlin.name.Name;
041 import org.jetbrains.kotlin.name.SpecialNames;
042 import org.jetbrains.kotlin.parsing.JetExpressionParsing;
043 import org.jetbrains.kotlin.psi.psiUtil.PsiUtilPackage;
044 import org.jetbrains.kotlin.resolve.ResolvePackage;
045 import org.jetbrains.kotlin.resolve.StatementFilter;
046 import org.jetbrains.kotlin.types.expressions.OperatorConventions;
047
048 import java.util.Collection;
049 import java.util.HashSet;
050 import java.util.List;
051 import java.util.Set;
052
053 public class JetPsiUtil {
054 private JetPsiUtil() {
055 }
056
057 public interface JetExpressionWrapper {
058 JetExpression getBaseExpression();
059 }
060
061 public static <D> void visitChildren(@NotNull JetElement element, @NotNull JetVisitor<Void, D> visitor, D data) {
062 PsiElement child = element.getFirstChild();
063 while (child != null) {
064 if (child instanceof JetElement) {
065 ((JetElement) child).accept(visitor, data);
066 }
067 child = child.getNextSibling();
068 }
069 }
070
071 @NotNull
072 public static JetExpression safeDeparenthesize(@NotNull JetExpression expression) {
073 return safeDeparenthesize(expression, true);
074 }
075
076 @NotNull
077 public static JetExpression safeDeparenthesize(@NotNull JetExpression expression, boolean deparenthesizeBinaryExpressionWithTypeRHS) {
078 JetExpression deparenthesized = deparenthesize(expression, deparenthesizeBinaryExpressionWithTypeRHS);
079 return deparenthesized != null ? deparenthesized : expression;
080 }
081
082 @Nullable
083 public static JetExpression deparenthesize(@Nullable JetExpression expression) {
084 return deparenthesize(expression, /* deparenthesizeBinaryExpressionWithTypeRHS = */ true);
085 }
086
087 @Nullable
088 public static JetExpression deparenthesize(
089 @Nullable JetExpression expression,
090 boolean deparenthesizeBinaryExpressionWithTypeRHS
091 ) {
092 return deparenthesizeWithResolutionStrategy(
093 expression, deparenthesizeBinaryExpressionWithTypeRHS, /* deparenthesizeRecursively = */ null);
094 }
095
096 @Nullable
097 public static JetExpression deparenthesizeOnce(
098 @Nullable JetExpression expression,
099 boolean deparenthesizeBinaryExpressionWithTypeRHS
100 ) {
101 return deparenthesizeOnce(expression, deparenthesizeBinaryExpressionWithTypeRHS, null);
102 }
103
104 @Nullable
105 public static JetExpression deparenthesizeWithResolutionStrategy(
106 @Nullable JetExpression expression,
107 @Nullable Function<JetTypeReference, Void> typeResolutionStrategy
108 ) {
109 return deparenthesizeWithResolutionStrategy(expression, true, typeResolutionStrategy);
110 }
111
112 @Nullable
113 private static JetExpression deparenthesizeWithResolutionStrategy(
114 @Nullable JetExpression expression,
115 boolean deparenthesizeBinaryExpressionWithTypeRHS,
116 @Nullable Function<JetTypeReference, Void> typeResolutionStrategy
117 ) {
118 while (true) {
119 JetExpression baseExpression =
120 deparenthesizeOnce(expression, deparenthesizeBinaryExpressionWithTypeRHS, typeResolutionStrategy);
121
122 if (baseExpression == expression) return baseExpression;
123 expression = baseExpression;
124 }
125 }
126
127 @Nullable
128 private static JetExpression deparenthesizeOnce(
129 @Nullable JetExpression expression,
130 boolean deparenthesizeBinaryExpressionWithTypeRHS,
131 @Nullable Function<JetTypeReference, Void> typeResolutionStrategy
132 ) {
133 if (deparenthesizeBinaryExpressionWithTypeRHS && expression instanceof JetBinaryExpressionWithTypeRHS) {
134 JetBinaryExpressionWithTypeRHS binaryExpression = (JetBinaryExpressionWithTypeRHS) expression;
135 JetSimpleNameExpression operationSign = binaryExpression.getOperationReference();
136 if (JetTokens.COLON.equals(operationSign.getReferencedNameElementType())) {
137 JetTypeReference typeReference = binaryExpression.getRight();
138 if (typeResolutionStrategy != null && typeReference != null) {
139 typeResolutionStrategy.apply(typeReference);
140 }
141 return binaryExpression.getLeft();
142 }
143 return expression;
144 }
145 else if (expression instanceof JetAnnotatedExpression) {
146 return ((JetAnnotatedExpression) expression).getBaseExpression();
147 }
148 else if (expression instanceof JetLabeledExpression) {
149 return ((JetLabeledExpression) expression).getBaseExpression();
150 }
151 else if (expression instanceof JetExpressionWrapper) {
152 return ((JetExpressionWrapper) expression).getBaseExpression();
153 }
154 else if (expression instanceof JetParenthesizedExpression) {
155 return ((JetParenthesizedExpression) expression).getExpression();
156 }
157 return expression;
158 }
159
160 @NotNull
161 public static Name safeName(@Nullable String name) {
162 return name == null ? SpecialNames.NO_NAME_PROVIDED : Name.identifier(name);
163 }
164
165 @NotNull
166 public static Set<JetElement> findRootExpressions(@NotNull Collection<JetElement> unreachableElements) {
167 Set<JetElement> rootElements = new HashSet<JetElement>();
168 final Set<JetElement> shadowedElements = new HashSet<JetElement>();
169 JetVisitorVoid shadowAllChildren = new JetVisitorVoid() {
170 @Override
171 public void visitJetElement(@NotNull JetElement element) {
172 if (shadowedElements.add(element)) {
173 element.acceptChildren(this);
174 }
175 }
176 };
177
178 for (JetElement element : unreachableElements) {
179 if (shadowedElements.contains(element)) continue;
180 element.acceptChildren(shadowAllChildren);
181
182 rootElements.removeAll(shadowedElements);
183 rootElements.add(element);
184 }
185 return rootElements;
186 }
187
188 @NotNull
189 public static String unquoteIdentifier(@NotNull String quoted) {
190 if (quoted.indexOf('`') < 0) {
191 return quoted;
192 }
193
194 if (quoted.startsWith("`") && quoted.endsWith("`") && quoted.length() >= 2) {
195 return quoted.substring(1, quoted.length() - 1);
196 }
197 else {
198 return quoted;
199 }
200 }
201
202 @NotNull
203 public static String unquoteIdentifierOrFieldReference(@NotNull String quoted) {
204 if (quoted.indexOf('`') < 0) {
205 return quoted;
206 }
207
208 if (quoted.startsWith("$")) {
209 return "$" + unquoteIdentifier(quoted.substring(1));
210 }
211 else {
212 return unquoteIdentifier(quoted);
213 }
214 }
215
216 /** @return <code>null</code> iff the tye has syntactic errors */
217 @Nullable
218 public static FqName toQualifiedName(@NotNull JetUserType userType) {
219 List<String> reversedNames = Lists.newArrayList();
220
221 JetUserType current = userType;
222 while (current != null) {
223 String name = current.getReferencedName();
224 if (name == null) return null;
225
226 reversedNames.add(name);
227 current = current.getQualifier();
228 }
229
230 return FqName.fromSegments(ContainerUtil.reverse(reversedNames));
231 }
232
233 @Nullable
234 public static Name getShortName(@NotNull JetAnnotationEntry annotation) {
235 JetTypeReference typeReference = annotation.getTypeReference();
236 assert typeReference != null : "Annotation entry hasn't typeReference " + annotation.getText();
237 JetTypeElement typeElement = typeReference.getTypeElement();
238 if (typeElement instanceof JetUserType) {
239 JetUserType userType = (JetUserType) typeElement;
240 String shortName = userType.getReferencedName();
241 if (shortName != null) {
242 return Name.identifier(shortName);
243 }
244 }
245 return null;
246 }
247
248 public static boolean isDeprecated(@NotNull JetModifierListOwner owner) {
249 JetModifierList modifierList = owner.getModifierList();
250 if (modifierList != null) {
251 List<JetAnnotationEntry> annotationEntries = modifierList.getAnnotationEntries();
252 for (JetAnnotationEntry annotation : annotationEntries) {
253 Name shortName = getShortName(annotation);
254 if (KotlinBuiltIns.FQ_NAMES.deprecated.shortName().equals(shortName)) {
255 return true;
256 }
257 }
258 }
259 return false;
260 }
261
262 @Nullable
263 public static <T extends PsiElement> T getDirectParentOfTypeForBlock(@NotNull JetBlockExpression block, @NotNull Class<T> aClass) {
264 T parent = PsiTreeUtil.getParentOfType(block, aClass);
265 if (parent instanceof JetIfExpression) {
266 JetIfExpression ifExpression = (JetIfExpression) parent;
267 if (ifExpression.getElse() == block || ifExpression.getThen() == block) {
268 return parent;
269 }
270 }
271 if (parent instanceof JetWhenExpression) {
272 JetWhenExpression whenExpression = (JetWhenExpression) parent;
273 for (JetWhenEntry whenEntry : whenExpression.getEntries()) {
274 if (whenEntry.getExpression() == block) {
275 return parent;
276 }
277 }
278 }
279 if (parent instanceof JetFunctionLiteral) {
280 JetFunctionLiteral functionLiteral = (JetFunctionLiteral) parent;
281 if (functionLiteral.getBodyExpression() == block) {
282 return parent;
283 }
284 }
285 if (parent instanceof JetTryExpression) {
286 JetTryExpression tryExpression = (JetTryExpression) parent;
287 if (tryExpression.getTryBlock() == block) {
288 return parent;
289 }
290 for (JetCatchClause clause : tryExpression.getCatchClauses()) {
291 if (clause.getCatchBody() == block) {
292 return parent;
293 }
294 }
295 }
296 return null;
297 }
298
299 @Nullable
300 public static Name getAliasName(@NotNull JetImportDirective importDirective) {
301 String aliasName = importDirective.getAliasName();
302 JetExpression importedReference = importDirective.getImportedReference();
303 if (importedReference == null) {
304 return null;
305 }
306 JetSimpleNameExpression referenceExpression = getLastReference(importedReference);
307 if (aliasName == null) {
308 aliasName = referenceExpression != null ? referenceExpression.getReferencedName() : null;
309 }
310
311 return aliasName != null && !aliasName.isEmpty() ? Name.identifier(aliasName) : null;
312 }
313
314 @Nullable
315 public static JetSimpleNameExpression getLastReference(@NotNull JetExpression importedReference) {
316 JetElement selector = PsiUtilPackage.getQualifiedElementSelector(importedReference);
317 return selector instanceof JetSimpleNameExpression ? (JetSimpleNameExpression) selector : null;
318 }
319
320 public static boolean isSelectorInQualified(@NotNull JetSimpleNameExpression nameExpression) {
321 JetElement qualifiedElement = PsiUtilPackage.getQualifiedElement(nameExpression);
322 return qualifiedElement instanceof JetQualifiedExpression
323 || ((qualifiedElement instanceof JetUserType) && ((JetUserType) qualifiedElement).getQualifier() != null);
324 }
325
326 public static boolean isLHSOfDot(@NotNull JetExpression expression) {
327 PsiElement parent = expression.getParent();
328 if (!(parent instanceof JetQualifiedExpression)) return false;
329 JetQualifiedExpression qualifiedParent = (JetQualifiedExpression) parent;
330 return qualifiedParent.getReceiverExpression() == expression || isLHSOfDot(qualifiedParent);
331 }
332
333 // SCRIPT: is declaration in script?
334 public static boolean isScriptDeclaration(@NotNull JetDeclaration namedDeclaration) {
335 return getScript(namedDeclaration) != null;
336 }
337
338 // SCRIPT: get script from top-level declaration
339 @Nullable
340 public static JetScript getScript(@NotNull JetDeclaration namedDeclaration) {
341 PsiElement parent = namedDeclaration.getParent();
342 if (parent != null && parent.getParent() instanceof JetScript) {
343 return (JetScript) parent.getParent();
344 }
345 else {
346 return null;
347 }
348 }
349
350 public static boolean isVariableNotParameterDeclaration(@NotNull JetDeclaration declaration) {
351 if (!(declaration instanceof JetVariableDeclaration)) return false;
352 if (declaration instanceof JetProperty) return true;
353 assert declaration instanceof JetMultiDeclarationEntry;
354 JetMultiDeclarationEntry multiDeclarationEntry = (JetMultiDeclarationEntry) declaration;
355 return !(multiDeclarationEntry.getParent().getParent() instanceof JetForExpression);
356 }
357
358 @Nullable
359 public static Name getConventionName(@NotNull JetSimpleNameExpression simpleNameExpression) {
360 if (simpleNameExpression.getIdentifier() != null) {
361 return simpleNameExpression.getReferencedNameAsName();
362 }
363
364 PsiElement firstChild = simpleNameExpression.getFirstChild();
365 if (firstChild != null) {
366 IElementType elementType = firstChild.getNode().getElementType();
367 if (elementType instanceof JetToken) {
368 JetToken jetToken = (JetToken) elementType;
369 return OperatorConventions.getNameForOperationSymbol(jetToken);
370 }
371 }
372
373 return null;
374 }
375
376 @Nullable
377 @Contract("null, _ -> null")
378 public static PsiElement getTopmostParentOfTypes(
379 @Nullable PsiElement element,
380 @NotNull Class<? extends PsiElement>... parentTypes) {
381 if (element instanceof PsiFile) return null;
382
383 PsiElement answer = PsiTreeUtil.getParentOfType(element, parentTypes);
384 if (answer instanceof PsiFile) return answer;
385
386 do {
387 PsiElement next = PsiTreeUtil.getParentOfType(answer, parentTypes);
388 if (next == null) break;
389 answer = next;
390 }
391 while (true);
392
393 return answer;
394 }
395
396 public static boolean isNullConstant(@NotNull JetExpression expression) {
397 JetExpression deparenthesized = deparenthesize(expression);
398 return deparenthesized instanceof JetConstantExpression && deparenthesized.getNode().getElementType() == JetNodeTypes.NULL;
399 }
400
401 public static boolean isTrueConstant(@Nullable JetExpression condition) {
402 return (condition != null && condition.getNode().getElementType() == JetNodeTypes.BOOLEAN_CONSTANT &&
403 condition.getNode().findChildByType(JetTokens.TRUE_KEYWORD) != null);
404 }
405
406 public static boolean isAbstract(@NotNull JetDeclarationWithBody declaration) {
407 return declaration.getBodyExpression() == null;
408 }
409
410 public static boolean isBackingFieldReference(@NotNull JetSimpleNameExpression expression) {
411 return expression.getReferencedNameElementType() == JetTokens.FIELD_IDENTIFIER;
412 }
413
414 public static boolean isBackingFieldReference(@Nullable JetElement element) {
415 return element instanceof JetSimpleNameExpression && isBackingFieldReference((JetSimpleNameExpression)element);
416 }
417
418 @Nullable
419 public static JetExpression getExpressionOrLastStatementInBlock(@Nullable JetExpression expression) {
420 if (expression instanceof JetBlockExpression) {
421 return getLastStatementInABlock((JetBlockExpression) expression);
422 }
423 return expression;
424 }
425
426 @Nullable
427 public static JetExpression getLastStatementInABlock(@Nullable JetBlockExpression blockExpression) {
428 if (blockExpression == null) return null;
429 List<JetExpression> statements = blockExpression.getStatements();
430 return statements.isEmpty() ? null : statements.get(statements.size() - 1);
431 }
432
433 public static boolean isTrait(@NotNull JetClassOrObject classOrObject) {
434 return classOrObject instanceof JetClass && ((JetClass) classOrObject).isInterface();
435 }
436
437 @Nullable
438 public static JetClassOrObject getOutermostClassOrObject(@NotNull JetClassOrObject classOrObject) {
439 JetClassOrObject current = classOrObject;
440 while (true) {
441 PsiElement parent = current.getParent();
442 assert classOrObject.getParent() != null : "Class with no parent: " + classOrObject.getText();
443
444 if (parent instanceof PsiFile) {
445 return current;
446 }
447 if (!(parent instanceof JetClassBody)) {
448 // It is a local class, no legitimate outer
449 return current;
450 }
451
452 current = (JetClassOrObject) parent.getParent();
453 }
454 }
455
456 @Nullable
457 public static JetClassOrObject getClassIfParameterIsProperty(@NotNull JetParameter jetParameter) {
458 if (jetParameter.hasValOrVar()) {
459 PsiElement grandParent = jetParameter.getParent().getParent();
460 if (grandParent instanceof JetPrimaryConstructor) {
461 return ((JetPrimaryConstructor) grandParent).getContainingClassOrObject();
462 }
463 }
464
465 return null;
466 }
467
468 @Nullable
469 private static IElementType getOperation(@NotNull JetExpression expression) {
470 if (expression instanceof JetQualifiedExpression) {
471 return ((JetQualifiedExpression) expression).getOperationSign();
472 }
473 else if (expression instanceof JetOperationExpression) {
474 return ((JetOperationExpression) expression).getOperationReference().getReferencedNameElementType();
475 }
476 return null;
477 }
478
479
480 private static int getPriority(@NotNull JetExpression expression) {
481 int maxPriority = JetExpressionParsing.Precedence.values().length + 1;
482
483 // same as postfix operations
484 if (expression instanceof JetPostfixExpression ||
485 expression instanceof JetQualifiedExpression ||
486 expression instanceof JetCallExpression ||
487 expression instanceof JetArrayAccessExpression) {
488 return maxPriority - 1;
489 }
490
491 if (expression instanceof JetPrefixExpression || expression instanceof JetLabeledExpression) return maxPriority - 2;
492
493 if (expression instanceof JetIfExpression) {
494 return JetExpressionParsing.Precedence.ASSIGNMENT.ordinal();
495 }
496
497 if (expression instanceof JetSuperExpression) {
498 return maxPriority;
499 }
500
501 if (expression instanceof JetDeclaration || expression instanceof JetStatementExpression) {
502 return 0;
503 }
504
505 IElementType operation = getOperation(expression);
506 for (JetExpressionParsing.Precedence precedence : JetExpressionParsing.Precedence.values()) {
507 if (precedence != JetExpressionParsing.Precedence.PREFIX && precedence != JetExpressionParsing.Precedence.POSTFIX &&
508 precedence.getOperations().contains(operation)) {
509 return maxPriority - precedence.ordinal() - 1;
510 }
511 }
512
513 return maxPriority;
514 }
515
516 public static boolean areParenthesesUseless(@NotNull JetParenthesizedExpression expression) {
517 JetExpression innerExpression = expression.getExpression();
518 if (innerExpression == null) return true;
519
520 PsiElement parent = expression.getParent();
521 if (!(parent instanceof JetExpression)) return true;
522
523 return !areParenthesesNecessary(innerExpression, expression, (JetExpression) parent);
524 }
525
526 public static boolean areParenthesesNecessary(@NotNull JetExpression innerExpression, @NotNull JetExpression currentInner, @NotNull JetExpression parentExpression) {
527 if (parentExpression instanceof JetParenthesizedExpression || innerExpression instanceof JetParenthesizedExpression) {
528 return false;
529 }
530
531 if (parentExpression instanceof JetPackageDirective) return false;
532
533 if (parentExpression instanceof JetWhenExpression || innerExpression instanceof JetWhenExpression) {
534 return false;
535 }
536
537 if (innerExpression instanceof JetIfExpression) {
538 PsiElement current = parentExpression;
539
540 while (!(current instanceof JetBlockExpression || current instanceof JetDeclaration || current instanceof JetStatementExpression)) {
541 if (current.getTextRange().getEndOffset() != currentInner.getTextRange().getEndOffset()) {
542 return current.getText().charAt(current.getTextLength() - 1) != ')'; // if current expression is "guarded" by parenthesis, no extra parenthesis is necessary
543 }
544
545 current = current.getParent();
546 }
547 }
548
549 if (parentExpression instanceof JetCallExpression && currentInner == ((JetCallExpression) parentExpression).getCalleeExpression()) {
550 if (innerExpression instanceof JetSimpleNameExpression) return false;
551 if (PsiUtilPackage.getQualifiedExpressionForSelector(parentExpression) != null) return true;
552 return !(innerExpression instanceof JetThisExpression
553 || innerExpression instanceof JetArrayAccessExpression
554 || innerExpression instanceof JetConstantExpression
555 || innerExpression instanceof JetStringTemplateExpression
556 || innerExpression instanceof JetCallExpression);
557 }
558
559 IElementType innerOperation = getOperation(innerExpression);
560 IElementType parentOperation = getOperation(parentExpression);
561
562 // 'return (@label{...})' case
563 if (parentExpression instanceof JetReturnExpression
564 && (innerExpression instanceof JetLabeledExpression || innerExpression instanceof JetAnnotatedExpression)) return true;
565
566 // '(x: Int) < y' case
567 if (innerExpression instanceof JetBinaryExpressionWithTypeRHS && parentOperation == JetTokens.LT) {
568 return true;
569 }
570
571 if (parentExpression instanceof JetLabeledExpression) return false;
572
573 // 'x ?: ...' case
574 if (parentExpression instanceof JetBinaryExpression && parentOperation == JetTokens.ELVIS && currentInner == ((JetBinaryExpression) parentExpression).getRight()) {
575 return false;
576 }
577
578 int innerPriority = getPriority(innerExpression);
579 int parentPriority = getPriority(parentExpression);
580
581 if (innerPriority == parentPriority) {
582 if (parentExpression instanceof JetBinaryExpression) {
583 if (innerOperation == JetTokens.ANDAND || innerOperation == JetTokens.OROR) {
584 return false;
585 }
586 return ((JetBinaryExpression) parentExpression).getRight() == currentInner;
587 }
588
589 //'-(-x)' case
590 if (parentExpression instanceof JetPrefixExpression && innerExpression instanceof JetPrefixExpression) {
591 return innerOperation == parentOperation && (innerOperation == JetTokens.PLUS || innerOperation == JetTokens.MINUS);
592 }
593 return false;
594 }
595
596 return innerPriority < parentPriority;
597 }
598
599 public static boolean isAssignment(@NotNull PsiElement element) {
600 return element instanceof JetBinaryExpression &&
601 JetTokens.ALL_ASSIGNMENTS.contains(((JetBinaryExpression) element).getOperationToken());
602 }
603
604 public static boolean isOrdinaryAssignment(@NotNull PsiElement element) {
605 return element instanceof JetBinaryExpression &&
606 ((JetBinaryExpression) element).getOperationToken().equals(JetTokens.EQ);
607 }
608
609 public static boolean checkVariableDeclarationInBlock(@NotNull JetBlockExpression block, @NotNull String varName) {
610 for (JetExpression element : block.getStatements()) {
611 if (element instanceof JetVariableDeclaration) {
612 if (((JetVariableDeclaration) element).getNameAsSafeName().asString().equals(varName)) {
613 return true;
614 }
615 }
616 }
617
618 return false;
619 }
620
621 public static boolean checkWhenExpressionHasSingleElse(@NotNull JetWhenExpression whenExpression) {
622 int elseCount = 0;
623 for (JetWhenEntry entry : whenExpression.getEntries()) {
624 if (entry.isElse()) {
625 elseCount++;
626 }
627 }
628 return (elseCount == 1);
629 }
630
631 @Nullable
632 public static PsiElement skipTrailingWhitespacesAndComments(@Nullable PsiElement element) {
633 return PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace.class, PsiComment.class);
634 }
635
636 public static final Predicate<JetElement> ANY_JET_ELEMENT = new Predicate<JetElement>() {
637 @Override
638 public boolean apply(@Nullable JetElement input) {
639 return true;
640 }
641 };
642
643 @NotNull
644 public static String getText(@Nullable PsiElement element) {
645 return element != null ? element.getText() : "";
646 }
647
648 @Nullable
649 public static String getNullableText(@Nullable PsiElement element) {
650 return element != null ? element.getText() : null;
651 }
652
653 /**
654 * CommentUtilCore.isComment fails if element <strong>inside</strong> comment.
655 *
656 * Also, we can not add KDocTokens to COMMENTS TokenSet, because it is used in JetParserDefinition.getCommentTokens(),
657 * and therefor all COMMENTS tokens will be ignored by PsiBuilder.
658 *
659 * @param element
660 * @return
661 */
662 public static boolean isInComment(PsiElement element) {
663 return CommentUtilCore.isComment(element) || element instanceof KDocElement;
664 }
665
666 @Nullable
667 public static PsiElement getOutermostParent(@NotNull PsiElement element, @NotNull PsiElement upperBound, boolean strict) {
668 PsiElement parent = strict ? element.getParent() : element;
669 while (parent != null && parent.getParent() != upperBound) {
670 parent = parent.getParent();
671 }
672
673 return parent;
674 }
675
676 public static <T extends PsiElement> T getLastChildByType(@NotNull PsiElement root, @NotNull Class<? extends T>... elementTypes) {
677 PsiElement[] children = root.getChildren();
678
679 for (int i = children.length - 1; i >= 0; i--) {
680 if (PsiTreeUtil.instanceOf(children[i], elementTypes)) {
681 //noinspection unchecked
682 return (T) children[i];
683 }
684 }
685
686 return null;
687 }
688
689 @Nullable
690 public static JetElement getOutermostDescendantElement(
691 @Nullable PsiElement root,
692 boolean first,
693 final @NotNull Predicate<JetElement> predicate
694 ) {
695 if (!(root instanceof JetElement)) return null;
696
697 final List<JetElement> results = Lists.newArrayList();
698
699 root.accept(
700 new JetVisitorVoid() {
701 @Override
702 public void visitJetElement(@NotNull JetElement element) {
703 if (predicate.apply(element)) {
704 //noinspection unchecked
705 results.add(element);
706 }
707 else {
708 element.acceptChildren(this);
709 }
710 }
711 }
712 );
713
714 if (results.isEmpty()) return null;
715
716 return first ? results.get(0) : results.get(results.size() - 1);
717 }
718
719 @Nullable
720 public static PsiElement findChildByType(@NotNull PsiElement element, @NotNull IElementType type) {
721 ASTNode node = element.getNode().findChildByType(type);
722 return node == null ? null : node.getPsi();
723 }
724
725 @Nullable
726 public static PsiElement skipSiblingsBackwardByPredicate(@Nullable PsiElement element, Predicate<PsiElement> elementsToSkip) {
727 if (element == null) return null;
728 for (PsiElement e = element.getPrevSibling(); e != null; e = e.getPrevSibling()) {
729 if (elementsToSkip.apply(e)) continue;
730 return e;
731 }
732 return null;
733 }
734
735 public static PsiElement ascendIfPropertyAccessor(PsiElement element) {
736 if (element instanceof JetPropertyAccessor) {
737 return element.getParent();
738 }
739 return element;
740 }
741
742 @Nullable
743 public static JetModifierList replaceModifierList(@NotNull JetModifierListOwner owner, @Nullable JetModifierList modifierList) {
744 JetModifierList oldModifierList = owner.getModifierList();
745 if (modifierList == null) {
746 if (oldModifierList != null) oldModifierList.delete();
747 return null;
748 }
749 else {
750 if (oldModifierList == null) {
751 PsiElement firstChild = owner.getFirstChild();
752 return (JetModifierList) owner.addBefore(modifierList, firstChild);
753 }
754 else {
755 return (JetModifierList) oldModifierList.replace(modifierList);
756 }
757 }
758 }
759
760 @Nullable
761 public static String getPackageName(@NotNull JetElement element) {
762 JetFile file = element.getContainingJetFile();
763 JetPackageDirective header = PsiTreeUtil.findChildOfType(file, JetPackageDirective.class);
764
765 return header != null ? header.getQualifiedName() : null;
766 }
767
768 @Nullable
769 public static JetElement getEnclosingElementForLocalDeclaration(@NotNull JetDeclaration declaration) {
770 return getEnclosingElementForLocalDeclaration(declaration, true);
771 }
772
773 private static boolean isMemberOfObjectExpression(@NotNull JetCallableDeclaration propertyOrFunction) {
774 PsiElement parent = PsiTreeUtil.getStubOrPsiParent(propertyOrFunction);
775 if (!(parent instanceof JetClassBody)) return false;
776 PsiElement grandparent = PsiTreeUtil.getStubOrPsiParent(parent);
777 if (!(grandparent instanceof JetObjectDeclaration)) return false;
778 return PsiTreeUtil.getStubOrPsiParent(grandparent) instanceof JetObjectLiteralExpression;
779 }
780
781 @Nullable
782 public static JetElement getEnclosingElementForLocalDeclaration(@NotNull JetDeclaration declaration, boolean skipParameters) {
783 if (declaration instanceof JetTypeParameter && skipParameters) {
784 declaration = PsiTreeUtil.getParentOfType(declaration, JetNamedDeclaration.class);
785 }
786 else if (declaration instanceof JetParameter) {
787 PsiElement parent = declaration.getParent();
788
789 // val/var parameter of primary constructor should be considered as local according to containing class
790 if (((JetParameter) declaration).hasValOrVar() && parent != null && parent.getParent() instanceof JetPrimaryConstructor) {
791 return getEnclosingElementForLocalDeclaration(((JetPrimaryConstructor) parent.getParent()).getContainingClassOrObject(), skipParameters);
792 }
793
794 else if (skipParameters && parent != null && parent.getParent() instanceof JetNamedFunction) {
795 declaration = (JetNamedFunction) parent.getParent();
796 }
797 }
798
799 if (declaration instanceof PsiFile) {
800 return declaration;
801 }
802
803 // No appropriate stub-tolerant method in PsiTreeUtil, nor JetStubbedPsiUtil, writing manually
804 PsiElement current = PsiTreeUtil.getStubOrPsiParent(declaration);
805 while (current != null) {
806 PsiElement parent = PsiTreeUtil.getStubOrPsiParent(current);
807 if (parent instanceof JetScript) return null;
808 if (current instanceof JetClassInitializer) {
809 return ((JetClassInitializer) current).getBody();
810 }
811 if (current instanceof JetProperty || current instanceof JetFunction) {
812 if (parent instanceof JetFile) {
813 return (JetElement) current;
814 }
815 else if (parent instanceof JetClassBody && !isMemberOfObjectExpression((JetCallableDeclaration) current)) {
816 return (JetElement) parent;
817 }
818 }
819 if (current instanceof JetBlockExpression || current instanceof JetParameter) {
820 return (JetElement) current;
821 }
822
823 current = parent;
824 }
825 return null;
826 }
827
828 public static boolean isLocal(@NotNull JetDeclaration declaration) {
829 return getEnclosingElementForLocalDeclaration(declaration) != null;
830 }
831
832 @Nullable
833 public static JetToken getOperationToken(@NotNull JetOperationExpression expression) {
834 JetSimpleNameExpression operationExpression = expression.getOperationReference();
835 IElementType elementType = operationExpression.getReferencedNameElementType();
836 assert elementType == null || elementType instanceof JetToken :
837 "JetOperationExpression should have operation token of type JetToken: " +
838 expression;
839 return (JetToken) elementType;
840 }
841
842 public static boolean isLabelIdentifierExpression(PsiElement element) {
843 return element instanceof JetLabelReferenceExpression;
844 }
845
846 @Nullable
847 public static JetExpression getParentCallIfPresent(@NotNull JetExpression expression) {
848 PsiElement parent = expression.getParent();
849 while (parent != null) {
850 if (parent instanceof JetBinaryExpression ||
851 parent instanceof JetUnaryExpression ||
852 parent instanceof JetLabeledExpression ||
853 parent instanceof JetDotQualifiedExpression ||
854 parent instanceof JetCallExpression ||
855 parent instanceof JetArrayAccessExpression ||
856 parent instanceof JetMultiDeclaration) {
857
858 if (parent instanceof JetLabeledExpression) {
859 parent = parent.getParent();
860 continue;
861 }
862
863 //check that it's in inlineable call would be in resolve call of parent
864 return (JetExpression) parent;
865 }
866 else if (parent instanceof JetParenthesizedExpression || parent instanceof JetBinaryExpressionWithTypeRHS) {
867 parent = parent.getParent();
868 }
869 else if (parent instanceof JetValueArgument || parent instanceof JetValueArgumentList) {
870 parent = parent.getParent();
871 }
872 else if (parent instanceof JetFunctionLiteralExpression) {
873 parent = parent.getParent();
874 }
875 else {
876 return null;
877 }
878 }
879 return null;
880 }
881
882 public static boolean isDeprecatedLambdaSyntax(@NotNull JetFunctionLiteralExpression functionLiteralExpression) {
883 JetFunctionLiteral functionLiteral = functionLiteralExpression.getFunctionLiteral();
884 if (functionLiteral.hasDeclaredReturnType() || functionLiteral.getReceiverTypeReference() != null) return true;
885
886 JetParameterList valueParameterList = functionLiteral.getValueParameterList();
887 if (valueParameterList != null && valueParameterList.isParenthesized()) return true;
888
889 return false;
890 }
891
892 @Nullable
893 public static JetExpression getLastElementDeparenthesized(
894 @Nullable JetExpression expression,
895 @NotNull StatementFilter statementFilter
896 ) {
897 JetExpression deparenthesizedExpression = JetPsiUtil.deparenthesize(expression, false);
898 if (deparenthesizedExpression instanceof JetBlockExpression) {
899 JetBlockExpression blockExpression = (JetBlockExpression) deparenthesizedExpression;
900 // todo
901 // This case is a temporary hack for 'if' branches.
902 // The right way to implement this logic is to interpret 'if' branches as function literals with explicitly-typed signatures
903 // (no arguments and no receiver) and therefore analyze them straight away (not in the 'complete' phase).
904 JetExpression lastStatementInABlock = ResolvePackage.getLastStatementInABlock(statementFilter, blockExpression);
905 if (lastStatementInABlock != null) {
906 return getLastElementDeparenthesized(lastStatementInABlock, statementFilter);
907 }
908 }
909 return deparenthesizedExpression;
910 }
911 }