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