Skip to content

Commit 1e81938

Browse files
JS-1429 Fix S4030 crash on Svelte use: directives (#6567)
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent ea55935 commit 1e81938

6 files changed

Lines changed: 59 additions & 4 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script lang="ts">
2+
let x = 0
3+
function action() {}
4+
</script>
5+
6+
<div use:action={x}></div>

its/eslint10-plugin-sonarjs/index.test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,18 @@ test('should work with TSESLint config', async t => {
6262
);
6363
verifyErrors(result.stdout);
6464
});
65+
66+
test('should not crash on Svelte use: directives (JS-1429)', async t => {
67+
// Regression test: S4030 crashed with TypeError when linting a Svelte file
68+
// containing a use: action directive parsed by svelte-eslint-parser.
69+
const result = spawn.sync(
70+
'npx',
71+
['eslint', '-c', 'svelte.config.mjs', path.join(fixturesDir, 'file.svelte')],
72+
{
73+
cwd: __dirname,
74+
encoding: 'utf-8',
75+
},
76+
);
77+
assert(!result.stderr.includes('TypeError'), `ESLint crashed:\n${result.stderr}`);
78+
assert.notStrictEqual(result.status, 2, `ESLint exited with fatal error:\n${result.stderr}`);
79+
});

its/eslint10-plugin-sonarjs/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
"eslint": "^10",
1111
"typescript": "^5",
1212
"typescript-eslint": "^8",
13-
"@typescript-eslint/parser": "^8"
13+
"@typescript-eslint/parser": "^8",
14+
"svelte": "^5",
15+
"svelte-eslint-parser": "^1"
1416
}
1517
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* SonarQube JavaScript Plugin
3+
* Copyright (C) 2011-2025 SonarSource Sàrl
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
import plugin from 'eslint-plugin-sonarjs';
18+
import svelteParser from 'svelte-eslint-parser';
19+
import tsParser from '@typescript-eslint/parser';
20+
21+
export default [
22+
plugin.configs.recommended,
23+
{
24+
files: ['**/*.svelte'],
25+
languageOptions: {
26+
parser: svelteParser,
27+
parserOptions: {
28+
parser: tsParser,
29+
},
30+
},
31+
},
32+
];

packages/jsts/src/rules/S4030/rule.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ function isReferenceAssigningCollection(ref: Scope.Reference) {
103103
if (declOrExprStmt.type === 'ExpressionStatement') {
104104
const { expression } = declOrExprStmt;
105105
return (
106-
expression.type === 'AssignmentExpression' &&
106+
expression?.type === 'AssignmentExpression' &&
107107
isReferenceTo(ref, expression.left as estree.Node) &&
108108
isCollectionType(expression.right)
109109
);
@@ -143,7 +143,7 @@ function isRead(ref: Scope.Reference) {
143143
* myArray.push(1);
144144
*/
145145
function isWritingMethodCall(statement: estree.ExpressionStatement, ref: Scope.Reference) {
146-
if (statement.expression.type === 'CallExpression') {
146+
if (statement.expression?.type === 'CallExpression') {
147147
const { callee } = statement.expression;
148148
if (callee.type === 'MemberExpression') {
149149
const { property } = callee;

packages/jsts/src/rules/helpers/ast.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ export function isElementWrite(
251251
ref: Scope.Reference,
252252
recursive = true,
253253
): boolean {
254-
if (statement.expression.type === 'AssignmentExpression') {
254+
if (statement.expression?.type === 'AssignmentExpression') {
255255
const assignmentExpression = statement.expression;
256256
const lhs = assignmentExpression.left;
257257
return isMemberExpressionReference(lhs, ref, recursive);

0 commit comments

Comments
 (0)