diff --git a/java-checks-test-sources/default/src/main/files/non-compiling/checks/BadLocalVariableNameCheckSample.java b/java-checks-test-sources/default/src/main/files/non-compiling/checks/BadLocalVariableNameCheckSample.java new file mode 100644 index 00000000000..ca058871cc0 --- /dev/null +++ b/java-checks-test-sources/default/src/main/files/non-compiling/checks/BadLocalVariableNameCheckSample.java @@ -0,0 +1,73 @@ +import java.util.function.IntUnaryOperator; + +class BadLocalVariableName { + + // Instance/class fields are not checked by this rule at all, regardless of underscores + int _instanceField; + static int _staticField; + + BadLocalVariableName( + int godParam, + int BAD_CONSTRUCTOR_PARAM, // Noncompliant {{Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.}} +// ^^^^^^^^^^^^^^^^^^^^^ + int _goodConstructorParam // Noncompliant {{Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.}} +// ^^^^^^^^^^^^^^^^^^^^^ + ) {} + + void method( + int goodParam, + int BAD_FORMAL_PARAMETER, // Noncompliant {{Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.}} +// ^^^^^^^^^^^^^^^^^^^^ + int _goodParam // Noncompliant {{Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.}} +// ^^^^^^^^^^ + ) { + int BAD; // Noncompliant + int good; + int _good; // Compliant, leading underscore stripped before check + int good_; // Compliant, trailing underscore stripped before check + int __myVar__; // Compliant, surrounding underscores stripped before check + int _BAD; // Noncompliant {{Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.}} +// ^^^^ + int BAD_; // Noncompliant {{Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.}} +// ^^^^ + + for (int I = 0; I < 10; I++) { + int D; // Noncompliant + } + + for (good = 0; good < 10; good++) { + } + + try (Closeable BAD_RESOURCE = open()) { // Noncompliant + } catch (IOException BAD_EXCEPTION) { // Noncompliant + } catch (IllegalStateException E) { // compliant + } catch (Exception EX) { // Noncompliant + } + } + + Object FIELD_SHOULD_NOT_BE_CHECKED = new Object(){ + { + int BAD; // Noncompliant + } + }; + + void forEachMethod() { + int MY_CONSTANT_IS_NOT_A_CONSTANT = 21; // Noncompliant + final int MY_LOCAL_CONSTANT = 42; // Compliant + final String MY_OTHER_LOCAL_CONSTANT = "42"; // Compliant + final Integer MY_ALTERNATE_CONSTANT = Integer.valueOf(42); // Compliant + for (byte C : "".getBytes()) { + int D; // Noncompliant + } + } + + void foo() { + IntUnaryOperator f1 = (int _) -> 0; // Compliant, unnamed variable + IntUnaryOperator f2 = _ -> 0; // Compliant, unnamed variable + } + + // Method names are not checked by this rule + void _underscoreMethod() { + int good; + } +} diff --git a/java-checks/src/test/files/checks/naming/BadLocalVariableName.java b/java-checks-test-sources/default/src/main/files/non-compiling/checks/BadLocalVariableNameCheckSample_Custom.java similarity index 100% rename from java-checks/src/test/files/checks/naming/BadLocalVariableName.java rename to java-checks-test-sources/default/src/main/files/non-compiling/checks/BadLocalVariableNameCheckSample_Custom.java diff --git a/java-checks/src/main/java/org/sonar/java/checks/naming/BadLocalVariableNameCheck.java b/java-checks/src/main/java/org/sonar/java/checks/naming/BadLocalVariableNameCheck.java index e23159ce37f..a968bbd8b2f 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/naming/BadLocalVariableNameCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/naming/BadLocalVariableNameCheck.java @@ -91,12 +91,28 @@ public void visitCatch(CatchTree tree) { @Override public void visitVariable(VariableTree tree) { IdentifierTree simpleName = tree.simpleName(); - if (!simpleName.isUnnamedVariable() && !pattern.matcher(simpleName.name()).matches() && !isLocalConstant(tree)) { + String name = simpleName.name(); + Tree parent = tree.parent(); + boolean isMethodParameter = parent != null && parent.is(Tree.Kind.METHOD, Tree.Kind.CONSTRUCTOR); + String nameToCheck = isMethodParameter ? name : stripLeadingTrailingUnderscores(name); + if (!simpleName.isUnnamedVariable() && !pattern.matcher(nameToCheck).matches() && !isLocalConstant(tree)) { context.reportIssue(this, simpleName, "Rename this local variable to match the regular expression '" + format + "'."); } super.visitVariable(tree); } + private static String stripLeadingTrailingUnderscores(String name) { + int start = 0; + int end = name.length(); + while (start < end && name.charAt(start) == '_') { + start++; + } + while (end > start && name.charAt(end - 1) == '_') { + end--; + } + return name.substring(start, end); + } + private boolean isLocalConstant(VariableTree tree) { return context.getSemanticModel() != null && isConstantType(tree.symbol().type()) && tree.symbol().isFinal(); } diff --git a/java-checks/src/test/files/checks/naming/BadLocalVariableNameNoncompliant.java b/java-checks/src/test/files/checks/naming/BadLocalVariableNameNoncompliant.java deleted file mode 100644 index 1f88c4cc4bc..00000000000 --- a/java-checks/src/test/files/checks/naming/BadLocalVariableNameNoncompliant.java +++ /dev/null @@ -1,45 +0,0 @@ -import java.util.function.IntUnaryOperator; - -class BadLocalVariableName { - void method( - int BAD_FORMAL_PARAMETER // Noncompliant {{Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.}} -// ^^^^^^^^^^^^^^^^^^^^ - ) { - int BAD; // Noncompliant - int good; - - for (int I = 0; I < 10; I++) { - int D; // Noncompliant - } - - for (good = 0; good < 10; good++) { - } - - try (Closeable BAD_RESOURCE = open()) { // Noncompliant - } catch (IOException BAD_EXCEPTION) { // Noncompliant - } catch (IllegalStateException E) { // compliant - } catch (Exception EX) { // Noncompliant - } - } - - Object FIELD_SHOULD_NOT_BE_CHECKED = new Object(){ - { - int BAD; // Noncompliant - } - }; - - void forEachMethod() { - int MY_CONSTANT_IS_NOT_A_CONSTANT = 21; // Noncompliant - final int MY_LOCAL_CONSTANT = 42; // Compliant - final String MY_OTHER_LOCAL_CONSTANT = "42"; // Compliant - final Integer MY_ALTERNATE_CONSTANT = Integer.valueOf(42); // Compliant - for (byte C : "".getBytes()) { - int D; // Noncompliant - } - } - - void foo() { - IntUnaryOperator f1 = (int _) -> 0; // Compliant, unnamed variable - IntUnaryOperator f2 = _ -> 0; // Compliant, unnamed variable - } -} diff --git a/java-checks/src/test/java/org/sonar/java/checks/naming/BadLocalVariableNameCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/naming/BadLocalVariableNameCheckTest.java index b9a98351536..4acf5cb0a93 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/naming/BadLocalVariableNameCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/naming/BadLocalVariableNameCheckTest.java @@ -19,12 +19,14 @@ import org.junit.jupiter.api.Test; import org.sonar.java.checks.verifier.CheckVerifier; +import static org.sonar.java.checks.verifier.TestUtils.nonCompilingTestSourcesPath; + class BadLocalVariableNameCheckTest { @Test void test() { CheckVerifier.newVerifier() - .onFile("src/test/files/checks/naming/BadLocalVariableNameNoncompliant.java") + .onFile(nonCompilingTestSourcesPath("checks/BadLocalVariableNameCheckSample.java")) .withCheck(new BadLocalVariableNameCheck()) .verifyIssues(); } @@ -34,7 +36,7 @@ void test2() { BadLocalVariableNameCheck check = new BadLocalVariableNameCheck(); check.format = "^[a-zA-Z0-9_][a-zA-Z0-9_][a-zA-Z0-9_][a-zA-Z0-9_]*$"; CheckVerifier.newVerifier() - .onFile("src/test/files/checks/naming/BadLocalVariableName.java") + .onFile(nonCompilingTestSourcesPath("checks/BadLocalVariableNameCheckSample_Custom.java")) .withCheck(check) .verifyNoIssues(); } diff --git a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S117.html b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S117.html index 7f22aab4b48..88c57552d54 100644 --- a/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S117.html +++ b/sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S117.html @@ -39,6 +39,19 @@
Local variables inside a method body may have one or more leading underscores, trailing underscores, or both. This convention signals that the +variable is temporary, unimportant, or intentionally unnamed. The name between the underscores must still match the configured format.
+
+public class MyClass {
+ public void doSomething() {
+ int _result = compute(); // Compliant
+ int value__ = 0; // Compliant
+ int __tmp__ = swap(); // Compliant
+ }
+}
+
+Note that this exception applies only to local variables declared inside a method body. Method parameters must match the configured format without +leading or trailing underscores.
First, familiarize yourself with the particular naming convention of the project in question. Then, update the name to match the convention, as well as all usages of the name. For many IDEs, you can use built-in renaming and refactoring features to update all usages at once.
@@ -47,8 +60,9 @@With the default regular expression ^[a-z][a-zA-Z0-9]*$:
public class MyClass {
- public void doSomething(int myParam) {
- int LOCAL; // Noncompliant
+ public void doSomething(int _myParam) { // Noncompliant - method parameters cannot have leading or trailing underscores
+ int LOCAL; // Noncompliant - local variable does not match the naming convention
+ int __BAD__; // Noncompliant - inner part "BAD" does not match the naming convention
// ...
}
}
@@ -56,8 +70,11 @@ Noncompliant code example
Compliant solution
public class MyClass {
- public void doSomething(int my_param) {
- int local;
+ public void doSomething(int myParam) { // Compliant
+ int local; // Compliant
+ int _local; // Compliant - leading underscore allowed for local variables
+ int result__; // Compliant - trailing underscores allowed for local variables
+ int __tmp__; // Compliant - leading and trailing underscores allowed for local variables
// ...
}
}