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