eslint-plugin-svelte 2.12.0 → 2.13.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 +1 -0
- package/lib/rules/html-self-closing.js +12 -6
- package/lib/rules/no-dom-manipulating.d.ts +2 -0
- package/lib/rules/no-dom-manipulating.js +109 -0
- package/lib/rules/prefer-destructured-store-props.js +4 -2
- package/lib/types.d.ts +5 -4
- package/lib/utils/ast-utils.d.ts +3 -3
- package/lib/utils/ast-utils.js +6 -8
- package/lib/utils/rules.js +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -298,6 +298,7 @@ These rules relate to possible syntax or logic errors in Svelte code:
|
|
|
298
298
|
|
|
299
299
|
| Rule ID | Description | |
|
|
300
300
|
|:--------|:------------|:---|
|
|
301
|
+
| [svelte/no-dom-manipulating](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dom-manipulating/) | disallow DOM manipulating | |
|
|
301
302
|
| [svelte/no-dupe-else-if-blocks](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dupe-else-if-blocks/) | disallow duplicate conditions in `{#if}` / `{:else if}` chains | :star: |
|
|
302
303
|
| [svelte/no-dupe-style-properties](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dupe-style-properties/) | disallow duplicate style properties | :star: |
|
|
303
304
|
| [svelte/no-dynamic-slot-name](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-dynamic-slot-name/) | disallow dynamic slot name | :star::wrench: |
|
|
@@ -49,14 +49,14 @@ exports.default = (0, utils_1.createRule)("html-self-closing", {
|
|
|
49
49
|
},
|
|
50
50
|
],
|
|
51
51
|
},
|
|
52
|
-
create(
|
|
52
|
+
create(context) {
|
|
53
53
|
let options = {
|
|
54
54
|
void: "always",
|
|
55
55
|
normal: "always",
|
|
56
56
|
component: "always",
|
|
57
57
|
svelte: "always",
|
|
58
58
|
};
|
|
59
|
-
const option =
|
|
59
|
+
const option = context.options?.[0];
|
|
60
60
|
switch (option) {
|
|
61
61
|
case "none":
|
|
62
62
|
options = {
|
|
@@ -103,16 +103,22 @@ exports.default = (0, utils_1.createRule)("html-self-closing", {
|
|
|
103
103
|
}
|
|
104
104
|
return true;
|
|
105
105
|
}
|
|
106
|
-
function report(node,
|
|
106
|
+
function report(node, shouldBeClosed) {
|
|
107
107
|
const elementType = getElementType(node);
|
|
108
|
-
|
|
108
|
+
context.report({
|
|
109
109
|
node,
|
|
110
|
-
|
|
110
|
+
loc: {
|
|
111
|
+
start: context
|
|
112
|
+
.getSourceCode()
|
|
113
|
+
.getLocFromIndex(node.startTag.range[1] - (node.startTag.selfClosing ? 2 : 1)),
|
|
114
|
+
end: node.loc.end,
|
|
115
|
+
},
|
|
116
|
+
messageId: shouldBeClosed ? "requireClosing" : "disallowClosing",
|
|
111
117
|
data: {
|
|
112
118
|
type: TYPE_MESSAGES[elementType],
|
|
113
119
|
},
|
|
114
120
|
*fix(fixer) {
|
|
115
|
-
if (
|
|
121
|
+
if (shouldBeClosed) {
|
|
116
122
|
for (const child of node.children) {
|
|
117
123
|
yield fixer.removeRange(child.range);
|
|
118
124
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
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 eslint_utils_1 = require("eslint-utils");
|
|
6
|
+
const DOM_MANIPULATING_METHODS = new Set([
|
|
7
|
+
"appendChild",
|
|
8
|
+
"insertBefore",
|
|
9
|
+
"normalize",
|
|
10
|
+
"removeChild",
|
|
11
|
+
"replaceChild",
|
|
12
|
+
"after",
|
|
13
|
+
"append",
|
|
14
|
+
"before",
|
|
15
|
+
"insertAdjacentElement",
|
|
16
|
+
"insertAdjacentHTML",
|
|
17
|
+
"insertAdjacentText",
|
|
18
|
+
"prepend",
|
|
19
|
+
"remove",
|
|
20
|
+
"replaceChildren",
|
|
21
|
+
"replaceWith",
|
|
22
|
+
]);
|
|
23
|
+
const DOM_MANIPULATING_PROPERTIES = new Set([
|
|
24
|
+
"textContent",
|
|
25
|
+
"innerHTML",
|
|
26
|
+
"outerHTML",
|
|
27
|
+
"innerText",
|
|
28
|
+
"outerText",
|
|
29
|
+
]);
|
|
30
|
+
exports.default = (0, utils_1.createRule)("no-dom-manipulating", {
|
|
31
|
+
meta: {
|
|
32
|
+
docs: {
|
|
33
|
+
description: "disallow DOM manipulating",
|
|
34
|
+
category: "Possible Errors",
|
|
35
|
+
recommended: false,
|
|
36
|
+
},
|
|
37
|
+
schema: [],
|
|
38
|
+
messages: {
|
|
39
|
+
disallowManipulateDOM: "Don't manipulate the DOM directly. The Svelte runtime can get confused if there is a difference between the actual DOM and the DOM expected by the Svelte runtime.",
|
|
40
|
+
},
|
|
41
|
+
type: "problem",
|
|
42
|
+
},
|
|
43
|
+
create(context) {
|
|
44
|
+
const domVariables = new Set();
|
|
45
|
+
function verifyIdentifier(node) {
|
|
46
|
+
const member = node.parent;
|
|
47
|
+
if (member?.type !== "MemberExpression" || member.object !== node) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const name = (0, eslint_utils_1.getPropertyName)(member);
|
|
51
|
+
if (!name) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
let target = member;
|
|
55
|
+
let parent = target.parent;
|
|
56
|
+
while (parent?.type === "ChainExpression") {
|
|
57
|
+
target = parent;
|
|
58
|
+
parent = parent.parent;
|
|
59
|
+
}
|
|
60
|
+
if (!parent) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (parent.type === "CallExpression") {
|
|
64
|
+
if (parent.callee !== target || !DOM_MANIPULATING_METHODS.has(name)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else if (parent.type === "AssignmentExpression") {
|
|
69
|
+
if (parent.left !== target || !DOM_MANIPULATING_PROPERTIES.has(name)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
context.report({
|
|
74
|
+
node: member,
|
|
75
|
+
messageId: "disallowManipulateDOM",
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
"SvelteDirective[kind='Binding']"(node) {
|
|
80
|
+
if (node.key.name.name !== "this" ||
|
|
81
|
+
!node.expression ||
|
|
82
|
+
node.expression.type !== "Identifier") {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const element = node.parent.parent;
|
|
86
|
+
if (element.type !== "SvelteElement" || !isHTMLElement(element)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const variable = (0, ast_utils_1.findVariable)(context, node.expression);
|
|
90
|
+
if (!variable ||
|
|
91
|
+
(variable.scope.type !== "module" && variable.scope.type !== "global")) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
domVariables.add(variable);
|
|
95
|
+
},
|
|
96
|
+
"Program:exit"() {
|
|
97
|
+
for (const variable of domVariables) {
|
|
98
|
+
for (const reference of variable.references) {
|
|
99
|
+
verifyIdentifier(reference.identifier);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
function isHTMLElement(node) {
|
|
105
|
+
return (node.kind === "html" ||
|
|
106
|
+
(node.kind === "special" && (0, ast_utils_1.getNodeName)(node) === "svelte:element"));
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
});
|
|
@@ -47,7 +47,8 @@ exports.default = (0, utils_1.createRule)("prefer-destructured-store-props", {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
function isReactiveVariableDefinitionWithMemberExpression(node) {
|
|
50
|
-
return (node.
|
|
50
|
+
return (node.type === "Identifier" &&
|
|
51
|
+
node.parent?.type === "MemberExpression" &&
|
|
51
52
|
node.parent.object === node &&
|
|
52
53
|
(0, eslint_utils_1.getPropertyName)(node.parent) === propName &&
|
|
53
54
|
node.parent.parent?.type === "AssignmentExpression" &&
|
|
@@ -58,7 +59,8 @@ exports.default = (0, utils_1.createRule)("prefer-destructured-store-props", {
|
|
|
58
59
|
.parent?.type === "SvelteReactiveStatement");
|
|
59
60
|
}
|
|
60
61
|
function isReactiveVariableDefinitionWithDestructuring(node) {
|
|
61
|
-
return (node.
|
|
62
|
+
return (node.type === "Identifier" &&
|
|
63
|
+
node.parent?.type === "AssignmentExpression" &&
|
|
62
64
|
node.parent.right === node &&
|
|
63
65
|
node.parent.left.type === "ObjectPattern" &&
|
|
64
66
|
node.parent.parent?.type === "ExpressionStatement" &&
|
package/lib/types.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { JSONSchema4 } from "json-schema";
|
|
2
|
-
import type { Linter, Rule,
|
|
2
|
+
import type { Linter, Rule, SourceCode as ESLintSourceCode } from "eslint";
|
|
3
3
|
import type { AST } from "svelte-eslint-parser";
|
|
4
4
|
import type { TSESTree } from "@typescript-eslint/types";
|
|
5
|
+
import type { ScopeManager, Scope, Variable } from "@typescript-eslint/scope-manager";
|
|
5
6
|
import type { ASTNode, ASTNodeWithParent, ASTNodeListener } from "./types-for-node";
|
|
6
7
|
export type { ASTNode, ASTNodeWithParent, ASTNodeListener };
|
|
7
8
|
export interface RuleListener extends ASTNodeListener {
|
|
@@ -99,9 +100,9 @@ export declare type RuleContext = {
|
|
|
99
100
|
parserOptions: Linter.ParserOptions;
|
|
100
101
|
parserServices: ESLintSourceCode.ParserServices;
|
|
101
102
|
getAncestors(): ASTNode[];
|
|
102
|
-
getDeclaredVariables(node: TSESTree.Node):
|
|
103
|
+
getDeclaredVariables(node: TSESTree.Node): Variable[];
|
|
103
104
|
getFilename(): string;
|
|
104
|
-
getScope(): Scope
|
|
105
|
+
getScope(): Scope;
|
|
105
106
|
getSourceCode(): SourceCode;
|
|
106
107
|
markVariableAsUsed(name: string): boolean;
|
|
107
108
|
report(descriptor: ReportDescriptor): void;
|
|
@@ -160,7 +161,7 @@ export interface SourceCode {
|
|
|
160
161
|
lines: string[];
|
|
161
162
|
hasBOM: boolean;
|
|
162
163
|
parserServices: ESLintSourceCode.ParserServices;
|
|
163
|
-
scopeManager:
|
|
164
|
+
scopeManager: ScopeManager;
|
|
164
165
|
visitorKeys: ESLintSourceCode.VisitorKeys;
|
|
165
166
|
getText(node?: NodeOrToken, beforeCount?: number, afterCount?: number): string;
|
|
166
167
|
getLines(): string[];
|
package/lib/utils/ast-utils.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ASTNode, RuleContext, SourceCode } from "../types";
|
|
2
2
|
import type { TSESTree } from "@typescript-eslint/types";
|
|
3
|
+
import type { Scope, Variable } from "@typescript-eslint/scope-manager";
|
|
3
4
|
import type { AST as SvAST } from "svelte-eslint-parser";
|
|
4
|
-
import type { Scope } from "eslint";
|
|
5
5
|
export declare function equalTokens(left: ASTNode, right: ASTNode, sourceCode: SourceCode): boolean;
|
|
6
6
|
export declare function getStringIfConstant(node: TSESTree.Expression | TSESTree.PrivateIdentifier): string | null;
|
|
7
7
|
export declare function needParentheses(node: TSESTree.Expression, kind: "not" | "logical"): boolean;
|
|
@@ -29,8 +29,8 @@ export declare function findBindDirective<N extends string>(node: SvAST.SvelteEl
|
|
|
29
29
|
}) | null;
|
|
30
30
|
export declare function getStaticAttributeValue(node: SvAST.SvelteAttribute): string | null;
|
|
31
31
|
export declare function getLangValue(node: SvAST.SvelteScriptElement | SvAST.SvelteStyleElement): string | null;
|
|
32
|
-
export declare function findVariable(context: RuleContext, node: TSESTree.Identifier):
|
|
33
|
-
export declare function getScope(context: RuleContext, currentNode: TSESTree.Node): Scope
|
|
32
|
+
export declare function findVariable(context: RuleContext, node: TSESTree.Identifier): Variable | null;
|
|
33
|
+
export declare function getScope(context: RuleContext, currentNode: TSESTree.Node): Scope;
|
|
34
34
|
export declare function getParent(node: TSESTree.Node): TSESTree.Node | null;
|
|
35
35
|
export declare type QuoteAndRange = {
|
|
36
36
|
quote: "unquoted" | "double" | "single";
|
package/lib/utils/ast-utils.js
CHANGED
|
@@ -339,19 +339,17 @@ function getAttributeValueRangeTokens(attr, sourceCode) {
|
|
|
339
339
|
};
|
|
340
340
|
}
|
|
341
341
|
function getNodeName(node) {
|
|
342
|
-
if (
|
|
342
|
+
if (node.name.type === "Identifier" || node.name.type === "SvelteName") {
|
|
343
343
|
return node.name.name;
|
|
344
344
|
}
|
|
345
|
-
|
|
345
|
+
const memberPath = [node.name.property.name];
|
|
346
346
|
let currentObject = node.name.object;
|
|
347
|
-
while ("
|
|
348
|
-
|
|
347
|
+
while (currentObject.type === "SvelteMemberExpressionName") {
|
|
348
|
+
memberPath.unshift(currentObject.property.name);
|
|
349
349
|
currentObject = currentObject.object;
|
|
350
350
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
return object + node.name.property.name;
|
|
351
|
+
memberPath.unshift(currentObject.name);
|
|
352
|
+
return memberPath.join(".");
|
|
355
353
|
}
|
|
356
354
|
exports.getNodeName = getNodeName;
|
|
357
355
|
function isVoidHtmlElement(node) {
|
package/lib/utils/rules.js
CHANGED
|
@@ -17,6 +17,7 @@ const max_attributes_per_line_1 = __importDefault(require("../rules/max-attribut
|
|
|
17
17
|
const mustache_spacing_1 = __importDefault(require("../rules/mustache-spacing"));
|
|
18
18
|
const no_at_debug_tags_1 = __importDefault(require("../rules/no-at-debug-tags"));
|
|
19
19
|
const no_at_html_tags_1 = __importDefault(require("../rules/no-at-html-tags"));
|
|
20
|
+
const no_dom_manipulating_1 = __importDefault(require("../rules/no-dom-manipulating"));
|
|
20
21
|
const no_dupe_else_if_blocks_1 = __importDefault(require("../rules/no-dupe-else-if-blocks"));
|
|
21
22
|
const no_dupe_style_properties_1 = __importDefault(require("../rules/no-dupe-style-properties"));
|
|
22
23
|
const no_dynamic_slot_name_1 = __importDefault(require("../rules/no-dynamic-slot-name"));
|
|
@@ -63,6 +64,7 @@ exports.rules = [
|
|
|
63
64
|
mustache_spacing_1.default,
|
|
64
65
|
no_at_debug_tags_1.default,
|
|
65
66
|
no_at_html_tags_1.default,
|
|
67
|
+
no_dom_manipulating_1.default,
|
|
66
68
|
no_dupe_else_if_blocks_1.default,
|
|
67
69
|
no_dupe_style_properties_1.default,
|
|
68
70
|
no_dynamic_slot_name_1.default,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-svelte",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.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",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"postcss-load-config": "^3.1.4",
|
|
74
74
|
"postcss-safe-parser": "^6.0.0",
|
|
75
75
|
"sourcemap-codec": "^1.4.8",
|
|
76
|
-
"svelte-eslint-parser": "^0.
|
|
76
|
+
"svelte-eslint-parser": "^0.21.0"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@1stg/browserslist-config": "^1.2.3",
|