@html-eslint/eslint-plugin 0.51.0 → 0.52.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/configs/recommended.js +8 -2
- package/lib/index.js +2 -6
- package/lib/languages/html-language.js +23 -15
- package/lib/languages/html-source-code.js +34 -26
- package/lib/languages/html-traversal-step.js +6 -10
- package/lib/rules/attrs-newline.js +15 -11
- package/lib/rules/class-spacing.js +148 -0
- package/lib/rules/element-newline.js +18 -21
- package/lib/rules/id-naming-convention.js +15 -12
- package/lib/rules/indent/indent-level.js +10 -17
- package/lib/rules/indent/indent.js +51 -56
- package/lib/rules/index.js +5 -1
- package/lib/rules/lowercase.js +11 -23
- package/lib/rules/max-element-depth.js +8 -10
- package/lib/rules/no-abstract-roles.js +8 -8
- package/lib/rules/no-accesskey-attrs.js +8 -8
- package/lib/rules/no-aria-hidden-body.js +2 -6
- package/lib/rules/no-aria-hidden-on-focusable.js +4 -5
- package/lib/rules/no-duplicate-attrs.js +12 -8
- package/lib/rules/no-duplicate-class.js +8 -14
- package/lib/rules/no-duplicate-id.js +9 -15
- package/lib/rules/no-duplicate-in-head.js +10 -17
- package/lib/rules/no-empty-headings.js +7 -8
- package/lib/rules/no-extra-spacing-attrs.js +13 -8
- package/lib/rules/no-extra-spacing-text.js +11 -14
- package/lib/rules/no-heading-inside-button.js +2 -6
- package/lib/rules/no-ineffective-attrs.js +14 -15
- package/lib/rules/no-inline-styles.js +2 -6
- package/lib/rules/no-invalid-entity.js +4 -8
- package/lib/rules/no-invalid-role.js +3 -10
- package/lib/rules/no-multiple-empty-lines.js +7 -7
- package/lib/rules/no-multiple-h1.js +4 -8
- package/lib/rules/no-nested-interactive.js +5 -9
- package/lib/rules/no-non-scalable-viewport.js +2 -6
- package/lib/rules/no-obsolete-attrs.js +83 -0
- package/lib/rules/no-obsolete-tags.js +2 -6
- package/lib/rules/no-positive-tabindex.js +8 -8
- package/lib/rules/no-restricted-attr-values.js +18 -21
- package/lib/rules/no-restricted-attrs.js +18 -22
- package/lib/rules/no-restricted-tags.js +17 -20
- package/lib/rules/no-script-style-type.js +7 -6
- package/lib/rules/no-skip-heading-levels.js +4 -8
- package/lib/rules/no-target-blank.js +3 -6
- package/lib/rules/no-trailing-spaces.js +7 -6
- package/lib/rules/no-whitespace-only-children.js +7 -8
- package/lib/rules/prefer-https.js +11 -14
- package/lib/rules/quotes.js +13 -16
- package/lib/rules/require-attrs.js +18 -17
- package/lib/rules/require-button-type.js +6 -5
- package/lib/rules/require-closing-tags.js +9 -12
- package/lib/rules/require-doctype.js +2 -6
- package/lib/rules/require-explicit-size.js +2 -5
- package/lib/rules/require-form-method.js +2 -6
- package/lib/rules/require-frame-title.js +2 -6
- package/lib/rules/require-img-alt.js +3 -6
- package/lib/rules/require-input-label.js +3 -5
- package/lib/rules/require-lang.js +2 -6
- package/lib/rules/require-li-container.js +2 -6
- package/lib/rules/require-meta-charset.js +6 -5
- package/lib/rules/require-meta-description.js +6 -5
- package/lib/rules/require-meta-viewport.js +6 -5
- package/lib/rules/require-open-graph-protocol.js +7 -8
- package/lib/rules/require-title.js +7 -5
- package/lib/rules/sort-attrs.js +13 -19
- package/lib/rules/use-baseline.js +8 -6
- package/lib/rules/utils/baseline.js +4 -6
- package/lib/rules/utils/naming.js +14 -7
- package/lib/rules/utils/node.js +63 -29
- package/lib/rules/utils/rule.js +1 -4
- package/lib/rules/utils/settings.js +9 -3
- package/lib/rules/utils/source-code.js +2 -6
- package/lib/rules/utils/template-literal.js +16 -7
- package/lib/rules/utils/visitors.js +4 -1
- package/lib/specs/index.js +5 -0
- package/lib/specs/obsolete-attrs.js +604 -0
- package/lib/types/ast.ts +5 -2
- package/package.json +6 -6
- package/types/configs/recommended.d.ts +2 -1
- package/types/configs/recommended.d.ts.map +1 -1
- package/types/index.d.ts +2 -6
- package/types/index.d.ts.map +1 -1
- package/types/languages/html-language.d.ts +12 -10
- package/types/languages/html-language.d.ts.map +1 -1
- package/types/languages/html-source-code.d.ts +13 -6
- package/types/languages/html-source-code.d.ts.map +1 -1
- package/types/languages/html-traversal-step.d.ts +5 -5
- package/types/languages/html-traversal-step.d.ts.map +1 -1
- package/types/rules/attrs-newline.d.ts +1 -1
- package/types/rules/attrs-newline.d.ts.map +1 -1
- package/types/rules/class-spacing.d.ts +4 -0
- package/types/rules/class-spacing.d.ts.map +1 -0
- package/types/rules/element-newline.d.ts +3 -3
- package/types/rules/element-newline.d.ts.map +1 -1
- package/types/rules/id-naming-convention.d.ts.map +1 -1
- package/types/rules/indent/indent-level.d.ts +10 -17
- package/types/rules/indent/indent-level.d.ts.map +1 -1
- package/types/rules/indent/indent.d.ts.map +1 -1
- package/types/rules/lowercase.d.ts.map +1 -1
- package/types/rules/max-element-depth.d.ts.map +1 -1
- package/types/rules/no-abstract-roles.d.ts.map +1 -1
- package/types/rules/no-accesskey-attrs.d.ts.map +1 -1
- package/types/rules/no-aria-hidden-body.d.ts.map +1 -1
- package/types/rules/no-aria-hidden-on-focusable.d.ts.map +1 -1
- package/types/rules/no-duplicate-attrs.d.ts.map +1 -1
- package/types/rules/no-duplicate-class.d.ts.map +1 -1
- package/types/rules/no-duplicate-id.d.ts.map +1 -1
- package/types/rules/no-duplicate-in-head.d.ts.map +1 -1
- package/types/rules/no-empty-headings.d.ts.map +1 -1
- package/types/rules/no-extra-spacing-attrs.d.ts.map +1 -1
- package/types/rules/no-extra-spacing-text.d.ts.map +1 -1
- package/types/rules/no-heading-inside-button.d.ts.map +1 -1
- package/types/rules/no-ineffective-attrs.d.ts.map +1 -1
- package/types/rules/no-inline-styles.d.ts.map +1 -1
- package/types/rules/no-invalid-entity.d.ts.map +1 -1
- package/types/rules/no-invalid-role.d.ts.map +1 -1
- package/types/rules/no-multiple-empty-lines.d.ts.map +1 -1
- package/types/rules/no-multiple-h1.d.ts.map +1 -1
- package/types/rules/no-nested-interactive.d.ts.map +1 -1
- package/types/rules/no-non-scalable-viewport.d.ts.map +1 -1
- package/types/rules/no-obsolete-attrs.d.ts +4 -0
- package/types/rules/no-obsolete-attrs.d.ts.map +1 -0
- package/types/rules/no-obsolete-tags.d.ts.map +1 -1
- package/types/rules/no-positive-tabindex.d.ts.map +1 -1
- package/types/rules/no-restricted-attr-values.d.ts.map +1 -1
- package/types/rules/no-restricted-attrs.d.ts.map +1 -1
- package/types/rules/no-restricted-tags.d.ts.map +1 -1
- package/types/rules/no-script-style-type.d.ts.map +1 -1
- package/types/rules/no-skip-heading-levels.d.ts.map +1 -1
- package/types/rules/no-target-blank.d.ts.map +1 -1
- package/types/rules/no-trailing-spaces.d.ts.map +1 -1
- package/types/rules/no-whitespace-only-children.d.ts.map +1 -1
- package/types/rules/prefer-https.d.ts.map +1 -1
- package/types/rules/quotes.d.ts.map +1 -1
- package/types/rules/require-attrs.d.ts.map +1 -1
- package/types/rules/require-button-type.d.ts.map +1 -1
- package/types/rules/require-closing-tags.d.ts.map +1 -1
- package/types/rules/require-doctype.d.ts.map +1 -1
- package/types/rules/require-explicit-size.d.ts.map +1 -1
- package/types/rules/require-form-method.d.ts.map +1 -1
- package/types/rules/require-frame-title.d.ts.map +1 -1
- package/types/rules/require-img-alt.d.ts.map +1 -1
- package/types/rules/require-input-label.d.ts.map +1 -1
- package/types/rules/require-lang.d.ts.map +1 -1
- package/types/rules/require-li-container.d.ts.map +1 -1
- package/types/rules/require-meta-charset.d.ts.map +1 -1
- package/types/rules/require-meta-description.d.ts.map +1 -1
- package/types/rules/require-meta-viewport.d.ts.map +1 -1
- package/types/rules/require-open-graph-protocol.d.ts.map +1 -1
- package/types/rules/require-title.d.ts.map +1 -1
- package/types/rules/sort-attrs.d.ts.map +1 -1
- package/types/rules/use-baseline.d.ts.map +1 -1
- package/types/rules/utils/baseline.d.ts +1 -3
- package/types/rules/utils/baseline.d.ts.map +1 -1
- package/types/rules/utils/naming.d.ts +14 -7
- package/types/rules/utils/naming.d.ts.map +1 -1
- package/types/rules/utils/node.d.ts +11 -10
- package/types/rules/utils/node.d.ts.map +1 -1
- package/types/rules/utils/rule.d.ts +1 -4
- package/types/rules/utils/rule.d.ts.map +1 -1
- package/types/rules/utils/settings.d.ts +0 -1
- package/types/rules/utils/settings.d.ts.map +1 -1
- package/types/rules/utils/source-code.d.ts +2 -6
- package/types/rules/utils/source-code.d.ts.map +1 -1
- package/types/rules/utils/template-literal.d.ts +2 -1
- package/types/rules/utils/template-literal.d.ts.map +1 -1
- package/types/rules/utils/visitors.d.ts.map +1 -1
- package/types/specs/index.d.ts +3 -0
- package/types/specs/index.d.ts.map +1 -0
- package/types/specs/obsolete-attrs.d.ts +19 -0
- package/types/specs/obsolete-attrs.d.ts.map +1 -0
- package/types/types/ast.d.ts +5 -2
- package/types/types/ast.d.ts.map +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @satisfies {import(
|
|
1
|
+
/** @satisfies {import("eslint").Linter.Config["rules"]} */
|
|
2
2
|
const recommendedRules = {
|
|
3
3
|
"html/require-lang": "error",
|
|
4
4
|
"html/require-img-alt": "error",
|
|
@@ -18,6 +18,7 @@ const recommendedRules = {
|
|
|
18
18
|
"html/require-li-container": "error",
|
|
19
19
|
"html/quotes": "error",
|
|
20
20
|
"html/no-obsolete-tags": "error",
|
|
21
|
+
"html/no-obsolete-attrs": "error",
|
|
21
22
|
"html/require-closing-tags": "error",
|
|
22
23
|
"html/no-duplicate-attrs": "error",
|
|
23
24
|
"html/use-baseline": "error",
|
|
@@ -29,7 +30,12 @@ const recommendedLegacyRules = Object.entries(recommendedRules).reduce(
|
|
|
29
30
|
acc[key.replace("html/", "@html-eslint/")] = value;
|
|
30
31
|
return acc;
|
|
31
32
|
},
|
|
32
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* @type {Record<
|
|
35
|
+
* string,
|
|
36
|
+
* (typeof recommendedRules)[keyof typeof recommendedRules]
|
|
37
|
+
* >}
|
|
38
|
+
*/
|
|
33
39
|
({})
|
|
34
40
|
);
|
|
35
41
|
|
package/lib/index.js
CHANGED
|
@@ -7,13 +7,9 @@ const { allRules } = require("./configs/all");
|
|
|
7
7
|
const { HTMLLanguage } = require("./languages/html-language");
|
|
8
8
|
const { name, version } = require("../package.json");
|
|
9
9
|
const parser = require("@html-eslint/parser");
|
|
10
|
-
/**
|
|
11
|
-
* @import { ESLint } from "eslint";
|
|
12
|
-
*/
|
|
10
|
+
/** @import {ESLint} from "eslint" */
|
|
13
11
|
|
|
14
|
-
/**
|
|
15
|
-
* @type {ESLint.Plugin}
|
|
16
|
-
*/
|
|
12
|
+
/** @type {ESLint.Plugin} */
|
|
17
13
|
const plugin = {
|
|
18
14
|
meta: {
|
|
19
15
|
name,
|
|
@@ -1,50 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import {
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* @import {
|
|
3
|
+
* File,
|
|
4
|
+
* FileError,
|
|
5
|
+
* Language,
|
|
6
|
+
* OkParseResult,
|
|
7
|
+
* ParseResult
|
|
8
|
+
* } from "@eslint/core"
|
|
9
|
+
* @import {ParserOptions} from "@html-eslint/parser"
|
|
10
|
+
* @import {AST} from "eslint"
|
|
5
11
|
*/
|
|
6
12
|
|
|
7
13
|
const { visitorKeys, parseForESLint } = require("@html-eslint/parser");
|
|
8
14
|
const { createHTMLSourceCode } = require("./html-source-code");
|
|
9
15
|
|
|
10
16
|
/**
|
|
11
|
-
* @implements {Language<{
|
|
17
|
+
* @implements {Language<{
|
|
18
|
+
* LangOptions: ParserOptions;
|
|
19
|
+
* Code: ReturnType<typeof createHTMLSourceCode>;
|
|
20
|
+
* RootNode: AST.Program;
|
|
21
|
+
* Node: {};
|
|
22
|
+
* }>}
|
|
12
23
|
*/
|
|
13
24
|
class HTMLLanguage {
|
|
14
25
|
constructor() {
|
|
15
26
|
/**
|
|
16
|
-
* @property
|
|
17
27
|
* @type {"text"}
|
|
28
|
+
* @property
|
|
18
29
|
*/
|
|
19
30
|
this.fileType = "text";
|
|
20
31
|
|
|
21
32
|
/**
|
|
33
|
+
* @type {0 | 1}
|
|
22
34
|
* @property
|
|
23
|
-
* @type {0|1}
|
|
24
35
|
*/
|
|
25
36
|
this.lineStart = 1;
|
|
26
37
|
|
|
27
38
|
/**
|
|
39
|
+
* @type {0 | 1}
|
|
28
40
|
* @property
|
|
29
|
-
* @type {0|1}
|
|
30
41
|
*/
|
|
31
42
|
this.columnStart = 0;
|
|
32
43
|
|
|
33
|
-
/**
|
|
34
|
-
* @type {string}
|
|
35
|
-
*/
|
|
44
|
+
/** @type {string} */
|
|
36
45
|
this.nodeTypeKey = "type";
|
|
37
46
|
|
|
38
47
|
/**
|
|
39
48
|
* The visitor keys for the es-html-parser AST.
|
|
49
|
+
*
|
|
40
50
|
* @type {Record<string, string[]>}
|
|
41
51
|
*/
|
|
42
52
|
this.visitorKeys = visitorKeys;
|
|
43
53
|
}
|
|
44
54
|
|
|
45
|
-
/**
|
|
46
|
-
* @param {ParserOptions} languageOptions
|
|
47
|
-
*/
|
|
55
|
+
/** @param {ParserOptions} languageOptions */
|
|
48
56
|
validateLanguageOptions(languageOptions) {
|
|
49
57
|
if (!languageOptions) {
|
|
50
58
|
return;
|
|
@@ -82,7 +90,7 @@ class HTMLLanguage {
|
|
|
82
90
|
* @returns {ParseResult<AST.Program>}
|
|
83
91
|
*/
|
|
84
92
|
parse(file, context) {
|
|
85
|
-
const code = /**
|
|
93
|
+
const code = /** @type {string} */ (file.body);
|
|
86
94
|
const languageOptions = (context && context.languageOptions) || {};
|
|
87
95
|
try {
|
|
88
96
|
const result = parseForESLint(code, languageOptions);
|
|
@@ -105,7 +113,7 @@ class HTMLLanguage {
|
|
|
105
113
|
*/
|
|
106
114
|
createSourceCode(file, parseResult) {
|
|
107
115
|
return createHTMLSourceCode({
|
|
108
|
-
text: /**
|
|
116
|
+
text: /** @type {string} */ (file.body),
|
|
109
117
|
ast: parseResult.ast,
|
|
110
118
|
comments: parseResult.comments,
|
|
111
119
|
});
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import {
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* @import {
|
|
7
|
-
*
|
|
2
|
+
* @import {
|
|
3
|
+
* SourceCode,
|
|
4
|
+
* TraversalStep
|
|
5
|
+
* } from "@eslint/core"
|
|
6
|
+
* @import {
|
|
7
|
+
* DirectiveType,
|
|
8
|
+
* SourceLocation
|
|
9
|
+
* } from "@eslint/plugin-kit"
|
|
10
|
+
* @import {
|
|
11
|
+
* AnyHTMLNode,
|
|
12
|
+
* CommentContent
|
|
13
|
+
* } from "@html-eslint/types"
|
|
14
|
+
* @import {AST} from "eslint"
|
|
15
|
+
* @import {BaseNode} from "../types"
|
|
8
16
|
*/
|
|
9
17
|
const {
|
|
10
18
|
TextSourceCodeBase,
|
|
@@ -26,19 +34,13 @@ const INLINE_CONFIG =
|
|
|
26
34
|
const commentParser = new ConfigCommentParser();
|
|
27
35
|
|
|
28
36
|
class HTMLSourceCode extends TextSourceCodeBase {
|
|
29
|
-
/**
|
|
30
|
-
* @param {{ast: AST.Program, text: string, comments: CommentContent[]}} config
|
|
31
|
-
*/
|
|
37
|
+
/** @param {{ ast: AST.Program; text: string; comments: CommentContent[] }} config */
|
|
32
38
|
constructor({ ast, text, comments }) {
|
|
33
39
|
super({ ast, text });
|
|
34
40
|
|
|
35
|
-
/**
|
|
36
|
-
* @property
|
|
37
|
-
*/
|
|
41
|
+
/** @property */
|
|
38
42
|
this.ast = ast;
|
|
39
|
-
/**
|
|
40
|
-
* @property
|
|
41
|
-
*/
|
|
43
|
+
/** @property */
|
|
42
44
|
this.comments = comments;
|
|
43
45
|
this.parentsMap = new Map();
|
|
44
46
|
|
|
@@ -77,12 +79,14 @@ class HTMLSourceCode extends TextSourceCodeBase {
|
|
|
77
79
|
|
|
78
80
|
getDisableDirectives() {
|
|
79
81
|
/**
|
|
80
|
-
* @type {{
|
|
82
|
+
* @type {{
|
|
83
|
+
* ruleId: null | string;
|
|
84
|
+
* message: string;
|
|
85
|
+
* loc: SourceLocation;
|
|
86
|
+
* }[]}
|
|
81
87
|
*/
|
|
82
88
|
const problems = [];
|
|
83
|
-
/**
|
|
84
|
-
* @type {Directive[]}
|
|
85
|
-
*/
|
|
89
|
+
/** @type {Directive[]} */
|
|
86
90
|
const directives = [];
|
|
87
91
|
|
|
88
92
|
this.getInlineConfigNodes().forEach((comment) => {
|
|
@@ -126,13 +130,10 @@ class HTMLSourceCode extends TextSourceCodeBase {
|
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
traverse() {
|
|
129
|
-
/**
|
|
130
|
-
* @type {TraversalStep[]}
|
|
131
|
-
*/
|
|
133
|
+
/** @type {TraversalStep[]} */
|
|
132
134
|
const steps = [];
|
|
133
135
|
|
|
134
136
|
/**
|
|
135
|
-
*
|
|
136
137
|
* @param {AnyHTMLNode | AST.Program} node
|
|
137
138
|
* @param {AnyHTMLNode | AST.Program | null} parent
|
|
138
139
|
*/
|
|
@@ -184,10 +185,17 @@ class HTMLSourceCode extends TextSourceCodeBase {
|
|
|
184
185
|
}
|
|
185
186
|
}
|
|
186
187
|
/**
|
|
187
|
-
* @param {{ast: AST.Program
|
|
188
|
+
* @param {{ ast: AST.Program; text: string; comments: CommentContent[] }} config
|
|
188
189
|
* @returns {TextSourceCodeBase<any> & {
|
|
189
|
-
* getDisableDirectives(): {
|
|
190
|
-
*
|
|
190
|
+
* getDisableDirectives(): {
|
|
191
|
+
* problems: {
|
|
192
|
+
* ruleId: null | string;
|
|
193
|
+
* message: string;
|
|
194
|
+
* loc: SourceLocation;
|
|
195
|
+
* }[];
|
|
196
|
+
* directives: Directive[];
|
|
197
|
+
* };
|
|
198
|
+
* getInlineConfigNodes(): CommentContent[];
|
|
191
199
|
* }}
|
|
192
200
|
*/
|
|
193
201
|
function createHTMLSourceCode(config) {
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import {
|
|
3
|
-
* @import {
|
|
2
|
+
* @import {AnyHTMLNode} from "@html-eslint/types"
|
|
3
|
+
* @import {AST} from "eslint"
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const { VisitNodeStep } = require("@eslint/plugin-kit");
|
|
7
7
|
|
|
8
8
|
const STEP_PHASE = {
|
|
9
|
-
/**
|
|
10
|
-
* @type {1}
|
|
11
|
-
*/
|
|
9
|
+
/** @type {1} */
|
|
12
10
|
ENTER: 1,
|
|
13
|
-
/**
|
|
14
|
-
* @type {2}
|
|
15
|
-
*/
|
|
11
|
+
/** @type {2} */
|
|
16
12
|
EXIT: 2,
|
|
17
13
|
};
|
|
18
14
|
|
|
@@ -20,8 +16,8 @@ class HTMLTraversalStep extends VisitNodeStep {
|
|
|
20
16
|
/**
|
|
21
17
|
* @param {Object} options
|
|
22
18
|
* @param {AnyHTMLNode | AST.Program} options.target
|
|
23
|
-
* @param {1|2} options.phase
|
|
24
|
-
* @param {
|
|
19
|
+
* @param {1 | 2} options.phase
|
|
20
|
+
* @param {any[]} options.args
|
|
25
21
|
*/
|
|
26
22
|
constructor({ target, phase, args }) {
|
|
27
23
|
super({ target, phase, args });
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import {
|
|
3
|
-
*
|
|
2
|
+
* @import {
|
|
3
|
+
* RuleFixer,
|
|
4
|
+
* RuleModule
|
|
5
|
+
* } from "../types"
|
|
4
6
|
* @typedef {Object} MessageId
|
|
5
7
|
* @property {"closeStyleWrong"} CLOSE_STYLE_WRONG
|
|
6
8
|
* @property {"newlineMissing"} NEWLINE_MISSING
|
|
@@ -14,17 +16,13 @@ const { RULE_CATEGORY } = require("../constants");
|
|
|
14
16
|
const { createVisitors } = require("./utils/visitors");
|
|
15
17
|
const { getRuleUrl } = require("./utils/rule");
|
|
16
18
|
|
|
17
|
-
/**
|
|
18
|
-
* @type {MessageId}
|
|
19
|
-
*/
|
|
19
|
+
/** @type {MessageId} */
|
|
20
20
|
const MESSAGE_ID = {
|
|
21
21
|
CLOSE_STYLE_WRONG: "closeStyleWrong",
|
|
22
22
|
NEWLINE_MISSING: "newlineMissing",
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
* @type {RuleModule<[Option]>}
|
|
27
|
-
*/
|
|
25
|
+
/** @type {RuleModule<[Option]>} */
|
|
28
26
|
module.exports = {
|
|
29
27
|
meta: {
|
|
30
28
|
type: "code",
|
|
@@ -70,15 +68,21 @@ module.exports = {
|
|
|
70
68
|
if (!shouldBeMultiline) return;
|
|
71
69
|
|
|
72
70
|
/**
|
|
73
|
-
* This doesn't do any indentation, so the result will look silly.
|
|
71
|
+
* This doesn't do any indentation, so the result will look silly.
|
|
72
|
+
* Indentation should be covered by the `indent` rule
|
|
73
|
+
*
|
|
74
74
|
* @param {RuleFixer} fixer
|
|
75
75
|
*/
|
|
76
76
|
function fix(fixer) {
|
|
77
77
|
let expected = node.openStart.value;
|
|
78
78
|
for (const attr of node.attributes) {
|
|
79
79
|
expected += `\n${attr.key.value}`;
|
|
80
|
-
if (attr.
|
|
81
|
-
|
|
80
|
+
if (attr.value) {
|
|
81
|
+
const startWrapper = attr.startWrapper
|
|
82
|
+
? attr.startWrapper.value
|
|
83
|
+
: "";
|
|
84
|
+
const endWrapper = attr.endWrapper ? attr.endWrapper.value : "";
|
|
85
|
+
expected += `=${startWrapper}${attr.value.value}${endWrapper}`;
|
|
82
86
|
}
|
|
83
87
|
}
|
|
84
88
|
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/** @import {RuleModule} from "../types" */
|
|
2
|
+
|
|
3
|
+
const { NODE_TYPES } = require("@html-eslint/parser");
|
|
4
|
+
const { RULE_CATEGORY } = require("../constants");
|
|
5
|
+
const { createVisitors } = require("./utils/visitors");
|
|
6
|
+
const { getRuleUrl } = require("./utils/rule");
|
|
7
|
+
|
|
8
|
+
const MESSAGE_IDS = {
|
|
9
|
+
EXTRA_SPACING_START: "extraSpacingStart",
|
|
10
|
+
EXTRA_SPACING_END: "extraSpacingEnd",
|
|
11
|
+
EXTRA_SPACING_BETWEEN: "extraSpacingBetween",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const CLASS_BETWEEN_EXTRA_SPACES_REGEX = /\s{2,}/;
|
|
15
|
+
|
|
16
|
+
/** @type {RuleModule<[]>} */
|
|
17
|
+
module.exports = {
|
|
18
|
+
meta: {
|
|
19
|
+
type: "layout",
|
|
20
|
+
docs: {
|
|
21
|
+
description: "Disallow extra spacing in class attribute values",
|
|
22
|
+
recommended: false,
|
|
23
|
+
category: RULE_CATEGORY.STYLE,
|
|
24
|
+
url: getRuleUrl("class-spacing"),
|
|
25
|
+
},
|
|
26
|
+
fixable: "code",
|
|
27
|
+
schema: [],
|
|
28
|
+
messages: {
|
|
29
|
+
[MESSAGE_IDS.EXTRA_SPACING_START]:
|
|
30
|
+
"Unexpected space at the start of class attribute value",
|
|
31
|
+
[MESSAGE_IDS.EXTRA_SPACING_END]:
|
|
32
|
+
"Unexpected space at the end of class attribute value",
|
|
33
|
+
[MESSAGE_IDS.EXTRA_SPACING_BETWEEN]:
|
|
34
|
+
"Unexpected extra spaces between class names",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
create(context) {
|
|
39
|
+
return createVisitors(context, {
|
|
40
|
+
Attribute(node) {
|
|
41
|
+
if (node.key.value.toLowerCase() !== "class") {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const attributeValue = node.value;
|
|
46
|
+
if (
|
|
47
|
+
!attributeValue ||
|
|
48
|
+
!attributeValue.value ||
|
|
49
|
+
attributeValue.parts.some((part) => part.type === NODE_TYPES.Template)
|
|
50
|
+
) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const classValue = attributeValue.value;
|
|
55
|
+
const trimmedValue = classValue.trim();
|
|
56
|
+
|
|
57
|
+
if (
|
|
58
|
+
classValue === trimmedValue &&
|
|
59
|
+
!CLASS_BETWEEN_EXTRA_SPACES_REGEX.test(classValue)
|
|
60
|
+
) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const normalizedValue = trimmedValue.replace(/\s+/g, " ");
|
|
65
|
+
|
|
66
|
+
// Check for leading spaces
|
|
67
|
+
if (classValue !== trimmedValue && classValue.startsWith(" ")) {
|
|
68
|
+
context.report({
|
|
69
|
+
loc: {
|
|
70
|
+
start: {
|
|
71
|
+
line: attributeValue.loc.start.line,
|
|
72
|
+
column: attributeValue.loc.start.column,
|
|
73
|
+
},
|
|
74
|
+
end: {
|
|
75
|
+
line: attributeValue.loc.start.line,
|
|
76
|
+
column:
|
|
77
|
+
attributeValue.loc.start.column +
|
|
78
|
+
(classValue.length - classValue.trimStart().length),
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
messageId: MESSAGE_IDS.EXTRA_SPACING_START,
|
|
82
|
+
fix(fixer) {
|
|
83
|
+
return fixer.replaceTextRange(
|
|
84
|
+
attributeValue.range,
|
|
85
|
+
normalizedValue
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check for trailing spaces
|
|
93
|
+
if (classValue !== trimmedValue && classValue.endsWith(" ")) {
|
|
94
|
+
context.report({
|
|
95
|
+
loc: {
|
|
96
|
+
start: {
|
|
97
|
+
line: attributeValue.loc.start.line,
|
|
98
|
+
column:
|
|
99
|
+
attributeValue.loc.start.column + classValue.trimEnd().length,
|
|
100
|
+
},
|
|
101
|
+
end: {
|
|
102
|
+
line: attributeValue.loc.start.line,
|
|
103
|
+
column: attributeValue.loc.start.column + classValue.length,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
messageId: MESSAGE_IDS.EXTRA_SPACING_END,
|
|
107
|
+
fix(fixer) {
|
|
108
|
+
return fixer.replaceTextRange(
|
|
109
|
+
attributeValue.range,
|
|
110
|
+
normalizedValue
|
|
111
|
+
);
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Check for extra spaces between class names
|
|
118
|
+
if (CLASS_BETWEEN_EXTRA_SPACES_REGEX.test(classValue)) {
|
|
119
|
+
const match = classValue.match(CLASS_BETWEEN_EXTRA_SPACES_REGEX);
|
|
120
|
+
if (match && match.index !== undefined) {
|
|
121
|
+
context.report({
|
|
122
|
+
loc: {
|
|
123
|
+
start: {
|
|
124
|
+
line: attributeValue.loc.start.line,
|
|
125
|
+
column: attributeValue.loc.start.column + match.index,
|
|
126
|
+
},
|
|
127
|
+
end: {
|
|
128
|
+
line: attributeValue.loc.start.line,
|
|
129
|
+
column:
|
|
130
|
+
attributeValue.loc.start.column +
|
|
131
|
+
match.index +
|
|
132
|
+
match[0].length,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
messageId: MESSAGE_IDS.EXTRA_SPACING_BETWEEN,
|
|
136
|
+
fix(fixer) {
|
|
137
|
+
return fixer.replaceTextRange(
|
|
138
|
+
attributeValue.range,
|
|
139
|
+
normalizedValue
|
|
140
|
+
);
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
};
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import {
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* @import {
|
|
3
|
+
* AnyNode,
|
|
4
|
+
* CloseTag,
|
|
5
|
+
* OpenTagEnd,
|
|
6
|
+
* Text
|
|
7
|
+
* } from "@html-eslint/types"
|
|
8
|
+
* @import {
|
|
9
|
+
* Line,
|
|
10
|
+
* RuleModule
|
|
11
|
+
* } from "../types"
|
|
12
|
+
* @typedef {AnyNode | Line} AnyNodeOrLine
|
|
13
|
+
*
|
|
5
14
|
* @typedef {Object} Option
|
|
6
15
|
* @property {string[]} [Option.skip]
|
|
7
16
|
* @property {string[]} [Option.inline]
|
|
@@ -24,9 +33,7 @@ const MESSAGE_IDS = {
|
|
|
24
33
|
EXPECT_NEW_LINE_AFTER: "expectAfter",
|
|
25
34
|
};
|
|
26
35
|
|
|
27
|
-
/**
|
|
28
|
-
* @type {Object.<string, Array<string>>}
|
|
29
|
-
*/
|
|
36
|
+
/** @type {Object<string, string[]>} */
|
|
30
37
|
const PRESETS = {
|
|
31
38
|
// From https://developer.mozilla.org/en-US/docs/Web/HTML/Element#inline_text_semantics
|
|
32
39
|
$inline: `
|
|
@@ -64,9 +71,7 @@ wbr
|
|
|
64
71
|
.split(`\n`),
|
|
65
72
|
};
|
|
66
73
|
|
|
67
|
-
/**
|
|
68
|
-
* @type {RuleModule<[Option]>}
|
|
69
|
-
*/
|
|
74
|
+
/** @type {RuleModule<[Option]>} */
|
|
70
75
|
module.exports = {
|
|
71
76
|
meta: {
|
|
72
77
|
type: "code",
|
|
@@ -108,9 +113,7 @@ module.exports = {
|
|
|
108
113
|
|
|
109
114
|
create(context) {
|
|
110
115
|
const option = context.options[0] || {};
|
|
111
|
-
/**
|
|
112
|
-
* @type {string[]}
|
|
113
|
-
*/
|
|
116
|
+
/** @type {string[]} */
|
|
114
117
|
const skipTags = option.skip || ["pre", "code"];
|
|
115
118
|
const inlineTags = optionsOrPresets(option.inline || []);
|
|
116
119
|
|
|
@@ -119,9 +122,7 @@ module.exports = {
|
|
|
119
122
|
* @returns {Exclude<AnyNodeOrLine, Text>[]}
|
|
120
123
|
*/
|
|
121
124
|
function getChildrenToCheck(children) {
|
|
122
|
-
/**
|
|
123
|
-
* @type {Exclude<AnyNodeOrLine, Text>[]}
|
|
124
|
-
*/
|
|
125
|
+
/** @type {Exclude<AnyNodeOrLine, Text>[]} */
|
|
125
126
|
const childrenToCheck = [];
|
|
126
127
|
|
|
127
128
|
for (const child of children) {
|
|
@@ -224,9 +225,7 @@ module.exports = {
|
|
|
224
225
|
|
|
225
226
|
childrenToCheck.forEach((child) => {
|
|
226
227
|
if (isTag(child)) {
|
|
227
|
-
/**
|
|
228
|
-
* @type {[OpenTagEnd, CloseTag] | undefined}
|
|
229
|
-
*/
|
|
228
|
+
/** @type {[OpenTagEnd, CloseTag] | undefined} */
|
|
230
229
|
const wrapper = child.close
|
|
231
230
|
? [child.openEnd, child.close]
|
|
232
231
|
: undefined;
|
|
@@ -298,9 +297,7 @@ module.exports = {
|
|
|
298
297
|
return `<${node.type}>`;
|
|
299
298
|
}
|
|
300
299
|
|
|
301
|
-
/**
|
|
302
|
-
* @param {Array<string>} options
|
|
303
|
-
*/
|
|
300
|
+
/** @param {string[]} options */
|
|
304
301
|
function optionsOrPresets(options) {
|
|
305
302
|
const result = [];
|
|
306
303
|
for (const option of options) {
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import {
|
|
3
|
-
*
|
|
2
|
+
* @import {
|
|
3
|
+
* ScriptTag,
|
|
4
|
+
* StyleTag,
|
|
5
|
+
* Tag
|
|
6
|
+
* } from "@html-eslint/types"
|
|
7
|
+
* @import {RuleModule} from "../types"
|
|
8
|
+
* @typedef {"camelCase"
|
|
9
|
+
* | "snake_case"
|
|
10
|
+
* | "PascalCase"
|
|
11
|
+
* | "kebab-case"
|
|
12
|
+
* | "regex"} Option1
|
|
13
|
+
*
|
|
4
14
|
*
|
|
5
|
-
* @typedef {"camelCase" | "snake_case" | "PascalCase" | "kebab-case" | "regex"} Option1
|
|
6
15
|
* @typedef {Object} Option2
|
|
7
16
|
* @property {string} pattern
|
|
8
17
|
* @property {string} [flags]
|
|
@@ -38,9 +47,7 @@ const CONVENTION_CHECKERS = {
|
|
|
38
47
|
[CONVENTIONS.KEBAB_CASE]: isKebabCase,
|
|
39
48
|
};
|
|
40
49
|
|
|
41
|
-
/**
|
|
42
|
-
* @type {RuleModule<[Option1, Option2]>}
|
|
43
|
-
*/
|
|
50
|
+
/** @type {RuleModule<[Option1, Option2]>} */
|
|
44
51
|
module.exports = {
|
|
45
52
|
meta: {
|
|
46
53
|
type: "code",
|
|
@@ -87,9 +94,7 @@ module.exports = {
|
|
|
87
94
|
).test(name)
|
|
88
95
|
: CONVENTION_CHECKERS[convention];
|
|
89
96
|
|
|
90
|
-
/**
|
|
91
|
-
* @param {Tag | ScriptTag | StyleTag} node
|
|
92
|
-
*/
|
|
97
|
+
/** @param {Tag | ScriptTag | StyleTag} node */
|
|
93
98
|
function check(node) {
|
|
94
99
|
if (isAttributesEmpty(node)) {
|
|
95
100
|
return;
|
|
@@ -112,9 +117,7 @@ module.exports = {
|
|
|
112
117
|
}
|
|
113
118
|
}
|
|
114
119
|
|
|
115
|
-
/**
|
|
116
|
-
* @param {Tag | ScriptTag | StyleTag} node
|
|
117
|
-
*/
|
|
120
|
+
/** @param {Tag | ScriptTag | StyleTag} node */
|
|
118
121
|
function checkInTemplate(node) {
|
|
119
122
|
if (isAttributesEmpty(node)) {
|
|
120
123
|
return;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import {AnyNode} from "@html-eslint/types"
|
|
3
|
-
* @typedef {{ [key in AnyNode[
|
|
2
|
+
* @import {AnyNode} from "@html-eslint/types"
|
|
3
|
+
* @typedef {{ [key in AnyNode["type"]]?: number }} IncLevelOptions
|
|
4
|
+
*
|
|
4
5
|
* @typedef {(node: AnyNode) => number} GetIncreasingLevel
|
|
5
6
|
*/
|
|
6
7
|
|
|
@@ -11,48 +12,40 @@ class IndentLevel {
|
|
|
11
12
|
*/
|
|
12
13
|
constructor(config) {
|
|
13
14
|
/**
|
|
14
|
-
* @member
|
|
15
15
|
* @private
|
|
16
|
+
* @member
|
|
16
17
|
* @type {number}
|
|
17
18
|
*/
|
|
18
19
|
this.level = -1;
|
|
19
20
|
/**
|
|
20
|
-
* @member
|
|
21
21
|
* @private
|
|
22
|
+
* @member
|
|
22
23
|
* @type {number}
|
|
23
24
|
*/
|
|
24
25
|
this.baseLevel = 0;
|
|
25
26
|
/**
|
|
26
|
-
* @member
|
|
27
27
|
* @private
|
|
28
|
+
* @member
|
|
28
29
|
*/
|
|
29
30
|
this.getInc = config.getIncreasingLevel;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
/**
|
|
33
|
-
* @returns {number}
|
|
34
|
-
*/
|
|
33
|
+
/** @returns {number} */
|
|
35
34
|
value() {
|
|
36
35
|
return this.level + this.baseLevel;
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
/**
|
|
40
|
-
* @param {AnyNode} node
|
|
41
|
-
*/
|
|
38
|
+
/** @param {AnyNode} node */
|
|
42
39
|
indent(node) {
|
|
43
40
|
this.level += this.getInc(node);
|
|
44
41
|
}
|
|
45
42
|
|
|
46
|
-
/**
|
|
47
|
-
* @param {AnyNode} node
|
|
48
|
-
*/
|
|
43
|
+
/** @param {AnyNode} node */
|
|
49
44
|
dedent(node) {
|
|
50
45
|
this.level -= this.getInc(node);
|
|
51
46
|
}
|
|
52
47
|
|
|
53
|
-
/**
|
|
54
|
-
* @param {number} base
|
|
55
|
-
*/
|
|
48
|
+
/** @param {number} base */
|
|
56
49
|
setBase(base) {
|
|
57
50
|
this.baseLevel = base;
|
|
58
51
|
}
|