eslint-plugin-svelte 3.16.0 → 3.17.1

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 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.16.0";
17
+ version: "3.17.1";
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.16.0";
2
+ export declare const version: "3.17.1";
package/lib/meta.js CHANGED
@@ -2,4 +2,4 @@
2
2
  // This file has been automatically generated,
3
3
  // in order to update its content execute "pnpm run update"
4
4
  export const name = 'eslint-plugin-svelte';
5
- export const version = '3.16.0';
5
+ export const version = '3.17.1';
@@ -2,6 +2,7 @@ import { createRule } from '../utils/index.js';
2
2
  import { ReferenceTracker } from '@eslint-community/eslint-utils';
3
3
  import { FindVariableContext } from '../utils/ast-utils.js';
4
4
  import { findVariable } from '../utils/ast-utils.js';
5
+ import { getTypeScriptTools } from '../utils/ts-utils/index.js';
5
6
  export default createRule('no-navigation-without-resolve', {
6
7
  meta: {
7
8
  docs: {
@@ -43,6 +44,7 @@ export default createRule('no-navigation-without-resolve', {
43
44
  ]
44
45
  },
45
46
  create(context) {
47
+ const tsTools = getTypeScriptTools(context);
46
48
  let resolveReferences = new Set();
47
49
  const ignoreGoto = context.options[0]?.ignoreGoto ?? false;
48
50
  const ignorePushState = context.options[0]?.ignorePushState ?? false;
@@ -55,27 +57,27 @@ export default createRule('no-navigation-without-resolve', {
55
57
  const { goto: gotoCalls, pushState: pushStateCalls, replaceState: replaceStateCalls } = extractFunctionCallReferences(referenceTracker);
56
58
  if (!ignoreGoto) {
57
59
  for (const gotoCall of gotoCalls) {
58
- checkGotoCall(context, gotoCall, resolveReferences);
60
+ checkGotoCall(context, gotoCall, resolveReferences, tsTools);
59
61
  }
60
62
  }
61
63
  if (!ignorePushState) {
62
64
  for (const pushStateCall of pushStateCalls) {
63
- checkShallowNavigationCall(context, pushStateCall, resolveReferences, 'pushStateWithoutResolve');
65
+ checkShallowNavigationCall(context, pushStateCall, resolveReferences, tsTools, 'pushStateWithoutResolve');
64
66
  }
65
67
  }
66
68
  if (!ignoreReplaceState) {
67
69
  for (const replaceStateCall of replaceStateCalls) {
68
- checkShallowNavigationCall(context, replaceStateCall, resolveReferences, 'replaceStateWithoutResolve');
70
+ checkShallowNavigationCall(context, replaceStateCall, resolveReferences, tsTools, 'replaceStateWithoutResolve');
69
71
  }
70
72
  }
71
73
  },
72
74
  ...(!ignoreLinks && {
73
75
  SvelteShorthandAttribute(node) {
74
- checkLinkAttribute(context, node, node.value, resolveReferences);
76
+ checkLinkAttribute(context, node, node.value, resolveReferences, tsTools);
75
77
  },
76
78
  SvelteAttribute(node) {
77
79
  if (node.value.length > 0) {
78
- checkLinkAttribute(context, node, node.value[0].type === 'SvelteMustacheTag' ? node.value[0].expression : node.value[0], resolveReferences);
80
+ checkLinkAttribute(context, node, node.value[0].type === 'SvelteMustacheTag' ? node.value[0].expression : node.value[0], resolveReferences, tsTools);
79
81
  }
80
82
  }
81
83
  })
@@ -143,28 +145,28 @@ function extractFunctionCallReferences(referenceTracker) {
143
145
  };
144
146
  }
145
147
  // Actual function checking
146
- function checkGotoCall(context, call, resolveReferences) {
148
+ function checkGotoCall(context, call, resolveReferences, tsTools) {
147
149
  if (call.arguments.length > 0 &&
148
- !isValueAllowed(new FindVariableContext(context), call.arguments[0], resolveReferences, {})) {
150
+ !isValueAllowed(new FindVariableContext(context), call.arguments[0], resolveReferences, tsTools, {})) {
149
151
  context.report({ loc: call.arguments[0].loc, messageId: 'gotoWithoutResolve' });
150
152
  }
151
153
  }
152
- function checkShallowNavigationCall(context, call, resolveReferences, messageId) {
154
+ function checkShallowNavigationCall(context, call, resolveReferences, tsTools, messageId) {
153
155
  if (call.arguments.length > 0 &&
154
- !isValueAllowed(new FindVariableContext(context), call.arguments[0], resolveReferences, {
156
+ !isValueAllowed(new FindVariableContext(context), call.arguments[0], resolveReferences, tsTools, {
155
157
  allowEmpty: true
156
158
  })) {
157
159
  context.report({ loc: call.arguments[0].loc, messageId });
158
160
  }
159
161
  }
160
- function checkLinkAttribute(context, attribute, value, resolveReferences) {
162
+ function checkLinkAttribute(context, attribute, value, resolveReferences, tsTools) {
161
163
  if (attribute.parent.parent.type === 'SvelteElement' &&
162
164
  attribute.parent.parent.kind === 'html' &&
163
165
  attribute.parent.parent.name.type === 'SvelteName' &&
164
166
  attribute.parent.parent.name.name === 'a' &&
165
167
  attribute.key.name === 'href' &&
166
168
  !hasRelExternal(new FindVariableContext(context), attribute.parent) &&
167
- !isValueAllowed(new FindVariableContext(context), value, resolveReferences, {
169
+ !isValueAllowed(new FindVariableContext(context), value, resolveReferences, tsTools, {
168
170
  allowAbsolute: true,
169
171
  allowFragment: true,
170
172
  allowNullish: true
@@ -201,26 +203,60 @@ function hasRelExternal(ctx, element) {
201
203
  }
202
204
  return false;
203
205
  }
204
- function isValueAllowed(ctx, value, resolveReferences, config) {
206
+ function isValueAllowed(ctx, value, resolveReferences, tsTools, config) {
205
207
  if (value.type === 'Identifier') {
206
208
  const variable = ctx.findVariable(value);
207
209
  if (variable !== null &&
208
210
  variable.identifiers.length > 0 &&
209
- variable.identifiers[0].parent.type === 'VariableDeclarator' &&
210
- variable.identifiers[0].parent.init !== null) {
211
- return isValueAllowed(ctx, variable.identifiers[0].parent.init, resolveReferences, config);
211
+ variable.identifiers[0].parent.type === 'VariableDeclarator') {
212
+ if (expressionIsResolvedPathname(variable.identifiers[0], tsTools)) {
213
+ return true;
214
+ }
215
+ if (variable.identifiers[0].parent.init !== null) {
216
+ return isValueAllowed(ctx, variable.identifiers[0].parent.init, resolveReferences, tsTools, config);
217
+ }
212
218
  }
213
219
  }
220
+ if (value.type === 'ConditionalExpression') {
221
+ return (isValueAllowed(ctx, value.consequent, resolveReferences, tsTools, config) &&
222
+ isValueAllowed(ctx, value.alternate, resolveReferences, tsTools, config));
223
+ }
214
224
  if ((config.allowAbsolute && expressionIsAbsoluteUrl(ctx, value)) ||
215
225
  (config.allowEmpty && expressionIsEmpty(value)) ||
216
226
  (config.allowFragment && expressionStartsWith(ctx, value, '#')) ||
217
227
  (config.allowNullish && expressionIsNullish(value)) ||
228
+ expressionIsResolvedPathname(value, tsTools) ||
218
229
  expressionIsResolveCall(ctx, value, resolveReferences)) {
219
230
  return true;
220
231
  }
221
232
  return false;
222
233
  }
223
234
  // Helper functions
235
+ function expressionIsResolvedPathname(value, tsTools) {
236
+ if (tsTools === null) {
237
+ return false;
238
+ }
239
+ const checker = tsTools.service.program.getTypeChecker();
240
+ const tsNode = tsTools.service.esTreeNodeToTSNodeMap.get(value);
241
+ if (tsNode === undefined) {
242
+ return false;
243
+ }
244
+ const nodeType = checker.getTypeAtLocation(tsNode);
245
+ const appTypesModule = checker.getAmbientModules().find((m) => m.name === '"$app/types"');
246
+ if (!appTypesModule) {
247
+ return false;
248
+ }
249
+ const resolvedPathnameSymbol = checker
250
+ .getExportsOfModule(appTypesModule)
251
+ .find((e) => e.name === 'ResolvedPathname');
252
+ if (!resolvedPathnameSymbol) {
253
+ return false;
254
+ }
255
+ const resolvedPathnameType = checker.getDeclaredTypeOfSymbol(resolvedPathnameSymbol);
256
+ // getTypeAtLocation returns the resolved (structural) type without alias information, so we cannot compare aliasSymbols directly. Instead we check structural equivalence by testing assignability in both directions: this correctly rejects strict subtypes like Pathname (Pathname ⊂ ResolvedPathname, so only one direction holds).
257
+ return (checker.isTypeAssignableTo(nodeType, resolvedPathnameType) &&
258
+ checker.isTypeAssignableTo(resolvedPathnameType, nodeType));
259
+ }
224
260
  function expressionIsResolveCall(ctx, node, resolveReferences) {
225
261
  if (node.type === 'CallExpression' &&
226
262
  ((node.callee.type === 'Identifier' && resolveReferences.has(node.callee)) ||
@@ -273,8 +309,8 @@ function expressionIsAbsoluteUrl(ctx, node) {
273
309
  }
274
310
  }
275
311
  function binaryExpressionIsAbsoluteUrl(ctx, node) {
276
- return ((node.left.type !== 'PrivateIdentifier' && expressionIsAbsoluteUrl(ctx, node.left)) ||
277
- expressionIsAbsoluteUrl(ctx, node.right));
312
+ return (node.operator === '+' &&
313
+ (expressionIsAbsoluteUrl(ctx, node.left) || expressionIsAbsoluteUrl(ctx, node.right)));
278
314
  }
279
315
  function templateLiteralIsAbsoluteUrl(ctx, node) {
280
316
  return (node.expressions.some((expression) => expressionIsAbsoluteUrl(ctx, expression)) ||
@@ -302,7 +338,7 @@ function expressionStartsWith(ctx, node, prefix) {
302
338
  }
303
339
  }
304
340
  function binaryExpressionStartsWith(ctx, node, prefix) {
305
- return node.left.type !== 'PrivateIdentifier' && expressionStartsWith(ctx, node.left, prefix);
341
+ return node.operator === '+' && expressionStartsWith(ctx, node.left, prefix);
306
342
  }
307
343
  function identifierStartsWith(ctx, node, prefix) {
308
344
  const variable = ctx.findVariable(node);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-svelte",
3
- "version": "3.16.0",
3
+ "version": "3.17.1",
4
4
  "description": "ESLint plugin for Svelte using AST",
5
5
  "repository": {
6
6
  "type": "git",
@@ -66,7 +66,7 @@
66
66
  "@typescript-eslint/types": "^8.48.1",
67
67
  "acorn": "^8.15.0",
68
68
  "assert": "^2.1.0",
69
- "esbuild": "^0.27.0",
69
+ "esbuild": "^0.28.0",
70
70
  "eslint-scope": "^9.0.0",
71
71
  "eslint-typegen": "^2.3.0",
72
72
  "eslint-visitor-keys": "^5.0.0",