Skip to content

SONARJAVA-7155: Implemented rule S9433 Literals should not be converted to strings using empty string concatenation - #6364

Open
romainbrenguier wants to merge 5 commits into
masterfrom
romain/new-rule-s9433-sonarjava-7155
Open

romainbrenguier wants to merge 5 commits into
masterfrom
romain/new-rule-s9433-sonarjava-7155

Conversation

@romainbrenguier

@romainbrenguier romainbrenguier commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

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.md

Tool 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.py for 2 iterations.
✔️ The PR is now ready for review.

@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

SONARJAVA-7155

…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.
@romainbrenguier
romainbrenguier force-pushed the romain/new-rule-s9433-sonarjava-7155 branch from ba6bc1c to df471ca Compare October 15, 2026 11:29
@datadog-sonarsource

This comment has been minimized.

…L header

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@romainbrenguier
romainbrenguier marked this pull request as ready for review October 15, 2026 12:53
…annotation defaults

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gitar-bot

gitar-bot Bot commented Oct 15, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 4 closed / 4 findings

🟡 Medium risk · Adding S9433 flags primitive-literal empty-string concatenations; incorrect constant-context detection could produce noisy or missed findings.

Adds rule S9433 to flag primitive literals converted to strings via empty string concatenation, recommending an explicit conversion instead. Annotation element defaults and constant contexts are now handled correctly, with the constant-context false positives resolved. No open issues remain.

✅ 4 closed
✅ Edge Case: Annotation element default values still flagged, though they must be constants

📄 java-checks/src/main/java/org/sonar/java/checks/LiteralToStringByConcatenationCheck.java:57-70
isInConstantContext stops walking as soon as it hits a METHOD node. An annotation element default like @interface A { String value() default "" + 1; } has the MethodTree as the binary expression's direct parent (via defaultValue), so the method returns false and the issue is raised. Following the advice (String.valueOf(1)) would not compile, because default values must be compile-time constants. This is the same kind of false positive the commit means to remove for annotation arguments. Fix: before stopping at METHOD, return true when the parent is a method whose defaultValue() contains the expression.

✅ Edge Case: Any expression inside a static final initializer is suppressed

📄 java-checks/src/main/java/org/sonar/java/checks/LiteralToStringByConcatenationCheck.java:57-71
isInConstantContext walks up through every kind of node until it reaches a VARIABLE, then returns isConstantField. So a concatenation anywhere inside a static final or interface field initializer is skipped, even when it sits inside a method call or another non-constant expression: static final List<String> L = List.of("" + 1);, static final String S = String.format("%s", "" + 42);, or an arrow-switch result case 1 -> "" + 1. None of these are compile-time constant contexts, and the Javadoc only promises to skip "initializers of constant fields", so these are false negatives. Fix: only treat the field as a constant context when the path from the concatenation up to the VARIABLE goes through constant-expression nodes only (parentheses, binary, or conditional expressions).

Closed: Edge Case: Flags compile-time constant contexts where the suggested fix won't compile

📄 java-checks/src/main/java/org/sonar/java/checks/LiteralToStringByConcatenationCheck.java:38-46 📄 java-checks-test-sources/default/src/main/java/checks/LiteralToStringByConcatenationCheckSample.java:42-53
"" + 123 is a compile-time constant expression (JLS 15.29), while Integer.toString(123) / String.valueOf(123) is not. The check reports every PLUS node with no context check, so it also flags code like @SuppressWarnings("" + 1), case "" + 42: in a String switch, and static final String X = "" + 42; (which the compiler inlines). In the first two cases the recommended explicit conversion doesn't compile, and in the third it changes constant-inlining semantics, so these reports are false positives the user can't fix. Fix: skip the report when the expression sits inside an annotation argument or a case label (and possibly a static final field initializer), and add those cases to the sample as compliant.

Closed: Quality: Test sample has an outdated LGPL license header unlike every other sample

📄 java-checks-test-sources/default/src/main/java/checks/LiteralToStringByConcatenationCheckSample.java:1-15
The new sample file opens with the old Copyright (C) 2012-2026 SonarSource SA GNU LGPL header. No other file under java-checks-test-sources has this header, and the repository is now under the Sonar Source-Available License (the header the new main and test classes use). This mislabels the file's license. Remove the header (other samples have none) or replace it with the current SSAL header.

Verification Rules 1/1 met Repos 3 inspected

Functional validation No results

Rules 1 passed 2 not applicable

✅ Repository license compliance

Broader impact Repos 3 inspected
Cross-repo 3 repositories inspected 0 open impacts

SonarSource/sonarlint-vscode Inspected
SonarSource/sonarlint-language-server Inspected
SonarSource/sonar-java-symbolic-execution Inspected

Auto-approval Not enabled

Set up

For more verification details, comment gitar display:verbose.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing compact verification results.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqube-next

Copy link
Copy Markdown
Contributor

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant