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