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