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