Skip to content

JS-1354: add CSS S2260 parsing-error support#6623

Open
vdiez wants to merge 17 commits intomasterfrom
codex/js-1354-css-s2260-special-case
Open

JS-1354: add CSS S2260 parsing-error support#6623
vdiez wants to merge 17 commits intomasterfrom
codex/js-1354-css-s2260-special-case

Conversation

@vdiez
Copy link
Contributor

@vdiez vdiez commented Mar 17, 2026

Ticket

Merge Preconditions (Important)

  • This PR depends on SonarSource/rspec#6373 (adds CSS metadata for S2260).
  • Please merge rspec#6373 first.
  • This PR commits a temporary rspec.sha pin to 56fd355bb9da489cb56b040e926f0aa45585ba33 so CI resolves the correct rspec revision while rspec#6373 is not merged.
  • Before merging this PR, remove rspec.sha and regenerate/sync against the merged rspec HEAD.

Summary

This implements CSS support for Sonar key S2260 (parsing errors) end-to-end, including:

  • rspec deployment/generation support for CSS S2260
  • Java class generation for a CSS parsing-error rule mapped to stylelint CssSyntaxError
  • language-aware parsing error propagation from bridge/jsts/grpc to Java processing
  • CSS-specific parsing-error resolution in plugin analysis, including HTML mixed-language scenarios

Detailed Changes

1. Rule metadata deployment and Java generation

  • Updated deploy tooling to include CSS S2260 as a special-case rule during resource copy:
    • tools/deploy-rule-data.ts
  • Updated Java rule generation tooling to generate a CSS S2260 check class mapped to stylelint key CssSyntaxError, and include it in generated CssRules:
    • tools/generate-java-rule-classes.ts
    • tools/templates/java/css-rules.template
  • CssRules generation now excludes CssSyntaxError from stylelint runtime rule list (getStylelintRules) while preserving Sonar rule-key mapping.

2. Language-aware parsing errors in JS/TS/CSS pipeline

  • Extended parsing error payload with optional language (js/ts/css):
    • packages/jsts/src/analysis/projectAnalysis/projectAnalysis.ts
  • Propagated parsing-error language through bridge middleware and file analysis:
    • packages/bridge/src/errors/middleware.ts
    • packages/jsts/src/analysis/projectAnalysis/analyzeFile.ts
  • Updated gRPC response transformation so S2260 issue repo is selected by parsing-error language:
    • js -> javascript
    • ts -> typescript
    • css -> css
    • file:
      • packages/grpc/src/transformers/response.ts

3. Java bridge + analysis processing

  • Extended bridge parsing error model with nullable language:
    • sonar-plugin/bridge/src/main/java/org/sonar/plugins/javascript/bridge/BridgeServer.java
  • Updated JS/TS checks to resolve parsing-error rule keys per language:
    • sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/JsTsChecks.java
  • Updated analysis processor to:
    • resolve parsing-error rule key using parsing-error language first,
    • fallback to file language and previous behavior,
    • route CSS parsing errors to CSS S2260 via CssSyntaxError mapping,
    • keep fail-fast behavior appropriate for CSS parsing errors,
    • file:
      • sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/AnalysisProcessor.java

4. Tests

Added/updated tests covering new behavior:

  • packages/bridge/tests/errors/middleware.test.ts
  • packages/grpc/tests/transformers.test.ts
  • packages/grpc/tests/server.test.ts
  • sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/JsTsChecksTest.java
  • sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/AnalysisProcessorTest.java

Validation

Executed successfully:

  1. npm run sync-rspec-all (with pinned rspec.sha)
  2. npx tsx --tsconfig packages/tsconfig.test.json --test packages/bridge/tests/errors/middleware.test.ts packages/grpc/tests/transformers.test.ts packages/grpc/tests/server.test.ts
  3. mvn -pl sonar-plugin/sonar-javascript-plugin -am "-Dtest=AnalysisProcessorTest,JsTsChecksTest" "-Dsurefire.failIfNoSpecifiedTests=false" test

Notes

  • README.md rule counts were updated by generation (count-rules) as part of the build flow.

@sonar-review-alpha
Copy link

sonar-review-alpha bot commented Mar 17, 2026

Summary

This PR adds end-to-end CSS S2260 (parsing error) support by tracking the language that produced a parsing error and routing it to the appropriate rule repository. The change flows through: JS/TS bridge (detecting CSS via file extensions), gRPC transformation (mapping errors to language-specific repos), and Java analysis (resolving rule keys by parsing error language). Rule counts increase by 1 for each language (JS: 479→480, TS: 496→497, CSS: 29→30).

What reviewers should know

Critical precondition: This PR depends on merging SonarSource/rspec#6373 first. The spec.sha pin allows CI to resolve the correct rspec revision while that PR is pending — remove it before merging this PR.

Key areas to review:

  1. Language tracking pipeline: Follow how ParsingErrorLanguage flows from analyzeFile.ts (detects CSS via suffix) → bridge middleware → gRPC transformer → Java ParsingError record. The detection logic at analyzeFile.ts:parsingErrorLanguage() is important: it treats CSS files specially even in mixed-language contexts (e.g., <style> in HTML).

  2. Rule resolution in AnalysisProcessor: The new parsingErrorRuleKey(ParsingError) method implements a precedence: (1) use parsing-error language if present, (2) fall back to file language, (3) fall back to legacy check-based resolution. This handles edge cases like CSS blocks in HTML files. The isCssParsingError() check also looks at parsing-error language first.

  3. CSS rule generation: S2260 is generated as a CSS rule via generateCssParsingErrorClass() and mapped to stylelint key CssSyntaxError. The CssRules template filters out CssSyntaxError from runtime stylelint rules since it's a pseudo-rule for parsing errors, not a real linter rule.

  4. Test coverage: New tests validate the three main scenarios: pure CSS files, JavaScript in HTML, and CSS in HTML — all should resolve to correct rule keys.


  • Generate Walkthrough
  • Generate Diagram

🗣️ Give feedback

@hashicorp-vault-sonar-prod
Copy link

hashicorp-vault-sonar-prod bot commented Mar 17, 2026

JS-1354

@github-actions
Copy link
Contributor

github-actions bot commented Mar 17, 2026

Ruling Report

Code no longer flagged (1 issue)

S2260

vuetify/packages/vuetify/src/components/VBottomSheet/VBottomSheet.sass:0

     1 | @import './_variables.scss'
     2 | 

New issues flagged (103 issues)

S2260

ace/demo/kitchen-sink/docs/css.css:35

    33 |         white-space: pre;
    34 |         display: block;
>   35 |         background: url(asdasd); "err
    36 |     }
    37 | }

ace/demo/kitchen-sink/docs/sass.sass:3

     1 | // sass ace mode;
     2 | 
>    3 | @import url(http://fonts.googleapis.com/css?family=Ace:700)
     4 | 
     5 | html, body

ace/tool/templates/theme.css:49

    47 | 
    48 | .%cssClass% .ace_marker-layer .ace_selected-word {
>   49 |   %selected_word_highlight%
    50 | }
    51 | 

angular.js/docs/config/templates/examples/template.css:1

>    1 | {$ doc.fileContents $}

ant-design/components/style/color/colorPalette.less:34

    32 |   };
    33 |   var getSaturation = function(hsv, i, isLight) {
>   34 |     // grey color don't change saturation
    35 |     if (hsv.h === 0 && hsv.s === 0) {
    36 |       return hsv.s;

ant-design/components/style/color/tinyColor.less:1082

  1080 | // (see matchers above for definition).
  1081 | function isValidCSSUnit(color) {
> 1082 |     return !!matchers.CSS_UNIT.exec(color);
  1083 | }
  1084 | 

bulma/docs/_includes/components/figure.html:1

>    1 | <figure class="bd-figure" style="{{ include.style }}">
     2 |   {%
     3 |     include elements/responsive-image-2x.html

bulma/docs/_sass/callout.sass:0

     1 | .bd-callout
     2 |   +block

bulma/docs/bulma-dark.sass:1

>    1 | @import ../sass/utilities/initial-variables.sass
     2 | 
     3 | // General colors

bulma/sass/components/navbar.sass:0

     1 | @import "../utilities/mixins"
     2 | 

...and 93 more

📋 View full report

Code no longer flagged (1)

S2260

New issues flagged (103)

S2260

Copy link

@sonar-review-alpha sonar-review-alpha bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conclusion: One NPE bug needs fixing before merge — old bridge responses without the new language field will crash AnalysisProcessor. Also flagging a string constant that lives independently in two Java classes and will silently diverge if ever changed.

🗣️ Give feedback

Copy link

@sonar-review-alpha sonar-review-alpha bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new commit correctly switches the fast-QA parsing-error test from log-level assertions to issue-based assertions (css:S2260). The change itself is clean. However, both previously flagged issues in AnalysisProcessor.java remain unaddressed and still need to be fixed before merge.

🗣️ Give feedback

Copy link
Contributor

@zglicz zglicz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly cosmetic changes

code: ErrorCode,
language?: ParsingErrorLanguage,
): ParsingErrorLanguage {
if (language !== undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (language) is not enough?

Comment on lines +70 to +92
function inferLanguageFromFilePath(filePath: unknown): ParsingErrorLanguage | undefined {
if (typeof filePath !== 'string') {
return undefined;
}
const path = filePath.toLowerCase();
if (
path.endsWith('.css') ||
path.endsWith('.scss') ||
path.endsWith('.sass') ||
path.endsWith('.less')
) {
return 'css';
}
if (
path.endsWith('.ts') ||
path.endsWith('.tsx') ||
path.endsWith('.cts') ||
path.endsWith('.mts')
) {
return 'ts';
}
return 'js';
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how do you get coverage on this code? Also, falling back to js is meh, maybe should log if unexpected, so that at least we see in logs if there is something we don't expect

Comment on lines +95 to +99
error: {
message: string;
code: ErrorCode;
data?: { line: number };
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't wanna name this type?

Comment on lines +103 to +106
message: string;
code: ErrorCode;
line: number | undefined;
language: ParsingErrorLanguage;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmmm

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this ParsingError from packages/jsts/src/analysis/projectAnalysis/projectAnalysis.ts ?

if ('parsingError' in fileResult) {
expect(fileResult.parsingError!.code).toBe(ErrorCode.Parsing);
expect(fileResult.parsingError!.line).toBe(3);
expect('parsingErrors' in fileResult).toBe(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you'd do an assert, the follwoing if would be unnecessary

Comment on lines 138 to 140
// CSS parsing errors are expected for certain preprocessor files (e.g. Sass)
// and should not abort the analysis in failFast mode.
LOG.warn("Failed to analyze CSS file [{}]: {}", file, message);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems unnecessary. We are unifying to simplify the code. I don't think there should be a special case here

Comment on lines +205 to +366
@Test
void should_raise_css_parsing_error_issue_when_language_is_css() {
var fileLinesContextFactory = mock(FileLinesContextFactory.class);
when(fileLinesContextFactory.createFor(any())).thenReturn(mock(FileLinesContext.class));
var cssRules = mock(CssRules.class);
when(cssRules.getActiveSonarKey("CssSyntaxError")).thenReturn(RuleKey.of("css", "S2260"));
var processor = new AnalysisProcessor(
mock(NoSonarFilter.class),
fileLinesContextFactory,
cssRules
);
var checks = mock(JsTsChecks.class);
when(checks.parsingErrorRuleKey()).thenReturn(RuleKey.of("javascript", "S2260"));

var context = new JsTsContext<SensorContextTester>(SensorContextTester.create(baseDir));
var file = TestInputFileBuilder.create("moduleKey", "file.css")
.setLanguage("css")
.setContents("a {\n color: red;\n")
.build();

var response = new AnalysisResponse(
List.of(new ParsingError("Unclosed block", 2, ParsingErrorCode.PARSING, "css")),
List.of(),
List.of(),
List.of(),
new Metrics(),
List.of(),
null
);

processor.processResponse(context, checks, file, response);

assertThat(context.getSensorContext().allIssues()).hasSize(1);
assertThat(context.getSensorContext().allIssues().iterator().next().ruleKey()).isEqualTo(
RuleKey.of("css", "S2260")
);
}

@Test
void should_resolve_parsing_error_rule_key_using_parsing_error_language() {
var fileLinesContextFactory = mock(FileLinesContextFactory.class);
when(fileLinesContextFactory.createFor(any())).thenReturn(mock(FileLinesContext.class));
var cssRules = mock(CssRules.class);
when(cssRules.getActiveSonarKey("CssSyntaxError")).thenReturn(RuleKey.of("css", "S2260"));
var processor = new AnalysisProcessor(
mock(NoSonarFilter.class),
fileLinesContextFactory,
cssRules
);
var checks = mock(JsTsChecks.class);
when(checks.parsingErrorRuleKey(Language.JAVASCRIPT)).thenReturn(
RuleKey.of("javascript", "S2260")
);
when(checks.parsingErrorRuleKey()).thenReturn(RuleKey.of("javascript", "S2260"));

var context = new JsTsContext<SensorContextTester>(SensorContextTester.create(baseDir));
var file = TestInputFileBuilder.create("moduleKey", "file.html")
.setLanguage("web")
.setContents("<script>\nconst a = ;\n</script>")
.build();

var response = new AnalysisResponse(
List.of(new ParsingError("Unexpected token", 2, ParsingErrorCode.PARSING, "js")),
List.of(),
List.of(),
List.of(),
new Metrics(),
List.of(),
null
);

processor.processResponse(context, checks, file, response);

assertThat(context.getSensorContext().allIssues()).hasSize(1);
assertThat(context.getSensorContext().allIssues().iterator().next().ruleKey()).isEqualTo(
RuleKey.of("javascript", "S2260")
);
}

@Test
void should_resolve_css_parsing_error_in_html_using_css_rule_key() {
var fileLinesContextFactory = mock(FileLinesContextFactory.class);
when(fileLinesContextFactory.createFor(any())).thenReturn(mock(FileLinesContext.class));
var cssRules = mock(CssRules.class);
when(cssRules.getActiveSonarKey("CssSyntaxError")).thenReturn(RuleKey.of("css", "S2260"));
var processor = new AnalysisProcessor(
mock(NoSonarFilter.class),
fileLinesContextFactory,
cssRules
);
var checks = mock(JsTsChecks.class);
when(checks.parsingErrorRuleKey(Language.JAVASCRIPT)).thenReturn(
RuleKey.of("javascript", "S2260")
);
when(checks.parsingErrorRuleKey()).thenReturn(RuleKey.of("javascript", "S2260"));

var context = new JsTsContext<SensorContextTester>(SensorContextTester.create(baseDir));
var file = TestInputFileBuilder.create("moduleKey", "file.html")
.setLanguage("web")
.setContents("<style>\na {\n</style>")
.build();

var response = new AnalysisResponse(
List.of(new ParsingError("Unclosed block", 2, ParsingErrorCode.PARSING, "css")),
List.of(),
List.of(),
List.of(),
new Metrics(),
List.of(),
null
);

processor.processResponse(context, checks, file, response);

assertThat(context.getSensorContext().allIssues()).hasSize(1);
assertThat(context.getSensorContext().allIssues().iterator().next().ruleKey()).isEqualTo(
RuleKey.of("css", "S2260")
);
}

@Test
void should_process_multiple_parsing_errors_for_the_same_file() {
var fileLinesContextFactory = mock(FileLinesContextFactory.class);
when(fileLinesContextFactory.createFor(any())).thenReturn(mock(FileLinesContext.class));
var cssRules = mock(CssRules.class);
when(cssRules.getActiveSonarKey("CssSyntaxError")).thenReturn(RuleKey.of("css", "S2260"));
var processor = new AnalysisProcessor(
mock(NoSonarFilter.class),
fileLinesContextFactory,
cssRules
);
var checks = mock(JsTsChecks.class);
when(checks.parsingErrorRuleKey(Language.JAVASCRIPT)).thenReturn(
RuleKey.of("javascript", "S2260")
);
when(checks.parsingErrorRuleKey()).thenReturn(RuleKey.of("javascript", "S2260"));

var context = new JsTsContext<SensorContextTester>(SensorContextTester.create(baseDir));
var file = TestInputFileBuilder.create("moduleKey", "file.html")
.setLanguage("web")
.setContents("<script>\nconst a = ;\n</script>\n<style>\na {\n</style>")
.build();

var response = new AnalysisResponse(
List.of(
new ParsingError("Unexpected token", 2, ParsingErrorCode.PARSING, "js"),
new ParsingError("Unclosed block", 5, ParsingErrorCode.PARSING, "css")
),
List.of(),
List.of(),
List.of(),
new Metrics(),
List.of(),
null
);

processor.processResponse(context, checks, file, response);

assertThat(context.getSensorContext().allIssues())
.extracting(issue -> issue.ruleKey().repository())
.containsExactlyInAnyOrder("javascript", "css");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think we should be adding this test code here. The business logic is in the bridge. We shouldn't be testing anything here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember to remove this, PR is accepted

Copy link

@sonar-review-alpha sonar-review-alpha bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conclusion: Both previously flagged issues are resolved. The new commit is clean — the toLanguage() null guard is in place, CssRules.CSS_PARSING_ERROR_STYLELINT_KEY replaces the duplicated string, and the overall approach of moving from a singular parsingError to a parsingErrors array (to support HTML/Vue files that run both JS/TS and CSS pipelines) is well-structured and consistently applied across all callers.

🗣️ Give feedback

Copy link

@sonar-review-alpha sonar-review-alpha bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conclusion

Solid, well-structured PR that properly wires CSS parsing errors through all layers — the design is clean and the test coverage is thorough. One real bug needs fixing before merge: the Express error middleware can silently fail to send a response when language inference throws.

🗣️ Give feedback

@vdiez vdiez force-pushed the codex/js-1354-css-s2260-special-case branch from d2a575d to 1c1ae11 Compare March 19, 2026 17:23
Copy link

@sonar-review-alpha sonar-review-alpha bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conclusion: One previously flagged bug remains unaddressed — inferParsingErrorLanguage can throw inside errorMiddleware, leaving the client without a response.

🗣️ Give feedback

@sonarqube-next
Copy link

Quality Gate failed Quality Gate failed

Failed conditions
13 New issues
72.3% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE SonarQube for IDE

Copy link

@sonar-review-alpha sonar-review-alpha bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conclusion: The unguarded inferParsingErrorLanguage(request) call in errorMiddleware — flagged in the previous review — has not been addressed in this commit.

🗣️ Give feedback

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.

2 participants