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