eslint-plugin-svelte 3.12.4 → 3.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/main.d.ts +1 -1
- package/lib/meta.d.ts +1 -1
- package/lib/meta.js +1 -1
- package/lib/rules/no-navigation-without-resolve.js +56 -8
- package/lib/rules/no-unused-props.js +39 -18
- package/package.json +4 -4
package/lib/main.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare const configs: {
|
|
|
14
14
|
export declare const rules: Record<string, Rule.RuleModule>;
|
|
15
15
|
export declare const meta: {
|
|
16
16
|
name: "eslint-plugin-svelte";
|
|
17
|
-
version: "3.
|
|
17
|
+
version: "3.13.0";
|
|
18
18
|
};
|
|
19
19
|
export declare const processors: {
|
|
20
20
|
'.svelte': typeof processor;
|
package/lib/meta.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const name: "eslint-plugin-svelte";
|
|
2
|
-
export declare const version: "3.
|
|
2
|
+
export declare const version: "3.13.0";
|
package/lib/meta.js
CHANGED
|
@@ -30,10 +30,10 @@ export default createRule('no-navigation-without-resolve', {
|
|
|
30
30
|
}
|
|
31
31
|
],
|
|
32
32
|
messages: {
|
|
33
|
-
gotoWithoutResolve:
|
|
34
|
-
linkWithoutResolve:
|
|
35
|
-
pushStateWithoutResolve:
|
|
36
|
-
replaceStateWithoutResolve:
|
|
33
|
+
gotoWithoutResolve: 'Unexpected goto() call without resolve().',
|
|
34
|
+
linkWithoutResolve: 'Unexpected href link without resolve().',
|
|
35
|
+
pushStateWithoutResolve: 'Unexpected pushState() call without resolve().',
|
|
36
|
+
replaceStateWithoutResolve: 'Unexpected replaceState() call without resolve().'
|
|
37
37
|
},
|
|
38
38
|
type: 'suggestion',
|
|
39
39
|
conditions: [
|
|
@@ -44,29 +44,49 @@ export default createRule('no-navigation-without-resolve', {
|
|
|
44
44
|
},
|
|
45
45
|
create(context) {
|
|
46
46
|
let resolveReferences = new Set();
|
|
47
|
+
const ignoreGoto = context.options[0]?.ignoreGoto ?? false;
|
|
48
|
+
const ignorePushState = context.options[0]?.ignorePushState ?? false;
|
|
49
|
+
const ignoreReplaceState = context.options[0]?.ignoreReplaceState ?? false;
|
|
50
|
+
const ignoreLinks = context.options[0]?.ignoreLinks ?? false;
|
|
47
51
|
return {
|
|
48
52
|
Program() {
|
|
49
53
|
const referenceTracker = new ReferenceTracker(context.sourceCode.scopeManager.globalScope);
|
|
50
54
|
resolveReferences = extractResolveReferences(referenceTracker, context);
|
|
51
55
|
const { goto: gotoCalls, pushState: pushStateCalls, replaceState: replaceStateCalls } = extractFunctionCallReferences(referenceTracker);
|
|
52
|
-
if (
|
|
56
|
+
if (!ignoreGoto) {
|
|
53
57
|
for (const gotoCall of gotoCalls) {
|
|
54
58
|
checkGotoCall(context, gotoCall, resolveReferences);
|
|
55
59
|
}
|
|
56
60
|
}
|
|
57
|
-
if (
|
|
61
|
+
if (!ignorePushState) {
|
|
58
62
|
for (const pushStateCall of pushStateCalls) {
|
|
59
63
|
checkShallowNavigationCall(context, pushStateCall, resolveReferences, 'pushStateWithoutResolve');
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
|
-
if (
|
|
66
|
+
if (!ignoreReplaceState) {
|
|
63
67
|
for (const replaceStateCall of replaceStateCalls) {
|
|
64
68
|
checkShallowNavigationCall(context, replaceStateCall, resolveReferences, 'replaceStateWithoutResolve');
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
},
|
|
72
|
+
SvelteShorthandAttribute(node) {
|
|
73
|
+
if (ignoreLinks ||
|
|
74
|
+
node.parent.parent.type !== 'SvelteElement' ||
|
|
75
|
+
node.parent.parent.kind !== 'html' ||
|
|
76
|
+
node.parent.parent.name.type !== 'SvelteName' ||
|
|
77
|
+
node.parent.parent.name.name !== 'a' ||
|
|
78
|
+
node.key.name !== 'href' ||
|
|
79
|
+
node.value.type !== 'Identifier') {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (!expressionIsAbsolute(new FindVariableContext(context), node.value) &&
|
|
83
|
+
!expressionIsFragment(new FindVariableContext(context), node.value) &&
|
|
84
|
+
!isResolveCall(new FindVariableContext(context), node.value, resolveReferences)) {
|
|
85
|
+
context.report({ loc: node.loc, messageId: 'linkWithoutResolve' });
|
|
86
|
+
}
|
|
87
|
+
},
|
|
68
88
|
SvelteAttribute(node) {
|
|
69
|
-
if (
|
|
89
|
+
if (ignoreLinks ||
|
|
70
90
|
node.parent.parent.type !== 'SvelteElement' ||
|
|
71
91
|
node.parent.parent.kind !== 'html' ||
|
|
72
92
|
node.parent.parent.name.type !== 'SvelteName' ||
|
|
@@ -75,9 +95,11 @@ export default createRule('no-navigation-without-resolve', {
|
|
|
75
95
|
return;
|
|
76
96
|
}
|
|
77
97
|
if ((node.value[0].type === 'SvelteLiteral' &&
|
|
98
|
+
!expressionIsNullish(new FindVariableContext(context), node.value[0]) &&
|
|
78
99
|
!expressionIsAbsolute(new FindVariableContext(context), node.value[0]) &&
|
|
79
100
|
!expressionIsFragment(new FindVariableContext(context), node.value[0])) ||
|
|
80
101
|
(node.value[0].type === 'SvelteMustacheTag' &&
|
|
102
|
+
!expressionIsNullish(new FindVariableContext(context), node.value[0].expression) &&
|
|
81
103
|
!expressionIsAbsolute(new FindVariableContext(context), node.value[0].expression) &&
|
|
82
104
|
!expressionIsFragment(new FindVariableContext(context), node.value[0].expression) &&
|
|
83
105
|
!isResolveCall(new FindVariableContext(context), node.value[0].expression, resolveReferences))) {
|
|
@@ -93,6 +115,9 @@ function extractResolveReferences(referenceTracker, context) {
|
|
|
93
115
|
for (const { node } of referenceTracker.iterateEsmReferences({
|
|
94
116
|
'$app/paths': {
|
|
95
117
|
[ReferenceTracker.ESM]: true,
|
|
118
|
+
asset: {
|
|
119
|
+
[ReferenceTracker.READ]: true
|
|
120
|
+
},
|
|
96
121
|
resolve: {
|
|
97
122
|
[ReferenceTracker.READ]: true
|
|
98
123
|
}
|
|
@@ -192,6 +217,29 @@ function expressionIsEmpty(url) {
|
|
|
192
217
|
url.quasis.length === 1 &&
|
|
193
218
|
url.quasis[0].value.raw === ''));
|
|
194
219
|
}
|
|
220
|
+
function expressionIsNullish(ctx, url) {
|
|
221
|
+
switch (url.type) {
|
|
222
|
+
case 'Identifier':
|
|
223
|
+
return identifierIsNullish(ctx, url);
|
|
224
|
+
case 'Literal':
|
|
225
|
+
return url.value === null; // Undefined is an Identifier in ESTree, null is a Literal
|
|
226
|
+
default:
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function identifierIsNullish(ctx, url) {
|
|
231
|
+
if (url.name === 'undefined') {
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
const variable = ctx.findVariable(url);
|
|
235
|
+
if (variable === null ||
|
|
236
|
+
variable.identifiers.length === 0 ||
|
|
237
|
+
variable.identifiers[0].parent.type !== 'VariableDeclarator' ||
|
|
238
|
+
variable.identifiers[0].parent.init === null) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
return expressionIsNullish(ctx, variable.identifiers[0].parent.init);
|
|
242
|
+
}
|
|
195
243
|
function expressionIsAbsolute(ctx, url) {
|
|
196
244
|
switch (url.type) {
|
|
197
245
|
case 'BinaryExpression':
|
|
@@ -109,6 +109,7 @@ export default createRule('no-unused-props', {
|
|
|
109
109
|
*/
|
|
110
110
|
function getPropertyPath(node) {
|
|
111
111
|
const paths = [];
|
|
112
|
+
let isSpread = false;
|
|
112
113
|
let currentNode = node;
|
|
113
114
|
let parentNode = currentNode.parent ?? null;
|
|
114
115
|
while (parentNode) {
|
|
@@ -120,14 +121,17 @@ export default createRule('no-unused-props', {
|
|
|
120
121
|
else if (property.type === 'Literal' && typeof property.value === 'string') {
|
|
121
122
|
paths.push(property.value);
|
|
122
123
|
}
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
if (parentNode.type === 'SpreadElement' || parentNode.type === 'SvelteSpreadAttribute') {
|
|
127
|
+
isSpread = true;
|
|
125
128
|
}
|
|
129
|
+
break;
|
|
126
130
|
}
|
|
127
131
|
currentNode = parentNode;
|
|
128
132
|
parentNode = currentNode.parent ?? null;
|
|
129
133
|
}
|
|
130
|
-
return paths;
|
|
134
|
+
return { paths, isSpread };
|
|
131
135
|
}
|
|
132
136
|
/**
|
|
133
137
|
* Finds all property access paths for a given variable.
|
|
@@ -135,18 +139,24 @@ export default createRule('no-unused-props', {
|
|
|
135
139
|
function getUsedNestedPropertyPathsArray(node) {
|
|
136
140
|
const variable = findVariable(context, node);
|
|
137
141
|
if (!variable)
|
|
138
|
-
return [];
|
|
142
|
+
return { paths: [], spreadPaths: [] };
|
|
139
143
|
const pathsArray = [];
|
|
144
|
+
const spreadPathsArray = [];
|
|
140
145
|
for (const reference of variable.references) {
|
|
141
146
|
if ('identifier' in reference &&
|
|
142
147
|
reference.identifier.type === 'Identifier' &&
|
|
143
148
|
(reference.identifier.range[0] !== node.range[0] ||
|
|
144
149
|
reference.identifier.range[1] !== node.range[1])) {
|
|
145
|
-
const
|
|
146
|
-
|
|
150
|
+
const { paths, isSpread } = getPropertyPath(reference.identifier);
|
|
151
|
+
if (isSpread) {
|
|
152
|
+
spreadPathsArray.push(paths);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
pathsArray.push(paths);
|
|
156
|
+
}
|
|
147
157
|
}
|
|
148
158
|
}
|
|
149
|
-
return pathsArray;
|
|
159
|
+
return { paths: pathsArray, spreadPaths: spreadPathsArray };
|
|
150
160
|
}
|
|
151
161
|
/**
|
|
152
162
|
* Checks if a property is from TypeScript's built-in type definitions.
|
|
@@ -204,7 +214,7 @@ export default createRule('no-unused-props', {
|
|
|
204
214
|
/**
|
|
205
215
|
* Recursively checks for unused properties in a type.
|
|
206
216
|
*/
|
|
207
|
-
function checkUnusedProperties({ propsType, usedPropertyPaths, declaredPropertyNames, reportNode, parentPath, checkedPropsTypes, reportedPropertyPaths }) {
|
|
217
|
+
function checkUnusedProperties({ propsType, usedPropertyPaths, usedSpreadPropertyPaths, declaredPropertyNames, reportNode, parentPath, checkedPropsTypes, reportedPropertyPaths }) {
|
|
208
218
|
// Skip checking if the type itself is a class
|
|
209
219
|
if (isClassType(propsType))
|
|
210
220
|
return;
|
|
@@ -224,6 +234,7 @@ export default createRule('no-unused-props', {
|
|
|
224
234
|
checkUnusedProperties({
|
|
225
235
|
propsType: propsBaseType,
|
|
226
236
|
usedPropertyPaths,
|
|
237
|
+
usedSpreadPropertyPaths,
|
|
227
238
|
declaredPropertyNames,
|
|
228
239
|
reportNode,
|
|
229
240
|
parentPath,
|
|
@@ -241,11 +252,14 @@ export default createRule('no-unused-props', {
|
|
|
241
252
|
if (shouldIgnoreProperty(propName))
|
|
242
253
|
continue;
|
|
243
254
|
const currentPath = [...parentPath, propName];
|
|
244
|
-
const currentPathStr =
|
|
255
|
+
const currentPathStr = currentPath.join('.');
|
|
245
256
|
if (reportedPropertyPaths.has(currentPathStr))
|
|
246
257
|
continue;
|
|
247
258
|
const propType = typeChecker.getTypeOfSymbol(prop);
|
|
248
|
-
const isUsedThisInPath = usedPropertyPaths.includes(currentPathStr)
|
|
259
|
+
const isUsedThisInPath = usedPropertyPaths.includes(currentPathStr) ||
|
|
260
|
+
usedSpreadPropertyPaths.some((path) => {
|
|
261
|
+
return path === '' || path === currentPathStr || path.startsWith(`${currentPathStr}.`);
|
|
262
|
+
});
|
|
249
263
|
const isUsedInPath = usedPropertyPaths.some((path) => {
|
|
250
264
|
return path.startsWith(`${currentPathStr}.`);
|
|
251
265
|
});
|
|
@@ -274,6 +288,7 @@ export default createRule('no-unused-props', {
|
|
|
274
288
|
checkUnusedProperties({
|
|
275
289
|
propsType: propType,
|
|
276
290
|
usedPropertyPaths,
|
|
291
|
+
usedSpreadPropertyPaths,
|
|
277
292
|
declaredPropertyNames,
|
|
278
293
|
reportNode,
|
|
279
294
|
parentPath: currentPath,
|
|
@@ -306,8 +321,6 @@ export default createRule('no-unused-props', {
|
|
|
306
321
|
function normalizeUsedPaths(paths, allowUnusedNestedProperties) {
|
|
307
322
|
const normalized = [];
|
|
308
323
|
for (const path of paths.sort((a, b) => a.length - b.length)) {
|
|
309
|
-
if (path.length === 0)
|
|
310
|
-
continue;
|
|
311
324
|
if (normalized.some((p) => p.every((part, idx) => part === path[idx]))) {
|
|
312
325
|
continue;
|
|
313
326
|
}
|
|
@@ -332,7 +345,8 @@ export default createRule('no-unused-props', {
|
|
|
332
345
|
if (!tsNode || !tsNode.type)
|
|
333
346
|
return;
|
|
334
347
|
const propsType = typeChecker.getTypeFromTypeNode(tsNode.type);
|
|
335
|
-
|
|
348
|
+
const usedPropertyPathsArray = [];
|
|
349
|
+
const usedSpreadPropertyPathsArray = [];
|
|
336
350
|
let declaredPropertyNames = new Set();
|
|
337
351
|
if (node.id.type === 'ObjectPattern') {
|
|
338
352
|
declaredPropertyNames = getUsedPropertyNamesFromPattern(node.id);
|
|
@@ -351,18 +365,25 @@ export default createRule('no-unused-props', {
|
|
|
351
365
|
}
|
|
352
366
|
}
|
|
353
367
|
for (const identifier of identifiers) {
|
|
354
|
-
const paths = getUsedNestedPropertyPathsArray(identifier);
|
|
368
|
+
const { paths, spreadPaths } = getUsedNestedPropertyPathsArray(identifier);
|
|
355
369
|
usedPropertyPathsArray.push(...paths.map((path) => [identifier.name, ...path]));
|
|
370
|
+
usedSpreadPropertyPathsArray.push(...spreadPaths.map((path) => [identifier.name, ...path]));
|
|
356
371
|
}
|
|
357
372
|
}
|
|
358
373
|
else if (node.id.type === 'Identifier') {
|
|
359
|
-
|
|
374
|
+
const { paths, spreadPaths } = getUsedNestedPropertyPathsArray(node.id);
|
|
375
|
+
usedPropertyPathsArray.push(...paths);
|
|
376
|
+
usedSpreadPropertyPathsArray.push(...spreadPaths);
|
|
377
|
+
}
|
|
378
|
+
function runNormalizeUsedPaths(paths) {
|
|
379
|
+
return normalizeUsedPaths(paths, options.allowUnusedNestedProperties).map((pathArray) => {
|
|
380
|
+
return pathArray.join('.');
|
|
381
|
+
});
|
|
360
382
|
}
|
|
361
383
|
checkUnusedProperties({
|
|
362
384
|
propsType,
|
|
363
|
-
usedPropertyPaths:
|
|
364
|
-
|
|
365
|
-
}),
|
|
385
|
+
usedPropertyPaths: runNormalizeUsedPaths(usedPropertyPathsArray),
|
|
386
|
+
usedSpreadPropertyPaths: runNormalizeUsedPaths(usedSpreadPropertyPathsArray),
|
|
366
387
|
declaredPropertyNames,
|
|
367
388
|
reportNode: node.id,
|
|
368
389
|
parentPath: [],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-svelte",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.13.0",
|
|
4
4
|
"description": "ESLint plugin for Svelte using AST",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"postcss-load-config": "^3.1.4",
|
|
46
46
|
"postcss-safe-parser": "^7.0.0",
|
|
47
47
|
"semver": "^7.6.3",
|
|
48
|
-
"svelte-eslint-parser": "^1.
|
|
48
|
+
"svelte-eslint-parser": "^1.4.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@babel/core": "^7.28.3",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"sass": "^1.92.0",
|
|
80
80
|
"source-map-js": "^1.2.1",
|
|
81
81
|
"stylus": "^0.64.0",
|
|
82
|
-
"svelte": "^5.
|
|
82
|
+
"svelte": "^5.41.0",
|
|
83
83
|
"svelte-i18n": "^4.0.1",
|
|
84
84
|
"tsx": "^4.20.5",
|
|
85
85
|
"type-coverage": "^2.29.7",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"debug": "pnpm run mocha \"tests/src/**/*.ts\" --reporter dot --timeout 60000",
|
|
109
109
|
"lint": "run-p lint:*",
|
|
110
110
|
"lint-fix": "pnpm run lint:es --fix",
|
|
111
|
-
"lint:es": "eslint --cache .",
|
|
111
|
+
"lint:es": "eslint --concurrency auto --cache .",
|
|
112
112
|
"mocha": "pnpm run ts ./node_modules/mocha/bin/mocha.js",
|
|
113
113
|
"new": "pnpm run ts ./tools/new-rule.ts",
|
|
114
114
|
"prebuild": "pnpm run clean",
|