Skip to content

Commit eef2fb7

Browse files
authored
JS-1523 Handle invalid parsing pointers from stylelint/postcss (#6714)
1 parent d981525 commit eef2fb7

File tree

5 files changed

+77
-9
lines changed

5 files changed

+77
-9
lines changed

packages/analysis/src/css/analysis/analyzer.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,10 @@ export async function analyzeCSS(
6464
if (isTestFile && !includeMetrics) {
6565
return { issues: [] };
6666
}
67-
const sanitizedCode = fileContent.replaceAll(/[\u2000-\u200F]/g, ' ');
67+
const sanitizedCode = fileContent
68+
.replaceAll(/[\u2000-\u200F]/g, ' ')
69+
// PostCSS tracks lines by splitting on '\n'; normalize CR-only files to keep locations stable.
70+
.replaceAll(/\r(?!\n)/g, '\n');
6871

6972
// TEST files keep highlighting (parity with old CssMetricSensor), but issues remain suppressed.
7073
const lintResult = await linter.lint(filePath, sanitizedCode, fileType);

packages/analysis/tests/analyzeProject-sonarqube.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { analyzeProject, cancelAnalysis } from '../src/analyzeProject.js';
2323
import { sourceFileStore, tsConfigStore } from '../src/file-stores/index.js';
2424
import { ErrorCode } from '../src/contracts/error.js';
2525
import ts from 'typescript';
26+
import { valid } from 'semver';
2627
import type { RuleConfig } from '../src/jsts/linter/config/rule-config.js';
2728
import type { RuleConfig as CssRuleConfig } from '../src/css/linter/config.js';
2829
import { getProgramCacheManager } from '../src/jsts/program/cache/programCache.js';
@@ -174,8 +175,11 @@ describe('SonarQube project analysis', () => {
174175
expect('issues' in backendResult! && backendResult!.issues.length).toBeGreaterThan(0);
175176

176177
expect(result.meta.telemetry).toBeDefined();
178+
expect(result.meta.telemetry?.typescriptVersions.length).toBeGreaterThan(0);
179+
for (const version of result.meta.telemetry!.typescriptVersions) {
180+
expect(valid(version)).toBeTruthy();
181+
}
177182
expect(result.meta.telemetry).toMatchObject({
178-
typescriptVersions: ['5.7.2', '7.0.0-dev.20260316.1'],
179183
typescriptNativePreview: true,
180184
ecmaScriptVersions: ['ES2020', 'ES2022'],
181185
esmFileCount: 0,

packages/analysis/tests/css/analysis/analyzer.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,16 @@ describe('analyzeCSS', () => {
143143
data: { line: 2, column: 2 },
144144
});
145145
});
146+
147+
it('should normalize CR-only line endings before parsing embedded CSS', async () => {
148+
await expect(
149+
analyzeCSS(await input('/some/fake/path.html', '<style>a { color: red; }\r\\n</style>')),
150+
).rejects.toMatchObject({
151+
code: ErrorCode.Parsing,
152+
message: expect.stringContaining('Unknown word'),
153+
data: { line: 2, column: 0 },
154+
});
155+
});
146156
});
147157

148158
describe('should emit correctly located issues regardless of invisible characters', () => {

sonar-plugin/sonar-javascript-plugin/src/main/java/org/sonar/plugins/javascript/analysis/AnalysisProcessor.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,21 @@ private void processParsingError(JsTsContext<?> context, ParsingError parsingErr
168168
newIssue.forRule(parsingErrorRuleKey).at(primaryLocation).save();
169169
}
170170

171-
context
172-
.getSensorContext()
173-
.newAnalysisError()
174-
.onFile(file)
175-
.at(file.newPointer(line != null ? line : 1, toParsingErrorColumn(line, column)))
176-
.message(message)
177-
.save();
171+
var newAnalysisError = context.getSensorContext().newAnalysisError().onFile(file);
172+
try {
173+
newAnalysisError.at(
174+
file.newPointer(line != null ? line : 1, toParsingErrorColumn(line, column))
175+
);
176+
} catch (RuntimeException e) {
177+
LOG.warn(
178+
"Failed to create parsing error pointer in {} at line {}, column {}. Falling back to file start.",
179+
file.uri(),
180+
line,
181+
column
182+
);
183+
newAnalysisError.at(file.newPointer(1, 0));
184+
}
185+
newAnalysisError.message(message).save();
178186
}
179187

180188
@Nullable

sonar-plugin/sonar-javascript-plugin/src/test/java/org/sonar/plugins/javascript/analysis/AnalysisProcessorTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import org.sonar.plugins.javascript.bridge.BridgeServer.Issue;
4040
import org.sonar.plugins.javascript.bridge.BridgeServer.Location;
4141
import org.sonar.plugins.javascript.bridge.BridgeServer.Metrics;
42+
import org.sonar.plugins.javascript.bridge.BridgeServer.ParsingError;
43+
import org.sonar.plugins.javascript.bridge.BridgeServer.ParsingErrorCode;
4244

4345
class AnalysisProcessorTest {
4446

@@ -197,4 +199,45 @@ void should_not_fail_when_invalid_issue() {
197199
processor.processResponse(context, mock(JsTsChecks.class), file, response);
198200
assertThat(logTester.logs()).contains("Failed to save issue in " + file.uri() + " at line 2");
199201
}
202+
203+
@Test
204+
void should_not_fail_when_invalid_parsing_error_pointer() {
205+
var fileLinesContextFactory = mock(FileLinesContextFactory.class);
206+
when(fileLinesContextFactory.createFor(any())).thenReturn(mock(FileLinesContext.class));
207+
var processor = new AnalysisProcessor(
208+
mock(NoSonarFilter.class),
209+
fileLinesContextFactory,
210+
mock(CssRules.class)
211+
);
212+
var context = new JsTsContext<SensorContextTester>(SensorContextTester.create(baseDir));
213+
var file = TestInputFileBuilder.create("moduleKey", "file.js")
214+
.setContents("x")
215+
.setLanguage("js")
216+
.build();
217+
218+
var parsingError = new ParsingError(
219+
"Parse error message",
220+
1,
221+
2,
222+
ParsingErrorCode.PARSING,
223+
"js"
224+
);
225+
var response = new AnalysisResponse(
226+
List.of(parsingError),
227+
List.of(),
228+
List.of(),
229+
List.of(),
230+
new Metrics(),
231+
List.of(),
232+
null
233+
);
234+
235+
processor.processResponse(context, mock(JsTsChecks.class), file, response);
236+
assertThat(context.getSensorContext().allAnalysisErrors()).hasSize(1);
237+
assertThat(logTester.logs()).contains(
238+
"Failed to create parsing error pointer in " +
239+
file.uri() +
240+
" at line 1, column 2. Falling back to file start."
241+
);
242+
}
200243
}

0 commit comments

Comments
 (0)