diff --git a/jdtls.ext/com.shengchen.checkstyle.runner/src/main/java/com/shengchen/checkstyle/quickfix/BaseQuickFix.java b/jdtls.ext/com.shengchen.checkstyle.runner/src/main/java/com/shengchen/checkstyle/quickfix/BaseQuickFix.java index 4ed4a39..b3b97ee 100644 --- a/jdtls.ext/com.shengchen.checkstyle.runner/src/main/java/com/shengchen/checkstyle/quickfix/BaseQuickFix.java +++ b/jdtls.ext/com.shengchen.checkstyle.runner/src/main/java/com/shengchen/checkstyle/quickfix/BaseQuickFix.java @@ -98,4 +98,24 @@ protected boolean replace(final ASTNode node, final ASTNode replacement) { } return false; } + + /** + * Find the nearest ancestor (or self) of the given node that is an instance of the + * given class. + * @param the node type + * @param node the node to start the search from + * @param ancestorClass the node class to find + * @return + */ + @SuppressWarnings("unchecked") + protected T ancestor(final ASTNode node, final Class ancestorClass) { + if (node == null) { + return null; + } + if (ancestorClass.isInstance(node)) { + return (T) node; + } else { + return ancestor(node.getParent(), ancestorClass); + } + } } diff --git a/jdtls.ext/com.shengchen.checkstyle.runner/src/main/java/com/shengchen/checkstyle/quickfix/FixableCheck.java b/jdtls.ext/com.shengchen.checkstyle.runner/src/main/java/com/shengchen/checkstyle/quickfix/FixableCheck.java index f6b1b41..a61d3ff 100644 --- a/jdtls.ext/com.shengchen.checkstyle.runner/src/main/java/com/shengchen/checkstyle/quickfix/FixableCheck.java +++ b/jdtls.ext/com.shengchen.checkstyle.runner/src/main/java/com/shengchen/checkstyle/quickfix/FixableCheck.java @@ -26,6 +26,7 @@ public enum FixableCheck { EMPTY_STATEMENT_CHECK("EmptyStatementCheck"), MISSING_SWITCH_DEFAULT_CHECK("MissingSwitchDefaultCheck"), EXPLICIT_INITIALIZATION_CHECK("ExplicitInitializationCheck"), REQUIRE_THIS_CHECK("RequireThisCheck"), SIMPLIFY_BOOLEAN_RETURN_CHECK("SimplifyBooleanReturnCheck"), STRING_LITERAL_EQUALITY("StringLiteralEqualityCheck"), + MAGIC_NUMBER_CHECK("MagicNumberCheck"), // Design DESIGN_FOR_EXTENSION_CHECK("DesignForExtensionCheck"), FINAL_CLASS_CHECK("FinalClassCheck"), diff --git a/jdtls.ext/com.shengchen.checkstyle.runner/src/main/java/com/shengchen/checkstyle/quickfix/QuickFixService.java b/jdtls.ext/com.shengchen.checkstyle.runner/src/main/java/com/shengchen/checkstyle/quickfix/QuickFixService.java index 5c49c99..bf97126 100644 --- a/jdtls.ext/com.shengchen.checkstyle.runner/src/main/java/com/shengchen/checkstyle/quickfix/QuickFixService.java +++ b/jdtls.ext/com.shengchen.checkstyle.runner/src/main/java/com/shengchen/checkstyle/quickfix/QuickFixService.java @@ -23,6 +23,7 @@ import com.shengchen.checkstyle.quickfix.coding.EmptyStatementQuickFix; import com.shengchen.checkstyle.quickfix.coding.ExplicitInitializationQuickFix; import com.shengchen.checkstyle.quickfix.coding.FinalLocalVariableQuickFix; +import com.shengchen.checkstyle.quickfix.coding.MagicNumberQuickFix; import com.shengchen.checkstyle.quickfix.coding.MissingSwitchDefaultQuickFix; import com.shengchen.checkstyle.quickfix.coding.RequireThisQuickFix; import com.shengchen.checkstyle.quickfix.coding.SimplifyBooleanReturnQuickFix; @@ -77,6 +78,7 @@ public QuickFixService() { quickFixMap.put(FixableCheck.ARRAY_TYPE_STYLE_CHECK.toString(), new ArrayTypeStyleQuickFix()); quickFixMap.put(FixableCheck.SIMPLIFY_BOOLEAN_RETURN_CHECK.toString(), new SimplifyBooleanReturnQuickFix()); quickFixMap.put(FixableCheck.STRING_LITERAL_EQUALITY.toString(), new StringLiteralEqualityQuickFix()); + quickFixMap.put(FixableCheck.MAGIC_NUMBER_CHECK.toString(), new MagicNumberQuickFix()); } public BaseQuickFix getQuickFix(String sourceName) { diff --git a/jdtls.ext/com.shengchen.checkstyle.runner/src/main/java/com/shengchen/checkstyle/quickfix/coding/MagicNumberQuickFix.java b/jdtls.ext/com.shengchen.checkstyle.runner/src/main/java/com/shengchen/checkstyle/quickfix/coding/MagicNumberQuickFix.java new file mode 100644 index 0000000..da83e0d --- /dev/null +++ b/jdtls.ext/com.shengchen.checkstyle.runner/src/main/java/com/shengchen/checkstyle/quickfix/coding/MagicNumberQuickFix.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) jdneo + + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.shengchen.checkstyle.quickfix.coding; + +import com.shengchen.checkstyle.quickfix.BaseQuickFix; + +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.FieldDeclaration; +import org.eclipse.jdt.core.dom.Modifier; +import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword; +import org.eclipse.jdt.core.dom.NumberLiteral; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jface.text.IRegion; + +import java.util.List; + +public class MagicNumberQuickFix extends BaseQuickFix { + + @Override + public ASTVisitor getCorrectingASTVisitor(IRegion lineInfo, int markerStartOffset) { + return new ASTVisitor() { + + @Override + public boolean visit(NumberLiteral node) { + if (containsPosition(node, markerStartOffset)) { + final TypeDeclaration container = ancestor(node, TypeDeclaration.class); + if (container != null) { + final String newConstantName = createOrReuseConstant(node, container); + + replace(node, node.getAST().newSimpleName(newConstantName)); + return true; + } + } + return true; + } + + @SuppressWarnings("unchecked") + private String createOrReuseConstant(NumberLiteral node, final TypeDeclaration container) { + OUTER: + for (int iteration = 0; ; iteration++) { + final String newConstantName = generateConstantName(node, iteration); + + /* Check for an existing constant with our newConstantName, to support correcting multiple + magic numbers at once. + */ + for (final Object candidate : container.bodyDeclarations()) { + if (!(candidate instanceof FieldDeclaration)) { + continue; + } + + final FieldDeclaration field = (FieldDeclaration) candidate; + for (final VariableDeclarationFragment fragment : + ((List) field.fragments())) { + if (newConstantName.equals(fragment.getName().getIdentifier())) { + if (field.getModifiers() == + (Modifier.FINAL | Modifier.PRIVATE | Modifier.STATIC) && + fragment.getInitializer() != null && + fragment.getInitializer() instanceof NumberLiteral && + node.getToken().equals(((NumberLiteral) fragment.getInitializer()).getToken())) { + /* We can use this existing constant */ + return newConstantName; + } else { + /* We cannot use this constant so we need to come up with a new name */ + continue OUTER; + } + } + } + } + + /* Make a new constant */ + final VariableDeclarationFragment fragment = node.getAST().newVariableDeclarationFragment(); + fragment.setName(node.getAST().newSimpleName(newConstantName)); + fragment.setInitializer(copy(node)); + + final FieldDeclaration constantElement = node.getAST().newFieldDeclaration(fragment); + constantElement.modifiers().add(node.getAST().newModifier(ModifierKeyword.PRIVATE_KEYWORD)); + constantElement.modifiers().add(node.getAST().newModifier(ModifierKeyword.STATIC_KEYWORD)); + constantElement.modifiers().add(node.getAST().newModifier(ModifierKeyword.FINAL_KEYWORD)); + container.bodyDeclarations().add(0, constantElement); + return newConstantName; + } + } + + private String generateConstantName(NumberLiteral node, int iteration) { + return "_" + (iteration > 0 ? iteration + "_" : "") + node.getToken(); + } + + }; + } +} diff --git a/src/constants/quickFix.ts b/src/constants/quickFix.ts index 1eb0582..9ca3604 100644 --- a/src/constants/quickFix.ts +++ b/src/constants/quickFix.ts @@ -15,6 +15,7 @@ export enum FixableCheck { RequireThisCheck = 'RequireThisCheck', SimplifyBooleanReturnCheck = 'SimplifyBooleanReturnCheck', StringLiteralEqualityCheck = 'StringLiteralEqualityCheck', + MagicNumberCheck = 'MagicNumberCheck', // Design DesignForExtensionCheck = 'DesignForExtensionCheck',