eslint-plugin-svelte 3.15.2 → 3.16.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/README.md CHANGED
@@ -32,14 +32,33 @@
32
32
 
33
33
  `eslint-plugin-svelte` is the official [ESLint](https://eslint.org/) plugin for [Svelte](https://svelte.dev/).\
34
34
  It leverages the AST generated by [svelte-eslint-parser](https://github.com/sveltejs/svelte-eslint-parser) to provide custom linting for Svelte.\
35
- Note that `eslint-plugin-svelte` and `svelte-eslint-parser` cannot be used alongside [eslint-plugin-svelte3](https://github.com/sveltejs/eslint-plugin-svelte3).
35
+
36
+ > [!NOTE]
37
+ >
38
+ > `eslint-plugin-svelte` and `svelte-eslint-parser` cannot be used alongside [eslint-plugin-svelte3](https://github.com/sveltejs/eslint-plugin-svelte3).
36
39
 
37
40
  <!--USAGE_SECTION_START-->
38
41
  <!--USAGE_GUIDE_START-->
39
42
 
40
43
  ## Installation
41
44
 
42
- ```bash
45
+ ### CLI
46
+
47
+ The recommended way to get started is to use the CLI.
48
+
49
+ ```sh
50
+ # new project
51
+ npx sv create
52
+
53
+ # existing project
54
+ npx sv add eslint
55
+ ```
56
+
57
+ See the [CLI docs](https://svelte.dev/docs/cli/eslint) for more details.
58
+
59
+ ### Manual Setup
60
+
61
+ ```sh
43
62
  npm install --save-dev svelte eslint eslint-plugin-svelte globals
44
63
  ```
45
64
 
@@ -52,7 +71,7 @@ npm install --save-dev svelte eslint eslint-plugin-svelte globals
52
71
 
53
72
  ## Usage
54
73
 
55
- Use the `eslint.config.js` file to configure rules. For more details, see the [ESLint documentation](https://eslint.org/docs/latest/use/configure/configuration-files-new).
74
+ Use `eslint.config.js` to configure rules. See [ESLint documentation](https://eslint.org/docs/latest/use/configure/configuration-files-new) for more details.
56
75
 
57
76
  ### Configuration
58
77
 
@@ -60,20 +79,22 @@ Use the `eslint.config.js` file to configure rules. For more details, see the [E
60
79
 
61
80
  ```js
62
81
  // eslint.config.js
82
+ import svelteConfig from './svelte.config.js';
83
+ import { defineConfig } from 'eslint/config';
84
+ import globals from 'globals';
63
85
  import js from '@eslint/js';
64
86
  import svelte from 'eslint-plugin-svelte';
65
- import globals from 'globals';
66
- import svelteConfig from './svelte.config.js';
67
87
 
68
- /** @type {import('eslint').Linter.Config[]} */
69
- export default [
88
+ export default defineConfig([
89
+ // ...
70
90
  js.configs.recommended,
71
- ...svelte.configs.recommended,
91
+ svelte.configs.recommended,
72
92
  {
73
93
  languageOptions: {
74
94
  globals: {
75
95
  ...globals.browser,
76
- ...globals.node // Add this if you are using SvelteKit in non-SPA mode
96
+ // for Sveltekit in non-SPA mode
97
+ ...globals.node
77
98
  }
78
99
  }
79
100
  },
@@ -81,13 +102,17 @@ export default [
81
102
  files: ['**/*.svelte', '**/*.svelte.js'],
82
103
  languageOptions: {
83
104
  parserOptions: {
84
- // We recommend importing and specifying svelte.config.js.
85
- // By doing so, some rules in eslint-plugin-svelte will automatically read the configuration and adjust their behavior accordingly.
86
- // While certain Svelte settings may be statically loaded from svelte.config.js even if you don’t specify it,
87
- // explicitly specifying it ensures better compatibility and functionality.
105
+ // explicitly importing allows for better compatibilty and functionality with rules and other tooling that depend on the config file.
88
106
  //
89
- // If non-serializable properties are included, running ESLint with the --cache flag will fail.
90
- // In that case, please remove the non-serializable properties. (e.g. `svelteConfig: { ...svelteConfig, kit: { ...svelteConfig.kit, typescript: undefined }}`)
107
+ // Note: `eslint --cache` will fail with non-serializable properties.
108
+ // In those cases, please remove the non-serializable properties.
109
+ // svelteConfig: {
110
+ // ...svelteConfig,
111
+ // kit: {
112
+ // ...svelteConfig.kit,
113
+ // typescript: undefined
114
+ // }
115
+ // }
91
116
  svelteConfig
92
117
  }
93
118
  }
@@ -98,7 +123,7 @@ export default [
98
123
  // 'svelte/rule-name': 'error'
99
124
  }
100
125
  }
101
- ];
126
+ ]);
102
127
  ```
103
128
 
104
129
  #### TypeScript project
@@ -109,23 +134,26 @@ npm install --save-dev typescript-eslint
109
134
 
110
135
  ```js
111
136
  // eslint.config.js
112
- import js from '@eslint/js';
113
- import svelte from 'eslint-plugin-svelte';
137
+ import svelteConfig from './svelte.config.js';
138
+ import { defineConfig } from 'eslint/config';
114
139
  import globals from 'globals';
140
+ import js from '@eslint/js';
115
141
  import ts from 'typescript-eslint';
116
- import svelteConfig from './svelte.config.js';
142
+ import svelte from 'eslint-plugin-svelte';
117
143
 
118
- export default ts.config(
144
+ export default defineConfig(
119
145
  js.configs.recommended,
120
- ...ts.configs.recommended,
121
- ...svelte.configs.recommended,
146
+ ts.configs.recommended,
147
+ svelte.configs.recommended,
122
148
  {
123
149
  languageOptions: {
124
150
  globals: {
125
151
  ...globals.browser,
152
+ // for Sveltekit in non-SPA mode
126
153
  ...globals.node
127
154
  }
128
155
  }
156
+ // ...
129
157
  },
130
158
  {
131
159
  files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
@@ -133,22 +161,28 @@ export default ts.config(
133
161
  languageOptions: {
134
162
  parserOptions: {
135
163
  projectService: true,
136
- extraFileExtensions: ['.svelte'], // Add support for additional file extensions, such as .svelte
137
- parser: ts.parser,
164
+ // Enable typescript parsing for `.svelte` files.
165
+ extraFileExtensions: ['.svelte'],
166
+
138
167
  // Specify a parser for each language, if needed:
139
168
  // parser: {
140
169
  // ts: ts.parser,
141
- // js: espree, // Use espree for .js files (add: import espree from 'espree')
142
170
  // typescript: ts.parser
171
+ // js: espree, // add `import espree from 'espree'`
143
172
  // },
173
+ parser: ts.parser,
144
174
 
145
- // We recommend importing and specifying svelte.config.js.
146
- // By doing so, some rules in eslint-plugin-svelte will automatically read the configuration and adjust their behavior accordingly.
147
- // While certain Svelte settings may be statically loaded from svelte.config.js even if you don’t specify it,
148
- // explicitly specifying it ensures better compatibility and functionality.
175
+ // explicitly importing allows for better compatibilty and functionality with rules and other tooling that depend on the config file.
149
176
  //
150
- // If non-serializable properties are included, running ESLint with the --cache flag will fail.
151
- // In that case, please remove the non-serializable properties. (e.g. `svelteConfig: { ...svelteConfig, kit: { ...svelteConfig.kit, typescript: undefined }}`)
177
+ // Note: `eslint --cache` will fail with non-serializable properties.
178
+ // In those cases, please remove the non-serializable properties.
179
+ // svelteConfig: {
180
+ // ...svelteConfig,
181
+ // kit: {
182
+ // ...svelteConfig.kit,
183
+ // typescript: undefined
184
+ // }
185
+ // }
152
186
  svelteConfig
153
187
  }
154
188
  }
@@ -171,10 +205,10 @@ export default ts.config(
171
205
 
172
206
  This plugin provides the following configurations:
173
207
 
174
- - **`eslintPluginSvelte.configs.base`** ... Enables correct Svelte parsing.
175
- - **`eslintPluginSvelte.configs.recommended`** ... Includes `base` configuration, plus rules to prevent errors or unintended behavior.
176
- - **`eslintPluginSvelte.configs.prettier`** ... Disables rules that may conflict with [Prettier](https://prettier.io/). You still need to configure Prettier to work with Svelte manually, for example, by using [prettier-plugin-svelte](https://github.com/sveltejs/prettier-plugin-svelte).
177
- - **`eslintPluginSvelte.configs.all`** ... Includes all available rules. **Note:** This configuration is not recommended for production use, as it changes with every minor and major version of `eslint-plugin-svelte`. Use at your own risk.
208
+ - **`svelte.configs.base`** - **Required** for Svelte parsing. Does not include any rules. Ideal for building a custom configurations.
209
+ - **`svelte.configs.recommended`** - Extends `base` and includes rules for best practices.
210
+ - **`svelte.configs.prettier`** - Extends `base` and disables rules that may conflict with [Prettier](https://prettier.io/). Prettier still needs to be configured to work with Svelte, for example, by using [prettier-plugin-svelte](https://github.com/sveltejs/prettier-plugin-svelte).
211
+ - **`svelte.configs.all`** - **Not Recommended** - Extends `base` and includes all rules. Subject to change with every major and minor release. Use at your own risk.
178
212
 
179
213
  For more details, see [the rule list](https://sveltejs.github.io/eslint-plugin-svelte/rules/) to explore the rules provided by this plugin.
180
214
 
@@ -184,7 +218,7 @@ You can customize the behavior of this plugin using specific settings.
184
218
 
185
219
  ```js
186
220
  // eslint.config.js
187
- export default [
221
+ export default defineConfig([
188
222
  // ...
189
223
  {
190
224
  settings: {
@@ -195,9 +229,10 @@ export default [
195
229
  '@typescript-eslint/no-unsafe-assignment',
196
230
  '@typescript-eslint/no-unsafe-member-access'
197
231
  ],
232
+
198
233
  // Specifies options for Svelte compilation.
199
234
  // This affects rules that rely on Svelte compilation,
200
- // such as svelte/valid-compile and svelte/no-unused-svelte-ignore.
235
+ // such as `svelte/valid-compile` and `svelte/no-unused-svelte-ignore`.
201
236
  // Note that this setting does not impact ESLint’s custom parser.
202
237
  compileOptions: {
203
238
  // Specifies options related to PostCSS. You can disable the PostCSS processing by setting it to false.
@@ -206,6 +241,7 @@ export default [
206
241
  configFilePath: './path/to/my/postcss.config.js'
207
242
  }
208
243
  },
244
+
209
245
  // Even if settings.svelte.kit is not specified, the rules will attempt to load information from svelte.config.js.
210
246
  // However, if the default behavior does not work as expected, you should specify settings.svelte.kit explicitly.
211
247
  // If you are using SvelteKit with a non-default configuration, you need to set the following options.
@@ -220,7 +256,7 @@ export default [
220
256
  }
221
257
  }
222
258
  // ...
223
- ];
259
+ ]);
224
260
  ```
225
261
 
226
262
  ## Editor Integrations
@@ -229,18 +265,12 @@ export default [
229
265
  Install [dbaeumer.vscode-eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint).\
230
266
  Configure `.svelte` files in `.vscode/settings.json`:
231
267
 
232
- ```json
233
- {
234
- "eslint.validate": ["javascript", "javascriptreact", "svelte"]
235
- }
236
- ```
237
-
238
268
  <!--USAGE_GUIDE_END-->
239
269
  <!--USAGE_SECTION_END-->
240
270
 
241
271
  ## Migration Guide
242
272
 
243
- If you’re migrating from `eslint-plugin-svelte` v1 or [`@ota-meshi/eslint-plugin-svelte`](https://www.npmjs.com/package/@ota-meshi/eslint-plugin-svelte), see the [migration guide](https://sveltejs.github.io/eslint-plugin-svelte/migration/).
273
+ If you’re migrating from `eslint-plugin-svelte@1` or [`@ota-meshi/eslint-plugin-svelte`](https://www.npmjs.com/package/@ota-meshi/eslint-plugin-svelte), see the [migration guide](https://sveltejs.github.io/eslint-plugin-svelte/migration/).
244
274
 
245
275
  ## Versioning Policy
246
276
 
@@ -341,6 +371,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
341
371
  | [svelte/html-self-closing](https://sveltejs.github.io/eslint-plugin-svelte/rules/html-self-closing/) | enforce self-closing style | :wrench: |
342
372
  | [svelte/indent](https://sveltejs.github.io/eslint-plugin-svelte/rules/indent/) | enforce consistent indentation | :wrench: |
343
373
  | [svelte/max-attributes-per-line](https://sveltejs.github.io/eslint-plugin-svelte/rules/max-attributes-per-line/) | enforce the maximum number of attributes per line | :wrench: |
374
+ | [svelte/max-lines-per-block](https://sveltejs.github.io/eslint-plugin-svelte/rules/max-lines-per-block/) | enforce maximum number of lines in svelte component blocks | |
344
375
  | [svelte/mustache-spacing](https://sveltejs.github.io/eslint-plugin-svelte/rules/mustache-spacing/) | enforce unified spacing in mustache | :wrench: |
345
376
  | [svelte/no-extra-reactive-curlies](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-extra-reactive-curlies/) | disallow wrapping single reactive statements in curly braces | :bulb: |
346
377
  | [svelte/no-restricted-html-elements](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-restricted-html-elements/) | disallow specific HTML elements | |
@@ -369,7 +400,7 @@ These rules relate to SvelteKit and its best Practices.
369
400
  | Rule ID | Description | |
370
401
  |:--------|:------------|:---|
371
402
  | [svelte/no-export-load-in-svelte-module-in-kit-pages](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-export-load-in-svelte-module-in-kit-pages/) | disallow exporting load functions in `*.svelte` module in SvelteKit page components. | :star: |
372
- | [svelte/no-navigation-without-resolve](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-navigation-without-resolve/) | disallow using navigation (links, goto, pushState, replaceState) without a resolve() | :star: |
403
+ | [svelte/no-navigation-without-resolve](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-navigation-without-resolve/) | disallow internal navigation (links, `goto()`, `pushState()`, `replaceState()`) without a `resolve()` | :star: |
373
404
  | [svelte/valid-prop-names-in-kit-pages](https://sveltejs.github.io/eslint-plugin-svelte/rules/valid-prop-names-in-kit-pages/) | disallow props other than data or errors in SvelteKit page components. | :star: |
374
405
 
375
406
  ## Experimental
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.15.2";
17
+ version: "3.16.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.15.2";
2
+ export declare const version: "3.16.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.15.2';
5
+ export const version = '3.16.0';
@@ -85,6 +85,11 @@ export interface RuleOptions {
85
85
  * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/max-attributes-per-line/
86
86
  */
87
87
  'svelte/max-attributes-per-line'?: Linter.RuleEntry<SvelteMaxAttributesPerLine>;
88
+ /**
89
+ * enforce maximum number of lines in svelte component blocks
90
+ * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/max-lines-per-block/
91
+ */
92
+ 'svelte/max-lines-per-block'?: Linter.RuleEntry<SvelteMaxLinesPerBlock>;
88
93
  /**
89
94
  * enforce unified spacing in mustache
90
95
  * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/mustache-spacing/
@@ -184,7 +189,7 @@ export interface RuleOptions {
184
189
  */
185
190
  'svelte/no-navigation-without-base'?: Linter.RuleEntry<SvelteNoNavigationWithoutBase>;
186
191
  /**
187
- * disallow using navigation (links, goto, pushState, replaceState) without a resolve()
192
+ * disallow internal navigation (links, `goto()`, `pushState()`, `replaceState()`) without a `resolve()`
188
193
  * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-navigation-without-resolve/
189
194
  */
190
195
  'svelte/no-navigation-without-resolve'?: Linter.RuleEntry<SvelteNoNavigationWithoutResolve>;
@@ -497,6 +502,15 @@ type SvelteMaxAttributesPerLine = [] | [
497
502
  singleline?: number;
498
503
  }
499
504
  ];
505
+ type SvelteMaxLinesPerBlock = [] | [
506
+ {
507
+ script?: number;
508
+ template?: number;
509
+ style?: number;
510
+ skipBlankLines?: boolean;
511
+ skipComments?: boolean;
512
+ }
513
+ ];
500
514
  type SvelteMustacheSpacing = [] | [
501
515
  {
502
516
  textExpressions?: ("never" | "always");
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../types.js").RuleModule;
2
+ export default _default;
@@ -0,0 +1,232 @@
1
+ import { Input } from 'postcss';
2
+ import tokenize from 'postcss/lib/tokenize';
3
+ import { createRule } from '../utils/index.js';
4
+ /** Check if a comment occupies the entire source line (matching ESLint core max-lines behavior). */
5
+ function isFullLineComment(line, lineNumber, loc) {
6
+ return ((loc.start.line < lineNumber || !line.slice(0, loc.start.column).trim()) &&
7
+ (loc.end.line > lineNumber || !line.slice(loc.end.column).trim()));
8
+ }
9
+ /** Collect line numbers where AST comments occupy the full line. */
10
+ function collectAstCommentLines(comments, sourceLines, startLine, endLine) {
11
+ const lines = new Set();
12
+ for (const comment of comments) {
13
+ if (comment.loc.end.line < startLine || comment.loc.start.line > endLine)
14
+ continue;
15
+ for (let i = Math.max(comment.loc.start.line, startLine); i <= Math.min(comment.loc.end.line, endLine); i++) {
16
+ if (isFullLineComment(sourceLines[i - 1], i, comment.loc)) {
17
+ lines.add(i);
18
+ }
19
+ }
20
+ }
21
+ return lines;
22
+ }
23
+ /** Collect line numbers where CSS comments occupy the full line, using postcss tokenizer. */
24
+ function collectCssCommentLines(sourceLines, startLine, endLine) {
25
+ const result = new Set();
26
+ const cssText = sourceLines.slice(startLine - 1, endLine).join('\n');
27
+ if (!cssText.trim())
28
+ return result;
29
+ try {
30
+ const input = new Input(cssText);
31
+ const tk = tokenize(input);
32
+ const commentLines = new Set();
33
+ const codeLines = new Set();
34
+ let token;
35
+ while ((token = tk.nextToken())) {
36
+ if (token[2] == null)
37
+ continue;
38
+ const startPos = input.fromOffset(token[2]);
39
+ if (!startPos)
40
+ continue;
41
+ const tokenLine = startPos.line + startLine - 1;
42
+ if (token[0] === 'comment') {
43
+ const endPos = token[3] != null ? input.fromOffset(token[3]) : null;
44
+ const commentEndLine = endPos ? endPos.line + startLine - 1 : tokenLine;
45
+ for (let i = tokenLine; i <= commentEndLine; i++) {
46
+ commentLines.add(i);
47
+ }
48
+ }
49
+ else {
50
+ codeLines.add(tokenLine);
51
+ }
52
+ }
53
+ for (const line of commentLines) {
54
+ if (!codeLines.has(line))
55
+ result.add(line);
56
+ }
57
+ }
58
+ catch {
59
+ // Malformed CSS — don't skip any lines
60
+ }
61
+ return result;
62
+ }
63
+ /** Count inner content lines, skipping blanks and/or comment lines. */
64
+ function countLines(sourceLines, startLine, endLine, skipBlankLines, commentLines) {
65
+ if (endLine - startLine <= 1)
66
+ return 0;
67
+ let count = 0;
68
+ for (let i = startLine + 1; i < endLine; i++) {
69
+ if (skipBlankLines && sourceLines[i - 1].trim().length === 0)
70
+ continue;
71
+ if (commentLines.has(i))
72
+ continue;
73
+ count++;
74
+ }
75
+ return count;
76
+ }
77
+ function isSvelteOptions(node) {
78
+ return node.name.type === 'SvelteName' && node.name.name === 'svelte:options';
79
+ }
80
+ export default createRule('max-lines-per-block', {
81
+ meta: {
82
+ docs: {
83
+ description: 'enforce maximum number of lines in svelte component blocks',
84
+ category: 'Stylistic Issues',
85
+ recommended: false,
86
+ conflictWithPrettier: false
87
+ },
88
+ schema: [
89
+ {
90
+ type: 'object',
91
+ properties: {
92
+ script: {
93
+ type: 'integer',
94
+ minimum: 1
95
+ },
96
+ template: {
97
+ type: 'integer',
98
+ minimum: 1
99
+ },
100
+ style: {
101
+ type: 'integer',
102
+ minimum: 1
103
+ },
104
+ skipBlankLines: {
105
+ type: 'boolean'
106
+ },
107
+ skipComments: {
108
+ type: 'boolean'
109
+ }
110
+ },
111
+ additionalProperties: false
112
+ }
113
+ ],
114
+ messages: {
115
+ tooManyLines: '{{block}} block has too many lines ({{lineCount}}). Maximum allowed is {{max}}.'
116
+ },
117
+ type: 'suggestion'
118
+ },
119
+ create(context) {
120
+ const options = context.options[0] ?? {};
121
+ const scriptMax = options.script;
122
+ const templateMax = options.template;
123
+ const styleMax = options.style;
124
+ const skipBlankLines = options.skipBlankLines ?? false;
125
+ const skipComments = options.skipComments ?? false;
126
+ const sourceCode = context.sourceCode;
127
+ const htmlCommentNodes = [];
128
+ const emptySet = new Set();
129
+ return {
130
+ SvelteHTMLComment(node) {
131
+ htmlCommentNodes.push(node);
132
+ },
133
+ SvelteScriptElement(node) {
134
+ if (scriptMax == null)
135
+ return;
136
+ const commentLines = skipComments
137
+ ? collectAstCommentLines(sourceCode.getAllComments(), sourceCode.lines, node.loc.start.line + 1, node.loc.end.line - 1)
138
+ : emptySet;
139
+ const lineCount = countLines(sourceCode.lines, node.loc.start.line, node.loc.end.line, skipBlankLines, commentLines);
140
+ if (lineCount > scriptMax) {
141
+ context.report({
142
+ node,
143
+ messageId: 'tooManyLines',
144
+ data: {
145
+ block: '<script>',
146
+ lineCount: String(lineCount),
147
+ max: String(scriptMax)
148
+ }
149
+ });
150
+ }
151
+ },
152
+ SvelteStyleElement(node) {
153
+ if (styleMax == null)
154
+ return;
155
+ const commentLines = skipComments
156
+ ? collectCssCommentLines(sourceCode.lines, node.loc.start.line + 1, node.loc.end.line - 1)
157
+ : emptySet;
158
+ const lineCount = countLines(sourceCode.lines, node.loc.start.line, node.loc.end.line, skipBlankLines, commentLines);
159
+ if (lineCount > styleMax) {
160
+ context.report({
161
+ node,
162
+ messageId: 'tooManyLines',
163
+ data: {
164
+ block: '<style>',
165
+ lineCount: String(lineCount),
166
+ max: String(styleMax)
167
+ }
168
+ });
169
+ }
170
+ },
171
+ 'Program:exit'(program) {
172
+ if (templateMax == null)
173
+ return;
174
+ const totalLines = sourceCode.lines.length;
175
+ // Exclude lines occupied by <script>, <style>, and <svelte:options>
176
+ const excludedLines = new Set();
177
+ for (const child of program.body) {
178
+ if (child.type === 'SvelteScriptElement' ||
179
+ child.type === 'SvelteStyleElement' ||
180
+ (child.type === 'SvelteElement' && isSvelteOptions(child))) {
181
+ for (let i = child.loc.start.line; i <= child.loc.end.line; i++) {
182
+ excludedLines.add(i);
183
+ }
184
+ }
185
+ }
186
+ // Collect full-line comment lines for template region
187
+ const commentLines = new Set();
188
+ if (skipComments) {
189
+ const allComments = [
190
+ ...htmlCommentNodes,
191
+ ...sourceCode.getAllComments()
192
+ ];
193
+ for (const comment of allComments) {
194
+ for (let i = comment.loc.start.line; i <= comment.loc.end.line; i++) {
195
+ if (excludedLines.has(i))
196
+ continue;
197
+ if (isFullLineComment(sourceCode.lines[i - 1], i, comment.loc)) {
198
+ commentLines.add(i);
199
+ }
200
+ }
201
+ }
202
+ }
203
+ let templateLineCount = 0;
204
+ for (let i = 1; i <= totalLines; i++) {
205
+ if (excludedLines.has(i))
206
+ continue;
207
+ if (skipBlankLines && sourceCode.lines[i - 1].trim().length === 0)
208
+ continue;
209
+ if (commentLines.has(i))
210
+ continue;
211
+ templateLineCount++;
212
+ }
213
+ if (templateLineCount > templateMax) {
214
+ const firstTemplateNode = program.body.find((child) => child.type !== 'SvelteScriptElement' &&
215
+ child.type !== 'SvelteStyleElement' &&
216
+ !(child.type === 'SvelteElement' && isSvelteOptions(child)));
217
+ if (firstTemplateNode) {
218
+ context.report({
219
+ node: firstTemplateNode,
220
+ messageId: 'tooManyLines',
221
+ data: {
222
+ block: 'template',
223
+ lineCount: String(templateLineCount),
224
+ max: String(templateMax)
225
+ }
226
+ });
227
+ }
228
+ }
229
+ }
230
+ };
231
+ }
232
+ });
@@ -5,7 +5,7 @@ import { findVariable } from '../utils/ast-utils.js';
5
5
  export default createRule('no-navigation-without-resolve', {
6
6
  meta: {
7
7
  docs: {
8
- description: 'disallow using navigation (links, goto, pushState, replaceState) without a resolve()',
8
+ description: 'disallow internal navigation (links, `goto()`, `pushState()`, `replaceState()`) without a `resolve()`',
9
9
  category: 'SvelteKit',
10
10
  recommended: true
11
11
  },
@@ -1,4 +1,6 @@
1
1
  const SVELTE_IGNORE_PATTERN = /^\s*svelte-ignore\s+/;
2
+ /** Matches parenthetical notes in svelte-ignore comments, e.g. "(because of reasons)" */
3
+ const PARENTHETICAL_NOTE_PATTERN = /\([^)]*\)/gu;
2
4
  /**
3
5
  * Map of legacy code -> new code
4
6
  * See https://github.com/sveltejs/svelte/blob/c9202a889612df3c2fcb369096a5573668be99d6/packages/svelte/src/compiler/utils/extract_svelte_ignore.js#L6
@@ -70,11 +72,14 @@ export function getSvelteIgnoreItems(context) {
70
72
  function extractSvelteIgnore(startIndex, token, codeList, ignoreStart) {
71
73
  const start = startIndex + ignoreStart;
72
74
  const results = [];
75
+ // Replace parenthetical notes (e.g., "(because of reasons)") with spaces of the same length
76
+ // to preserve character positions while preventing note words from being treated as rule names.
77
+ const processedCodeList = codeList.replace(PARENTHETICAL_NOTE_PATTERN, (match) => ' '.repeat(match.length));
73
78
  const separatorPattern = /\s*[\s,]\s*/g;
74
- const separators = codeList.matchAll(separatorPattern);
79
+ const separators = processedCodeList.matchAll(separatorPattern);
75
80
  let lastSeparatorEnd = 0;
76
81
  for (const separator of separators) {
77
- const code = codeList.slice(lastSeparatorEnd, separator.index);
82
+ const code = processedCodeList.slice(lastSeparatorEnd, separator.index);
78
83
  if (code) {
79
84
  results.push({
80
85
  code,
@@ -86,7 +91,7 @@ function extractSvelteIgnore(startIndex, token, codeList, ignoreStart) {
86
91
  lastSeparatorEnd = separator.index + separator[0].length;
87
92
  }
88
93
  if (results.length === 0) {
89
- const code = codeList;
94
+ const code = processedCodeList;
90
95
  results.push({
91
96
  code,
92
97
  codeForV5: V5_REPLACEMENTS[code] || code.replace(/-/gu, '_'),
@@ -98,5 +103,5 @@ function extractSvelteIgnore(startIndex, token, codeList, ignoreStart) {
98
103
  }
99
104
  /** Checks whether given comment has missing code svelte-ignore */
100
105
  function hasMissingCodeIgnore(codeList) {
101
- return !codeList.trim();
106
+ return !codeList.replace(PARENTHETICAL_NOTE_PATTERN, '').trim();
102
107
  }
@@ -14,6 +14,7 @@ import htmlSelfClosing from '../rules/html-self-closing.js';
14
14
  import indent from '../rules/indent.js';
15
15
  import infiniteReactiveLoop from '../rules/infinite-reactive-loop.js';
16
16
  import maxAttributesPerLine from '../rules/max-attributes-per-line.js';
17
+ import maxLinesPerBlock from '../rules/max-lines-per-block.js';
17
18
  import mustacheSpacing from '../rules/mustache-spacing.js';
18
19
  import noAddEventListener from '../rules/no-add-event-listener.js';
19
20
  import noAtDebugTags from '../rules/no-at-debug-tags.js';
@@ -94,6 +95,7 @@ export const rules = [
94
95
  indent,
95
96
  infiniteReactiveLoop,
96
97
  maxAttributesPerLine,
98
+ maxLinesPerBlock,
97
99
  mustacheSpacing,
98
100
  noAddEventListener,
99
101
  noAtDebugTags,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-svelte",
3
- "version": "3.15.2",
3
+ "version": "3.16.0",
4
4
  "description": "ESLint plugin for Svelte using AST",
5
5
  "repository": {
6
6
  "type": "git",