Repository navigation
SONARJAVA-7155: Implemented rule S9433 Literals should not be converted to strings using empty string concatenation - #6364
Conversation
…ed to strings using empty string concatenation Detects primitive literals converted to strings by direct concatenation with an empty string literal and recommends an explicit conversion.
ba6bc1c to
df471ca
Compare
This comment has been minimized.
This comment has been minimized.
…L header Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…annotation defaults Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
/pvf |
|
|
rombirli
left a comment
There was a problem hiding this comment.
LGTM but few questions to answer/address
| static final String FORMATTED = String.format("%s", "" + 42); // Noncompliant | ||
| static final String[] ARRAY = {"" + 1}; // Noncompliant | ||
| static final String CONDITIONAL = true ? "" + 1 : "x"; | ||
| static final String NESTED = "a" + ("" + 1); |
There was a problem hiding this comment.
why is it compliant? Known FN? I expect the rule to raise on ("" + 1) part (or without parentheses)
There was a problem hiding this comment.
This is intentional, not a known FN. "a" + ("" + 1) is still a compile-time constant expression, so NESTED is a constant field. Replacing the inner part with String.valueOf(1) would make it non-constant, the same issue as for CONSTANT (see the thread on line 8). The constant-context walk only goes through parentheses, + and conditionals, which can all appear in constant expressions. Inside any other expression (method call, array initializer, lambda, switch expression) the issue is still raised.
There was a problem hiding this comment.
Actually, we maybe just need a different message for that case.
| static final List<String> LIST = List.of("" + 1); // Noncompliant | ||
| static final String FORMATTED = String.format("%s", "" + 42); // Noncompliant | ||
| static final String[] ARRAY = {"" + 1}; // Noncompliant | ||
| static final String CONDITIONAL = true ? "" + 1 : "x"; |
There was a problem hiding this comment.
Same reasoning: true ? "" + 1 : "x" is a compile-time constant expression (JLS 15.29 allows the conditional operator), so CONDITIONAL is a constant field and the explicit conversion would break that.
| class LiteralToStringByConcatenationCheckSample { | ||
|
|
||
| private static final String EMPTY = ""; | ||
| static final String CONSTANT = "" + 42; |
There was a problem hiding this comment.
why are constants excluded? Are static final fields excluded from this rule and why?
There was a problem hiding this comment.
Only constant fields (static final, or interface fields) whose initializer is a compile-time constant expression are excluded. Other static final fields are still reported, see LIST, FORMATTED, ARRAY and LAMBDA in the sample. The reason: "" + 42 is a compile-time constant (JLS 15.29), but Integer.toString(42) / String.valueOf(42) is not. Applying the suggested fix would stop the field being a constant. Any case CONSTANT: or @Annotation(CONSTANT) using it would no longer compile, and the value would stop being inlined in other classes. javac also folds "" + 42 into "42" at compile time, so the performance argument from the RSPEC doesn't apply there.
There was a problem hiding this comment.
Maybe we should have a rule in such cases to write "42" instead
| return tree.is( | ||
| Tree.Kind.BOOLEAN_LITERAL, | ||
| Tree.Kind.CHAR_LITERAL, | ||
| Tree.Kind.INT_LITERAL, | ||
| Tree.Kind.LONG_LITERAL, | ||
| Tree.Kind.FLOAT_LITERAL, | ||
| Tree.Kind.DOUBLE_LITERAL); | ||
| } |
There was a problem hiding this comment.
This should be extracted to java-frontend/src/main/java/org/sonar/plugins/java/api/tree/Tree.java to prevent future problems like it was done in this ticket for class types.
It also seems to not be exhaustive, it should also contain
Tree.Kind.STRING_LITERAL,
Tree.Kind.NULL_LITERALThere was a problem hiding this comment.
Done in 8aca0fe: added Tree.Kind.LITERAL_KINDS with all kinds backed by LiteralTree, including STRING_LITERAL, TEXT_BLOCK and NULL_LITERAL. A TreeTest check keeps it exhaustive. The rule uses it but leaves out string, text block and null literals on purpose. "" + "x" (or a text block) is not a conversion, and "" + null has no clean explicit replacement: String.valueOf(null) resolves to the char[] overload and throws an NPE. Both cases are compliant in the sample.
| private static boolean isInConstantContext(Tree tree) { | ||
| Tree expression = tree; | ||
| Tree parent = tree.parent(); | ||
| while (parent.is(Tree.Kind.PARENTHESIZED_EXPRESSION, Tree.Kind.PLUS, Tree.Kind.CONDITIONAL_EXPRESSION)) { | ||
| expression = parent; | ||
| parent = parent.parent(); | ||
| } | ||
| if (parent.is(Tree.Kind.CASE_LABEL)) { | ||
| return true; | ||
| } | ||
| if (parent.is(Tree.Kind.VARIABLE)) { | ||
| return isConstantField((VariableTree) parent); | ||
| } | ||
| return isAnnotationValue(expression); | ||
| } |
There was a problem hiding this comment.
as explained in the sample i don't understand this heuristic, is it to kill the noise? should the rspec be updated accordingly?
There was a problem hiding this comment.
It's not there to reduce noise. It avoids raising issues where the suggested fix can't be applied. In annotation arguments, annotation element defaults and case labels, a compile-time constant is required, so Integer.toString(1) doesn't compile there. In constant field initializers, the fix silently turns the field into a non-constant, which breaks its use in those same places and changes inlining. Agreed that this should be documented in the RSPEC, as an Exceptions section listing these constant contexts. That needs a change in the rspec repository, not here.
…catenations Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Code Review ✅ Approved 5 closed / 5 findings🟡 Medium risk · Adding the S9433 analyzer rule could flag existing literal concatenations and change issue counts; Tree.Kind.LITERAL_KINDS also adds a public API constant. Adds rule S9433 to flag primitive literals converted to strings via empty string concatenation, and adds ✅ 5 closed✅ Edge Case: Annotation element default values still flagged, though they must be constants
✅ Edge Case: Any expression inside a static final initializer is suppressed
✅ Bug: char constants resolve to Integer, so the suggested replacement literal is wrong
Closed: Edge Case: Flags compile-time constant contexts where the suggested fix won't compile
Closed: Quality: Test sample has an outdated LGPL license header unlike every other sample
Verification Rules 1/1 met Repos 3 inspected · PRs 1 inspectedFunctional validation No results Rules 1 passed 2 not applicable
Broader impact Repos 3 inspected PRs 1 inspectedCross-repo 3 repositories inspected 0 open impacts
Cross-PR 1 PR inspected 0 open impactsAuto-approval Not enabledFor more verification details, comment OptionsAuto-apply is off → Gitar will not commit updates to this branch. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Gitar |
|
|
/pvf S9433 |
|
@romainbrenguier ❌ Your
|




Detects primitive literals converted to strings by direct concatenation with an empty string literal, and recommends an explicit conversion.
Agent workflow
PR created using
uv run new_rule_implementation.py S9433 -j SONARJAVA-7155 -a codex -g AGENTS.md .claude/skills/new-rule/SKILL.mdTool link: https://github.com/SonarSource/languages-experimental-tooling/tree/romain/my-tickets/personal/romain-brenguier
PR updated using
uv run update_with_claude.py --prompt "Implement the action plan described in the document /tmp/action_plan_romain/new-rule-s9433-sonarjava-7155.txt." -a "/tmp/action_plan_romain/new-rule-s9433-sonarjava-7155.txt" -g "claude"PR updated using
uv run update_with_claude.py --prompt "Implement the action plan described in the document /tmp/action_plan_romain/new-rule-s9433-sonarjava-7155.txt." -a "/tmp/action_plan_romain/new-rule-s9433-sonarjava-7155.txt" -g "claude"Iterated on the PR with
uv run ci_loop.pyfor 2 iterations.✔️ The PR is now ready for review.
Summary by Gitar
Tree.Kind.LITERAL_KINDSlisting all literal kindsThis will update automatically on new commits.
Addressed review comments in pr_report_6364.md using
uv run address_reviews.py pr_report_6364.md