diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/VariableDeclarationUsageDistanceCheck.java b/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/VariableDeclarationUsageDistanceCheck.java new file mode 100644 index 000000000..9655dd9a8 --- /dev/null +++ b/src/main/java/com/puppycrawl/tools/checkstyle/checks/coding/VariableDeclarationUsageDistanceCheck.java @@ -0,0 +1,892 @@ +//////////////////////////////////////////////////////////////////////////////// +// checkstyle: Checks Java source code for adherence to a set of rules. +// Copyright (C) 2001-2014 Oliver Burn +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library 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 +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//////////////////////////////////////////////////////////////////////////////// +package com.puppycrawl.tools.checkstyle.checks.coding; + +import java.util.AbstractMap.SimpleEntry; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import antlr.collections.ASTEnumeration; + +import com.puppycrawl.tools.checkstyle.api.Check; +import com.puppycrawl.tools.checkstyle.api.DetailAST; +import com.puppycrawl.tools.checkstyle.api.FullIdent; +import com.puppycrawl.tools.checkstyle.api.TokenTypes; + +/** + *
+ * Checks the distance between declaration of variable and its first usage. + *
+ * Example #1: + *
+ * int count;
+ * a = a + b;
+ * b = a + a;
+ * count = b; // DECLARATION OF VARIABLE 'count'
+ * // SHOULD BE HERE (distance = 3)
+ *
+ * Example #2:
+ *
+ * int count;
+ * {
+ * a = a + b;
+ * count = b; // DECLARATION OF VARIABLE 'count'
+ * // SHOULD BE HERE (distance = 2)
+ * }
+ *
+ *
+ * + * Check can detect a block of initialization methods. If a variable is used in + * such a block and there is no other statements after this variable then distance=1. + *
+ *+ * Case #1: + *
+ * int minutes = 5; + * Calendar cal = Calendar.getInstance(); + * cal.setTimeInMillis(timeNow); + * cal.set(Calendar.SECOND, 0); + * cal.set(Calendar.MILLISECOND, 0); + * cal.set(Calendar.HOUR_OF_DAY, hh); + * cal.set(Calendar.MINUTE, minutes); + * + * The distance for the variable minutes is 1 even + * though this variable is used in the fifth method's call. + *+ * + *
+ * Case #2: + *
+ * int minutes = 5; + * Calendar cal = Calendar.getInstance(); + * cal.setTimeInMillis(timeNow); + * cal.set(Calendar.SECOND, 0); + * cal.set(Calendar.MILLISECOND, 0); + * System.out.println(cal); + * cal.set(Calendar.HOUR_OF_DAY, hh); + * cal.set(Calendar.MINUTE, minutes); + * + * The distance for the variable minutes is 6 because there is one more expression + * (except the initialization block) between the declaration of this variable and its usage. + *+ * + * + * There are several additional options to configure the check: + *
+ * 1. allowedDistance - allows to set a distance + * between declaration of variable and its first usage. + * 2. ignoreVariablePattern - allows to set a RegEx pattern for + * ignoring the distance calculation for variables listed in this pattern. + * 3. validateBetweenScopes - allows to calculate the distance between + * declaration of variable and its first usage in the different scopes. + * 4. ignoreFinal - allows to ignore variables with a 'final' modifier. + *+ * ATTENTION!! (Not supported cases) + *
+ * Case #1:
+ * {
+ * int c;
+ * int a = 3;
+ * int b = 2;
+ * {
+ * a = a + b;
+ * c = b;
+ * }
+ * }
+ *
+ * Distance for variable 'a' = 1;
+ * Distance for variable 'b' = 1;
+ * Distance for variable 'c' = 2.
+ *
+ * As distance by default is 1 the Check doesn't raise warning for variables 'a'
+ * and 'b' to move them into the block.
+ *
+ * Case #2:
+ * int sum = 0;
+ * for (int i = 0; i < 20; i++) {
+ * a++;
+ * b--;
+ * sum++;
+ * if (sum > 10) {
+ * res = true;
+ * }
+ * }
+ * Distance for variable 'sum' = 3.
+ *
+ * + * As the distance is more then the default one, the Check raises warning for variable + * 'sum' to move it into the 'for(...)' block. But there is situation when + * variable 'sum' hasn't to be 0 within each iteration. So, to avoid such + * warnings you can use Suppression Filter, provided by Checkstyle, for the + * whole class. + *
+ * + *+ * An example how to configure this Check: + *
+ *+ * <module name="VariableDeclarationUsageDistance"/> + *+ *
+ * An example of how to configure this Check: + * - to set the allowed distance to 4; + * - to ignore variables with prefix '^temp'; + * - to force the validation between scopes; + * - to check the final variables; + *
+ *+ * <module name="VariableDeclarationUsageDistance"> + * <property name="allowedDistance" value="4"> + * <property name="ignoreVariablePattern" value="^temp.*"> + * <property name="validateBetweenScopes" value="true"> + * <property name="mIgnoreFinal" value="false"> + * </module> + *+ * + * @author Ruslan Diachenko + * @author Baratali Izmailov + */ +public class VariableDeclarationUsageDistanceCheck extends Check +{ + /** + * Warning message key. + */ + public static final String MSG_KEY = "variable.declaration.usage.distance"; + + /** + * Default value of distance between declaration of variable and its first + * usage. + */ + private static final int DEFAULT_DISTANCE = 3; + + /** Allowed distance between declaration of variable and its first usage. */ + private int mAllowedDistance = DEFAULT_DISTANCE; + + /** + * RegExp pattern to ignore distance calculation for variables listed in + * this pattern. + */ + private Pattern mIgnoreVariablePattern = Pattern.compile(""); + + /** + * Allows to calculate distance between declaration of variable and its + * first usage in different scopes. + */ + private boolean mValidateBetweenScopes; + + /** Allows to ignore variables with 'final' modifier. */ + private boolean mIgnoreFinal = true; + + /** + * Sets an allowed distance between declaration of variable and its first + * usage. + * @param aAllowedDistance + * Allowed distance between declaration of variable and its first + * usage. + */ + public void setAllowedDistance(int aAllowedDistance) + { + this.mAllowedDistance = aAllowedDistance; + } + + /** + * Sets RegExp pattern to ignore distance calculation for variables listed + * in this pattern. + * @param aIgnorePattern + * Pattern contains ignored variables. + */ + public void setIgnoreVariablePattern(String aIgnorePattern) + { + this.mIgnoreVariablePattern = Pattern.compile(aIgnorePattern); + } + + /** + * Sets option which allows to calculate distance between declaration of + * variable and its first usage in different scopes. + * @param aValidateBetweenScopes + * Defines if allow to calculate distance between declaration of + * variable and its first usage in different scopes or not. + */ + public void setValidateBetweenScopes(boolean aValidateBetweenScopes) + { + this.mValidateBetweenScopes = aValidateBetweenScopes; + } + + /** + * Sets ignore option for variables with 'final' modifier. + * @param aIgnoreFinal + * Defines if ignore variables with 'final' modifier or not. + */ + public void setIgnoreFinal(boolean aIgnoreFinal) + { + this.mIgnoreFinal = aIgnoreFinal; + } + + @Override + public int[] getDefaultTokens() + { + return new int[] {TokenTypes.VARIABLE_DEF}; + } + + @Override + public void visitToken(DetailAST aAST) + { + final int parentType = aAST.getParent().getType(); + final DetailAST modifiers = aAST.getFirstChild(); + + if ((mIgnoreFinal && modifiers.branchContains(TokenTypes.FINAL)) + || parentType == TokenTypes.OBJBLOCK) + { + ;// no code + } + else { + final DetailAST variable = aAST.findFirstToken(TokenTypes.IDENT); + + if (!isVariableMatchesIgnorePattern(variable.getText())) { + final DetailAST semicolonAst = aAST.getNextSibling(); + Entry
+ * boolean b = true;
+ * if (b) {...}
+ *
+ * Variable 'b' is in declaration of operator IF.
+ * @param aOperator
+ * Ast node which represents operator.
+ * @param aVariable
+ * Variable which is checked for content in operator.
+ * @return true if operator contains variable in its declaration, otherwise
+ * - false.
+ */
+ private boolean isVariableInOperatorExpr(
+ DetailAST aOperator, DetailAST aVariable)
+ {
+ boolean isVarInOperatorDeclr = false;
+ final DetailAST openingBracket =
+ aOperator.findFirstToken(TokenTypes.LPAREN);
+
+ if (openingBracket != null) {
+ // Get EXPR between brackets
+ DetailAST exprBetweenBrackets = openingBracket
+ .getNextSibling();
+
+ // Look if variable is in operator expression
+ while (exprBetweenBrackets.getType() != TokenTypes.RPAREN) {
+
+ if (isChild(exprBetweenBrackets, aVariable)) {
+ isVarInOperatorDeclr = true;
+ break;
+ }
+ exprBetweenBrackets = exprBetweenBrackets.getNextSibling();
+ }
+
+ // Variable may be met in ELSE declaration or in CASE declaration.
+ // So, check variable usage in these declarations.
+ if (!isVarInOperatorDeclr) {
+ switch (aOperator.getType()) {
+ case TokenTypes.LITERAL_IF:
+ final DetailAST elseBlock = aOperator.getLastChild();
+
+ if (elseBlock.getType() == TokenTypes.LITERAL_ELSE) {
+ // Get IF followed by ELSE
+ final DetailAST firstNodeInsideElseBlock = elseBlock
+ .getFirstChild();
+
+ if (firstNodeInsideElseBlock.getType()
+ == TokenTypes.LITERAL_IF)
+ {
+ isVarInOperatorDeclr |=
+ isVariableInOperatorExpr(
+ firstNodeInsideElseBlock,
+ aVariable);
+ }
+ }
+ break;
+
+ case TokenTypes.LITERAL_SWITCH:
+ DetailAST currentCaseBlock = aOperator
+ .findFirstToken(TokenTypes.CASE_GROUP);
+
+ while (currentCaseBlock != null
+ && currentCaseBlock.getType()
+ == TokenTypes.CASE_GROUP)
+ {
+ final DetailAST firstNodeInsideCaseBlock =
+ currentCaseBlock.getFirstChild();
+
+ if (isChild(firstNodeInsideCaseBlock,
+ aVariable))
+ {
+ isVarInOperatorDeclr = true;
+ break;
+ }
+ currentCaseBlock = currentCaseBlock.getNextSibling();
+ }
+ break;
+
+ default:
+ ;// no code
+ }
+ }
+ }
+
+ return isVarInOperatorDeclr;
+ }
+
+ /**
+ * Checks if Ast node contains given element.
+ * @param aParent
+ * Node of AST.
+ * @param aAST
+ * Ast element which is checked for content in Ast node.
+ * @return true if Ast element was found in Ast node, otherwise - false.
+ */
+ private static boolean isChild(DetailAST aParent, DetailAST aAST)
+ {
+ boolean isChild = false;
+ final ASTEnumeration astList = aParent.findAllPartial(aAST);
+
+ while (astList.hasMoreNodes()) {
+ final DetailAST ast = (DetailAST) astList.nextNode();
+ DetailAST astParent = ast.getParent();
+
+ while (astParent != null) {
+
+ if (astParent.equals(aParent)
+ && astParent.getLineNo() == aParent.getLineNo())
+ {
+ isChild = true;
+ break;
+ }
+ astParent = astParent.getParent();
+ }
+ }
+
+ return isChild;
+ }
+
+ /**
+ * Checks if entrance variable is contained in ignored pattern.
+ * @param aVariable
+ * Variable which is checked for content in ignored pattern.
+ * @return true if variable was found, otherwise - false.
+ */
+ private boolean isVariableMatchesIgnorePattern(String aVariable)
+ {
+ final Matcher matcher = mIgnoreVariablePattern.matcher(aVariable);
+ return matcher.matches();
+ }
+}
diff --git a/src/main/resources/com/puppycrawl/tools/checkstyle/checks/coding/messages.properties b/src/main/resources/com/puppycrawl/tools/checkstyle/checks/coding/messages.properties
index 871722a71..677e99f70 100644
--- a/src/main/resources/com/puppycrawl/tools/checkstyle/checks/coding/messages.properties
+++ b/src/main/resources/com/puppycrawl/tools/checkstyle/checks/coding/messages.properties
@@ -64,3 +64,4 @@ unnecessary.paren.literal=Unnecessary parentheses around literal ''{0}''.
unnecessary.paren.return=Unnecessary parentheses around return value.
unnecessary.paren.string=Unnecessary parentheses around string {0}.
package.dir.mismatch=Package declaration does not match directory ''{0}''.
+variable.declaration.usage.distance=Distance between variable ''{0}'' declaration and its first usage is {1}, but allowed {2}.
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/VariableDeclarationUsageDistanceCheckTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/VariableDeclarationUsageDistanceCheckTest.java
new file mode 100644
index 000000000..b7b41f549
--- /dev/null
+++ b/src/test/java/com/puppycrawl/tools/checkstyle/checks/coding/VariableDeclarationUsageDistanceCheckTest.java
@@ -0,0 +1,215 @@
+////////////////////////////////////////////////////////////////////////////////
+// checkstyle: Checks Java source code for adherence to a set of rules.
+// Copyright (C) 2001-2014 Oliver Burn
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library 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
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+////////////////////////////////////////////////////////////////////////////////
+package com.puppycrawl.tools.checkstyle.checks.coding;
+
+import static com.puppycrawl.tools.checkstyle.checks.coding.VariableDeclarationUsageDistanceCheck.MSG_KEY;
+import static java.text.MessageFormat.format;
+
+import org.junit.Test;
+
+import com.puppycrawl.tools.checkstyle.BaseCheckTestSupport;
+import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
+
+public class VariableDeclarationUsageDistanceCheckTest extends
+ BaseCheckTestSupport
+{
+ @Test
+ public void testGeneralLogic() throws Exception
+ {
+ final DefaultConfiguration checkConfig = createCheckConfig(VariableDeclarationUsageDistanceCheck.class);
+ checkConfig.addAttribute("allowedDistance", "1");
+ checkConfig.addAttribute("ignoreVariablePattern", "");
+ checkConfig.addAttribute("validateBetweenScopes", "true");
+ checkConfig.addAttribute("ignoreFinal", "false");
+ final String[] expected = {
+ "30: " + getCheckMessage(MSG_KEY, "a", 2, 1),
+ "38: " + getCheckMessage(MSG_KEY, "temp", 2, 1),
+ "44: " + getCheckMessage(MSG_KEY, "temp", 2, 1),
+ "57: " + getCheckMessage(MSG_KEY, "count", 2, 1),
+ "71: " + getCheckMessage(MSG_KEY, "count", 4, 1),
+ "96: " + getCheckMessage(MSG_KEY, "arg", 2, 1),
+ "144: " + getCheckMessage(MSG_KEY, "m", 3, 1),
+ "145: " + getCheckMessage(MSG_KEY, "n", 2, 1),
+ "184: " + getCheckMessage(MSG_KEY, "result", 2, 1),
+ "219: " + getCheckMessage(MSG_KEY, "t", 5, 1),
+ "222: " + getCheckMessage(MSG_KEY, "c", 3, 1),
+ "223: " + getCheckMessage(MSG_KEY, "d2", 3, 1),
+ "260: " + getCheckMessage(MSG_KEY, "selected", 2, 1),
+ "261: " + getCheckMessage(MSG_KEY, "model", 2, 1),
+ "287: " + getCheckMessage(MSG_KEY, "sw", 2, 1),
+ "300: " + getCheckMessage(MSG_KEY, "wh", 2, 1),
+ "343: " + getCheckMessage(MSG_KEY, "green", 2, 1),
+ "344: " + getCheckMessage(MSG_KEY, "blue", 3, 1),
+ "367: " + getCheckMessage(MSG_KEY, "intervalMs", 2, 1),
+ "454: " + getCheckMessage(MSG_KEY, "aOpt", 3, 1),
+ "455: " + getCheckMessage(MSG_KEY, "bOpt", 2, 1),
+ "471: " + getCheckMessage(MSG_KEY, "l1", 3, 1),
+ "471: " + getCheckMessage(MSG_KEY, "l2", 2, 1),
+ "479: " + getCheckMessage(MSG_KEY, "myOption", 7, 1),
+ "491: " + getCheckMessage(MSG_KEY, "myOption", 6, 1),
+ "504: " + getCheckMessage(MSG_KEY, "count", 4, 1),
+ "505: " + getCheckMessage(MSG_KEY, "files", 2, 1),
+ "540: " + getCheckMessage(MSG_KEY, "id", 2, 1),
+ "542: " + getCheckMessage(MSG_KEY, "parentId", 3, 1),
+ };
+ verify(checkConfig, getPath("coding/InputVariableDeclarationUsageDistanceCheck.java"), expected);
+ }
+
+ @Test
+ public void testDistance() throws Exception
+ {
+ final DefaultConfiguration checkConfig = createCheckConfig(VariableDeclarationUsageDistanceCheck.class);
+ checkConfig.addAttribute("allowedDistance", "3");
+ checkConfig.addAttribute("ignoreVariablePattern", "");
+ checkConfig.addAttribute("validateBetweenScopes", "true");
+ checkConfig.addAttribute("ignoreFinal", "false");
+ final String[] expected = {
+ "71: " + getCheckMessage(MSG_KEY, "count", 4, 3),
+ "219: " + getCheckMessage(MSG_KEY, "t", 5, 3),
+ "479: " + getCheckMessage(MSG_KEY, "myOption", 7, 3),
+ "491: " + getCheckMessage(MSG_KEY, "myOption", 6, 3),
+ "504: " + getCheckMessage(MSG_KEY, "count", 4, 3),
+ };
+ verify(checkConfig, getPath("coding/InputVariableDeclarationUsageDistanceCheck.java"), expected);
+ }
+
+ @Test
+ public void testVariableRegExp() throws Exception
+ {
+ final DefaultConfiguration checkConfig = createCheckConfig(VariableDeclarationUsageDistanceCheck.class);
+ checkConfig.addAttribute("allowedDistance", "1");
+ checkConfig.addAttribute("ignoreVariablePattern",
+ "a|b|c|d|block|dist|t|m");
+ checkConfig.addAttribute("validateBetweenScopes", "true");
+ checkConfig.addAttribute("ignoreFinal", "false");
+ final String[] expected = {
+ "38: " + getCheckMessage(MSG_KEY, "temp", 2, 1),
+ "44: " + getCheckMessage(MSG_KEY, "temp", 2, 1),
+ "57: " + getCheckMessage(MSG_KEY, "count", 2, 1),
+ "71: " + getCheckMessage(MSG_KEY, "count", 4, 1),
+ "96: " + getCheckMessage(MSG_KEY, "arg", 2, 1),
+ "145: " + getCheckMessage(MSG_KEY, "n", 2, 1),
+ "184: " + getCheckMessage(MSG_KEY, "result", 2, 1),
+ "223: " + getCheckMessage(MSG_KEY, "d2", 3, 1),
+ "260: " + getCheckMessage(MSG_KEY, "selected", 2, 1),
+ "261: " + getCheckMessage(MSG_KEY, "model", 2, 1),
+ "287: " + getCheckMessage(MSG_KEY, "sw", 2, 1),
+ "300: " + getCheckMessage(MSG_KEY, "wh", 2, 1),
+ "343: " + getCheckMessage(MSG_KEY, "green", 2, 1),
+ "344: " + getCheckMessage(MSG_KEY, "blue", 3, 1),
+ "367: " + getCheckMessage(MSG_KEY, "intervalMs", 2, 1),
+ "454: " + getCheckMessage(MSG_KEY, "aOpt", 3, 1),
+ "455: " + getCheckMessage(MSG_KEY, "bOpt", 2, 1),
+ "471: " + getCheckMessage(MSG_KEY, "l1", 3, 1),
+ "471: " + getCheckMessage(MSG_KEY, "l2", 2, 1),
+ "479: " + getCheckMessage(MSG_KEY, "myOption", 7, 1),
+ "491: " + getCheckMessage(MSG_KEY, "myOption", 6, 1),
+ "504: " + getCheckMessage(MSG_KEY, "count", 4, 1),
+ "505: " + getCheckMessage(MSG_KEY, "files", 2, 1),
+ "540: " + getCheckMessage(MSG_KEY, "id", 2, 1),
+ "542: " + getCheckMessage(MSG_KEY, "parentId", 3, 1),
+ };
+ verify(checkConfig, getPath("coding/InputVariableDeclarationUsageDistanceCheck.java"), expected);
+ }
+
+ @Test
+ public void testValidateBetweenScopesOption() throws Exception
+ {
+ final DefaultConfiguration checkConfig = createCheckConfig(VariableDeclarationUsageDistanceCheck.class);
+ checkConfig.addAttribute("allowedDistance", "1");
+ checkConfig.addAttribute("ignoreVariablePattern", "");
+ checkConfig.addAttribute("validateBetweenScopes", "false");
+ checkConfig.addAttribute("ignoreFinal", "false");
+ final String[] expected = {
+ "30: " + getCheckMessage(MSG_KEY, "a", 2, 1),
+ "38: " + getCheckMessage(MSG_KEY, "temp", 2, 1),
+ "44: " + getCheckMessage(MSG_KEY, "temp", 2, 1),
+ "71: " + getCheckMessage(MSG_KEY, "count", 4, 1),
+ "96: " + getCheckMessage(MSG_KEY, "arg", 2, 1),
+ "219: " + getCheckMessage(MSG_KEY, "t", 5, 1),
+ "222: " + getCheckMessage(MSG_KEY, "c", 3, 1),
+ "223: " + getCheckMessage(MSG_KEY, "d2", 3, 1),
+ "300: " + getCheckMessage(MSG_KEY, "wh", 2, 1),
+ "343: " + getCheckMessage(MSG_KEY, "green", 2, 1),
+ "344: " + getCheckMessage(MSG_KEY, "blue", 3, 1),
+ "367: " + getCheckMessage(MSG_KEY, "intervalMs", 2, 1),
+ "454: " + getCheckMessage(MSG_KEY, "aOpt", 3, 1),
+ "455: " + getCheckMessage(MSG_KEY, "bOpt", 2, 1),
+ "471: " + getCheckMessage(MSG_KEY, "l1", 3, 1),
+ "471: " + getCheckMessage(MSG_KEY, "l2", 2, 1),
+ "479: " + getCheckMessage(MSG_KEY, "myOption", 7, 1),
+ "491: Distance between variable 'myOption' declaration and its first usage is 6, but allowed 1.",
+ "505: Distance between variable 'files' declaration and its first usage is 2, but allowed 1.",
+ "540: Distance between variable 'id' declaration and its first usage is 2, but allowed 1.",
+ "542: Distance between variable 'parentId' declaration and its first usage is 4, but allowed 1.",
+ };
+ verify(checkConfig, getPath("coding/InputVariableDeclarationUsageDistanceCheck.java"), expected);
+ }
+
+ @Test
+ public void testIgnoreFinalOption() throws Exception
+ {
+ final DefaultConfiguration checkConfig = createCheckConfig(VariableDeclarationUsageDistanceCheck.class);
+ checkConfig.addAttribute("allowedDistance", "1");
+ checkConfig.addAttribute("ignoreVariablePattern", "");
+ checkConfig.addAttribute("validateBetweenScopes", "true");
+ checkConfig.addAttribute("ignoreFinal", "true");
+ final String[] expected = {
+ "30: " + getCheckMessage(MSG_KEY, "a", 2, 1),
+ "38: " + getCheckMessage(MSG_KEY, "temp", 2, 1),
+ "44: " + getCheckMessage(MSG_KEY, "temp", 2, 1),
+ "57: " + getCheckMessage(MSG_KEY, "count", 2, 1),
+ "71: " + getCheckMessage(MSG_KEY, "count", 4, 1),
+ "96: " + getCheckMessage(MSG_KEY, "arg", 2, 1),
+ "144: " + getCheckMessage(MSG_KEY, "m", 3, 1),
+ "145: " + getCheckMessage(MSG_KEY, "n", 2, 1),
+ "184: " + getCheckMessage(MSG_KEY, "result", 2, 1),
+ "219: " + getCheckMessage(MSG_KEY, "t", 5, 1),
+ "222: " + getCheckMessage(MSG_KEY, "c", 3, 1),
+ "223: " + getCheckMessage(MSG_KEY, "d2", 3, 1),
+ "260: " + getCheckMessage(MSG_KEY, "selected", 2, 1),
+ "261: " + getCheckMessage(MSG_KEY, "model", 2, 1),
+ "287: " + getCheckMessage(MSG_KEY, "sw", 2, 1),
+ "300: " + getCheckMessage(MSG_KEY, "wh", 2, 1),
+ "343: " + getCheckMessage(MSG_KEY, "green", 2, 1),
+ "344: " + getCheckMessage(MSG_KEY, "blue", 3, 1),
+ "454: " + getCheckMessage(MSG_KEY, "aOpt", 3, 1),
+ "455: " + getCheckMessage(MSG_KEY, "bOpt", 2, 1),
+ "471: " + getCheckMessage(MSG_KEY, "l1", 3, 1),
+ "471: " + getCheckMessage(MSG_KEY, "l2", 2, 1),
+ "479: " + getCheckMessage(MSG_KEY, "myOption", 7, 1),
+ "491: " + getCheckMessage(MSG_KEY, "myOption", 6, 1),
+ "504: " + getCheckMessage(MSG_KEY, "count", 4, 1),
+ "505: " + getCheckMessage(MSG_KEY, "files", 2, 1),
+ "540: " + getCheckMessage(MSG_KEY, "id", 2, 1),
+ "542: " + getCheckMessage(MSG_KEY, "parentId", 3, 1),
+ };
+ verify(checkConfig, getPath("coding/InputVariableDeclarationUsageDistanceCheck.java"), expected);
+ }
+
+ /**
+ * Gets the check message 'as is' from appropriate 'messages.properties' file.
+ * @param messageKey the key of message in 'messages.properties' file.
+ * @param arguments the arguments of message in 'messages.properties' file.
+ */
+ public String getCheckMessage(String messageKey, Object ... arguments)
+ {
+ return format(getCheckMessage(messageKey), arguments);
+ }
+}
diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/coding/InputVariableDeclarationUsageDistanceCheck.java b/src/test/resources/com/puppycrawl/tools/checkstyle/coding/InputVariableDeclarationUsageDistanceCheck.java
new file mode 100644
index 000000000..d8c400d86
--- /dev/null
+++ b/src/test/resources/com/puppycrawl/tools/checkstyle/coding/InputVariableDeclarationUsageDistanceCheck.java
@@ -0,0 +1,567 @@
+package com.puppycrawl.tools.checkstyle.design;
+
+public class InputVariableDeclarationUsageDistanceCheck {
+
+ private static final int test1;
+
+ static {
+ int b;
+ int d;
+ {
+ d = ++b;
+ }
+ }
+
+ static {
+ int c;
+ int a = 3;
+ int b = 2;
+ {
+ a = a + b;
+ c = b;
+ }
+ {
+ c--;
+ }
+ a = 7;
+ }
+
+ static {
+ int a = -1;
+ int b = 2;
+ b++;
+ int c = --b;
+ a = b; // DECLARATION OF VARIABLE 'a' SHOULD BE HERE (distance = 2)
+ }
+
+ public InputVariableDeclarationUsageDistanceCheck(int test1) {
+ int temp = -1;
+ this.test1 = test1;
+ temp = test1; // DECLARATION OF VARIABLE 'temp' SHOULD BE HERE (distance = 2)
+ }
+
+ public boolean testMethod() {
+ int temp = 7;
+ new InputVariableDeclarationUsageDistanceCheck(2);
+ InputVariableDeclarationUsageDistanceCheck(temp); // DECLARATION OF VARIABLE 'temp' SHOULD BE HERE (distance = 2)
+ boolean result = false;
+ String str = "";
+ if (test1 > 1) {
+ str = "123";
+ result = true;
+ }
+ return result;
+ }
+
+ public void testMethod2() {
+ int count;
+ int a = 3;
+ int b = 2;
+ {
+ a = a
+ + b
+ - 5
+ + 2
+ * a;
+ count = b; // DECLARATION OF VARIABLE 'count' SHOULD BE HERE (distance = 2)
+ }
+ }
+
+ public void testMethod3() {
+ int count;
+ int a = 3;
+ int b = 3;
+ a = a + b;
+ b = a + a;
+ testMethod2();
+ count = b; // DECLARATION OF VARIABLE 'count' SHOULD BE HERE (distance = 4)
+ }
+
+ public void testMethod4(int arg) {
+ int d;
+ for (int i = 0; i < 10; i++) {
+ d++;
+ if (i > 5) {
+ d += arg;
+ }
+ }
+
+ String ar[] = { "1", "2" };
+ for (String st : ar) {
+ System.out.println(st);
+ }
+ }
+
+ public void testMethod5() {
+ int arg = 7;
+ boolean b = true;
+ boolean bb = false;
+ if (b)
+ if (!bb)
+ b = false;
+ testMethod4(arg); // DECLARATION OF VARIABLE 'arg' SHOULD BE HERE (distance = 2)
+ }
+
+ public void testMethod6() {
+ int blockNumWithSimilarVar = 3;
+ int dist = 0;
+ int index = 0;
+ int block = 0;
+
+ if (blockNumWithSimilarVar <= 1) {
+ do {
+ dist++;
+ if (block > 4) {
+ break;
+ }
+ index++;
+ block++;
+ } while (index < 7);
+ } else {
+ while (index < 8) {
+ dist += block;
+ index++;
+ block++;
+ }
+ }
+ }
+
+ public boolean testMethod7(int a) {
+ boolean res;
+ switch (a) {
+ case 1:
+ res = true;
+ break;
+ default:
+ res = false;
+ }
+ return res;
+ }
+
+ public void testMethod8() {
+ int b;
+ int c;
+ int m;
+ int n;
+ {
+ c++;
+ b++;
+ }
+ {
+ n++; // DECLARATION OF VARIABLE 'n' SHOULD BE HERE (distance = 2)
+ m++; // DECLARATION OF VARIABLE 'm' SHOULD BE HERE (distance = 3)
+ b++;
+ }
+ }
+
+ public void testMethod9() {
+ boolean result = false;
+ boolean b1 = true;
+ boolean b2 = false;
+ if (b1) {
+ if (!b2) {
+ result = true;
+ }
+ result = true;
+ }
+ }
+
+ public boolean testMethod10() {
+ boolean result;
+ try {
+ result = true;
+ } catch (IOException e) {
+ result = false;
+ } finally {
+ result = false;
+ }
+ return result;
+ }
+
+ public void testMethod11() {
+ int a = 0;
+ int b = 10;
+ boolean result;
+ try {
+ b--;
+ } catch (IOException e) {
+ b++;
+ result = false; // DECLARATION OF VARIABLE 'result' SHOULD BE HERE (distance = 2)
+ } finally {
+ a++;
+ }
+ }
+
+ public void testMethod12() {
+ boolean result = false;
+ boolean b3 = true;
+ boolean b1 = true;
+ boolean b2 = false;
+ if (b1) {
+ if (b3) {
+ if (!b2) {
+ result = true;
+ }
+ result = true;
+ }
+ }
+ }
+
+ public void testMethod13() {
+ int i = 9;
+ int j = 6;
+ int g = i + 8;
+ int k = j + 10;
+ }
+
+ public void testMethod14() {
+ Session s = openSession();
+ Transaction t = s.beginTransaction();
+ A a = new A();
+ E d1 = new E();
+ C1 c = new C1();
+ E d2 = new E();
+ a.setForward(d1);
+ d1.setReverse(a);
+ c.setForward(d2); // DECLARATION OF VARIABLE 'c' SHOULD BE HERE (distance = 3)
+ // DECLARATION OF VARIABLE 'd2' SHOULD BE HERE (distance = 3)
+ d2.setReverse(c);
+ Serializable aid = s.save(a);
+ Serializable d2id = s.save(d2);
+ t.commit(); // DECLARATION OF VARIABLE 't' SHOULD BE HERE (distance = 5)
+ s.close();
+ }
+
+ public boolean isCheckBoxEnabled(TreePath path) {
+ DataLabelModel model = (DataLabelModel) getModel();
+ if (recursiveState) {
+ for (int index = 0; index < path.getPathCount(); ++index) {
+ int nodeIndex = model.getNodeIndex(path.getPathComponent(index));
+ if (disabled.contains(nodeIndex)) {
+ return false;
+ }
+ }
+ } else {
+ int nodeIndex = model.getNodeIndex(path.getLastPathComponent());
+ if (disabled.contains(nodeIndex)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public Object readObject(IObjectInputStream in) throws IOException {
+ SimpleDay startDay = new SimpleDay(in.readInt());
+ SimpleDay endDay = new SimpleDay(in.readInt());
+ return new SimplePeriod(startDay, endDay);
+ }
+
+ public int[] getSelectedIndices() {
+ int[] selected = new int[paths.length];
+ DataLabelModel model = (DataLabelModel) getModel();
+ int a = 0;
+ a++;
+ for (int index = 0; index < paths.length; ++index) {
+ selected[index] = model.getNodeIndex(paths[index].getLastPathComponent()); // DECLARATION OF VARIABLE 'selected' SHOULD BE HERE (distance = 2)
+ // DECLARATION OF VARIABLE 'model' SHOULD BE HERE (distance = 2)
+ }
+ return selected;
+ }
+
+ public void testMethod15() {
+ String confDebug = subst(element.getAttribute(CONFIG_DEBUG_ATTR));
+ if (!confDebug.equals("") && !confDebug.equals("null")) {
+ LogLog.warn("The \"" + CONFIG_DEBUG_ATTR + "\" attribute is deprecated.");
+ LogLog.warn("Use the \"" + INTERNAL_DEBUG_ATTR + "\" attribute instead.");
+ LogLog.setInternalDebugging(OptionConverter.toBoolean(confDebug, true));
+ }
+
+ int i = 0;
+ int k = 7;
+ boolean b = false;
+ for (; i < k; i++) {
+ b = true;
+ k++;
+ }
+
+ int sw;
+ switch (i) {
+ case 0:
+ k++;
+ sw = 0; // DECLARATION OF VARIABLE 'sw' SHOULD BE HERE (distance = 2)
+ break;
+ case 1:
+ b = false;
+ break;
+ default:
+ b = true;
+ }
+
+ int wh;
+ b = true;
+ do {
+ k--;
+ i++;
+ } while (wh > 0); // DECLARATION OF VARIABLE 'wh' SHOULD BE HERE (distance = 2)
+
+ if (wh > 0) {
+ k++;
+ } else if (!b) {
+ i++;
+ } else {
+ i--;
+ }
+ }
+
+ public void testMethod16() {
+ int wh = 1;
+ if (i > 0) {
+ k++;
+ } else if (wh > 0) {
+ i++;
+ } else {
+ i--;
+ }
+ }
+
+ protected JMenuItem createSubMenuItem(LogLevel level) {
+ final JMenuItem result = new JMenuItem(level.toString());
+ final LogLevel logLevel = level;
+ result.setMnemonic(level.toString().charAt(0));
+ result.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ showLogLevelColorChangeDialog(result, logLevel); // DECLARATION OF VARIABLE 'logLevel' SHOULD BE HERE (distance = 2)
+ }
+ });
+
+ return result;
+
+ }
+
+ public static Color darker(Color color, double fraction) {
+ int red = (int) Math.round(color.getRed() * (1.0 - fraction));
+ int green = (int) Math.round(color.getGreen() * (1.0 - fraction));
+ int blue = (int) Math.round(color.getBlue() * (1.0 - fraction));
+
+ if (red < 0) {
+ red = 0;
+ } else if (red > 255) {
+ red = 255;
+ }
+ if (green < 0) { // DECLARATION OF VARIABLE 'green' SHOULD BE HERE (distance = 2)
+ green = 0;
+ } else if (green > 255) {
+ green = 255;
+ }
+ if (blue < 0) { // DECLARATION OF VARIABLE 'blue' SHOULD BE HERE (distance = 3)
+ // blue = 0;
+ }
+
+ int alpha = color.getAlpha();
+
+ return new Color(red, green, blue, alpha);
+ }
+
+ public void testFinal() {
+ AuthUpdateTask authUpdateTask = null;
+ final long intervalMs = 30 * 60000L; // 30 min
+
+ authUpdateTask = new AuthUpdateTask(authCheckUrl, authInfo, new IAuthListener() {
+ @Override
+ public void authTokenChanged(String cookie, String token) {
+ fireAuthTokenChanged(cookie, token);
+ }
+ });
+
+ Timer authUpdateTimer = new Timer("Auth Guard", true);
+ authUpdateTimer.schedule(authUpdateTask, intervalMs / 2, intervalMs); // DECLARATION OF VARIABLE 'intervalMs' SHOULD BE HERE (distance = 2)
+ }
+
+ public void testForCycle() {
+ int filterCount = 0;
+ for (int i = 0; i < 10; i++, filterCount++) {
+ int abc = 0;
+ System.out.println(abc);
+
+ for (int j = 0; j < 10; j++) {
+ abc = filterCount;
+ System.out.println(abc);
+ }
+ }
+ }
+
+ public void testIssue32_1()
+ {
+ Option srcDdlFile = OptionBuilder.create("f");
+ Option logDdlFile = OptionBuilder.create("o");
+ Option help = OptionBuilder.create("h");
+
+ Options options = new Options();
+ options.something();
+ options.something();
+ options.something();
+ options.something();
+ options.addOption(srcDdlFile, logDdlFile, help); // distance=1
+ }
+
+ public void testIssue32_2()
+ {
+ int mm = Integer.parseInt(time.substring(div + 1).trim());
+
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(timeNow);
+ cal.set(Calendar.SECOND, 0);
+ cal.set(Calendar.MILLISECOND, 0);
+ cal.set(Calendar.HOUR_OF_DAY, hh);
+ cal.set(Calendar.MINUTE, mm); // distance=1
+ }
+
+ public void testIssue32_3(MyObject[] objects) {
+ Calendar cal = Calendar.getInstance();
+ for(int i=0; i+ Checks the distance between declaration of variable and its first usage. +
+| name | +description | +type | +default value | +
|---|---|---|---|
| allowedDistance | +A distance between declaration of variable and its first usage | +integer | +3 | +
| ignoreVariablePattern | +pattern for ignoring the distance calculation | +regular expression | +(not applied) | +
| validateBetweenScopes | +Allows to calculate the distance between declaration of variable and its first usage in the different scopes. | +Boolean | +false |
+
| ignoreFinal | +Allows to ignore variables with a 'final' modifier. | +Boolean | +true |
+
+ Example #1: +
++ Example #2: +
++ Check can detect a block of initialization methods. If a variable is used in + such a block and there is no other statements after this variable then distance=1. +
++ Case #1: +
++ The distance for the variable minutes is 1 even + though this variable is used in the fifth method's call. +
++ Case #2: +
++ The distance for the variable minutes is 6 because there is one more expression + (except the initialization block) between the declaration of this variable and its usage. +
++ An example how to configure this Check: +
++ An example of how to configure this Check: + - to set the allowed distance to 4; + - to ignore variables with prefix '^temp'; + - to force the validation between scopes; + - to check the final variables; +
++ ATTENTION!! (Not supported cases) +
++ Distance for variable 'a' = 1; + Distance for variable 'b' = 1; + Distance for variable 'c' = 2. +
++ As distance by default is 1 the Check doesn't raise warning for + variables 'a' and 'b' to move them into the block. +
++ Case #2: +
++ Distance for variable 'sum' = 3. +
++ As the distance is more then the default one, the Check + raises warning for variable 'sum' to move it into the 'for(...)' block. + But there is situation when variable 'sum' hasn't to be 0 within each iteration. + So, to avoid such warnings you can use Suppression Filter, provided by + Checkstyle, for the whole class. +
++ com.puppycrawl.tools.checkstyle.checks.coding +
++ TreeWalker +
+