Permalink
Browse files

Replace = with :: Quick Fix

If `=` is used instead of `::` for a type specification, mark it as an
error and add Quick Fix to convert the `=` to `::`.
  • Loading branch information...
1 parent b2fd06c commit 310a7af3e2bb01137f63e5c55116b9ef262d1954 @KronicDeth committed Dec 4, 2016
@@ -0,0 +1,20 @@
+<html>
+<body>
+Type specifications separate the name from the definition using `::`. Replace `=` with `::`.
+<!-- tooltip end -->
+
+<code>
+<pre>
+@type name = definition
+</pre>
+</code>
+
+Replace the `=` with ` ::`
+
+<code>
+<pre>
+@type name :: definition
+</pre>
+</code>
+</body>
+</html>
@@ -2123,6 +2123,10 @@
enabledByDefault="true" groupName="Elixir"
implementationClass="org.elixir_lang.inspection.KeywordPairColonInsteadOfTypeOperator"
language="Elixir" level="ERROR" shortName="KeywordPairColonInsteadOfTypeOperator"/>
+ <localInspection displayName="Match operator (=) used in type spec instead of type operator (:)"
+ enabledByDefault="true" groupName="Elixir"
+ implementationClass="org.elixir_lang.inspection.MatchOperatorInsteadOfTypeOperator"
+ language="Elixir" level="ERROR" shortName="MatchOperatorInsteadOfTypeOperator"/>
<!-- module attribute refactoring -->
<renameHandler implementation="org.elixir_lang.refactoring.module_attribute.rename.Handler"/>
@@ -270,8 +270,8 @@ private void highlightType(@NotNull final AtUnqualifiedNoParenthesesCall atUnqua
if (grandChildren.length == 1) {
PsiElement grandChild = grandChildren[0];
- if (grandChild instanceof Match /* Match is invalid. It will be marked by MatchInsteadOfTypeOperator
- inspection as an error */
+ 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();
@@ -0,0 +1,115 @@
+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 "Keyword pair colon (:) used in type spec instead of type operator (:)";
+ }
+
+ @Nls
+ @NotNull
+ @Override
+ public String getGroupDisplayName() {
+ return "Elixir";
+ }
+
+ @NotNull
+ @Override
+ public String getShortName() {
+ return "KeywordPairColonInsteadOfTypeOperator";
+ }
+
+ @Override
+ public boolean isEnabledByDefault() {
+ return true;
+ }
+}
@@ -0,0 +1,63 @@
+package org.elixir_lang.local_quick_fix;
+
+import com.intellij.codeInspection.LocalQuickFix;
+import com.intellij.codeInspection.ProblemDescriptor;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.psi.text.BlockSupport;
+import org.jetbrains.annotations.NotNull;
+
+public class ConvertMatchToTypeOperation implements LocalQuickFix {
+ @NotNull
+ private final ASTNode matchOperatorASTNode;
+
+ /*
+ * Constructors
+ */
+
+ public ConvertMatchToTypeOperation(@NotNull ASTNode matchOperatorASTNode) {
+ this.matchOperatorASTNode = matchOperatorASTNode;
+ }
+
+ /*
+ * Instance Methods
+ */
+
+ /**
+ * Called to apply the fix.
+ *
+ * @param project {@link Project}
+ * @param descriptor problem reported by the tool which provided this quick fix action
+ */
+ @Override
+ public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
+ BlockSupport blockSupport = BlockSupport.getInstance(project);
+ TextRange textRange = matchOperatorASTNode.getTextRange();
+ blockSupport.reparseRange(
+ matchOperatorASTNode.getPsi().getContainingFile(),
+ textRange.getStartOffset(),
+ textRange.getEndOffset(),
+ "::"
+ );
+ }
+
+ /**
+ * @return text to appear in "Apply Fix" popup when multiple Quick Fixes exist (in the results of batch code
+ * inspection). For example, * if the name of the quickfix is "Create template &lt;filename&gt", the return value of
+ * getFamilyName() should be "Create template". * If the name of the quickfix does not depend on a specific element,
+ * simply return getName().
+ */
+ @NotNull
+ @Override
+ public String getFamilyName() {
+ return "Fix type specification";
+ }
+
+
+ @NotNull
+ @Override
+ public String getName() {
+ return "Replace `=` in keyword pair with `::` to convert to a valid type specification";
+ }
+}
@@ -0,0 +1 @@
+@type status_effects<error descr="Type specifications separate the name from the definition using `::`, not `=`">=</error> [any]
@@ -2,9 +2,6 @@
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
-/**
- * Created by luke.imhoff on 12/5/14.
- */
public class KeywordPairColonInsteadOfTypeOperatorTestCase extends LightCodeInsightFixtureTestCase {
public void testIssue525() {
myFixture.configureByFiles("issue_525.ex");
@@ -0,0 +1,16 @@
+package org.elixir_lang.inspection;
+
+import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
+
+public class MatchOperatorInsteadOfTypeOperatorTestCase extends LightCodeInsightFixtureTestCase {
+ public void testMatchOperator() {
+ myFixture.configureByFiles("match_operator.ex");
+ myFixture.enableInspections(MatchOperatorInsteadOfTypeOperator.class);
+ myFixture.checkHighlighting();
+ }
+
+ @Override
+ protected String getTestDataPath() {
+ return "testData/org/elixir_lang/inspection/match_operator_instead_of_type_operator_test_case";
+ }
+}

0 comments on commit 310a7af

Please sign in to comment.