package org.elixir_lang.inspection; import com.intellij.codeInspection.*; import com.intellij.lang.ASTNode; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiRecursiveElementWalkingVisitor; import org.elixir_lang.local_quick_fix.ConvertMatchToTypeOperation; import org.elixir_lang.psi.*; import org.elixir_lang.psi.operation.Infix; import org.elixir_lang.psi.operation.Match; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.identifierName; import static org.elixir_lang.psi.impl.ElixirPsiImplUtil.operatorTokenNode; import static org.elixir_lang.reference.ModuleAttribute.isTypeName; public class MatchOperatorInsteadOfTypeOperator extends LocalInspectionTool { /* * Instance Methods */ @NotNull @Override public ProblemDescriptor[] checkFile(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly) { final ProblemsHolder problemsHolder = new ProblemsHolder(manager, file, isOnTheFly); file.accept( new PsiRecursiveElementWalkingVisitor() { @Override public void visitElement(@NotNull final PsiElement element) { // See org.elixir_lang.annotator.ModuleAttribute.annotate for path of checks if (element instanceof AtUnqualifiedNoParenthesesCall) { visitAtUnqualifiedNoParenthesesCall((AtUnqualifiedNoParenthesesCall) element); } super.visitElement(element); } private void visitAtUnqualifiedNoParenthesesCall( @NotNull final AtUnqualifiedNoParenthesesCall atUnqualifiedNoParenthesesCall ) { ElixirAtIdentifier atIdentifier = atUnqualifiedNoParenthesesCall.getAtIdentifier(); String identifier = identifierName(atIdentifier); if (isTypeName(identifier)) { PsiElement child = atUnqualifiedNoParenthesesCall.getNoParenthesesOneArgument(); PsiElement[] grandChildren = child.getChildren(); if (grandChildren.length == 1) { PsiElement grandChild = grandChildren[0]; if (grandChild instanceof Match) { Infix infix = (Infix) grandChild; Operator operator = infix.operator(); int elementStartOffset = operator.getTextOffset(); ASTNode astNode = operatorTokenNode(operator); int nodeStartOffset = astNode.getStartOffset(); int nodeTextLength = astNode.getTextLength(); int relativeStart = nodeStartOffset - elementStartOffset; TextRange relativeTextRange = new TextRange( relativeStart, relativeStart + nodeTextLength ); LocalQuickFix localQuickFix = new ConvertMatchToTypeOperation(astNode); problemsHolder.registerProblem( operator, "Type specifications separate the name from the definition using `::`, not `=`", ProblemHighlightType.ERROR, relativeTextRange, localQuickFix ); } } } } } ); return problemsHolder.getResultsArray(); } @Nls @NotNull @Override public String getDisplayName() { return "Match operator (=) used in type spec instead of type operator (::)"; } @Nls @NotNull @Override public String getGroupDisplayName() { return "Elixir"; } @NotNull @Override public String getShortName() { return "MatchOperatorInsteadOfTypeOperator"; } @Override public boolean isEnabledByDefault() { return true; } }