eslint-plugin-svelte 3.0.3 → 3.1.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 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.0.3";
17
+ version: "3.1.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.0.3";
2
+ export declare const version = "3.1.0";
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.0.3';
5
+ export const version = '3.1.0';
@@ -541,6 +541,7 @@ type SveltePreferConst = [] | [
541
541
  {
542
542
  destructuring?: ("any" | "all");
543
543
  ignoreReadBeforeAssign?: boolean;
544
+ excludedRunes?: string[];
544
545
  }
545
546
  ];
546
547
  type SvelteShorthandAttribute = [] | [
@@ -49,6 +49,7 @@ export default createRule('consistent-selector-style', {
49
49
  const styleSelectorNodeLoc = sourceCode.parserServices.styleSelectorNodeLoc;
50
50
  const checkGlobal = context.options[0]?.checkGlobal ?? false;
51
51
  const style = context.options[0]?.style ?? ['type', 'id', 'class'];
52
+ const whitelistedClasses = [];
52
53
  const classSelections = new Map();
53
54
  const idSelections = new Map();
54
55
  const typeSelections = new Map();
@@ -89,6 +90,9 @@ export default createRule('consistent-selector-style', {
89
90
  * Checks a class selector
90
91
  */
91
92
  function checkClassSelector(node) {
93
+ if (whitelistedClasses.includes(node.value)) {
94
+ return;
95
+ }
92
96
  const selection = classSelections.get(node.value) ?? [];
93
97
  for (const styleValue of style) {
94
98
  if (styleValue === 'class') {
@@ -171,6 +175,9 @@ export default createRule('consistent-selector-style', {
171
175
  addToArrayMap(classSelections, className, node);
172
176
  }
173
177
  for (const attribute of node.startTag.attributes) {
178
+ if (attribute.type === 'SvelteDirective' && attribute.kind === 'Class') {
179
+ whitelistedClasses.push(attribute.key.name.name);
180
+ }
174
181
  if (attribute.type !== 'SvelteAttribute' || attribute.key.name !== 'id') {
175
182
  continue;
176
183
  }
@@ -71,13 +71,14 @@ export default createRule('no-navigation-without-base', {
71
71
  }
72
72
  const hrefValue = node.value[0];
73
73
  if (hrefValue.type === 'SvelteLiteral') {
74
- if (!expressionIsAbsolute(hrefValue)) {
74
+ if (!expressionIsAbsolute(hrefValue) && !expressionIsFragment(hrefValue)) {
75
75
  context.report({ loc: hrefValue.loc, messageId: 'linkNotPrefixed' });
76
76
  }
77
77
  return;
78
78
  }
79
79
  if (!expressionStartsWithBase(context, hrefValue.expression, basePathNames) &&
80
- !expressionIsAbsolute(hrefValue.expression)) {
80
+ !expressionIsAbsolute(hrefValue.expression) &&
81
+ !expressionIsFragment(hrefValue.expression)) {
81
82
  context.report({ loc: hrefValue.loc, messageId: 'linkNotPrefixed' });
82
83
  }
83
84
  }
@@ -247,3 +248,27 @@ function templateLiteralIsAbsolute(url) {
247
248
  function urlValueIsAbsolute(url) {
248
249
  return url.includes('://');
249
250
  }
251
+ function expressionIsFragment(url) {
252
+ switch (url.type) {
253
+ case 'BinaryExpression':
254
+ return binaryExpressionIsFragment(url);
255
+ case 'Literal':
256
+ return typeof url.value === 'string' && urlValueIsFragment(url.value);
257
+ case 'SvelteLiteral':
258
+ return urlValueIsFragment(url.value);
259
+ case 'TemplateLiteral':
260
+ return templateLiteralIsFragment(url);
261
+ default:
262
+ return false;
263
+ }
264
+ }
265
+ function binaryExpressionIsFragment(url) {
266
+ return url.left.type !== 'PrivateIdentifier' && expressionIsFragment(url.left);
267
+ }
268
+ function templateLiteralIsFragment(url) {
269
+ return ((url.expressions.length >= 1 && expressionIsFragment(url.expressions[0])) ||
270
+ (url.quasis.length >= 1 && urlValueIsFragment(url.quasis[0].value.raw)));
271
+ }
272
+ function urlValueIsFragment(url) {
273
+ return url.startsWith('#');
274
+ }
@@ -15,7 +15,7 @@ function findDeclarationCallee(node) {
15
15
  * Determines if a declaration should be skipped in the const preference analysis.
16
16
  * Specifically checks for Svelte's state management utilities ($props, $derived).
17
17
  */
18
- function shouldSkipDeclaration(declaration) {
18
+ function shouldSkipDeclaration(declaration, excludedRunes) {
19
19
  if (!declaration) {
20
20
  return false;
21
21
  }
@@ -23,15 +23,13 @@ function shouldSkipDeclaration(declaration) {
23
23
  if (!callee) {
24
24
  return false;
25
25
  }
26
- if (callee.type === 'Identifier' && ['$props', '$derived'].includes(callee.name)) {
26
+ if (callee.type === 'Identifier' && excludedRunes.includes(callee.name)) {
27
27
  return true;
28
28
  }
29
29
  if (callee.type !== 'MemberExpression' || callee.object.type !== 'Identifier') {
30
30
  return false;
31
31
  }
32
- if (callee.object.name === '$derived' &&
33
- callee.property.type === 'Identifier' &&
34
- callee.property.name === 'by') {
32
+ if (excludedRunes.includes(callee.object.name)) {
35
33
  return true;
36
34
  }
37
35
  return false;
@@ -44,16 +42,34 @@ export default createRule('prefer-const', {
44
42
  category: 'Best Practices',
45
43
  recommended: false,
46
44
  extensionRule: 'prefer-const'
47
- }
45
+ },
46
+ schema: [
47
+ {
48
+ type: 'object',
49
+ properties: {
50
+ destructuring: { enum: ['any', 'all'] },
51
+ ignoreReadBeforeAssign: { type: 'boolean' },
52
+ excludedRunes: {
53
+ type: 'array',
54
+ items: {
55
+ type: 'string'
56
+ }
57
+ }
58
+ },
59
+ additionalProperties: false
60
+ }
61
+ ]
48
62
  },
49
63
  create(context) {
64
+ const config = context.options[0] ?? {};
65
+ const excludedRunes = config.excludedRunes ?? ['$props', '$derived'];
50
66
  return defineWrapperListener(coreRule, context, {
51
67
  createListenerProxy(coreListener) {
52
68
  return {
53
69
  ...coreListener,
54
70
  VariableDeclaration(node) {
55
71
  for (const decl of node.declarations) {
56
- if (shouldSkipDeclaration(decl.init)) {
72
+ if (shouldSkipDeclaration(decl.init, excludedRunes)) {
57
73
  return;
58
74
  }
59
75
  }
@@ -7,11 +7,11 @@ function checkProp(node, context, expectedPropNames) {
7
7
  return;
8
8
  for (const p of node.id.properties) {
9
9
  if (p.type === 'Property' &&
10
- p.value.type === 'Identifier' &&
11
- !expectedPropNames.includes(p.value.name)) {
10
+ p.key.type === 'Identifier' &&
11
+ !expectedPropNames.includes(p.key.name)) {
12
12
  context.report({
13
- node: p.value,
14
- loc: p.value.loc,
13
+ node: p.key,
14
+ loc: p.key.loc,
15
15
  messageId: 'unexpected'
16
16
  });
17
17
  }
@@ -16,7 +16,7 @@ function getSvelteFileType(filePath) {
16
16
  return null;
17
17
  }
18
18
  function getSvelteKitFileTypeFromFilePath(filePath) {
19
- const fileName = filePath.split('/').pop();
19
+ const fileName = filePath.split(/[/\\]/).pop();
20
20
  switch (fileName) {
21
21
  case '+page.svelte': {
22
22
  return '+page.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-svelte",
3
- "version": "3.0.3",
3
+ "version": "3.1.0",
4
4
  "description": "ESLint plugin for Svelte using AST",
5
5
  "repository": "git+https://github.com/sveltejs/eslint-plugin-svelte.git",
6
6
  "homepage": "https://sveltejs.github.io/eslint-plugin-svelte",