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 +77 -46
- package/lib/main.d.ts +1 -1
- package/lib/meta.d.ts +1 -1
- package/lib/meta.js +1 -1
- package/lib/rule-types.d.ts +15 -1
- package/lib/rules/max-lines-per-block.d.ts +2 -0
- package/lib/rules/max-lines-per-block.js +232 -0
- package/lib/rules/no-navigation-without-resolve.js +1 -1
- package/lib/shared/svelte-compile-warns/ignore-comment.js +9 -4
- package/lib/utils/rules.js +2 -0
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
69
|
-
|
|
88
|
+
export default defineConfig([
|
|
89
|
+
// ...
|
|
70
90
|
js.configs.recommended,
|
|
71
|
-
|
|
91
|
+
svelte.configs.recommended,
|
|
72
92
|
{
|
|
73
93
|
languageOptions: {
|
|
74
94
|
globals: {
|
|
75
95
|
...globals.browser,
|
|
76
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
90
|
-
// In
|
|
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
|
|
113
|
-
import
|
|
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
|
|
142
|
+
import svelte from 'eslint-plugin-svelte';
|
|
117
143
|
|
|
118
|
-
export default
|
|
144
|
+
export default defineConfig(
|
|
119
145
|
js.configs.recommended,
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
137
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
151
|
-
// In
|
|
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
|
-
- **`
|
|
175
|
-
- **`
|
|
176
|
-
- **`
|
|
177
|
-
- **`
|
|
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`
|
|
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
|
|
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.
|
|
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.
|
|
2
|
+
export declare const version: "3.16.0";
|
package/lib/meta.js
CHANGED
package/lib/rule-types.d.ts
CHANGED
|
@@ -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
|
|
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,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
|
|
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 =
|
|
79
|
+
const separators = processedCodeList.matchAll(separatorPattern);
|
|
75
80
|
let lastSeparatorEnd = 0;
|
|
76
81
|
for (const separator of separators) {
|
|
77
|
-
const code =
|
|
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 =
|
|
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
|
}
|
package/lib/utils/rules.js
CHANGED
|
@@ -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,
|