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