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