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