eslint-plugin-svelte 2.2.0 → 2.4.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
@@ -35,7 +35,7 @@ The [svelte-eslint-parser] and the `eslint-plugin-svelte` can not be used with t
35
35
 
36
36
  ## Migration Guide
37
37
 
38
- To migrate from `eslint-plugin-svelte` v1, or `@ota-meshi/eslint-plugin-svelte`, please refer to the [migration guide](https://ota-meshi.github.io/eslint-plugin-svelte/migration/).
38
+ To migrate from `eslint-plugin-svelte` v1, or [`@ota-meshi/eslint-plugin-svelte`](https://www.npmjs.com/package/@ota-meshi/eslint-plugin-svelte), please refer to the [migration guide](https://ota-meshi.github.io/eslint-plugin-svelte/migration/).
39
39
 
40
40
  ## :book: Documentation
41
41
 
@@ -244,8 +244,9 @@ Example **.vscode/settings.json**:
244
244
 
245
245
  <!--RULES_SECTION_START-->
246
246
 
247
- The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) automatically fixes problems reported by rules which have a wrench :wrench: below.
248
- The rules with the following star :star: are included in the configs.
247
+ :wrench: Indicates that the rule is fixable, and using `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the reported problems.
248
+ :bulb: Indicates that some problems reported by the rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
249
+ :star: Indicates that the rule is included in the `plugin:svelte/recommended` config.
249
250
 
250
251
  <!--RULES_TABLE_START-->
251
252
 
@@ -281,6 +282,7 @@ These rules relate to better ways of doing things to help you avoid problems:
281
282
  |:--------|:------------|:---|
282
283
  | [svelte/button-has-type](https://ota-meshi.github.io/eslint-plugin-svelte/rules/button-has-type/) | disallow usage of button without an explicit type attribute | |
283
284
  | [svelte/no-at-debug-tags](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-at-debug-tags/) | disallow the use of `{@debug}` | :star: |
285
+ | [svelte/no-reactive-literals](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-reactive-literals/) | Don't assign literal values in reactive statements | :bulb: |
284
286
  | [svelte/no-unused-svelte-ignore](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-unused-svelte-ignore/) | disallow unused svelte-ignore comments | :star: |
285
287
  | [svelte/no-useless-mustaches](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-useless-mustaches/) | disallow unnecessary mustache interpolations | :wrench: |
286
288
  | [svelte/require-optimized-style-attribute](https://ota-meshi.github.io/eslint-plugin-svelte/rules/require-optimized-style-attribute/) | require style attributes that can be optimized | |
@@ -292,14 +294,18 @@ These rules relate to style guidelines, and are therefore quite subjective:
292
294
  | Rule ID | Description | |
293
295
  |:--------|:------------|:---|
294
296
  | [svelte/first-attribute-linebreak](https://ota-meshi.github.io/eslint-plugin-svelte/rules/first-attribute-linebreak/) | enforce the location of first attribute | :wrench: |
297
+ | [svelte/html-closing-bracket-spacing](https://ota-meshi.github.io/eslint-plugin-svelte/rules/html-closing-bracket-spacing/) | require or disallow a space before tag's closing brackets | :wrench: |
295
298
  | [svelte/html-quotes](https://ota-meshi.github.io/eslint-plugin-svelte/rules/html-quotes/) | enforce quotes style of HTML attributes | :wrench: |
296
299
  | [svelte/indent](https://ota-meshi.github.io/eslint-plugin-svelte/rules/indent/) | enforce consistent indentation | :wrench: |
297
300
  | [svelte/max-attributes-per-line](https://ota-meshi.github.io/eslint-plugin-svelte/rules/max-attributes-per-line/) | enforce the maximum number of attributes per line | :wrench: |
298
301
  | [svelte/mustache-spacing](https://ota-meshi.github.io/eslint-plugin-svelte/rules/mustache-spacing/) | enforce unified spacing in mustache | :wrench: |
302
+ | [svelte/no-extra-reactive-curlies](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-extra-reactive-curlies/) | disallow wrapping single reactive statements in curly braces | :bulb: |
303
+ | [svelte/no-spaces-around-equal-signs-in-attribute](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-spaces-around-equal-signs-in-attribute/) | disallow spaces around equal signs in attribute | :wrench: |
299
304
  | [svelte/prefer-class-directive](https://ota-meshi.github.io/eslint-plugin-svelte/rules/prefer-class-directive/) | require class directives instead of ternary expressions | :wrench: |
300
305
  | [svelte/prefer-style-directive](https://ota-meshi.github.io/eslint-plugin-svelte/rules/prefer-style-directive/) | require style directives instead of style attribute | :wrench: |
301
306
  | [svelte/shorthand-attribute](https://ota-meshi.github.io/eslint-plugin-svelte/rules/shorthand-attribute/) | enforce use of shorthand syntax in attribute | :wrench: |
302
307
  | [svelte/shorthand-directive](https://ota-meshi.github.io/eslint-plugin-svelte/rules/shorthand-directive/) | enforce use of shorthand syntax in directives | :wrench: |
308
+ | [svelte/sort-attributes](https://ota-meshi.github.io/eslint-plugin-svelte/rules/sort-attributes/) | enforce order of attributes | :wrench: |
303
309
  | [svelte/spaced-html-comment](https://ota-meshi.github.io/eslint-plugin-svelte/rules/spaced-html-comment/) | enforce consistent spacing after the `<!--` and before the `-->` in a HTML comment | :wrench: |
304
310
 
305
311
  ## Extension Rules
@@ -334,7 +340,9 @@ Please use GitHub's Issues/PRs.
334
340
 
335
341
  - `yarn test` runs tests.
336
342
  - `yarn cover` runs tests and measures coverage.
343
+ - `yarn new [new-rule-name]` generate the files needed to implement the new rule.
337
344
  - `yarn update` runs in order to update readme and recommended configuration.
345
+ - `yarn docs:watch` launch the document site in development mode.
338
346
 
339
347
  ### Test the Rule
340
348
 
@@ -2,10 +2,12 @@ declare const _default: {
2
2
  extends: string[];
3
3
  rules: {
4
4
  "svelte/first-attribute-linebreak": string;
5
+ "svelte/html-closing-bracket-spacing": string;
5
6
  "svelte/html-quotes": string;
6
7
  "svelte/indent": string;
7
8
  "svelte/max-attributes-per-line": string;
8
9
  "svelte/mustache-spacing": string;
10
+ "svelte/no-spaces-around-equal-signs-in-attribute": string;
9
11
  "svelte/shorthand-attribute": string;
10
12
  "svelte/shorthand-directive": string;
11
13
  };
@@ -9,10 +9,12 @@ module.exports = {
9
9
  extends: [baseExtend],
10
10
  rules: {
11
11
  "svelte/first-attribute-linebreak": "off",
12
+ "svelte/html-closing-bracket-spacing": "off",
12
13
  "svelte/html-quotes": "off",
13
14
  "svelte/indent": "off",
14
15
  "svelte/max-attributes-per-line": "off",
15
16
  "svelte/mustache-spacing": "off",
17
+ "svelte/no-spaces-around-equal-signs-in-attribute": "off",
16
18
  "svelte/shorthand-attribute": "off",
17
19
  "svelte/shorthand-directive": "off",
18
20
  },
package/lib/index.d.ts CHANGED
@@ -37,10 +37,12 @@ declare const _default: {
37
37
  extends: string[];
38
38
  rules: {
39
39
  "svelte/first-attribute-linebreak": string;
40
+ "svelte/html-closing-bracket-spacing": string;
40
41
  "svelte/html-quotes": string;
41
42
  "svelte/indent": string;
42
43
  "svelte/max-attributes-per-line": string;
43
44
  "svelte/mustache-spacing": string;
45
+ "svelte/no-spaces-around-equal-signs-in-attribute": string;
44
46
  "svelte/shorthand-attribute": string;
45
47
  "svelte/shorthand-directive": string;
46
48
  };
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../types").RuleModule;
2
+ export default _default;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("../utils");
4
+ exports.default = (0, utils_1.createRule)("html-closing-bracket-spacing", {
5
+ meta: {
6
+ docs: {
7
+ description: "require or disallow a space before tag's closing brackets",
8
+ category: "Stylistic Issues",
9
+ conflictWithPrettier: true,
10
+ recommended: false,
11
+ },
12
+ schema: [
13
+ {
14
+ type: "object",
15
+ properties: {
16
+ startTag: {
17
+ enum: ["always", "never", "ignore"],
18
+ },
19
+ endTag: {
20
+ enum: ["always", "never", "ignore"],
21
+ },
22
+ selfClosingTag: {
23
+ enum: ["always", "never", "ignore"],
24
+ },
25
+ },
26
+ additionalProperties: false,
27
+ },
28
+ ],
29
+ messages: {
30
+ expectedSpace: "Expected space before '>', but not found.",
31
+ unexpectedSpace: "Expected no space before '>', but found.",
32
+ },
33
+ fixable: "whitespace",
34
+ type: "layout",
35
+ },
36
+ create(ctx) {
37
+ const options = {
38
+ startTag: "never",
39
+ endTag: "never",
40
+ selfClosingTag: "always",
41
+ ...ctx.options[0],
42
+ };
43
+ const src = ctx.getSourceCode();
44
+ function containsNewline(string) {
45
+ return string.includes("\n");
46
+ }
47
+ function report(node, shouldHave) {
48
+ const tagSrc = src.getText(node);
49
+ const match = /(\s*)\/?>$/.exec(tagSrc);
50
+ const end = node.range[1];
51
+ const start = node.range[1] - match[0].length;
52
+ const loc = {
53
+ start: src.getLocFromIndex(start),
54
+ end: src.getLocFromIndex(end),
55
+ };
56
+ ctx.report({
57
+ loc,
58
+ messageId: shouldHave ? "expectedSpace" : "unexpectedSpace",
59
+ *fix(fixer) {
60
+ if (shouldHave) {
61
+ yield fixer.insertTextBeforeRange([start, end], " ");
62
+ }
63
+ else {
64
+ const spaces = match[1];
65
+ yield fixer.removeRange([start, start + spaces.length]);
66
+ }
67
+ },
68
+ });
69
+ }
70
+ return {
71
+ "SvelteStartTag, SvelteEndTag"(node) {
72
+ const tagType = node.type === "SvelteEndTag"
73
+ ? "endTag"
74
+ : node.selfClosing
75
+ ? "selfClosingTag"
76
+ : "startTag";
77
+ if (options[tagType] === "ignore")
78
+ return;
79
+ const tagSrc = src.getText(node);
80
+ const match = /(\s*)\/?>$/.exec(tagSrc);
81
+ if (containsNewline(match[1]))
82
+ return;
83
+ if (options[tagType] === "always" && !match[1]) {
84
+ report(node, true);
85
+ }
86
+ else if (options[tagType] === "never" && match[1]) {
87
+ report(node, false);
88
+ }
89
+ },
90
+ };
91
+ },
92
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../types").RuleModule;
2
+ export default _default;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("../utils");
4
+ exports.default = (0, utils_1.createRule)("no-extra-reactive-curlies", {
5
+ meta: {
6
+ docs: {
7
+ description: "disallow wrapping single reactive statements in curly braces",
8
+ category: "Stylistic Issues",
9
+ recommended: false,
10
+ conflictWithPrettier: false,
11
+ },
12
+ hasSuggestions: true,
13
+ schema: [],
14
+ messages: {
15
+ extraCurlies: `Do not wrap reactive statements in curly braces unless necessary.`,
16
+ removeExtraCurlies: `Remove the unnecessary curly braces.`,
17
+ },
18
+ type: "suggestion",
19
+ },
20
+ create(context) {
21
+ return {
22
+ [`SvelteReactiveStatement > BlockStatement[body.length=1]`]: (node) => {
23
+ const source = context.getSourceCode();
24
+ return context.report({
25
+ node,
26
+ loc: node.loc,
27
+ messageId: "extraCurlies",
28
+ suggest: [
29
+ {
30
+ messageId: "removeExtraCurlies",
31
+ fix(fixer) {
32
+ const tokens = source.getTokens(node, { includeComments: true });
33
+ return [
34
+ fixer.removeRange([tokens[0].range[0], tokens[1].range[0]]),
35
+ fixer.removeRange([
36
+ tokens[tokens.length - 2].range[1],
37
+ tokens[tokens.length - 1].range[1],
38
+ ]),
39
+ ];
40
+ },
41
+ },
42
+ ],
43
+ });
44
+ },
45
+ };
46
+ },
47
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../types").RuleModule;
2
+ export default _default;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("../utils");
4
+ exports.default = (0, utils_1.createRule)("no-reactive-literals", {
5
+ meta: {
6
+ docs: {
7
+ description: "Don't assign literal values in reactive statements",
8
+ category: "Best Practices",
9
+ recommended: false,
10
+ },
11
+ hasSuggestions: true,
12
+ schema: [],
13
+ messages: {
14
+ noReactiveLiterals: `Do not assign literal values inside reactive statements unless absolutely necessary.`,
15
+ fixReactiveLiteral: `Move the literal out of the reactive statement into an assignment`,
16
+ },
17
+ type: "suggestion",
18
+ },
19
+ create(context) {
20
+ return {
21
+ [`SvelteReactiveStatement > ExpressionStatement > AssignmentExpression${[
22
+ `[right.type="Literal"]`,
23
+ `[right.type="ArrayExpression"][right.elements.length=0]`,
24
+ `[right.type="ObjectExpression"][right.properties.length=0]`,
25
+ ].join(",")}`](node) {
26
+ const parent = node.parent?.parent;
27
+ if (!parent) {
28
+ return false;
29
+ }
30
+ const source = context.getSourceCode();
31
+ return context.report({
32
+ node: parent,
33
+ loc: parent.loc,
34
+ messageId: "noReactiveLiterals",
35
+ suggest: [
36
+ {
37
+ messageId: "fixReactiveLiteral",
38
+ fix(fixer) {
39
+ return [
40
+ fixer.insertTextBefore(parent, `let ${source.getText(node)}`),
41
+ fixer.remove(parent),
42
+ ];
43
+ },
44
+ },
45
+ ],
46
+ });
47
+ },
48
+ };
49
+ },
50
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../types").RuleModule;
2
+ export default _default;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("../utils");
4
+ exports.default = (0, utils_1.createRule)("no-spaces-around-equal-signs-in-attribute", {
5
+ meta: {
6
+ docs: {
7
+ description: "disallow spaces around equal signs in attribute",
8
+ category: "Stylistic Issues",
9
+ recommended: false,
10
+ conflictWithPrettier: true,
11
+ },
12
+ schema: {},
13
+ fixable: "whitespace",
14
+ messages: {
15
+ noSpaces: "Unexpected spaces found around equal signs.",
16
+ },
17
+ type: "layout",
18
+ },
19
+ create(ctx) {
20
+ const source = ctx.getSourceCode();
21
+ function getAttrEq(node) {
22
+ const keyRange = node.key.range;
23
+ const eqSource = /^[\s=]*/u.exec(source.text.slice(keyRange[1], node.range[1]))[0];
24
+ const valueStart = keyRange[1] + eqSource.length;
25
+ return [eqSource, [keyRange[1], valueStart]];
26
+ }
27
+ function containsWhitespace(string) {
28
+ return /\s/u.test(string);
29
+ }
30
+ return {
31
+ "SvelteAttribute, SvelteDirective, SvelteStyleDirective, SvelteSpecialDirective"(node) {
32
+ const [eqSource, range] = getAttrEq(node);
33
+ if (!containsWhitespace(eqSource))
34
+ return;
35
+ const loc = {
36
+ start: source.getLocFromIndex(range[0]),
37
+ end: source.getLocFromIndex(range[1]),
38
+ };
39
+ ctx.report({
40
+ loc,
41
+ messageId: "noSpaces",
42
+ *fix(fixer) {
43
+ yield fixer.replaceTextRange(range, "=");
44
+ },
45
+ });
46
+ },
47
+ };
48
+ },
49
+ });
@@ -18,6 +18,9 @@ exports.default = (0, utils_1.createRule)("no-unused-svelte-ignore", {
18
18
  type: "suggestion",
19
19
  },
20
20
  create(context) {
21
+ if (!context.parserServices.isSvelte) {
22
+ return {};
23
+ }
21
24
  const sourceCode = context.getSourceCode();
22
25
  const ignoreComments = [];
23
26
  for (const item of (0, ignore_comment_1.getSvelteIgnoreItems)(context)) {
@@ -0,0 +1,2 @@
1
+ declare const _default: import("../types").RuleModule;
2
+ export default _default;
@@ -0,0 +1,260 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("../utils");
4
+ const ast_utils_1 = require("../utils/ast-utils");
5
+ const regexp_1 = require("../utils/regexp");
6
+ const DEFAULT_ORDER = [
7
+ "this",
8
+ "bind:this",
9
+ "id",
10
+ "name",
11
+ "slot",
12
+ { match: "/^--/u", sort: "alphabetical" },
13
+ ["style", "/^style:/u"],
14
+ "class",
15
+ { match: "/^class:/u", sort: "alphabetical" },
16
+ {
17
+ match: ["!/:/u", "!/^(?:this|id|name|style|class)$/u", "!/^--/u"],
18
+ sort: "alphabetical",
19
+ },
20
+ ["/^bind:/u", "!bind:this", "/^on:/u"],
21
+ { match: "/^use:/u", sort: "alphabetical" },
22
+ { match: "/^transition:/u", sort: "alphabetical" },
23
+ { match: "/^in:/u", sort: "alphabetical" },
24
+ { match: "/^out:/u", sort: "alphabetical" },
25
+ { match: "/^animate:/u", sort: "alphabetical" },
26
+ { match: "/^let:/u", sort: "alphabetical" },
27
+ ];
28
+ function parseOption(option) {
29
+ const order = option?.order ?? DEFAULT_ORDER;
30
+ const compiled = order.map(compileOption);
31
+ return {
32
+ ignore: (key) => {
33
+ return !compiled.some((c) => c.match(key));
34
+ },
35
+ compare: (a, b) => {
36
+ for (const c of compiled) {
37
+ const matchA = c.match(a);
38
+ const matchB = c.match(b);
39
+ if (matchA && matchB) {
40
+ if (c.sort === "alphabetical") {
41
+ return a === b ? 0 : a < b ? -1 : 1;
42
+ }
43
+ return 0;
44
+ }
45
+ if (matchA) {
46
+ return -1;
47
+ }
48
+ if (matchB) {
49
+ return 1;
50
+ }
51
+ }
52
+ throw new Error("Illegal state");
53
+ },
54
+ };
55
+ }
56
+ function compileOption(option) {
57
+ const cache = {};
58
+ const compiled = compileOptionWithoutCache(option);
59
+ return {
60
+ match: (str) => {
61
+ if (cache[str] != null)
62
+ return cache[str];
63
+ return (cache[str] = compiled.match(str));
64
+ },
65
+ sort: compiled.sort,
66
+ };
67
+ function compileOptionWithoutCache(option) {
68
+ if (typeof option === "string") {
69
+ const match = compileMatcher([option]);
70
+ return { match, sort: "ignore" };
71
+ }
72
+ if (Array.isArray(option)) {
73
+ const match = compileMatcher(option);
74
+ return { match, sort: "ignore" };
75
+ }
76
+ const { match } = compileOptionWithoutCache(option.match);
77
+ return { match, sort: option.sort || "ignore" };
78
+ }
79
+ }
80
+ function compileMatcher(pattern) {
81
+ const rules = [];
82
+ for (const p of pattern) {
83
+ let negative, patternStr;
84
+ if (p.startsWith("!")) {
85
+ negative = true;
86
+ patternStr = p.substring(1);
87
+ }
88
+ else {
89
+ negative = false;
90
+ patternStr = p;
91
+ }
92
+ const regex = (0, regexp_1.toRegExp)(patternStr);
93
+ rules.push({ negative, match: (str) => regex.test(str) });
94
+ }
95
+ return (str) => {
96
+ let result = Boolean(rules[0]?.negative);
97
+ for (const { negative, match } of rules) {
98
+ if (result === !negative) {
99
+ continue;
100
+ }
101
+ if (match(str)) {
102
+ result = !negative;
103
+ }
104
+ }
105
+ return result;
106
+ };
107
+ }
108
+ exports.default = (0, utils_1.createRule)("sort-attributes", {
109
+ meta: {
110
+ docs: {
111
+ description: "enforce order of attributes",
112
+ category: "Stylistic Issues",
113
+ recommended: false,
114
+ conflictWithPrettier: false,
115
+ },
116
+ schema: [
117
+ {
118
+ type: "object",
119
+ properties: {
120
+ order: {
121
+ type: "array",
122
+ items: {
123
+ anyOf: [
124
+ { type: "string" },
125
+ {
126
+ type: "array",
127
+ items: {
128
+ type: "string",
129
+ },
130
+ uniqueItems: true,
131
+ minItems: 1,
132
+ },
133
+ {
134
+ type: "object",
135
+ properties: {
136
+ match: {
137
+ anyOf: [
138
+ { type: "string" },
139
+ {
140
+ type: "array",
141
+ items: {
142
+ type: "string",
143
+ },
144
+ uniqueItems: true,
145
+ minItems: 1,
146
+ },
147
+ ],
148
+ },
149
+ sort: {
150
+ enum: ["alphabetical", "ignore"],
151
+ },
152
+ },
153
+ required: ["match", "sort"],
154
+ additionalProperties: false,
155
+ },
156
+ ],
157
+ },
158
+ uniqueItems: true,
159
+ additionalItems: false,
160
+ },
161
+ alphabetical: { type: "boolean" },
162
+ },
163
+ additionalProperties: false,
164
+ },
165
+ ],
166
+ messages: {
167
+ shouldBefore: "Attribute '{{currentKey}}' should go before '{{prevKey}}'.",
168
+ },
169
+ type: "layout",
170
+ fixable: "code",
171
+ },
172
+ create(context) {
173
+ const option = parseOption(context.options[0]);
174
+ const cacheKeyText = new Map();
175
+ function getKeyText(node) {
176
+ const k = cacheKeyText.get(node);
177
+ if (k != null)
178
+ return k;
179
+ const result = (0, ast_utils_1.getAttributeKeyText)(node);
180
+ cacheKeyText.set(node, result);
181
+ return result;
182
+ }
183
+ function report(node, previousNode) {
184
+ const currentKey = getKeyText(node);
185
+ const prevKey = getKeyText(previousNode);
186
+ context.report({
187
+ node,
188
+ messageId: "shouldBefore",
189
+ data: {
190
+ currentKey,
191
+ prevKey,
192
+ },
193
+ fix(fixer) {
194
+ const attributes = node.parent.attributes;
195
+ const previousNodes = attributes.slice(attributes.indexOf(previousNode), attributes.indexOf(node));
196
+ const moveNodes = [node, ...previousNodes];
197
+ const sourceCode = context.getSourceCode();
198
+ return moveNodes.map((moveNode, index) => {
199
+ const text = sourceCode.getText(moveNode);
200
+ return fixer.replaceText(previousNodes[index] || node, text);
201
+ });
202
+ },
203
+ });
204
+ }
205
+ function hasSpreadAttribute(node, previousNode) {
206
+ const attributes = node.parent.attributes;
207
+ const previousNodes = attributes.slice(attributes.indexOf(previousNode), attributes.indexOf(node));
208
+ return previousNodes.some((a) => a.type === "SvelteSpreadAttribute");
209
+ }
210
+ function verifyForSpreadAttributeExist(node) {
211
+ const previousNodes = [];
212
+ const attributes = node.parent.attributes;
213
+ for (const previousNode of attributes
214
+ .slice(0, attributes.indexOf(node))
215
+ .reverse()) {
216
+ if (previousNode.type === "SvelteSpreadAttribute") {
217
+ break;
218
+ }
219
+ previousNodes.unshift(previousNode);
220
+ }
221
+ const key = getKeyText(node);
222
+ const invalidPreviousNode = previousNodes.find((previousNode) => {
223
+ const prevKey = getKeyText(previousNode);
224
+ if (option.ignore(prevKey)) {
225
+ return false;
226
+ }
227
+ return option.compare(prevKey, key) > 0;
228
+ });
229
+ if (invalidPreviousNode) {
230
+ report(node, invalidPreviousNode);
231
+ }
232
+ }
233
+ return {
234
+ SvelteStartTag(node) {
235
+ const validPreviousNodes = [];
236
+ for (const attr of node.attributes) {
237
+ if (attr.type === "SvelteSpreadAttribute") {
238
+ continue;
239
+ }
240
+ const key = getKeyText(attr);
241
+ if (option.ignore(key)) {
242
+ continue;
243
+ }
244
+ const invalidPreviousNode = validPreviousNodes.find((previousNode) => option.compare(getKeyText(previousNode), key) > 0);
245
+ if (invalidPreviousNode) {
246
+ if (attr.type !== "SvelteAttribute" ||
247
+ !hasSpreadAttribute(attr, invalidPreviousNode)) {
248
+ report(attr, invalidPreviousNode);
249
+ }
250
+ else {
251
+ verifyForSpreadAttributeExist(attr);
252
+ }
253
+ continue;
254
+ }
255
+ validPreviousNodes.push(attr);
256
+ }
257
+ },
258
+ };
259
+ },
260
+ });
package/lib/types.d.ts CHANGED
@@ -33,6 +33,7 @@ export interface RuleMetaData {
33
33
  [messageId: string]: string;
34
34
  };
35
35
  fixable?: "code" | "whitespace";
36
+ hasSuggestions?: boolean;
36
37
  schema: JSONSchema4 | JSONSchema4[];
37
38
  deprecated?: boolean;
38
39
  replacedBy?: string[];
@@ -59,6 +60,7 @@ export interface PartialRuleMetaData {
59
60
  [messageId: string]: string;
60
61
  };
61
62
  fixable?: "code" | "whitespace";
63
+ hasSuggestions?: boolean;
62
64
  schema: JSONSchema4 | JSONSchema4[];
63
65
  deprecated?: boolean;
64
66
  replacedBy?: string[];
@@ -46,3 +46,5 @@ export declare function getMustacheTokens(node: SvAST.SvelteDirective | SvAST.Sv
46
46
  openToken: SvAST.Token;
47
47
  closeToken: SvAST.Token;
48
48
  } | null;
49
+ export declare function getAttributeKeyText(node: SvAST.SvelteAttribute | SvAST.SvelteShorthandAttribute | SvAST.SvelteStyleDirective | SvAST.SvelteDirective | SvAST.SvelteSpecialDirective): string;
50
+ export declare function getDirectiveName(node: SvAST.SvelteDirective): string;
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.getMustacheTokens = exports.getAttributeValueQuoteAndRange = exports.getScope = exports.findVariable = exports.getLangValue = exports.getStaticAttributeValue = exports.findBindDirective = exports.findShorthandAttribute = exports.findAttribute = exports.isHTMLElementLike = exports.needParentheses = exports.getStringIfConstant = exports.equalTokens = void 0;
26
+ exports.getDirectiveName = exports.getAttributeKeyText = exports.getMustacheTokens = exports.getAttributeValueQuoteAndRange = exports.getScope = exports.findVariable = exports.getLangValue = exports.getStaticAttributeValue = exports.findBindDirective = exports.findShorthandAttribute = exports.findAttribute = exports.isHTMLElementLike = exports.needParentheses = exports.getStringIfConstant = exports.equalTokens = void 0;
27
27
  const eslintUtils = __importStar(require("eslint-utils"));
28
28
  function equalTokens(left, right, sourceCode) {
29
29
  const tokensL = sourceCode.getTokens(left);
@@ -260,6 +260,47 @@ function getMustacheTokens(node, sourceCode) {
260
260
  };
261
261
  }
262
262
  exports.getMustacheTokens = getMustacheTokens;
263
+ function getAttributeKeyText(node) {
264
+ switch (node.type) {
265
+ case "SvelteAttribute":
266
+ case "SvelteShorthandAttribute":
267
+ return node.key.name;
268
+ case "SvelteStyleDirective":
269
+ return `style:${node.key.name.name}`;
270
+ case "SvelteSpecialDirective":
271
+ return node.kind;
272
+ case "SvelteDirective": {
273
+ const dir = getDirectiveName(node);
274
+ return `${dir}:${node.key.name.name}${node.key.modifiers.length ? `|${node.key.modifiers.join("|")}` : ""}`;
275
+ }
276
+ default:
277
+ throw new Error(`Unknown node type: ${node.type}`);
278
+ }
279
+ }
280
+ exports.getAttributeKeyText = getAttributeKeyText;
281
+ function getDirectiveName(node) {
282
+ switch (node.kind) {
283
+ case "Action":
284
+ return "use";
285
+ case "Animation":
286
+ return "animate";
287
+ case "Binding":
288
+ return "bind";
289
+ case "Class":
290
+ return "class";
291
+ case "EventHandler":
292
+ return "on";
293
+ case "Let":
294
+ return "let";
295
+ case "Transition":
296
+ return node.intro && node.outro ? "transition" : node.intro ? "in" : "out";
297
+ case "Ref":
298
+ return "ref";
299
+ default:
300
+ throw new Error("Unknown directive kind");
301
+ }
302
+ }
303
+ exports.getDirectiveName = getDirectiveName;
263
304
  function getAttributeValueRangeTokens(attr, sourceCode) {
264
305
  if (attr.type === "SvelteAttribute" || attr.type === "SvelteStyleDirective") {
265
306
  if (!attr.value.length) {
@@ -1,2 +1,4 @@
1
- export declare function toRegExp(string: string): RegExp;
1
+ export declare function toRegExp(string: string): {
2
+ test(s: string): boolean;
3
+ };
2
4
  export declare function isRegExp(string: string): boolean;
@@ -1,20 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isRegExp = exports.toRegExp = void 0;
4
- const RE_REGEXP_CHAR = /[$()*+.?[\\\]^{|}]/gu;
5
- const RE_HAS_REGEXP_CHAR = new RegExp(RE_REGEXP_CHAR.source);
6
4
  const RE_REGEXP_STR = /^\/(.+)\/([A-Za-z]*)$/u;
7
- function escape(string) {
8
- return string && RE_HAS_REGEXP_CHAR.test(string)
9
- ? string.replace(RE_REGEXP_CHAR, "\\$&")
10
- : string;
11
- }
12
5
  function toRegExp(string) {
13
6
  const parts = RE_REGEXP_STR.exec(string);
14
7
  if (parts) {
15
8
  return new RegExp(parts[1], parts[2]);
16
9
  }
17
- return new RegExp(`^${escape(string)}$`);
10
+ return { test: (s) => s === string };
18
11
  }
19
12
  exports.toRegExp = toRegExp;
20
13
  function isRegExp(string) {
@@ -7,6 +7,7 @@ exports.rules = void 0;
7
7
  const button_has_type_1 = __importDefault(require("../rules/button-has-type"));
8
8
  const comment_directive_1 = __importDefault(require("../rules/comment-directive"));
9
9
  const first_attribute_linebreak_1 = __importDefault(require("../rules/first-attribute-linebreak"));
10
+ const html_closing_bracket_spacing_1 = __importDefault(require("../rules/html-closing-bracket-spacing"));
10
11
  const html_quotes_1 = __importDefault(require("../rules/html-quotes"));
11
12
  const indent_1 = __importDefault(require("../rules/indent"));
12
13
  const max_attributes_per_line_1 = __importDefault(require("../rules/max-attributes-per-line"));
@@ -16,10 +17,13 @@ const no_at_html_tags_1 = __importDefault(require("../rules/no-at-html-tags"));
16
17
  const no_dupe_else_if_blocks_1 = __importDefault(require("../rules/no-dupe-else-if-blocks"));
17
18
  const no_dupe_style_properties_1 = __importDefault(require("../rules/no-dupe-style-properties"));
18
19
  const no_dynamic_slot_name_1 = __importDefault(require("../rules/no-dynamic-slot-name"));
20
+ const no_extra_reactive_curlies_1 = __importDefault(require("../rules/no-extra-reactive-curlies"));
19
21
  const no_inner_declarations_1 = __importDefault(require("../rules/no-inner-declarations"));
20
22
  const no_not_function_handler_1 = __importDefault(require("../rules/no-not-function-handler"));
21
23
  const no_object_in_text_mustaches_1 = __importDefault(require("../rules/no-object-in-text-mustaches"));
24
+ const no_reactive_literals_1 = __importDefault(require("../rules/no-reactive-literals"));
22
25
  const no_shorthand_style_property_overrides_1 = __importDefault(require("../rules/no-shorthand-style-property-overrides"));
26
+ const no_spaces_around_equal_signs_in_attribute_1 = __importDefault(require("../rules/no-spaces-around-equal-signs-in-attribute"));
23
27
  const no_target_blank_1 = __importDefault(require("../rules/no-target-blank"));
24
28
  const no_unknown_style_directive_property_1 = __importDefault(require("../rules/no-unknown-style-directive-property"));
25
29
  const no_unused_svelte_ignore_1 = __importDefault(require("../rules/no-unused-svelte-ignore"));
@@ -29,6 +33,7 @@ const prefer_style_directive_1 = __importDefault(require("../rules/prefer-style-
29
33
  const require_optimized_style_attribute_1 = __importDefault(require("../rules/require-optimized-style-attribute"));
30
34
  const shorthand_attribute_1 = __importDefault(require("../rules/shorthand-attribute"));
31
35
  const shorthand_directive_1 = __importDefault(require("../rules/shorthand-directive"));
36
+ const sort_attributes_1 = __importDefault(require("../rules/sort-attributes"));
32
37
  const spaced_html_comment_1 = __importDefault(require("../rules/spaced-html-comment"));
33
38
  const system_1 = __importDefault(require("../rules/system"));
34
39
  const valid_compile_1 = __importDefault(require("../rules/valid-compile"));
@@ -36,6 +41,7 @@ exports.rules = [
36
41
  button_has_type_1.default,
37
42
  comment_directive_1.default,
38
43
  first_attribute_linebreak_1.default,
44
+ html_closing_bracket_spacing_1.default,
39
45
  html_quotes_1.default,
40
46
  indent_1.default,
41
47
  max_attributes_per_line_1.default,
@@ -45,10 +51,13 @@ exports.rules = [
45
51
  no_dupe_else_if_blocks_1.default,
46
52
  no_dupe_style_properties_1.default,
47
53
  no_dynamic_slot_name_1.default,
54
+ no_extra_reactive_curlies_1.default,
48
55
  no_inner_declarations_1.default,
49
56
  no_not_function_handler_1.default,
50
57
  no_object_in_text_mustaches_1.default,
58
+ no_reactive_literals_1.default,
51
59
  no_shorthand_style_property_overrides_1.default,
60
+ no_spaces_around_equal_signs_in_attribute_1.default,
52
61
  no_target_blank_1.default,
53
62
  no_unknown_style_directive_property_1.default,
54
63
  no_unused_svelte_ignore_1.default,
@@ -58,6 +67,7 @@ exports.rules = [
58
67
  require_optimized_style_attribute_1.default,
59
68
  shorthand_attribute_1.default,
60
69
  shorthand_directive_1.default,
70
+ sort_attributes_1.default,
61
71
  spaced_html_comment_1.default,
62
72
  system_1.default,
63
73
  valid_compile_1.default,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-svelte",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "ESLint plugin for Svelte using AST",
5
5
  "repository": "git+https://github.com/ota-meshi/eslint-plugin-svelte.git",
6
6
  "homepage": "https://ota-meshi.github.io/eslint-plugin-svelte",
@@ -43,7 +43,7 @@
43
43
  "prepublishOnly": "yarn clean && yarn build",
44
44
  "pretest:base": "cross-env DEBUG=eslint-plugin-svelte*",
45
45
  "preversion": "yarn test && git add .",
46
- "svelte-kit": "node --experimental-loader ./svelte-kit-import-hook.mjs node_modules/vite/bin/vite.js --config vite.config.mjs",
46
+ "svelte-kit": "node --experimental-loader ./svelte-kit-import-hook.mjs node_modules/vite/bin/vite.js",
47
47
  "test": "yarn mocha \"tests/src/**/*.ts\" --reporter dot --timeout 60000",
48
48
  "ts": "node -r esbuild-register",
49
49
  "update": "yarn ts ./tools/update.ts && yarn format-for-gen-file",
@@ -66,7 +66,7 @@
66
66
  "postcss-load-config": "^3.1.4",
67
67
  "postcss-safe-parser": "^6.0.0",
68
68
  "sourcemap-codec": "^1.4.8",
69
- "svelte-eslint-parser": "^0.16.0"
69
+ "svelte-eslint-parser": "^0.17.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@babel/core": "^7.16.0",
@@ -74,19 +74,25 @@
74
74
  "@babel/plugin-proposal-function-bind": "^7.16.7",
75
75
  "@babel/types": "^7.16.0",
76
76
  "@fontsource/fira-mono": "^4.5.0",
77
- "@ota-meshi/eslint-plugin": "^0.11.0",
77
+ "@ota-meshi/eslint-plugin": "^0.11.3",
78
78
  "@sindresorhus/slugify": "^2.1.0",
79
79
  "@sveltejs/adapter-static": "^1.0.0-next.26",
80
80
  "@sveltejs/kit": "^1.0.0-next.360",
81
81
  "@types/babel__core": "^7.1.19",
82
+ "@types/cross-spawn": "^6.0.2",
83
+ "@types/escape-html": "^1.0.2",
82
84
  "@types/eslint": "^8.0.0",
83
85
  "@types/eslint-scope": "^3.7.0",
84
86
  "@types/eslint-visitor-keys": "^1.0.0",
85
- "@types/estree": "^0.0.52",
87
+ "@types/estree": "^1.0.0",
86
88
  "@types/less": "^3.0.3",
89
+ "@types/markdown-it": "^12.2.3",
90
+ "@types/markdown-it-container": "^2.0.5",
91
+ "@types/markdown-it-emoji": "^2.0.2",
87
92
  "@types/mocha": "^9.0.0",
88
93
  "@types/node": "^16.0.0",
89
94
  "@types/postcss-safe-parser": "^5.0.1",
95
+ "@types/prismjs": "^1.26.0",
90
96
  "@types/stylus": "^0.48.38",
91
97
  "@typescript-eslint/eslint-plugin": "^5.4.0",
92
98
  "@typescript-eslint/parser": "^5.4.1-0",
@@ -99,16 +105,17 @@
99
105
  "eslint": "^8.0.0",
100
106
  "eslint-config-prettier": "^8.3.0",
101
107
  "eslint-plugin-eslint-comments": "^3.2.0",
102
- "eslint-plugin-eslint-plugin": "^4.0.0",
103
- "eslint-plugin-json-schema-validator": "^3.0.0",
108
+ "eslint-plugin-eslint-plugin": "^5.0.0",
109
+ "eslint-plugin-json-schema-validator": "^4.0.0",
104
110
  "eslint-plugin-jsonc": "^2.0.0",
105
- "eslint-plugin-markdown": "^2.1.0",
111
+ "eslint-plugin-markdown": "^3.0.0",
106
112
  "eslint-plugin-node": "^11.1.0",
107
113
  "eslint-plugin-node-dependencies": "^0.9.0",
108
114
  "eslint-plugin-prettier": "^4.0.0",
109
115
  "eslint-plugin-regexp": "^1.0.0",
110
116
  "eslint-plugin-svelte": "^2.0.0",
111
117
  "eslint-plugin-yml": "^1.0.0",
118
+ "eslint-scope": "^7.1.1",
112
119
  "estree-walker": "^3.0.0",
113
120
  "less": "^4.1.2",
114
121
  "locate-character": "^2.0.5",
@@ -119,7 +126,6 @@
119
126
  "mocha": "^10.0.0",
120
127
  "nyc": "^15.1.0",
121
128
  "pako": "^2.0.3",
122
- "pirates": "^4.0.1",
123
129
  "postcss-nested": "^5.0.6",
124
130
  "prettier": "^2.2.1",
125
131
  "prettier-plugin-pkg": "^0.16.0",
@@ -134,8 +140,8 @@
134
140
  "svelte": "^3.46.1",
135
141
  "svelte-adapter-ghpages": "0.0.2",
136
142
  "typescript": "^4.5.2",
137
- "vite": "^2.9.13",
138
- "vite-plugin-svelte-md": "^0.1.3"
143
+ "vite": "^3.0.0-0",
144
+ "vite-plugin-svelte-md": "^0.1.5"
139
145
  },
140
146
  "publishConfig": {
141
147
  "access": "public"