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