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 JetObjectDeclaration) {
289                    return ImmutableList.of(((JetObjectDeclaration) element).getObjectKeyword().getTextRange());
290                }
291    
292                if (element instanceof JetPropertyAccessor) {
293                    return ImmutableList.of(((JetPropertyAccessor) element).getNamePlaceholder().getTextRange());
294                }
295    
296                if (element instanceof JetClassInitializer) {
297                    return ImmutableList.of(element.getTextRange());
298                }
299    
300                if (element instanceof JetClassObject) {
301                    JetObjectDeclaration objectDeclaration = ((JetClassObject) element).getObjectDeclaration();
302                    return ImmutableList.of(objectDeclaration.getObjectKeyword().getTextRange());
303                }
304    
305                throw new IllegalArgumentException(
306                        String.format("Can't find text range for element '%s' with the text '%s'",
307                                      element.getClass().getCanonicalName(), element.getText()));
308            }
309        };
310    
311        public static final PositioningStrategy<JetTypeProjection> VARIANCE_IN_PROJECTION = new PositioningStrategy<JetTypeProjection>() {
312            @NotNull
313            @Override
314            public List<TextRange> mark(@NotNull JetTypeProjection element) {
315                return markNode(element.getProjectionNode());
316            }
317        };
318    
319        public static final PositioningStrategy<JetParameter> PARAMETER_DEFAULT_VALUE = new PositioningStrategy<JetParameter>() {
320            @NotNull
321            @Override
322            public List<TextRange> mark(@NotNull JetParameter element) {
323                return markNode(element.getDefaultValue().getNode());
324            }
325        };
326    
327        public static final PositioningStrategy<PsiElement> CALL_ELEMENT = new PositioningStrategy<PsiElement>() {
328            @NotNull
329            @Override
330            public List<TextRange> mark(@NotNull PsiElement callElement) {
331                if (callElement instanceof JetCallElement) {
332                    JetExpression calleeExpression = ((JetCallElement) callElement).getCalleeExpression();
333                    if (calleeExpression != null) {
334                        return markElement(calleeExpression);
335                    }
336                }
337                return markElement(callElement);
338            }
339        };
340    
341        public static final PositioningStrategy<JetDeclarationWithBody> DECLARATION_WITH_BODY = new PositioningStrategy<JetDeclarationWithBody>() {
342            @NotNull
343            @Override
344            public List<TextRange> mark(@NotNull JetDeclarationWithBody element) {
345                JetExpression bodyExpression = element.getBodyExpression();
346                if ((bodyExpression instanceof JetBlockExpression)) {
347                    TextRange lastBracketRange = ((JetBlockExpression) bodyExpression).getLastBracketRange();
348                    if (lastBracketRange != null) {
349                        return markRange(lastBracketRange);
350                    }
351                }
352                return markElement(element);
353            }
354    
355            @Override
356            public boolean isValid(@NotNull JetDeclarationWithBody element) {
357                if (!super.isValid(element)) return false;
358    
359                JetExpression bodyExpression = element.getBodyExpression();
360                if (!(bodyExpression instanceof JetBlockExpression)) return false;
361                if (((JetBlockExpression) bodyExpression).getLastBracketRange() == null) return false;
362                return true;
363            }
364        };
365    
366        public static final PositioningStrategy<JetProperty> VAL_OR_VAR_NODE = new PositioningStrategy<JetProperty>() {
367            @NotNull
368            @Override
369            public List<TextRange> mark(@NotNull JetProperty property) {
370                return markNode(property.getValOrVarNode());
371            }
372        };
373    
374        public static final PositioningStrategy<JetWhenEntry> ELSE_ENTRY = new PositioningStrategy<JetWhenEntry>() {
375            @NotNull
376            @Override
377            public List<TextRange> mark(@NotNull JetWhenEntry entry) {
378                PsiElement elseKeywordElement = entry.getElseKeywordElement();
379                assert elseKeywordElement != null;
380                return markElement(elseKeywordElement);
381            }
382        };
383    
384        public static final PositioningStrategy<JetWhenExpression> WHEN_EXPRESSION = new PositioningStrategy<JetWhenExpression>() {
385            @NotNull
386            @Override
387            public List<TextRange> mark(@NotNull JetWhenExpression element) {
388                return markElement(element.getWhenKeywordElement());
389            }
390        };
391    
392        public static final PositioningStrategy<JetWhenConditionInRange> WHEN_CONDITION_IN_RANGE =
393                new PositioningStrategy<JetWhenConditionInRange>() {
394                    @NotNull
395                    @Override
396                    public List<TextRange> mark(@NotNull JetWhenConditionInRange condition) {
397                        return markElement(condition.getOperationReference());
398                    }
399                };
400    
401        public static final PositioningStrategy<JetNullableType> NULLABLE_TYPE = new PositioningStrategy<JetNullableType>() {
402            @NotNull
403            @Override
404            public List<TextRange> mark(@NotNull JetNullableType element) {
405                return markNode(element.getQuestionMarkNode());
406            }
407        };
408    
409        public static final PositioningStrategy<PsiElement> CALL_EXPRESSION = new PositioningStrategy<PsiElement>() {
410            @NotNull
411            @Override
412            public List<TextRange> mark(@NotNull PsiElement element) {
413                if (element instanceof JetCallExpression) {
414                    JetCallExpression callExpression = (JetCallExpression) element;
415                    PsiElement endElement;
416                    JetTypeArgumentList typeArgumentList = callExpression.getTypeArgumentList();
417                    JetExpression calleeExpression = callExpression.getCalleeExpression();
418                    if (typeArgumentList != null) {
419                        endElement = typeArgumentList;
420                    }
421                    else if (calleeExpression != null) {
422                        endElement = calleeExpression;
423                    }
424                    else {
425                        endElement = element;
426                    }
427                    return markRange(new TextRange(element.getTextRange().getStartOffset(), endElement.getTextRange().getEndOffset()));
428                }
429                return super.mark(element);
430            }
431        };
432    
433        public static final PositioningStrategy<JetElement> VALUE_ARGUMENTS = new PositioningStrategy<JetElement>() {
434            @NotNull
435            @Override
436            public List<TextRange> mark(@NotNull JetElement element) {
437                if (element instanceof JetValueArgumentList) {
438                    PsiElement rightParenthesis = ((JetValueArgumentList) element).getRightParenthesis();
439                    if (rightParenthesis != null) {
440                        return markElement(rightParenthesis);
441                    }
442    
443                }
444                return super.mark(element);
445            }
446        };
447    
448        public static final PositioningStrategy<JetFunctionLiteral> FUNCTION_LITERAL_PARAMETERS = new PositioningStrategy<JetFunctionLiteral>() {
449            @NotNull
450            @Override
451            public List<TextRange> mark(@NotNull JetFunctionLiteral functionLiteral) {
452                JetParameterList valueParameterList = functionLiteral.getValueParameterList();
453                if (valueParameterList != null) {
454                    return markElement(valueParameterList);
455                }
456                return markNode(functionLiteral.getOpenBraceNode());
457            }
458        };
459    
460        public static final PositioningStrategy<JetElement> CUT_CHAR_QUOTES = new PositioningStrategy<JetElement>() {
461            @NotNull
462            @Override
463            public List<TextRange> mark(@NotNull JetElement element) {
464                if (element instanceof JetConstantExpression) {
465                    if (element.getNode().getElementType() == JetNodeTypes.CHARACTER_CONSTANT) {
466                        TextRange elementTextRange = element.getTextRange();
467                        return Collections.singletonList(
468                                TextRange.create(elementTextRange.getStartOffset() + 1, elementTextRange.getEndOffset() - 1));
469                    }
470                }
471                return super.mark(element);
472            }
473        };
474    
475        public static final PositioningStrategy<JetElement> LONG_LITERAL_SUFFIX = new PositioningStrategy<JetElement>() {
476            @NotNull
477            @Override
478            public List<TextRange> mark(@NotNull JetElement element) {
479                if (element instanceof JetConstantExpression) {
480                    if (element.getNode().getElementType() == JetNodeTypes.INTEGER_CONSTANT) {
481                        int endOffset = element.getTextRange().getEndOffset();
482                        return Collections.singletonList(TextRange.create(endOffset - 1, endOffset));
483                    }
484                }
485                return super.mark(element);
486            }
487        };
488    
489        private PositioningStrategies() {
490        }
491    }