eslint-plugin-svelte 3.12.5 → 3.13.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.12.5";
17
+ version: "3.13.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.12.5";
2
+ export declare const version: "3.13.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.12.5';
5
+ export const version = '3.13.1';
@@ -542,14 +542,24 @@ export function defineVisitor(context) {
542
542
  includeComments: false
543
543
  });
544
544
  offsets.setOffsetToken(leftParenToken, 1, firstToken);
545
- const argument = node.argument ||
546
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- typescript-eslint<v6 node
547
- node.parameter;
548
- const rightParenToken = sourceCode.getTokenAfter(argument, {
545
+ const args = [];
546
+ if (node.source) {
547
+ args.push(node.source);
548
+ if (node.options) {
549
+ args.push(node.options);
550
+ }
551
+ }
552
+ else {
553
+ // typescript-eslint<v8.48 node
554
+ args.push(node.argument ||
555
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- typescript-eslint<v6 node
556
+ node.parameter);
557
+ }
558
+ const rightParenToken = sourceCode.getTokenAfter(args[args.length - 1], {
549
559
  filter: isClosingParenToken,
550
560
  includeComments: false
551
561
  });
552
- offsets.setOffsetElementList([argument], leftParenToken, rightParenToken, 1);
562
+ offsets.setOffsetElementList(args, leftParenToken, rightParenToken, 1);
553
563
  if (node.qualifier) {
554
564
  const dotToken = sourceCode.getTokenBefore(node.qualifier);
555
565
  const propertyToken = sourceCode.getTokenAfter(dotToken);
@@ -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 (context.options[0]?.ignoreGoto !== true) {
56
+ if (!ignoreGoto) {
53
57
  for (const gotoCall of gotoCalls) {
54
58
  checkGotoCall(context, gotoCall, resolveReferences);
55
59
  }
56
60
  }
57
- if (context.options[0]?.ignorePushState !== true) {
61
+ if (!ignorePushState) {
58
62
  for (const pushStateCall of pushStateCalls) {
59
63
  checkShallowNavigationCall(context, pushStateCall, resolveReferences, 'pushStateWithoutResolve');
60
64
  }
61
65
  }
62
- if (context.options[0]?.ignoreReplaceState !== true) {
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 (context.options[0]?.ignoreLinks === true ||
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))) {
@@ -195,6 +217,29 @@ function expressionIsEmpty(url) {
195
217
  url.quasis.length === 1 &&
196
218
  url.quasis[0].value.raw === ''));
197
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
+ }
198
243
  function expressionIsAbsolute(ctx, url) {
199
244
  switch (url.type) {
200
245
  case 'BinaryExpression':
@@ -2,6 +2,8 @@ import { getPropertyName } from '@eslint-community/eslint-utils';
2
2
  import { keyword } from 'esutils';
3
3
  import { createRule } from '../utils/index.js';
4
4
  import { findAttribute, isExpressionIdentifier, findVariable } from '../utils/ast-utils.js';
5
+ import { getSvelteContext } from '../utils/svelte-context.js';
6
+ import { SVELTE_RUNES } from '../shared/runes.js';
5
7
  export default createRule('prefer-destructured-store-props', {
6
8
  meta: {
7
9
  docs: {
@@ -20,6 +22,7 @@ export default createRule('prefer-destructured-store-props', {
20
22
  },
21
23
  create(context) {
22
24
  let mainScript = null;
25
+ const svelteContext = getSvelteContext(context);
23
26
  // Store off instances of probably-destructurable statements
24
27
  const reports = [];
25
28
  let inScriptElement = false;
@@ -105,6 +108,9 @@ export default createRule('prefer-destructured-store-props', {
105
108
  "MemberExpression[object.type='Identifier'][object.name=/^\\$[^\\$]/]"(node) {
106
109
  if (inScriptElement)
107
110
  return; // Within a script tag
111
+ // Skip Svelte 5 runes (e.g., $derived.by, $state.raw, $effect.pre)
112
+ if (svelteContext?.runes === true && SVELTE_RUNES.has(node.object.name))
113
+ return;
108
114
  storeMemberAccessStack.unshift({ node, identifiers: [] });
109
115
  },
110
116
  Identifier(node) {
@@ -0,0 +1 @@
1
+ export declare const SVELTE_RUNES: Set<string>;
@@ -0,0 +1,9 @@
1
+ export const SVELTE_RUNES = new Set([
2
+ '$state',
3
+ '$derived',
4
+ '$effect',
5
+ '$props',
6
+ '$bindable',
7
+ '$inspect',
8
+ '$host'
9
+ ]);
@@ -10,6 +10,7 @@ import { transform as transformWithStylus } from './transform/stylus.js';
10
10
  import { getSvelteIgnoreItems } from './ignore-comment.js';
11
11
  import { extractLeadingComments } from './extract-leading-comments.js';
12
12
  import { findAttribute, getLangValue } from '../../utils/ast-utils.js';
13
+ import { getSvelteVersion } from '../../utils/svelte-context.js';
13
14
  import path from 'path';
14
15
  import fs from 'fs';
15
16
  const STYLE_TRANSFORMS = {
@@ -287,12 +288,22 @@ function isCustomElement(program) {
287
288
  return Boolean(findAttribute(body, 'tag')) || Boolean(findAttribute(body, 'customElement'));
288
289
  });
289
290
  }
291
+ const svelteVersion = getSvelteVersion();
290
292
  /**
291
293
  * Get compile warnings
292
294
  */
293
295
  function getWarningsFromCode(code, context) {
294
296
  try {
297
+ const svelteConfig = context.sourceCode.parserServices.svelteParseContext?.svelteConfig;
298
+ const compilerOptions = svelteConfig?.compilerOptions ?? {};
295
299
  const result = compiler.compile(code, {
300
+ ...(svelteVersion === '5'
301
+ ? {
302
+ experimental: {
303
+ async: compilerOptions.experimental?.async
304
+ }
305
+ }
306
+ : {}),
296
307
  generate: false,
297
308
  ...(isCustomElement(context.sourceCode.ast) ? { customElement: true } : {})
298
309
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-svelte",
3
- "version": "3.12.5",
3
+ "version": "3.13.1",
4
4
  "description": "ESLint plugin for Svelte using AST",
5
5
  "repository": {
6
6
  "type": "git",
@@ -62,15 +62,15 @@
62
62
  "@types/postcss-safe-parser": "^5.0.4",
63
63
  "@types/semver": "^7.7.0",
64
64
  "@types/stylus": "^0.48.43",
65
- "@typescript-eslint/scope-manager": "^8.43.0",
66
- "@typescript-eslint/types": "^8.43.0",
65
+ "@typescript-eslint/scope-manager": "^8.48.1",
66
+ "@typescript-eslint/types": "^8.48.1",
67
67
  "acorn": "^8.15.0",
68
68
  "assert": "^2.1.0",
69
- "esbuild": "^0.25.9",
70
- "eslint-scope": "^8.4.0",
69
+ "esbuild": "^0.27.0",
70
+ "eslint-scope": "^9.0.0",
71
71
  "eslint-typegen": "^2.3.0",
72
- "eslint-visitor-keys": "^4.2.1",
73
- "espree": "^10.4.0",
72
+ "eslint-visitor-keys": "^5.0.0",
73
+ "espree": "^11.0.0",
74
74
  "jiti": "^2.5.1",
75
75
  "less": "^4.4.1",
76
76
  "mocha": "~11.7.2",