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
017package org.jetbrains.jet.lang.diagnostics;
018
019
020import com.google.common.collect.ImmutableList;
021import com.google.common.collect.Lists;
022import com.intellij.lang.ASTNode;
023import com.intellij.openapi.util.TextRange;
024import com.intellij.psi.PsiElement;
025import com.intellij.psi.PsiNameIdentifierOwner;
026import org.jetbrains.annotations.NotNull;
027import org.jetbrains.jet.lang.psi.*;
028import org.jetbrains.jet.lexer.JetKeywordToken;
029import org.jetbrains.jet.lexer.JetTokens;
030
031import java.util.Arrays;
032import java.util.Collections;
033import java.util.List;
034
035public 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}