From 0fc9acc122554b8e43d0c85a6059224a47dd0268 Mon Sep 17 00:00:00 2001 From: asya-vorobeva Date: Mon, 29 Jun 2026 11:35:53 +0200 Subject: [PATCH 1/3] SONARJAVA-6525 Improve S117: allow leading/trailing underscores in local variable names Local variables inside methods may now use leading or trailing underscores (e.g. `_good`, `good_`, `__myVar__`); the core name between underscores is still validated against the configured format. The leniency does not apply to method parameters or fields. Test samples migrated to java-checks-test-sources. Co-Authored-By: Claude Sonnet 4.6 --- .../BadLocalVariableNameCheckSample.java | 22 ++++++++++++++++++- ...adLocalVariableNameCheckSample_Custom.java | 0 .../naming/BadLocalVariableNameCheck.java | 18 ++++++++++++++- .../naming/BadLocalVariableNameCheckTest.java | 6 +++-- 4 files changed, 42 insertions(+), 4 deletions(-) rename java-checks/src/test/files/checks/naming/BadLocalVariableNameNoncompliant.java => java-checks-test-sources/default/src/main/files/non-compiling/checks/BadLocalVariableNameCheckSample.java (52%) rename java-checks/src/test/files/checks/naming/BadLocalVariableName.java => java-checks-test-sources/default/src/main/files/non-compiling/checks/BadLocalVariableNameCheckSample_Custom.java (100%) diff --git a/java-checks/src/test/files/checks/naming/BadLocalVariableNameNoncompliant.java b/java-checks-test-sources/default/src/main/files/non-compiling/checks/BadLocalVariableNameCheckSample.java similarity index 52% rename from java-checks/src/test/files/checks/naming/BadLocalVariableNameNoncompliant.java rename to java-checks-test-sources/default/src/main/files/non-compiling/checks/BadLocalVariableNameCheckSample.java index 1f88c4cc4bc..20343668cb5 100644 --- a/java-checks/src/test/files/checks/naming/BadLocalVariableNameNoncompliant.java +++ b/java-checks-test-sources/default/src/main/files/non-compiling/checks/BadLocalVariableNameCheckSample.java @@ -1,12 +1,27 @@ 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; + void method( - int BAD_FORMAL_PARAMETER // Noncompliant {{Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.}} + 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 @@ -42,4 +57,9 @@ 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..b75c2743b43 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); + 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/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(); } From a31bb45194ce2d5517bf3841bcf8c2f7ee49e7ee Mon Sep 17 00:00:00 2001 From: asya-vorobeva Date: Mon, 29 Jun 2026 11:38:18 +0200 Subject: [PATCH 2/3] Update rule description --- .../org/sonar/l10n/java/rules/java/S117.html | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) 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 @@

Exceptions

} catch (Exception e) { // Compliant } +

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.

How to fix it

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 @@

Noncompliant code example

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
       // ...
     }
 }

From 94ac5a90bfd7b2491452860010ca8a5325a3a722 Mon Sep 17 00:00:00 2001
From: asya-vorobeva 
Date: Mon, 29 Jun 2026 11:48:05 +0200
Subject: [PATCH 3/3] SONARJAVA-6525 Extend S117 underscore leniency exclusion
 to constructor parameters

Co-Authored-By: Claude Sonnet 4.6 
---
 .../checks/BadLocalVariableNameCheckSample.java           | 8 ++++++++
 .../java/checks/naming/BadLocalVariableNameCheck.java     | 2 +-
 2 files changed, 9 insertions(+), 1 deletion(-)

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
index 20343668cb5..ca058871cc0 100644
--- 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
@@ -6,6 +6,14 @@ class BadLocalVariableName {
   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]*$'.}}
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 b75c2743b43..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
@@ -93,7 +93,7 @@ public void visitVariable(VariableTree tree) {
     IdentifierTree simpleName = tree.simpleName();
     String name = simpleName.name();
     Tree parent = tree.parent();
-    boolean isMethodParameter = parent != null && parent.is(Tree.Kind.METHOD);
+    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 + "'.");