package org.elixir_lang.annonator; import com.intellij.lang.ASTNode; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.Annotator; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiRecursiveElementVisitor; import com.intellij.psi.tree.TokenSet; import org.elixir_lang.ElixirSyntaxHighlighter; import org.elixir_lang.errorreport.Logger; import org.elixir_lang.psi.*; import org.elixir_lang.psi.call.Call; import org.elixir_lang.psi.operation.Infix; import org.elixir_lang.psi.operation.Match; import org.elixir_lang.psi.operation.Type; import org.elixir_lang.psi.operation.When; import org.jetbrains.annotations.NotNull; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import static org.elixir_lang.psi.call.name.Function.UNQUOTE; import static org.elixir_lang.psi.call.name.Module.KERNEL; import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.identifierName; import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.stripAccessExpression; import static org.elixir_lang.reference.ModuleAttribute.*; import static org.elixir_lang.structure_view.element.CallDefinitionHead.stripAllOuterParentheses; /** * Annotates module attributes. */ public class ModuleAttribute implements Annotator, DumbAware { /* * Public Instance Methods */ /** * Annotates the specified PSI element. * It is guaranteed to be executed in non-reentrant fashion. * I.e there will be no call of this method for this instance before previous call get completed. * Multiple instances of the annotator might exist simultaneously, though. * * @param element to annotate. * @param holder the container which receives annotations created by the plugin. */ @Override public void annotate(@NotNull final PsiElement element, @NotNull final AnnotationHolder holder) { element.accept( new PsiRecursiveElementVisitor() { /* * * Instance Methods * */ /* * Public Instance Methods */ @Override public void visitElement(@NotNull final PsiElement element) { if (element instanceof AtNonNumericOperation) { visitMaybeUsage((AtNonNumericOperation) element); } else if (element instanceof AtUnqualifiedNoParenthesesCall) { visitDeclaration((AtUnqualifiedNoParenthesesCall) element); } } /* * Private Instance Methods */ private void visitDeclaration(@NotNull final AtUnqualifiedNoParenthesesCall atUnqualifiedNoParenthesesCall) { ElixirAtIdentifier atIdentifier = atUnqualifiedNoParenthesesCall.getAtIdentifier(); TextRange textRange = atIdentifier.getTextRange(); String identifier = identifierName(atIdentifier); if (isCallbackName(identifier)) { highlight(textRange, holder, ElixirSyntaxHighlighter.MODULE_ATTRIBUTE); highlightCallback(atUnqualifiedNoParenthesesCall, holder); } else if (isDocumentationName(identifier)) { highlight(textRange, holder, ElixirSyntaxHighlighter.DOCUMENTATION_MODULE_ATTRIBUTE); highlightDocumentationText(atUnqualifiedNoParenthesesCall, holder); } else if (isTypeName(identifier)) { highlight(textRange, holder, ElixirSyntaxHighlighter.MODULE_ATTRIBUTE); highlightType(atUnqualifiedNoParenthesesCall, holder); } else if (isSpecificationName(identifier)) { highlight(textRange, holder, ElixirSyntaxHighlighter.MODULE_ATTRIBUTE); highlightSpecification(atUnqualifiedNoParenthesesCall, holder); } else { highlight(textRange, holder, ElixirSyntaxHighlighter.MODULE_ATTRIBUTE); } } private void visitMaybeUsage(@NotNull final AtNonNumericOperation element) { PsiElement operand = element.operand(); if (operand != null && !(operand instanceof ElixirAccessExpression)) { visitUsage(element); } } private void visitUsage(@NotNull final AtNonNumericOperation atNonNumericOperation) { highlight( atNonNumericOperation.getTextRange(), holder, ElixirSyntaxHighlighter.MODULE_ATTRIBUTE ); if (!isNonReferencing(atNonNumericOperation) && atNonNumericOperation.getReference().resolve() == null) { holder.createErrorAnnotation(atNonNumericOperation, "Unresolved module attribute"); } } } ); } /* * Private Instance Methods */ private void cannotHighlightTypes(PsiElement element) { error("Cannot highlight types", element); } private void error(@NotNull String userMessage, PsiElement element) { Logger.error(this.getClass(), userMessage, element); } /** * Highlights `textRange` with the given `textAttributesKey`. * * @param textRange textRange in the document to highlight * @param annotationHolder the container which receives annotations created by the plugin. * @param textAttributesKey text attributes to apply to the `node`. */ private void highlight(@NotNull final TextRange textRange, @NotNull AnnotationHolder annotationHolder, @NotNull final TextAttributesKey textAttributesKey) { annotationHolder.createInfoAnnotation(textRange, null).setEnforcedTextAttributes(TextAttributes.ERASE_MARKER); annotationHolder.createInfoAnnotation(textRange, null).setEnforcedTextAttributes(EditorColorsManager.getInstance().getGlobalScheme().getAttributes(textAttributesKey)); } private void highlightCallback(@NotNull final AtUnqualifiedNoParenthesesCall atUnqualifiedNoParenthesesCall, @NotNull final AnnotationHolder annotationHolder) { highlightSpecification( atUnqualifiedNoParenthesesCall, annotationHolder, ElixirSyntaxHighlighter.CALLBACK, ElixirSyntaxHighlighter.TYPE ); } private void highlightDocumentationText( @NotNull final AtUnqualifiedNoParenthesesCall atUnqualifiedNoParenthesesCall, @NotNull final AnnotationHolder holder ) { PsiElement noParenthesesOneArgument = atUnqualifiedNoParenthesesCall.getNoParenthesesOneArgument(); PsiElement[] grandChildren = noParenthesesOneArgument.getChildren(); if (grandChildren.length == 1) { PsiElement grandChild = grandChildren[0]; PsiElement[] greatGrandChildren = grandChild.getChildren(); if (greatGrandChildren.length == 1) { PsiElement greatGrandChild = greatGrandChildren[0]; if (greatGrandChild instanceof ElixirAtomKeyword) { ElixirAtomKeyword atomKeyword = (ElixirAtomKeyword) greatGrandChild; String text = atomKeyword.getText(); if (text.equals("false")) { holder.createWeakWarningAnnotation( atomKeyword, "Will make documented invisible to the documentation extraction tools like ExDoc."); } } else if (greatGrandChild instanceof Heredoc) { Heredoc heredoc = (Heredoc) greatGrandChild; List<HeredocLine> heredocLineList = heredoc.getHeredocLineList(); for (Bodied bodied : heredocLineList) { Body body = bodied.getBody(); highlightFragments( heredoc, body, holder, ElixirSyntaxHighlighter.DOCUMENTATION_TEXT ); } } else if (greatGrandChild instanceof Line) { Line line = (Line) greatGrandChild; Body body = line.getBody(); highlightFragments( line, body, holder, ElixirSyntaxHighlighter.DOCUMENTATION_TEXT ); } } } } /** * Highlights fragment ASTNodes under `body` that have fragment type from `fragmented.getFragmentType()`. * * @param fragmented supplies fragment type * @param body contains fragments * @param annotationHolder the container which receives annotations created by the plugin. * @param textAttributesKey text attributes to apply to the fragments. */ private void highlightFragments(@NotNull final Fragmented fragmented, @NotNull final Body body, @NotNull AnnotationHolder annotationHolder, @NotNull final TextAttributesKey textAttributesKey) { ASTNode bodyNode = body.getNode(); ASTNode[] fragmentNodes = bodyNode.getChildren( TokenSet.create(fragmented.getFragmentType()) ); for (ASTNode fragmentNode : fragmentNodes) { highlight( fragmentNode.getTextRange(), annotationHolder, textAttributesKey ); } } /** * Highlights the function call name as a `ElixirSyntaxHighlighter.SPECIFICATION * * @param atUnqualifiedNoParenthesesCall * @param annotationHolder */ private void highlightSpecification(@NotNull final AtUnqualifiedNoParenthesesCall atUnqualifiedNoParenthesesCall, @NotNull final AnnotationHolder annotationHolder) { highlightSpecification( atUnqualifiedNoParenthesesCall, annotationHolder, ElixirSyntaxHighlighter.SPECIFICATION, ElixirSyntaxHighlighter.TYPE ); } /** * Highlights the function name of the declared @type, @typep, or @opaque as an {@link ElixirSyntaxHighlighter.TYPE} * and the its parameters as {@link ElixirSyntaxHighlighter.TYPE_PARAMETER}. */ private void highlightType(@NotNull final AtUnqualifiedNoParenthesesCall atUnqualifiedNoParenthesesCall, @NotNull final AnnotationHolder annotationHolder) { PsiElement noParenthesesOneArgument = atUnqualifiedNoParenthesesCall.getNoParenthesesOneArgument(); PsiElement[] grandChildren = noParenthesesOneArgument.getChildren(); if (grandChildren.length == 1) { PsiElement grandChild = grandChildren[0]; if (grandChild instanceof Match /* Match is invalid. It will be marked by MatchOperatorInsteadOfTypeOperator inspection as an error */ || grandChild instanceof Type) { Infix infix = (Infix) grandChild; PsiElement leftOperand = infix.leftOperand(); Set<String> typeParameterNameSet = Collections.emptySet(); if (leftOperand instanceof Call) { Call call = (Call) leftOperand; highlightTypeName(call, annotationHolder); if (call instanceof ElixirMatchedUnqualifiedNoArgumentsCall) { // no arguments, so nothing else to do } else if (call instanceof ElixirMatchedUnqualifiedParenthesesCall) { typeParameterNameSet = highlightTypeLeftOperand( (ElixirMatchedUnqualifiedParenthesesCall) call, annotationHolder ); } else { cannotHighlightTypes(call); } } else { cannotHighlightTypes(leftOperand); } PsiElement rightOperand = infix.rightOperand(); if (rightOperand != null) { highlightTypesAndTypeParameterUsages( rightOperand, typeParameterNameSet, annotationHolder, ElixirSyntaxHighlighter.TYPE ); } } else if (grandChild instanceof ElixirMatchedUnqualifiedParenthesesCall) { // seen as `unquote(ast)`, but could also be just the beginning of typing ElixirMatchedUnqualifiedParenthesesCall matchedUnqualifiedParenthesesCall = (ElixirMatchedUnqualifiedParenthesesCall) grandChild; if (matchedUnqualifiedParenthesesCall.functionName().equals(UNQUOTE)) { PsiElement[] secondaryArguments = matchedUnqualifiedParenthesesCall.secondaryArguments(); if (secondaryArguments != null) { Set<String> typeParameterNameSet = typeTypeParameterNameSet(secondaryArguments); highlightTypesAndTypeTypeParameterDeclarations( secondaryArguments, typeParameterNameSet, annotationHolder, ElixirSyntaxHighlighter.TYPE ); } } else { ElixirMatchedUnqualifiedParenthesesCall grandChildCall = (ElixirMatchedUnqualifiedParenthesesCall) grandChild; highlightTypeName(grandChildCall, annotationHolder); // Assume it's `@type foo(bar)` before completed as `@type foo(bar) :: bar` highlightTypeLeftOperand( (ElixirMatchedUnqualifiedParenthesesCall) grandChild, annotationHolder ); } } else if (grandChild instanceof QuotableKeywordList) { QuotableKeywordList quotableKeywordList = (QuotableKeywordList) grandChild; List<QuotableKeywordPair> quotableKeywordPairList = quotableKeywordList.quotableKeywordPairList(); // occurs when user does `my_type: definition` instead of `my_type :: definition` if (quotableKeywordPairList.size() == 1) { QuotableKeywordPair quotableKeywordPair = quotableKeywordPairList.get(0); Quotable quotableKeywordKey = quotableKeywordPair.getKeywordKey(); if (quotableKeywordKey instanceof ElixirKeywordKey) { ElixirKeywordKey keywordKey = (ElixirKeywordKey) quotableKeywordKey; highlight( keywordKey.getTextRange(), annotationHolder, ElixirSyntaxHighlighter.TYPE ); } Quotable quotableKeywordValue = quotableKeywordPair.getKeywordValue(); highlightTypesAndTypeParameterUsages( quotableKeywordValue, Collections.<String>emptySet(), annotationHolder, ElixirSyntaxHighlighter.TYPE ); } // Otherwise, allow the normal, non-type highlighting } else if (grandChild instanceof UnqualifiedNoArgumentsCall) { // assume it's a type name that is being typed Call grandChildCall = (Call) grandChild; PsiElement functionNameElement = grandChildCall.functionNameElement(); if (functionNameElement != null) { highlight( functionNameElement.getTextRange(), annotationHolder, ElixirSyntaxHighlighter.TYPE ); } } else if (grandChild instanceof UnqualifiedNoParenthesesCall) { /* Pretend that `::` separates the functionNameElement from the arguments, so that ``` @type coefficient non_neg_integer | :qNaN | :sNaN | :inf ``` is retreated like ``` @type coefficient :: non_neg_integer | :qNaN | :sNaN | :inf ``` */ UnqualifiedNoParenthesesCall unqualifiedNoParenthesesCall = (UnqualifiedNoParenthesesCall) grandChild; PsiElement functionNameElement = unqualifiedNoParenthesesCall.functionNameElement(); if (functionNameElement != null) { highlight( functionNameElement.getTextRange(), annotationHolder, ElixirSyntaxHighlighter.TYPE ); } highlightTypesAndTypeParameterUsages( unqualifiedNoParenthesesCall.getNoParenthesesOneArgument(), Collections.<String>emptySet(), annotationHolder, ElixirSyntaxHighlighter.TYPE ); } else { cannotHighlightTypes(grandChild); } } } private void highlightTypeError(@NotNull PsiElement element, @NotNull AnnotationHolder annotationHolder, @NotNull String message) { annotationHolder.createErrorAnnotation(element, message); } private Set<String> highlightTypeLeftOperand(@NotNull final ElixirMatchedUnqualifiedParenthesesCall call, @NotNull final AnnotationHolder annotationHolder) { PsiElement[] primaryArguments = call.primaryArguments(); PsiElement[] secondaryArguments = call.secondaryArguments(); Set<String> typeParameterNameSet; /* if there are secondaryArguments, then it is the type parameters as in `@type quote(type_name)(param1, param2) :: {param1, param2}` */ if (secondaryArguments != null) { typeParameterNameSet = typeTypeParameterNameSet(secondaryArguments); highlightTypesAndTypeParameterUsages( primaryArguments, /* as stated above, if there are secondary arguments, then the primary arguments are to quote or some equivalent metaprogramming. */ Collections.<String>emptySet(), annotationHolder, ElixirSyntaxHighlighter.TYPE ); highlightTypesAndTypeTypeParameterDeclarations( secondaryArguments, typeParameterNameSet, annotationHolder, ElixirSyntaxHighlighter.TYPE ); } else { typeParameterNameSet = typeTypeParameterNameSet(primaryArguments); highlightTypesAndTypeTypeParameterDeclarations( primaryArguments, typeParameterNameSet, annotationHolder, ElixirSyntaxHighlighter.TYPE ); } return typeParameterNameSet; } private void highlightTypeName(@NotNull final Call call, @NotNull AnnotationHolder annotationHolder) { PsiElement functionNameElement = call.functionNameElement(); if (functionNameElement != null) { highlight( functionNameElement.getTextRange(), annotationHolder, ElixirSyntaxHighlighter.TYPE ); } } private void highlightTypesAndTypeTypeParameterDeclarations(ElixirUnmatchedUnqualifiedNoArgumentsCall psiElement, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey typeTextAttributesKey) { String name = psiElement.getText(); TextAttributesKey textAttributesKey; if (typeParameterNameSet.contains(name)) { textAttributesKey = ElixirSyntaxHighlighter.TYPE_PARAMETER; } else { textAttributesKey = typeTextAttributesKey; } highlight(psiElement.getTextRange(), annotationHolder, textAttributesKey); } private void highlightTypesAndTypeTypeParameterDeclarations(PsiElement psiElement, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey typeTextAttributesKey) { if (psiElement instanceof ElixirAccessExpression || psiElement instanceof ElixirList || psiElement instanceof ElixirTuple) { highlightTypesAndTypeTypeParameterDeclarations( psiElement.getChildren(), typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof ElixirUnmatchedUnqualifiedNoArgumentsCall) { highlightTypesAndTypeTypeParameterDeclarations( (ElixirUnmatchedUnqualifiedNoArgumentsCall) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else { if (!(psiElement instanceof ElixirAtomKeyword)) { error("Cannot highlight types and type parameter declarations", psiElement); } } } private void highlightTypesAndTypeTypeParameterDeclarations( PsiElement[] psiElements, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey typeTextAttributesKey) { for (PsiElement psiElement : psiElements) { highlightTypesAndTypeTypeParameterDeclarations( psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } } /** * Recursively highlights the types under `atUnqualifiedNoParenthesesCall`. * * @param atUnqualifiedNoParenthesesCall * @param annotationHolder * @param leftMostFunctionNameTextAttributesKey the {@link ElixirSyntaxHighlighter} {@link TextAttributesKey} for the * name of the callback, type, or function being declared * @param leftMostFunctionArgumentsTextAttributesKey the {@link ElixirSyntaxHighlighter} {@link TextAttributesKey} for the * arguments of the callback, type, or function being declared */ private void highlightSpecification(AtUnqualifiedNoParenthesesCall atUnqualifiedNoParenthesesCall, AnnotationHolder annotationHolder, TextAttributesKey leftMostFunctionNameTextAttributesKey, TextAttributesKey leftMostFunctionArgumentsTextAttributesKey) { PsiElement noParenthesesOneArgument = atUnqualifiedNoParenthesesCall.getNoParenthesesOneArgument(); PsiElement[] grandChildren = noParenthesesOneArgument.getChildren(); if (grandChildren.length == 1) { PsiElement grandChild = grandChildren[0]; if (grandChild instanceof Type) { Infix infix = (Infix) grandChild; PsiElement leftOperand = infix.leftOperand(); if (leftOperand instanceof Call) { Call call = (Call) leftOperand; PsiElement functionNameElement = call.functionNameElement(); if (functionNameElement != null) { highlight( functionNameElement.getTextRange(), annotationHolder, leftMostFunctionNameTextAttributesKey ); } PsiElement[] primaryArguments = call.primaryArguments(); if (primaryArguments != null) { highlightTypesAndTypeParameterUsages( primaryArguments, Collections.EMPTY_SET, annotationHolder, leftMostFunctionArgumentsTextAttributesKey ); } PsiElement[] secondaryArguments = call.secondaryArguments(); if (secondaryArguments != null) { highlightTypesAndTypeParameterUsages( secondaryArguments, Collections.EMPTY_SET, annotationHolder, leftMostFunctionArgumentsTextAttributesKey ); } } PsiElement rightOperand = infix.rightOperand(); if (rightOperand != null) { highlightTypesAndTypeParameterUsages( rightOperand, Collections.EMPTY_SET, annotationHolder, ElixirSyntaxHighlighter.TYPE ); } } else if (grandChild instanceof ElixirMatchedWhenOperation) { ElixirMatchedWhenOperation matchedWhenOperation = (ElixirMatchedWhenOperation) grandChild; Set<String> typeParameterNameSet = specificationTypeParameterNameSet(matchedWhenOperation.rightOperand()); PsiElement leftOperand = matchedWhenOperation.leftOperand(); if (leftOperand instanceof Type) { Type typeOperation = (Type) leftOperand; PsiElement typeOperationLeftOperand = typeOperation.leftOperand(); PsiElement strippedTypeOperationLeftOperand = null; if (typeOperationLeftOperand != null) { strippedTypeOperationLeftOperand = stripAllOuterParentheses(typeOperationLeftOperand); } if (strippedTypeOperationLeftOperand instanceof Call) { Call call = (Call) strippedTypeOperationLeftOperand; PsiElement functionNameElement = call.functionNameElement(); if (functionNameElement != null) { highlight( functionNameElement.getTextRange(), annotationHolder, leftMostFunctionNameTextAttributesKey ); } PsiElement[] primaryArguments = call.primaryArguments(); if (primaryArguments != null) { highlightTypesAndTypeParameterUsages( primaryArguments, typeParameterNameSet, annotationHolder, leftMostFunctionArgumentsTextAttributesKey ); } PsiElement[] secondaryArguments = call.secondaryArguments(); if (secondaryArguments != null) { highlightTypesAndTypeParameterUsages( secondaryArguments, typeParameterNameSet, annotationHolder, leftMostFunctionArgumentsTextAttributesKey ); } } else { cannotHighlightTypes(strippedTypeOperationLeftOperand); } PsiElement matchedTypeOperationRightOperand = typeOperation.rightOperand(); if (matchedTypeOperationRightOperand != null) { highlightTypesAndTypeParameterUsages( matchedTypeOperationRightOperand, typeParameterNameSet, annotationHolder, ElixirSyntaxHighlighter.TYPE ); } } else { cannotHighlightTypes(leftOperand); } Quotable rightOperand = matchedWhenOperation.rightOperand(); if (rightOperand != null) { highlightTypesAndSpecificationTypeParameterDeclarations( rightOperand, typeParameterNameSet, annotationHolder, ElixirSyntaxHighlighter.TYPE ); } } } } private void highlightTypesAndSpecificationTypeParameterDeclarations(QuotableKeywordPair quotableKeywordPair, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey typeTextAttributesKey) { PsiElement keywordKey = quotableKeywordPair.getKeywordKey(); if (typeParameterNameSet.contains(keywordKey.getText())) { highlight(keywordKey.getTextRange(), annotationHolder, ElixirSyntaxHighlighter.TYPE_PARAMETER); } else { highlightTypesAndTypeParameterUsages( keywordKey, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } highlightTypesAndTypeParameterUsages( quotableKeywordPair.getKeywordValue(), typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } private void highlightTypesAndSpecificationTypeParameterDeclarations(@NotNull PsiElement psiElement, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey typeTextAttributesKey) { if (psiElement instanceof ElixirAccessExpression || psiElement instanceof ElixirKeywords || psiElement instanceof ElixirList || psiElement instanceof ElixirNoParenthesesKeywords) { highlightTypesAndSpecificationTypeParameterDeclarations( psiElement.getChildren(), typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof QuotableKeywordPair) { highlightTypesAndSpecificationTypeParameterDeclarations( (QuotableKeywordPair) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof UnqualifiedNoArgumentsCall) { highlightTypesAndSpecificationTypeParameterDeclarations( (UnqualifiedNoArgumentsCall) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else { error("Cannot highlight types and specification type parameter declarations", psiElement); } } private void highlightTypesAndSpecificationTypeParameterDeclarations(PsiElement[] psiElements, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey typeTextAttributesKey) { for (PsiElement psiElement : psiElements) { highlightTypesAndSpecificationTypeParameterDeclarations( psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } } private void highlightTypesAndSpecificationTypeParameterDeclarations( UnqualifiedNoArgumentsCall unqualifiedNoArgumentsCall, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, @SuppressWarnings("unused") TextAttributesKey textAttributesKey) { if (typeParameterNameSet.contains(unqualifiedNoArgumentsCall.functionName())) { PsiElement functionNameElement = unqualifiedNoArgumentsCall.functionNameElement(); highlight( functionNameElement.getTextRange(), annotationHolder, ElixirSyntaxHighlighter.TYPE_PARAMETER ); } } private void highlightTypesAndTypeParameterUsages(Arguments arguments, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey textAttributesKey) { highlightTypesAndTypeParameterUsages( arguments.arguments(), typeParameterNameSet, annotationHolder, textAttributesKey ); } private void highlightTypesAndTypeParameterUsages(ElixirMapOperation mapOperation, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey typeTextAttributesKey) { highlightTypesAndTypeParameterUsages( mapOperation.getMapArguments(), typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } private void highlightTypesAndTypeParameterUsages(@NotNull ElixirMapUpdateArguments mapUpdateArguments, Set<String> typeParameterNameSet, @NotNull AnnotationHolder annotationHolder, @NotNull TextAttributesKey typeTextAttributesKey) { for (PsiElement child : mapUpdateArguments.getChildren()) { if (!(child instanceof Operator)) { highlightTypesAndTypeParameterUsages( child, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } } } private void highlightTypesAndTypeParameterUsages( ElixirStabOperation stabOperation, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey typeTextAttributesKey) { ElixirStabParenthesesSignature stabParenthesesSignature = stabOperation.getStabParenthesesSignature(); if (stabParenthesesSignature != null) { highlightTypesAndTypeParameterUsages( stabParenthesesSignature, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else { ElixirStabNoParenthesesSignature stabNoParenthesesSignatures = stabOperation.getStabNoParenthesesSignature(); if (stabNoParenthesesSignatures != null) { highlightTypesAndTypeParameterUsages( stabNoParenthesesSignatures, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } } ElixirStabBody stabBody = stabOperation.getStabBody(); if (stabBody != null) { highlightTypesAndTypeParameterUsages( stabBody, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } } private void highlightTypesAndTypeParameterUsages( ElixirStabParenthesesSignature stabParenthesesSignature, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey typeTextAttributesKey) { PsiElement[] children = stabParenthesesSignature.getChildren(); if (children.length == 1) { highlightTypesAndTypeParameterUsages( children[0], typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (children.length == 3) { highlightTypesAndTypeParameterUsages( (When) stabParenthesesSignature, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else { error("Cannot highlight types and type parameter usages", stabParenthesesSignature); } } private void highlightTypesAndTypeParameterUsages( ElixirStructOperation structOperation, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey typeTextAttributesKey) { highlightTypesAndTypeParameterUsages( structOperation.getMapArguments(), typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } private void highlightTypesAndTypeParameterUsages( @NotNull Infix infix, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey typeTextAttributesKey) { highlightTypesAndTypeParameterUsages( infix.leftOperand(), typeParameterNameSet, annotationHolder, typeTextAttributesKey ); PsiElement rightOperand = infix.rightOperand(); if (rightOperand != null) { highlightTypesAndTypeParameterUsages( rightOperand, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } } private void highlightTypesAndTypeParameterUsages(@NotNull PsiElement psiElement, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey typeTextAttributesKey) { if (psiElement instanceof Arguments) { highlightTypesAndTypeParameterUsages( (Arguments) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof AtUnqualifiedNoParenthesesCall) { /* Occurs in the case of typing a {@code @type name ::} above a {@code @doc <HEREDOC>} and the {@code @doc <HEREDOC>} is interpreted as the right-operand of {@code ::} */ } else if (psiElement instanceof ElixirAccessExpression || psiElement instanceof ElixirAssociationsBase || psiElement instanceof ElixirAssociations || psiElement instanceof ElixirContainerAssociationOperation || psiElement instanceof ElixirKeywordPair || psiElement instanceof ElixirKeywords || psiElement instanceof ElixirList || psiElement instanceof ElixirMapArguments || psiElement instanceof ElixirMapConstructionArguments || psiElement instanceof ElixirNoParenthesesArguments || psiElement instanceof ElixirParentheticalStab || psiElement instanceof ElixirStab || psiElement instanceof ElixirStabBody || psiElement instanceof ElixirStabNoParenthesesSignature || psiElement instanceof ElixirTuple) { highlightTypesAndTypeParameterUsages( psiElement.getChildren(), typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof BracketOperation || psiElement instanceof ElixirAlias || psiElement instanceof ElixirAtom || psiElement instanceof ElixirAtomKeyword || psiElement instanceof ElixirBitString || psiElement instanceof ElixirCharToken || psiElement instanceof ElixirDecimalWholeNumber || psiElement instanceof ElixirKeywordKey || /* happens when :: is typed in `@spec` above function clause that uses `do:` */ psiElement instanceof ElixirNoParenthesesKeywords || psiElement instanceof ElixirStringLine || psiElement instanceof ElixirUnaryNumericOperation || psiElement instanceof ElixirVariable) { // leave normal highlighting } else if (psiElement instanceof ElixirMapOperation) { highlightTypesAndTypeParameterUsages( (ElixirMapOperation) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof ElixirMapUpdateArguments) { highlightTypesAndTypeParameterUsages( (ElixirMapUpdateArguments) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof ElixirRelativeIdentifier || psiElement instanceof UnqualifiedNoArgumentsCall) { // highlight entire element String name = psiElement.getText(); TextAttributesKey textAttributesKey; if (typeParameterNameSet.contains(name)) { textAttributesKey = ElixirSyntaxHighlighter.TYPE_PARAMETER; } else { textAttributesKey = typeTextAttributesKey; } highlight(psiElement.getTextRange(), annotationHolder, textAttributesKey); } else if (psiElement instanceof ElixirStabParenthesesSignature) { highlightTypesAndTypeParameterUsages( (ElixirStabParenthesesSignature) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof ElixirStabOperation) { highlightTypesAndTypeParameterUsages( (ElixirStabOperation) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof ElixirStructOperation) { highlightTypesAndTypeParameterUsages( (ElixirStructOperation) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof InterpolatedString) { highlightTypeError(psiElement, annotationHolder, "Strings aren't allowed in types"); } else if (psiElement instanceof When) { /* NOTE: MUST be before `Infix` as `When` is a subinterface of `Infix` */ highlightTypesAndTypeParameterUsages( (When) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof Infix) { highlightTypesAndTypeParameterUsages( (Infix) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof QualifiedParenthesesCall) { highlightTypesAndTypeParameterUsages( (QualifiedParenthesesCall) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof QualifiedAlias) { highlightTypesAndTypeParameterUsages( (QualifiedAlias) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof QualifiedNoArgumentsCall) { highlightTypesAndTypeParameterUsages( (QualifiedNoArgumentsCall) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof QualifiedNoParenthesesCall) { highlightTypesAndTypeParameterUsages( (QualifiedNoParenthesesCall) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof UnqualifiedNoParenthesesCall) { highlightTypesAndTypeParameterUsages( (UnqualifiedNoParenthesesCall) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else if (psiElement instanceof UnqualifiedParenthesesCall) { highlightTypesAndTypeParameterUsages( (UnqualifiedParenthesesCall) psiElement, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else { cannotHighlightTypes(psiElement); } } private void highlightTypesAndTypeParameterUsages( PsiElement[] psiElements, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey textAttributesKey) { for (PsiElement psiElement : psiElements) { if (psiElement != null) { highlightTypesAndTypeParameterUsages( psiElement, typeParameterNameSet, annotationHolder, textAttributesKey ); } } } private void highlightTypesAndTypeParameterUsages(QualifiedAlias qualifiedAlias, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey textAttributesKey) { highlightTypesAndTypeParameterUsages( qualifiedAlias.getFirstChild(), typeParameterNameSet, annotationHolder, textAttributesKey ); highlightTypesAndTypeParameterUsages( qualifiedAlias.getLastChild(), typeParameterNameSet, annotationHolder, textAttributesKey ); } private void highlightTypesAndTypeParameterUsages( QualifiedNoArgumentsCall qualifiedNoArgumentsCall, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey textAttributesKey) { highlightTypesAndTypeParameterUsages( qualifiedNoArgumentsCall.getFirstChild(), typeParameterNameSet, annotationHolder, textAttributesKey ); highlightTypesAndTypeParameterUsages( qualifiedNoArgumentsCall.getRelativeIdentifier(), typeParameterNameSet, annotationHolder, textAttributesKey ); } private void highlightTypesAndTypeParameterUsages( QualifiedNoParenthesesCall qualifiedNoParenthesesCall, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey textAttributesKey) { highlightTypesAndTypeParameterUsages( qualifiedNoParenthesesCall.getFirstChild(), typeParameterNameSet, annotationHolder, textAttributesKey ); highlightTypesAndTypeParameterUsages( qualifiedNoParenthesesCall.getRelativeIdentifier(), typeParameterNameSet, annotationHolder, textAttributesKey ); highlightTypesAndTypeParameterUsages( qualifiedNoParenthesesCall.primaryArguments(), typeParameterNameSet, annotationHolder, textAttributesKey ); } private void highlightTypesAndTypeParameterUsages( QualifiedParenthesesCall qualifiedParenthesesCall, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey textAttributesKey) { highlightTypesAndTypeParameterUsages( qualifiedParenthesesCall.getFirstChild(), typeParameterNameSet, annotationHolder, textAttributesKey ); highlightTypesAndTypeParameterUsages( qualifiedParenthesesCall.getRelativeIdentifier(), typeParameterNameSet, annotationHolder, textAttributesKey ); highlightTypesAndTypeParameterUsages( qualifiedParenthesesCall.primaryArguments(), typeParameterNameSet, annotationHolder, textAttributesKey ); PsiElement[] secondaryArguments = qualifiedParenthesesCall.secondaryArguments(); if (secondaryArguments != null) { highlightTypesAndTypeParameterUsages( secondaryArguments, typeParameterNameSet, annotationHolder, textAttributesKey ); } } private void highlightTypesAndTypeParameterUsages( UnqualifiedNoParenthesesCall unqualifiedNoParenthesesCall, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey typeTextAttributesKey) { PsiElement functionNameElement = unqualifiedNoParenthesesCall.functionNameElement(); if (functionNameElement != null) { highlight(functionNameElement.getTextRange(), annotationHolder, typeTextAttributesKey); highlightTypesAndTypeParameterUsages( unqualifiedNoParenthesesCall.primaryArguments(), typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } else { error("Cannot highlight types and type parameter usages", unqualifiedNoParenthesesCall); } } private void highlightTypesAndTypeParameterUsages( UnqualifiedParenthesesCall unqualifiedParenthesesCall, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey typeTextAttributesKey) { if (!unqualifiedParenthesesCall.isCalling(KERNEL, UNQUOTE, 1)) { PsiElement functionNameElement = unqualifiedParenthesesCall.functionNameElement(); if (functionNameElement != null) { highlight(functionNameElement.getTextRange(), annotationHolder, typeTextAttributesKey); highlightTypesAndTypeParameterUsages( unqualifiedParenthesesCall.primaryArguments(), typeParameterNameSet, annotationHolder, typeTextAttributesKey ); PsiElement[] secondaryArguments = unqualifiedParenthesesCall.secondaryArguments(); if (secondaryArguments != null) { highlightTypesAndTypeParameterUsages( secondaryArguments, typeParameterNameSet, annotationHolder, typeTextAttributesKey ); } } else { error("Cannot highlight types and type parameter usages", unqualifiedParenthesesCall); } } } private void highlightTypesAndTypeParameterUsages( When when, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey typeTextAttributesKey) { return; } private void highlightTypesAndSpecificationTypeParameterDeclarations( ElixirUnmatchedUnqualifiedNoArgumentsCall unmatchedUnqualifiedNoArgumentsCall, Set<String> typeParameterNameSet, AnnotationHolder annotationHolder, TextAttributesKey textAttributesKey ) { String variable = unmatchedUnqualifiedNoArgumentsCall.getText(); if (typeParameterNameSet.contains(variable)) { highlight( unmatchedUnqualifiedNoArgumentsCall.getTextRange(), annotationHolder, ElixirSyntaxHighlighter.TYPE_PARAMETER ); } else { highlightTypesAndTypeParameterUsages( unmatchedUnqualifiedNoArgumentsCall, typeParameterNameSet, annotationHolder, textAttributesKey ); } } @NotNull private Set<String> specificationTypeParameterNameSet(ElixirKeywordPair keywordPair) { return Collections.singleton(keywordPair.getKeywordKey().getText()); } @NotNull private Set<String> specificationTypeParameterNameSet(ElixirNoParenthesesKeywordPair noParenthesesKeywordPair) { return Collections.singleton(noParenthesesKeywordPair.getKeywordKey().getText()); } private Set<String> specificationTypeParameterNameSet(@NotNull PsiElement psiElement) { Set<String> parameterNameSet; if (psiElement instanceof ElixirAccessExpression || psiElement instanceof ElixirKeywords || psiElement instanceof ElixirList || psiElement instanceof ElixirNoParenthesesKeywords) { parameterNameSet = specificationTypeParameterNameSet(psiElement.getChildren()); } else if (psiElement instanceof ElixirKeywordPair) { parameterNameSet = specificationTypeParameterNameSet((ElixirKeywordPair) psiElement); } else if (psiElement instanceof ElixirNoParenthesesKeywordPair) { parameterNameSet = specificationTypeParameterNameSet((ElixirNoParenthesesKeywordPair) psiElement); } else if (psiElement instanceof UnqualifiedNoArgumentsCall) { parameterNameSet = specificationTypeParameterNameSet((UnqualifiedNoArgumentsCall) psiElement); } else { error("Cannot extract specification type parameter name set", psiElement); parameterNameSet = Collections.emptySet(); } return parameterNameSet; } private Set<String> specificationTypeParameterNameSet(PsiElement[] psiElements) { Set<String> accumulatedTypeParameterNameSet = new HashSet<String>(); for (PsiElement psiElement : psiElements) { accumulatedTypeParameterNameSet.addAll(specificationTypeParameterNameSet(psiElement)); } return accumulatedTypeParameterNameSet; } /** * Occurs temporarily while typing before {@code :} in KeywordPairs after the {@code when}, such as in * {@code @spec foo(id) :: id when id} before finishing typing {@code @spec foo(id) :: id when id: String.t}. */ private Set<String> specificationTypeParameterNameSet(UnqualifiedNoArgumentsCall unqualifiedNoArgumentsCall) { String name = unqualifiedNoArgumentsCall.functionName(); Set<String> nameSet; if (name != null) { nameSet = Collections.singleton(unqualifiedNoArgumentsCall.functionName()); } else { nameSet = Collections.emptySet(); } return nameSet; } private Set<String> typeTypeParameterNameSet(@NotNull ElixirTuple tuple) { Set<String> typeParameterNameSet = null; PsiElement[] children = tuple.getChildren(); if (children.length == 3) { PsiElement firstChild = children[0]; if (firstChild instanceof UnqualifiedNoArgumentsCall) { PsiElement strippedSecondChild = stripAccessExpression(children[1]); if (strippedSecondChild instanceof ElixirList) { PsiElement strippedThirdChild = stripAccessExpression(children[2]); if (strippedThirdChild instanceof ElixirAtomKeyword && strippedThirdChild.getText().equals("nil")) { typeParameterNameSet = Collections.singleton(firstChild.getText()); } } } } if (typeParameterNameSet == null) { error("Cannot extract type type parameter name set", tuple); typeParameterNameSet = Collections.emptySet(); } return typeParameterNameSet; } private Set<String> typeTypeParameterNameSet(PsiElement psiElement) { Set<String> typeParameterNameSet; if (psiElement instanceof ElixirAccessExpression) { typeParameterNameSet = typeTypeParameterNameSet(psiElement.getChildren()); } else if (psiElement instanceof ElixirTuple) { typeParameterNameSet = typeTypeParameterNameSet((ElixirTuple) psiElement); } else if (psiElement instanceof ElixirUnmatchedUnqualifiedNoArgumentsCall) { typeParameterNameSet = Collections.singleton(psiElement.getText()); } else { error("Cannot extract type type parameter name set", psiElement); typeParameterNameSet = Collections.emptySet(); } return typeParameterNameSet; } private Set<String> typeTypeParameterNameSet(PsiElement[] psiElements) { Set<String> typeParameerNameSet = new HashSet<String>(); for (PsiElement psiElement : psiElements) { typeParameerNameSet.addAll(typeTypeParameterNameSet(psiElement)); } return typeParameerNameSet; } }