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