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.diagnostics;
018    
019    
020    import com.google.common.collect.ImmutableList;
021    import com.google.common.collect.Lists;
022    import com.intellij.lang.ASTNode;
023    import com.intellij.openapi.util.TextRange;
024    import com.intellij.psi.PsiElement;
025    import com.intellij.psi.PsiNameIdentifierOwner;
026    import kotlin.Function1;
027    import org.jetbrains.annotations.NotNull;
028    import org.jetbrains.jet.JetNodeTypes;
029    import org.jetbrains.jet.lang.psi.*;
030    import org.jetbrains.jet.lexer.JetKeywordToken;
031    import org.jetbrains.jet.lexer.JetModifierKeywordToken;
032    import org.jetbrains.jet.lexer.JetTokens;
033    
034    import java.util.Arrays;
035    import java.util.Collections;
036    import java.util.List;
037    
038    public class PositioningStrategies {
039    
040        public static final PositioningStrategy<PsiElement> DEFAULT = new PositioningStrategy<PsiElement>() {
041            @NotNull
042            @Override
043            public List<TextRange> mark(@NotNull PsiElement element) {
044                if (element instanceof JetObjectLiteralExpression) {
045                    JetObjectDeclaration objectDeclaration = ((JetObjectLiteralExpression) element).getObjectDeclaration();
046                    PsiElement objectKeyword = objectDeclaration.getObjectKeyword();
047                    JetDelegationSpecifierList delegationSpecifierList = objectDeclaration.getDelegationSpecifierList();
048                    if (delegationSpecifierList == null) {
049                        return markElement(objectKeyword);
050                    }
051                    return markRange(objectKeyword.getTextRange().union(delegationSpecifierList.getTextRange()));
052                }
053                return super.mark(element);
054            }
055        };
056    
057        public static final PositioningStrategy<JetDeclaration> DECLARATION_RETURN_TYPE = new PositioningStrategy<JetDeclaration>() {
058            @NotNull
059            @Override
060            public List<TextRange> mark(@NotNull JetDeclaration declaration) {
061                return markElement(getElementToMark(declaration));
062            }
063    
064            @Override
065            public boolean isValid(@NotNull JetDeclaration declaration) {
066                return !hasSyntaxErrors(getElementToMark(declaration));
067            }
068    
069            private PsiElement getElementToMark(@NotNull JetDeclaration declaration) {
070                JetTypeReference returnTypeRef = null;
071                PsiElement nameIdentifierOrPlaceholder = null;
072                if (declaration instanceof JetNamedFunction) {
073                    JetFunction function = (JetNamedFunction) declaration;
074                    returnTypeRef = function.getReturnTypeRef();
075                    nameIdentifierOrPlaceholder = function.getNameIdentifier();
076                }
077                else if (declaration instanceof JetProperty) {
078                    JetProperty property = (JetProperty) declaration;
079                    returnTypeRef = property.getTypeRef();
080                    nameIdentifierOrPlaceholder = property.getNameIdentifier();
081                }
082                else if (declaration instanceof JetPropertyAccessor) {
083                    JetPropertyAccessor accessor = (JetPropertyAccessor) declaration;
084                    returnTypeRef = accessor.getReturnTypeReference();
085                    nameIdentifierOrPlaceholder = accessor.getNamePlaceholder();
086                }
087    
088                if (returnTypeRef != null) return returnTypeRef;
089                if (nameIdentifierOrPlaceholder != null) return nameIdentifierOrPlaceholder;
090                return declaration;
091            }
092        };
093    
094        public static final PositioningStrategy<PsiNameIdentifierOwner> NAME_IDENTIFIER = new PositioningStrategy<PsiNameIdentifierOwner>() {
095            @NotNull
096            @Override
097            public List<TextRange> mark(@NotNull PsiNameIdentifierOwner element) {
098                PsiElement nameIdentifier = element.getNameIdentifier();
099                if (nameIdentifier != null) {
100                    return markElement(nameIdentifier);
101                }
102                if (element instanceof JetObjectDeclaration) {
103                    PsiElement objectKeyword = ((JetObjectDeclaration) element).getObjectKeyword();
104                    PsiElement parent = element.getParent();
105                    if (parent instanceof JetClassObject) {
106                        PsiElement classKeyword = ((JetClassObject) parent).getClassKeywordNode();
107                        PsiElement start = classKeyword == null ? objectKeyword : classKeyword;
108                        return markRange(new TextRange(start.getTextRange().getStartOffset(), objectKeyword.getTextRange().getEndOffset()));
109                    }
110                    return markElement(objectKeyword);
111                }
112                return markElement(element);
113            }
114        };
115    
116        public static final PositioningStrategy<PsiNameIdentifierOwner> NAMED_ELEMENT = new DeclarationHeader<PsiNameIdentifierOwner>() {
117            @Override
118            public boolean isValid(@NotNull PsiNameIdentifierOwner element) {
119                return (element.getNameIdentifier() != null || element instanceof JetObjectDeclaration) && super.isValid(element);
120            }
121        };
122    
123        private static class DeclarationHeader<T extends PsiElement> extends PositioningStrategy<T> {
124            @NotNull
125            @Override
126            public List<TextRange> mark(@NotNull T element) {
127                if (element instanceof JetNamedFunction) {
128                    JetNamedFunction function = (JetNamedFunction)element;
129                    PsiElement endOfSignatureElement;
130                    JetParameterList valueParameterList = function.getValueParameterList();
131                    JetElement returnTypeRef = function.getReturnTypeRef();
132                    PsiElement nameIdentifier = function.getNameIdentifier();
133                    if (returnTypeRef != null) {
134                        endOfSignatureElement = returnTypeRef;
135                    }
136                    else if (valueParameterList != null) {
137                        endOfSignatureElement = valueParameterList;
138                    }
139                    else if (nameIdentifier != null) {
140                        endOfSignatureElement = nameIdentifier;
141                    }
142                    else {
143                        endOfSignatureElement = function;
144                    }
145                    return markRange(new TextRange(
146                            function.getTextRange().getStartOffset(), endOfSignatureElement.getTextRange().getEndOffset()));
147                }
148                else if (element instanceof JetProperty) {
149                    JetProperty property = (JetProperty) element;
150                    PsiElement endOfSignatureElement;
151                    JetTypeReference propertyTypeRef = property.getTypeRef();
152                    PsiElement nameIdentifier = property.getNameIdentifier();
153                    if (propertyTypeRef != null) {
154                        endOfSignatureElement = propertyTypeRef;
155                    }
156                    else if (nameIdentifier != null) {
157                        endOfSignatureElement = nameIdentifier;
158                    }
159                    else {
160                        endOfSignatureElement = property;
161                    }
162                    return markRange(new TextRange(
163                            property.getTextRange().getStartOffset(), endOfSignatureElement.getTextRange().getEndOffset()));
164                }
165                else if (element instanceof JetPropertyAccessor) {
166                    JetPropertyAccessor accessor = (JetPropertyAccessor) element;
167                    PsiElement endOfSignatureElement = accessor.getReturnTypeReference();
168                    if (endOfSignatureElement == null) {
169                        ASTNode rpar = accessor.getRightParenthesis();
170                        endOfSignatureElement = rpar == null ? null : rpar.getPsi();
171                    }
172                    if (endOfSignatureElement == null) {
173                        endOfSignatureElement = accessor.getNamePlaceholder();
174                    }
175                    return markRange(new TextRange(
176                            accessor.getTextRange().getStartOffset(), endOfSignatureElement.getTextRange().getEndOffset()));
177                }
178                else if (element instanceof JetClass) {
179                    PsiElement nameAsDeclaration = ((JetClass) element).getNameIdentifier();
180                    if (nameAsDeclaration == null) {
181                        return markElement(element);
182                    }
183                    PsiElement primaryConstructorParameterList = ((JetClass) element).getPrimaryConstructorParameterList();
184                    if (primaryConstructorParameterList == null) {
185                        return markRange(nameAsDeclaration.getTextRange());
186                    }
187                    return markRange(new TextRange(
188                            nameAsDeclaration.getTextRange().getStartOffset(), primaryConstructorParameterList.getTextRange().getEndOffset()));
189                }
190                else if (element instanceof JetObjectDeclaration) {
191                    return NAME_IDENTIFIER.mark((JetObjectDeclaration) element);
192                }
193                return super.mark(element);
194            }
195        }
196    
197        public static final PositioningStrategy<JetDeclaration> DECLARATION = new PositioningStrategy<JetDeclaration>() {
198            @NotNull
199            @Override
200            public List<TextRange> mark(@NotNull JetDeclaration element) {
201                if (element instanceof PsiNameIdentifierOwner) {
202                    return NAMED_ELEMENT.mark((PsiNameIdentifierOwner) element);
203                }
204                return super.mark(element);
205            }
206    
207            @Override
208            public boolean isValid(@NotNull JetDeclaration element) {
209                if (element instanceof PsiNameIdentifierOwner) {
210                    return NAMED_ELEMENT.isValid((PsiNameIdentifierOwner) element);
211                }
212                return super.isValid(element);
213            }
214        };
215    
216        public static final PositioningStrategy<PsiElement> DECLARATION_OR_DEFAULT = new DeclarationHeader<PsiElement>() {
217            @NotNull
218            @Override
219            public List<TextRange> mark(@NotNull PsiElement element) {
220                if (element instanceof JetDeclaration) {
221                    return super.mark((JetDeclaration) element);
222                }
223                return DEFAULT.mark(element);
224            }
225    
226            @Override
227            public boolean isValid(@NotNull PsiElement element) {
228                if (element instanceof JetDeclaration) {
229                    return DECLARATION.isValid((JetDeclaration) element);
230                }
231                return DEFAULT.isValid(element);
232            }
233        };
234    
235        public static final PositioningStrategy<JetModifierListOwner> ABSTRACT_MODIFIER = modifierSetPosition(JetTokens.ABSTRACT_KEYWORD);
236    
237        public static final PositioningStrategy<JetModifierListOwner> OVERRIDE_MODIFIER = modifierSetPosition(JetTokens.OVERRIDE_KEYWORD);
238    
239        public static final PositioningStrategy<JetModifierListOwner> FINAL_MODIFIER = modifierSetPosition(JetTokens.FINAL_KEYWORD);
240    
241        public static final PositioningStrategy<JetModifierListOwner> VARIANCE_MODIFIER = modifierSetPosition(JetTokens.IN_KEYWORD,
242                                                                                                              JetTokens.OUT_KEYWORD);
243        public static final PositioningStrategy<PsiElement> FOR_REDECLARATION = new PositioningStrategy<PsiElement>() {
244            @NotNull
245            @Override
246            public List<TextRange> mark(@NotNull PsiElement element) {
247                if (element instanceof JetNamedDeclaration) {
248                    PsiElement nameIdentifier = ((JetNamedDeclaration) element).getNameIdentifier();
249                    if (nameIdentifier != null) {
250                        return markElement(nameIdentifier);
251                    }
252                }
253                else if (element instanceof JetFile) {
254                    JetFile file = (JetFile) element;
255                    PsiElement nameIdentifier = file.getPackageDirective().getNameIdentifier();
256                    if (nameIdentifier != null) {
257                        return markElement(nameIdentifier);
258                    }
259                }
260                return markElement(element);
261            }
262        };
263        public static final PositioningStrategy<JetReferenceExpression> FOR_UNRESOLVED_REFERENCE =
264                new PositioningStrategy<JetReferenceExpression>() {
265                    @NotNull
266                    @Override
267                    public List<TextRange> mark(@NotNull JetReferenceExpression element) {
268                        if (element instanceof JetArrayAccessExpression) {
269                            List<TextRange> ranges = ((JetArrayAccessExpression) element).getBracketRanges();
270                            if (!ranges.isEmpty()) {
271                                return ranges;
272                            }
273                        }
274                        return Collections.singletonList(element.getTextRange());
275                    }
276                };
277    
278        public static PositioningStrategy<JetModifierListOwner> modifierSetPosition(final JetKeywordToken... tokens) {
279            return new PositioningStrategy<JetModifierListOwner>() {
280                @NotNull
281                @Override
282                public List<TextRange> mark(@NotNull JetModifierListOwner modifierListOwner) {
283                    JetModifierList modifierList = modifierListOwner.getModifierList();
284                    assert modifierList != null : "No modifier list, but modifier has been found by the analyzer";
285    
286                    for (JetKeywordToken token : tokens) {
287                        ASTNode node = modifierList.getModifierNode(token);
288                        if (node != null) {
289                            return markNode(node);
290                        }
291                    }
292                    throw new IllegalStateException("None of the modifiers is found: " + Arrays.asList(tokens));
293                }
294            };
295        }
296    
297        public static final PositioningStrategy<JetArrayAccessExpression> ARRAY_ACCESS = new PositioningStrategy<JetArrayAccessExpression>() {
298            @NotNull
299            @Override
300            public List<TextRange> mark(@NotNull JetArrayAccessExpression element) {
301                return markElement(element.getIndicesNode());
302            }
303        };
304    
305        public static final PositioningStrategy<JetModifierListOwner> VISIBILITY_MODIFIER = new PositioningStrategy<JetModifierListOwner>() {
306            @NotNull
307            @Override
308            public List<TextRange> mark(@NotNull JetModifierListOwner element) {
309                List<JetModifierKeywordToken> visibilityTokens = Lists.newArrayList(
310                        JetTokens.PRIVATE_KEYWORD, JetTokens.PROTECTED_KEYWORD, JetTokens.PUBLIC_KEYWORD, JetTokens.INTERNAL_KEYWORD);
311                List<TextRange> result = Lists.newArrayList();
312                for (JetModifierKeywordToken token : visibilityTokens) {
313                    if (element.hasModifier(token)) {
314                        //noinspection ConstantConditions
315                        result.add(element.getModifierList().getModifierNode(token).getTextRange());
316                    }
317                }
318    
319                if (!result.isEmpty()) return result;
320    
321                // Try to resolve situation when there's no visibility modifiers written before element
322    
323                if (element instanceof PsiNameIdentifierOwner) {
324                    PsiElement nameIdentifier = ((PsiNameIdentifierOwner) element).getNameIdentifier();
325                    if (nameIdentifier != null) {
326                        return ImmutableList.of(nameIdentifier.getTextRange());
327                    }
328                }
329    
330                if (element instanceof JetObjectDeclaration) {
331                    return ImmutableList.of(((JetObjectDeclaration) element).getObjectKeyword().getTextRange());
332                }
333    
334                if (element instanceof JetPropertyAccessor) {
335                    return ImmutableList.of(((JetPropertyAccessor) element).getNamePlaceholder().getTextRange());
336                }
337    
338                if (element instanceof JetClassInitializer) {
339                    return ImmutableList.of(element.getTextRange());
340                }
341    
342                if (element instanceof JetClassObject) {
343                    JetObjectDeclaration objectDeclaration = ((JetClassObject) element).getObjectDeclaration();
344                    return ImmutableList.of(objectDeclaration.getObjectKeyword().getTextRange());
345                }
346    
347                throw new IllegalArgumentException(
348                        String.format("Can't find text range for element '%s' with the text '%s'",
349                                      element.getClass().getCanonicalName(), element.getText()));
350            }
351        };
352    
353        public static final PositioningStrategy<JetTypeProjection> VARIANCE_IN_PROJECTION = new PositioningStrategy<JetTypeProjection>() {
354            @NotNull
355            @Override
356            public List<TextRange> mark(@NotNull JetTypeProjection element) {
357                return markNode(element.getProjectionNode());
358            }
359        };
360    
361        public static final PositioningStrategy<JetParameter> PARAMETER_DEFAULT_VALUE = new PositioningStrategy<JetParameter>() {
362            @NotNull
363            @Override
364            public List<TextRange> mark(@NotNull JetParameter element) {
365                return markNode(element.getDefaultValue().getNode());
366            }
367        };
368    
369        public static final PositioningStrategy<PsiElement> CALL_ELEMENT = new PositioningStrategy<PsiElement>() {
370            @NotNull
371            @Override
372            public List<TextRange> mark(@NotNull PsiElement callElement) {
373                if (callElement instanceof JetCallElement) {
374                    JetExpression calleeExpression = ((JetCallElement) callElement).getCalleeExpression();
375                    if (calleeExpression != null) {
376                        return markElement(calleeExpression);
377                    }
378                }
379                return markElement(callElement);
380            }
381        };
382    
383        public static final PositioningStrategy<JetDeclarationWithBody> DECLARATION_WITH_BODY = new PositioningStrategy<JetDeclarationWithBody>() {
384            @NotNull
385            @Override
386            public List<TextRange> mark(@NotNull JetDeclarationWithBody element) {
387                JetExpression bodyExpression = element.getBodyExpression();
388                if ((bodyExpression instanceof JetBlockExpression)) {
389                    TextRange lastBracketRange = ((JetBlockExpression) bodyExpression).getLastBracketRange();
390                    if (lastBracketRange != null) {
391                        return markRange(lastBracketRange);
392                    }
393                }
394                return markElement(element);
395            }
396    
397            @Override
398            public boolean isValid(@NotNull JetDeclarationWithBody element) {
399                if (!super.isValid(element)) return false;
400    
401                JetExpression bodyExpression = element.getBodyExpression();
402                if (!(bodyExpression instanceof JetBlockExpression)) return false;
403                if (((JetBlockExpression) bodyExpression).getLastBracketRange() == null) return false;
404                return true;
405            }
406        };
407    
408        public static final PositioningStrategy<JetProperty> VAL_OR_VAR_NODE = new PositioningStrategy<JetProperty>() {
409            @NotNull
410            @Override
411            public List<TextRange> mark(@NotNull JetProperty property) {
412                return markNode(property.getValOrVarNode());
413            }
414        };
415    
416        public static final PositioningStrategy<JetWhenEntry> ELSE_ENTRY = new PositioningStrategy<JetWhenEntry>() {
417            @NotNull
418            @Override
419            public List<TextRange> mark(@NotNull JetWhenEntry entry) {
420                PsiElement elseKeywordElement = entry.getElseKeywordElement();
421                assert elseKeywordElement != null;
422                return markElement(elseKeywordElement);
423            }
424        };
425    
426        public static final PositioningStrategy<JetWhenExpression> WHEN_EXPRESSION = new PositioningStrategy<JetWhenExpression>() {
427            @NotNull
428            @Override
429            public List<TextRange> mark(@NotNull JetWhenExpression element) {
430                return markElement(element.getWhenKeywordElement());
431            }
432        };
433    
434        public static final PositioningStrategy<JetWhenConditionInRange> WHEN_CONDITION_IN_RANGE =
435                new PositioningStrategy<JetWhenConditionInRange>() {
436                    @NotNull
437                    @Override
438                    public List<TextRange> mark(@NotNull JetWhenConditionInRange condition) {
439                        return markElement(condition.getOperationReference());
440                    }
441                };
442    
443        public static final PositioningStrategy<JetNullableType> NULLABLE_TYPE = new PositioningStrategy<JetNullableType>() {
444            @NotNull
445            @Override
446            public List<TextRange> mark(@NotNull JetNullableType element) {
447                return markNode(element.getQuestionMarkNode());
448            }
449        };
450    
451        public static final PositioningStrategy<PsiElement> CALL_EXPRESSION = new PositioningStrategy<PsiElement>() {
452            @NotNull
453            @Override
454            public List<TextRange> mark(@NotNull PsiElement element) {
455                if (element instanceof JetCallExpression) {
456                    JetCallExpression callExpression = (JetCallExpression) element;
457                    PsiElement endElement;
458                    JetTypeArgumentList typeArgumentList = callExpression.getTypeArgumentList();
459                    JetExpression calleeExpression = callExpression.getCalleeExpression();
460                    if (typeArgumentList != null) {
461                        endElement = typeArgumentList;
462                    }
463                    else if (calleeExpression != null) {
464                        endElement = calleeExpression;
465                    }
466                    else {
467                        endElement = element;
468                    }
469                    return markRange(new TextRange(element.getTextRange().getStartOffset(), endElement.getTextRange().getEndOffset()));
470                }
471                return super.mark(element);
472            }
473        };
474    
475        public static final PositioningStrategy<JetElement> VALUE_ARGUMENTS = new PositioningStrategy<JetElement>() {
476            @NotNull
477            @Override
478            public List<TextRange> mark(@NotNull JetElement element) {
479                if (element instanceof JetValueArgumentList) {
480                    PsiElement rightParenthesis = ((JetValueArgumentList) element).getRightParenthesis();
481                    if (rightParenthesis != null) {
482                        return markElement(rightParenthesis);
483                    }
484    
485                }
486                return super.mark(element);
487            }
488        };
489    
490        public static final PositioningStrategy<JetFunctionLiteral> FUNCTION_LITERAL_PARAMETERS = new PositioningStrategy<JetFunctionLiteral>() {
491            @NotNull
492            @Override
493            public List<TextRange> mark(@NotNull JetFunctionLiteral functionLiteral) {
494                JetParameterList valueParameterList = functionLiteral.getValueParameterList();
495                if (valueParameterList != null) {
496                    return markElement(valueParameterList);
497                }
498                return markNode(functionLiteral.getOpenBraceNode());
499            }
500        };
501    
502        public static final PositioningStrategy<JetElement> CUT_CHAR_QUOTES = new PositioningStrategy<JetElement>() {
503            @NotNull
504            @Override
505            public List<TextRange> mark(@NotNull JetElement element) {
506                if (element instanceof JetConstantExpression) {
507                    if (element.getNode().getElementType() == JetNodeTypes.CHARACTER_CONSTANT) {
508                        TextRange elementTextRange = element.getTextRange();
509                        return Collections.singletonList(
510                                TextRange.create(elementTextRange.getStartOffset() + 1, elementTextRange.getEndOffset() - 1));
511                    }
512                }
513                return super.mark(element);
514            }
515        };
516    
517        public static final PositioningStrategy<JetElement> LONG_LITERAL_SUFFIX = new PositioningStrategy<JetElement>() {
518            @NotNull
519            @Override
520            public List<TextRange> mark(@NotNull JetElement element) {
521                if (element instanceof JetConstantExpression) {
522                    if (element.getNode().getElementType() == JetNodeTypes.INTEGER_CONSTANT) {
523                        int endOffset = element.getTextRange().getEndOffset();
524                        return Collections.singletonList(TextRange.create(endOffset - 1, endOffset));
525                    }
526                }
527                return super.mark(element);
528            }
529        };
530    
531        public static PositioningStrategy<PsiElement> markTextRangesFromDiagnostic(
532                @NotNull final Function1<Diagnostic, List<TextRange>> getTextRanges
533        ) {
534            return new PositioningStrategy<PsiElement>() {
535                @NotNull
536                @Override
537                public List<TextRange> markDiagnostic(@NotNull ParametrizedDiagnostic<? extends PsiElement> diagnostic) {
538                    return getTextRanges.invoke(diagnostic);
539                }
540            };
541        }
542    
543        private PositioningStrategies() {
544        }
545    }