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