@html-eslint/eslint-plugin 0.44.0 → 0.45.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/lib/rules/id-naming-convention.js +1 -1
- package/lib/rules/indent/indent.js +4 -3
- package/lib/rules/index.js +2 -0
- package/lib/rules/lowercase.js +1 -1
- package/lib/rules/max-element-depth.js +1 -1
- package/lib/rules/no-abstract-roles.js +1 -1
- package/lib/rules/no-accesskey-attrs.js +1 -1
- package/lib/rules/no-aria-hidden-body.js +1 -1
- package/lib/rules/no-duplicate-attrs.js +1 -1
- package/lib/rules/no-duplicate-class.js +1 -1
- package/lib/rules/no-duplicate-id.js +1 -1
- package/lib/rules/no-extra-spacing-attrs.js +1 -1
- package/lib/rules/no-ineffective-attrs.js +184 -0
- package/lib/rules/no-obsolete-tags.js +1 -1
- package/lib/rules/require-attrs.js +9 -3
- package/lib/rules/require-button-type.js +2 -1
- package/lib/rules/require-doctype.js +1 -1
- package/lib/rules/require-img-alt.js +1 -1
- package/lib/rules/require-lang.js +1 -1
- package/lib/rules/require-li-container.js +1 -1
- package/lib/rules/require-meta-charset.js +1 -1
- package/lib/rules/require-meta-viewport.js +1 -1
- package/lib/rules/require-open-graph-protocol.js +1 -1
- package/lib/rules/require-title.js +1 -1
- package/lib/rules/sort-attrs.js +61 -7
- package/lib/rules/utils/baseline.js +4 -2
- package/lib/rules/utils/node.js +12 -0
- package/package.json +6 -6
- package/types/rules/indent/indent.d.ts.map +1 -1
- package/types/rules/no-ineffective-attrs.d.ts +14 -0
- package/types/rules/no-ineffective-attrs.d.ts.map +1 -0
- package/types/rules/require-attrs.d.ts +1 -0
- package/types/rules/require-attrs.d.ts.map +1 -1
- package/types/rules/sort-attrs.d.ts +3 -1
- package/types/rules/sort-attrs.d.ts.map +1 -1
- package/types/rules/utils/baseline.d.ts.map +1 -1
- package/types/rules/utils/node.d.ts +6 -0
- package/types/rules/utils/node.d.ts.map +1 -1
|
@@ -46,7 +46,7 @@ module.exports = {
|
|
|
46
46
|
type: "code",
|
|
47
47
|
|
|
48
48
|
docs: {
|
|
49
|
-
description: "Enforce consistent naming id attributes",
|
|
49
|
+
description: "Enforce consistent naming of id attributes",
|
|
50
50
|
category: RULE_CATEGORY.STYLE,
|
|
51
51
|
recommended: false,
|
|
52
52
|
url: getRuleUrl("id-naming-convention"),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import {AnyNode, Tag, TemplateText, TemplateLiteral, OpenTemplate, CloseTemplate, ScriptTag, StyleTag} from "@html-eslint/types";
|
|
2
|
+
* @import {AnyNode, Tag, TemplateText, TemplateLiteral, OpenTemplate, CloseTemplate, ScriptTag, StyleTag, Text} from "@html-eslint/types";
|
|
3
3
|
* @import {Line, RuleListener, Context, RuleModule} from "../../types";
|
|
4
4
|
* @import {AST} from "eslint";
|
|
5
5
|
*
|
|
@@ -30,6 +30,7 @@ const {
|
|
|
30
30
|
hasTemplate,
|
|
31
31
|
isScript,
|
|
32
32
|
isStyle,
|
|
33
|
+
isText,
|
|
33
34
|
} = require("../utils/node");
|
|
34
35
|
const {
|
|
35
36
|
shouldCheckTaggedTemplateExpression,
|
|
@@ -112,7 +113,7 @@ module.exports = {
|
|
|
112
113
|
const { indentType, indentSize, indentChar } = getIndentOptionInfo(context);
|
|
113
114
|
|
|
114
115
|
/**
|
|
115
|
-
* @param {Tag | ScriptTag | StyleTag} node
|
|
116
|
+
* @param {Tag | ScriptTag | StyleTag | Text} node
|
|
116
117
|
* @return {number}
|
|
117
118
|
*/
|
|
118
119
|
function getTagIncreasingLevel(node) {
|
|
@@ -141,7 +142,7 @@ module.exports = {
|
|
|
141
142
|
if (isLine(node)) {
|
|
142
143
|
return 1;
|
|
143
144
|
}
|
|
144
|
-
if (isTag(node) || isScript(node) || isStyle(node)) {
|
|
145
|
+
if (isTag(node) || isScript(node) || isStyle(node) || isText(node)) {
|
|
145
146
|
return getTagIncreasingLevel(node);
|
|
146
147
|
}
|
|
147
148
|
const type = node.type;
|
package/lib/rules/index.js
CHANGED
|
@@ -51,6 +51,7 @@ const noDuplicateClass = require("./no-duplicate-class");
|
|
|
51
51
|
const noEmptyHeadings = require("./no-empty-headings");
|
|
52
52
|
const noInvalidEntity = require("./no-invalid-entity");
|
|
53
53
|
const noDuplicateInHead = require("./no-duplicate-in-head");
|
|
54
|
+
const noIneffectiveAttrs = require("./no-ineffective-attrs");
|
|
54
55
|
// import new rule here ↑
|
|
55
56
|
// DO NOT REMOVE THIS COMMENT
|
|
56
57
|
|
|
@@ -108,6 +109,7 @@ const rules = {
|
|
|
108
109
|
"no-empty-headings": noEmptyHeadings,
|
|
109
110
|
"no-invalid-entity": noInvalidEntity,
|
|
110
111
|
"no-duplicate-in-head": noDuplicateInHead,
|
|
112
|
+
"no-ineffective-attrs": noIneffectiveAttrs,
|
|
111
113
|
// export new rule here ↑
|
|
112
114
|
// DO NOT REMOVE THIS COMMENT
|
|
113
115
|
};
|
package/lib/rules/lowercase.js
CHANGED
|
@@ -21,7 +21,7 @@ module.exports = {
|
|
|
21
21
|
type: "suggestion",
|
|
22
22
|
|
|
23
23
|
docs: {
|
|
24
|
-
description: "Enforce
|
|
24
|
+
description: "Enforce use of lowercase for tag and attribute names.",
|
|
25
25
|
category: RULE_CATEGORY.STYLE,
|
|
26
26
|
recommended: false,
|
|
27
27
|
url: getRuleUrl("lowercase"),
|
|
@@ -20,7 +20,7 @@ module.exports = {
|
|
|
20
20
|
type: "code",
|
|
21
21
|
|
|
22
22
|
docs: {
|
|
23
|
-
description: "Disallow
|
|
23
|
+
description: "Disallow use of accesskey attribute",
|
|
24
24
|
category: RULE_CATEGORY.ACCESSIBILITY,
|
|
25
25
|
recommended: false,
|
|
26
26
|
url: getRuleUrl("no-accesskey-attrs"),
|
|
@@ -20,7 +20,7 @@ module.exports = {
|
|
|
20
20
|
|
|
21
21
|
docs: {
|
|
22
22
|
description:
|
|
23
|
-
"Disallow
|
|
23
|
+
"Disallow use of aria-hidden attributes on the `body` element.",
|
|
24
24
|
category: RULE_CATEGORY.ACCESSIBILITY,
|
|
25
25
|
recommended: false,
|
|
26
26
|
url: getRuleUrl("no-aria-hidden-body"),
|
|
@@ -21,7 +21,7 @@ module.exports = {
|
|
|
21
21
|
type: "code",
|
|
22
22
|
|
|
23
23
|
docs: {
|
|
24
|
-
description: "Disallow
|
|
24
|
+
description: "Disallow duplicate attributes",
|
|
25
25
|
category: RULE_CATEGORY.BEST_PRACTICE,
|
|
26
26
|
recommended: true,
|
|
27
27
|
url: getRuleUrl("no-duplicate-attrs"),
|
|
@@ -23,7 +23,7 @@ module.exports = {
|
|
|
23
23
|
meta: {
|
|
24
24
|
type: "code",
|
|
25
25
|
docs: {
|
|
26
|
-
description: "Disallow
|
|
26
|
+
description: "Disallow duplicate class names",
|
|
27
27
|
category: RULE_CATEGORY.BEST_PRACTICE,
|
|
28
28
|
recommended: false,
|
|
29
29
|
url: getRuleUrl("no-duplicate-class"),
|
|
@@ -36,7 +36,7 @@ module.exports = {
|
|
|
36
36
|
type: "code",
|
|
37
37
|
|
|
38
38
|
docs: {
|
|
39
|
-
description: "Disallow
|
|
39
|
+
description: "Disallow extra spacing around attributes",
|
|
40
40
|
category: RULE_CATEGORY.STYLE,
|
|
41
41
|
recommended: true,
|
|
42
42
|
url: getRuleUrl("no-extra-spacing-attrs"),
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import {RuleModule} from "../types";
|
|
3
|
+
* @import {Tag, ScriptTag} from "@html-eslint/types"
|
|
4
|
+
* @typedef {{ attr: string; when: (node: Tag | ScriptTag) => boolean; message: string; }} AttributeChecker
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { RULE_CATEGORY } = require("../constants");
|
|
8
|
+
const { hasAttr, hasTemplate, findAttr } = require("./utils/node");
|
|
9
|
+
const { createVisitors } = require("./utils/visitors");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {Tag | ScriptTag} node
|
|
13
|
+
* @param {string} attrName
|
|
14
|
+
* @returns {string | undefined}
|
|
15
|
+
*/
|
|
16
|
+
function getAttrValue(node, attrName) {
|
|
17
|
+
const attr = node.attributes.find(
|
|
18
|
+
(a) => a.type === "Attribute" && a.key.value === attrName
|
|
19
|
+
);
|
|
20
|
+
if (!attr || !attr.value) return undefined;
|
|
21
|
+
return attr.value.value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {Tag | ScriptTag} node
|
|
26
|
+
* @param {string} attrName
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
function isTemplateValueAttr(node, attrName) {
|
|
30
|
+
const attr = findAttr(node, attrName);
|
|
31
|
+
if (!attr || !attr.value) return false;
|
|
32
|
+
return hasTemplate(attr.value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @type {Record<string, AttributeChecker[]>}
|
|
37
|
+
*/
|
|
38
|
+
const checkersByTag = {
|
|
39
|
+
input: [
|
|
40
|
+
{
|
|
41
|
+
attr: "multiple",
|
|
42
|
+
when: (node) => {
|
|
43
|
+
const type = getAttrValue(node, "type") || "text";
|
|
44
|
+
return [
|
|
45
|
+
"text",
|
|
46
|
+
"password",
|
|
47
|
+
"radio",
|
|
48
|
+
"checkbox",
|
|
49
|
+
"image",
|
|
50
|
+
"hidden",
|
|
51
|
+
"reset",
|
|
52
|
+
"button",
|
|
53
|
+
].includes(type);
|
|
54
|
+
},
|
|
55
|
+
message: 'The "multiple" attribute has no effect on this input type.',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
attr: "accept",
|
|
59
|
+
when: (node) => {
|
|
60
|
+
if (isTemplateValueAttr(node, "type")) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const type = getAttrValue(node, "type") || "text";
|
|
64
|
+
return type !== "file";
|
|
65
|
+
},
|
|
66
|
+
message:
|
|
67
|
+
'The "accept" attribute has no effect unless input type is "file".',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
attr: "readonly",
|
|
71
|
+
when: (node) => {
|
|
72
|
+
const type = getAttrValue(node, "type") || "text";
|
|
73
|
+
return ["checkbox", "radio", "file", "range", "color"].includes(type);
|
|
74
|
+
},
|
|
75
|
+
message: 'The "readonly" attribute has no effect on this input type.',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
script: [
|
|
79
|
+
{
|
|
80
|
+
attr: "defer",
|
|
81
|
+
when: (node) => !hasAttr(node, "src"),
|
|
82
|
+
message: 'The "defer" attribute has no effect on inline scripts.',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
attr: "async",
|
|
86
|
+
when: (node) => !hasAttr(node, "src"),
|
|
87
|
+
message: 'The "async" attribute has no effect on inline scripts.',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
a: [
|
|
91
|
+
{
|
|
92
|
+
attr: "download",
|
|
93
|
+
when: (node) => !hasAttr(node, "href"),
|
|
94
|
+
message: 'The "download" attribute has no effect without an "href".',
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
audio: [
|
|
98
|
+
{
|
|
99
|
+
attr: "controlslist",
|
|
100
|
+
when: (node) => !hasAttr(node, "controls"),
|
|
101
|
+
message: 'The "controlslist" attribute has no effect without "controls".',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
video: [
|
|
105
|
+
{
|
|
106
|
+
attr: "controlslist",
|
|
107
|
+
when: (node) => !hasAttr(node, "controls"),
|
|
108
|
+
message: 'The "controlslist" attribute has no effect without "controls".',
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @type {RuleModule<[]>}
|
|
115
|
+
*/
|
|
116
|
+
module.exports = {
|
|
117
|
+
name: "no-ineffective-attrs",
|
|
118
|
+
meta: {
|
|
119
|
+
docs: {
|
|
120
|
+
description:
|
|
121
|
+
"Disallow HTML attributes that have no effect in their context",
|
|
122
|
+
category: RULE_CATEGORY.BEST_PRACTICE,
|
|
123
|
+
recommended: false,
|
|
124
|
+
},
|
|
125
|
+
messages: {
|
|
126
|
+
ineffective: "{{ message }}",
|
|
127
|
+
},
|
|
128
|
+
schema: [],
|
|
129
|
+
type: "problem",
|
|
130
|
+
},
|
|
131
|
+
defaultOptions: [],
|
|
132
|
+
create(context) {
|
|
133
|
+
return createVisitors(context, {
|
|
134
|
+
/**
|
|
135
|
+
* @param {Tag} node
|
|
136
|
+
*/
|
|
137
|
+
Tag(node) {
|
|
138
|
+
const tagCheckers = checkersByTag[node.name];
|
|
139
|
+
if (!tagCheckers) return;
|
|
140
|
+
|
|
141
|
+
for (const check of tagCheckers) {
|
|
142
|
+
for (const attr of node.attributes) {
|
|
143
|
+
if (attr.type !== "Attribute") continue;
|
|
144
|
+
if (attr.key.value !== check.attr) continue;
|
|
145
|
+
|
|
146
|
+
if (check.when(node)) {
|
|
147
|
+
context.report({
|
|
148
|
+
loc: attr.loc,
|
|
149
|
+
messageId: "ineffective",
|
|
150
|
+
data: {
|
|
151
|
+
message: check.message,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
/**
|
|
159
|
+
* @param {ScriptTag} node
|
|
160
|
+
*/
|
|
161
|
+
ScriptTag(node) {
|
|
162
|
+
const scriptCheckers = checkersByTag.script;
|
|
163
|
+
if (!scriptCheckers) return;
|
|
164
|
+
|
|
165
|
+
for (const check of scriptCheckers) {
|
|
166
|
+
for (const attr of node.attributes) {
|
|
167
|
+
if (attr.type !== "Attribute") continue;
|
|
168
|
+
if (attr.key.value !== check.attr) continue;
|
|
169
|
+
|
|
170
|
+
if (check.when(node)) {
|
|
171
|
+
context.report({
|
|
172
|
+
loc: attr.loc,
|
|
173
|
+
messageId: "ineffective",
|
|
174
|
+
data: {
|
|
175
|
+
message: check.message,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
};
|
|
@@ -20,7 +20,7 @@ module.exports = {
|
|
|
20
20
|
type: "code",
|
|
21
21
|
|
|
22
22
|
docs: {
|
|
23
|
-
description: "Disallow
|
|
23
|
+
description: "Disallow use of obsolete elements in HTML5",
|
|
24
24
|
category: RULE_CATEGORY.BEST_PRACTICE,
|
|
25
25
|
recommended: true,
|
|
26
26
|
url: getRuleUrl("no-obsolete-tags"),
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* @property {string} tag
|
|
7
7
|
* @property {string} attr
|
|
8
8
|
* @property {string} [value]
|
|
9
|
+
* @property {string} [message]
|
|
9
10
|
*
|
|
10
11
|
*/
|
|
11
12
|
|
|
@@ -41,6 +42,7 @@ module.exports = {
|
|
|
41
42
|
tag: { type: "string" },
|
|
42
43
|
attr: { type: "string" },
|
|
43
44
|
value: { type: "string" },
|
|
45
|
+
message: { type: "string" },
|
|
44
46
|
},
|
|
45
47
|
required: ["tag", "attr"],
|
|
46
48
|
additionalProperties: false,
|
|
@@ -59,7 +61,7 @@ module.exports = {
|
|
|
59
61
|
*/
|
|
60
62
|
const options = context.options || [];
|
|
61
63
|
/**
|
|
62
|
-
* @type {Map<string, { tag: string, attr: string, value?: string}[]>}
|
|
64
|
+
* @type {Map<string, { tag: string, attr: string, value?: string, message?: string}[]>}
|
|
63
65
|
*/
|
|
64
66
|
const tagOptionsMap = new Map();
|
|
65
67
|
|
|
@@ -90,7 +92,9 @@ module.exports = {
|
|
|
90
92
|
);
|
|
91
93
|
if (!attr) {
|
|
92
94
|
context.report({
|
|
93
|
-
|
|
95
|
+
...(option.message
|
|
96
|
+
? { message: option.message }
|
|
97
|
+
: { messageId: MESSAGE_IDS.MISSING }),
|
|
94
98
|
node,
|
|
95
99
|
data: {
|
|
96
100
|
attr: attrName,
|
|
@@ -111,7 +115,9 @@ module.exports = {
|
|
|
111
115
|
(!attr.value || attr.value.value !== option.value)
|
|
112
116
|
) {
|
|
113
117
|
context.report({
|
|
114
|
-
|
|
118
|
+
...(option.message
|
|
119
|
+
? { message: option.message }
|
|
120
|
+
: { messageId: MESSAGE_IDS.UNEXPECTED }),
|
|
115
121
|
node: attr,
|
|
116
122
|
data: {
|
|
117
123
|
attr: attrName,
|
|
@@ -26,7 +26,8 @@ module.exports = {
|
|
|
26
26
|
type: "code",
|
|
27
27
|
|
|
28
28
|
docs: {
|
|
29
|
-
description:
|
|
29
|
+
description:
|
|
30
|
+
"Require use of the button element with a valid type attribute.",
|
|
30
31
|
category: RULE_CATEGORY.BEST_PRACTICE,
|
|
31
32
|
recommended: false,
|
|
32
33
|
url: getRuleUrl("require-button-type"),
|
|
@@ -23,7 +23,7 @@ module.exports = {
|
|
|
23
23
|
type: "suggestion",
|
|
24
24
|
|
|
25
25
|
docs: {
|
|
26
|
-
description: "Require `alt` attribute
|
|
26
|
+
description: "Require `alt` attribute on `<img>` tag",
|
|
27
27
|
category: RULE_CATEGORY.ACCESSIBILITY,
|
|
28
28
|
recommended: true,
|
|
29
29
|
url: getRuleUrl("require-img-alt"),
|
|
@@ -20,7 +20,7 @@ module.exports = {
|
|
|
20
20
|
type: "code",
|
|
21
21
|
|
|
22
22
|
docs: {
|
|
23
|
-
description: "Enforce `<li>` to be in
|
|
23
|
+
description: "Enforce `<li>` to be in `<ul>`, `<ol>` or `<menu>`.",
|
|
24
24
|
category: RULE_CATEGORY.BEST_PRACTICE,
|
|
25
25
|
recommended: true,
|
|
26
26
|
url: getRuleUrl("require-li-container"),
|
|
@@ -29,7 +29,7 @@ module.exports = {
|
|
|
29
29
|
type: "code",
|
|
30
30
|
|
|
31
31
|
docs: {
|
|
32
|
-
description: 'Enforce
|
|
32
|
+
description: 'Enforce use of `<meta charset="...">` in `<head>`',
|
|
33
33
|
category: RULE_CATEGORY.BEST_PRACTICE,
|
|
34
34
|
recommended: false,
|
|
35
35
|
url: getRuleUrl("require-meta-charset"),
|
|
@@ -37,7 +37,7 @@ module.exports = {
|
|
|
37
37
|
type: "code",
|
|
38
38
|
|
|
39
39
|
docs: {
|
|
40
|
-
description: 'Enforce
|
|
40
|
+
description: 'Enforce use of `<meta name="viewport">` in `<head>`',
|
|
41
41
|
category: RULE_CATEGORY.ACCESSIBILITY,
|
|
42
42
|
recommended: false,
|
|
43
43
|
url: getRuleUrl("require-meta-viewport"),
|
|
@@ -41,7 +41,7 @@ module.exports = {
|
|
|
41
41
|
|
|
42
42
|
docs: {
|
|
43
43
|
description:
|
|
44
|
-
"Enforce
|
|
44
|
+
"Enforce use of specified meta tags for open graph protocol.",
|
|
45
45
|
category: RULE_CATEGORY.SEO,
|
|
46
46
|
recommended: false,
|
|
47
47
|
url: getRuleUrl("require-open-graph-protocol"),
|
|
@@ -37,7 +37,7 @@ module.exports = {
|
|
|
37
37
|
type: "code",
|
|
38
38
|
|
|
39
39
|
docs: {
|
|
40
|
-
description: "Require `<title
|
|
40
|
+
description: "Require `<title>` in the `<head>`",
|
|
41
41
|
category: RULE_CATEGORY.SEO,
|
|
42
42
|
recommended: true,
|
|
43
43
|
url: getRuleUrl("require-title"),
|
package/lib/rules/sort-attrs.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @import {RuleFixer, RuleModule} from "../types";
|
|
4
4
|
*
|
|
5
5
|
* @typedef {Object} Option
|
|
6
|
-
* @property {string
|
|
6
|
+
* @property {Array<string | {pattern: string}>} [Option.priority]
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const { hasTemplate } = require("./utils/node");
|
|
@@ -37,9 +37,23 @@ module.exports = {
|
|
|
37
37
|
priority: {
|
|
38
38
|
type: "array",
|
|
39
39
|
items: {
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
oneOf: [
|
|
41
|
+
{
|
|
42
|
+
type: "string",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
pattern: {
|
|
48
|
+
type: "string",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
required: ["pattern"],
|
|
52
|
+
additionalProperties: false,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
42
55
|
},
|
|
56
|
+
uniqueItems: true,
|
|
43
57
|
},
|
|
44
58
|
},
|
|
45
59
|
additionalProperties: false,
|
|
@@ -55,9 +69,49 @@ module.exports = {
|
|
|
55
69
|
priority: ["id", "type", "class", "style"],
|
|
56
70
|
};
|
|
57
71
|
/**
|
|
58
|
-
* @type {string
|
|
72
|
+
* @type {Array<string | {pattern: string, regex: RegExp}>}
|
|
59
73
|
*/
|
|
60
|
-
const priority = option.priority || []
|
|
74
|
+
const priority = (option.priority || []).map((item) => {
|
|
75
|
+
if (item && typeof item === "object" && "pattern" in item) {
|
|
76
|
+
return {
|
|
77
|
+
...item,
|
|
78
|
+
regex: new RegExp(item.pattern, "u"),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return item;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {string} attrName
|
|
86
|
+
* @param {string | {pattern: string, regex: RegExp}} priorityItem
|
|
87
|
+
* @returns {boolean}
|
|
88
|
+
*/
|
|
89
|
+
function matchesPriority(attrName, priorityItem) {
|
|
90
|
+
if (typeof priorityItem === "string") {
|
|
91
|
+
return attrName === priorityItem;
|
|
92
|
+
}
|
|
93
|
+
if (
|
|
94
|
+
priorityItem &&
|
|
95
|
+
typeof priorityItem === "object" &&
|
|
96
|
+
priorityItem.regex
|
|
97
|
+
) {
|
|
98
|
+
return priorityItem.regex.test(attrName);
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {string} attrName
|
|
105
|
+
* @returns {number}
|
|
106
|
+
*/
|
|
107
|
+
function getPriorityIndex(attrName) {
|
|
108
|
+
for (let i = 0; i < priority.length; i++) {
|
|
109
|
+
if (matchesPriority(attrName, priority[i])) {
|
|
110
|
+
return i;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return -1;
|
|
114
|
+
}
|
|
61
115
|
|
|
62
116
|
/**
|
|
63
117
|
* @param {Attribute} attrA
|
|
@@ -68,8 +122,8 @@ module.exports = {
|
|
|
68
122
|
const keyA = attrA.key.value;
|
|
69
123
|
const keyB = attrB.key.value;
|
|
70
124
|
|
|
71
|
-
const keyAReservedValue =
|
|
72
|
-
const keyBReservedValue =
|
|
125
|
+
const keyAReservedValue = getPriorityIndex(keyA);
|
|
126
|
+
const keyBReservedValue = getPriorityIndex(keyB);
|
|
73
127
|
if (keyAReservedValue >= 0 && keyBReservedValue >= 0) {
|
|
74
128
|
return keyAReservedValue - keyBReservedValue;
|
|
75
129
|
} else if (keyAReservedValue >= 0) {
|
|
@@ -277,6 +277,7 @@ const elements = new Map([
|
|
|
277
277
|
["input.type_button", "10:2015"],
|
|
278
278
|
["input.type_checkbox", "10:2015"],
|
|
279
279
|
["input.type_color", "0:"],
|
|
280
|
+
["input.type_color.accepts_css_colors", "0:"],
|
|
280
281
|
["input.type_date", "10:2021"],
|
|
281
282
|
["input.type_datetime-local", "10:2021"],
|
|
282
283
|
["input.type_email", "10:2015"],
|
|
@@ -289,7 +290,8 @@ const elements = new Map([
|
|
|
289
290
|
["input.type_password.insecure_login_handling", "0:"],
|
|
290
291
|
["input.type_radio", "10:2015"],
|
|
291
292
|
["input.type_range", "10:2017"],
|
|
292
|
-
["input.type_range.
|
|
293
|
+
["input.type_range.labeled_values", "0:"],
|
|
294
|
+
["input.type_range.tick_marks", "10:2023"],
|
|
293
295
|
["input.type_range.vertical_orientation", "5:2024"],
|
|
294
296
|
["input.type_reset", "10:2015"],
|
|
295
297
|
["input.type_search", "10:2015"],
|
|
@@ -380,7 +382,7 @@ const elements = new Map([
|
|
|
380
382
|
["ol.start", "10:2015"],
|
|
381
383
|
["ol.type", "10:2015"],
|
|
382
384
|
["optgroup", "10:2015"],
|
|
383
|
-
["optgroup.disabled", "
|
|
385
|
+
["optgroup.disabled", "0:"],
|
|
384
386
|
["optgroup.label", "10:2015"],
|
|
385
387
|
["option", "10:2015"],
|
|
386
388
|
["option.disabled", "10:2015"],
|
package/lib/rules/utils/node.js
CHANGED
|
@@ -17,6 +17,17 @@ function findAttr(node, key) {
|
|
|
17
17
|
);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* @param {Tag | ScriptTag} node
|
|
22
|
+
* @param {string} attrName
|
|
23
|
+
* @returns {boolean}
|
|
24
|
+
*/
|
|
25
|
+
function hasAttr(node, attrName) {
|
|
26
|
+
return node.attributes.some(
|
|
27
|
+
(a) => a.type === "Attribute" && a.key.value === attrName
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
20
31
|
/**
|
|
21
32
|
* Checks whether a node's attributes is empty or not.
|
|
22
33
|
* @param {Tag | ScriptTag | StyleTag} node
|
|
@@ -254,4 +265,5 @@ module.exports = {
|
|
|
254
265
|
isRangesOverlap,
|
|
255
266
|
getTemplateTokens,
|
|
256
267
|
hasTemplate,
|
|
268
|
+
hasAttr,
|
|
257
269
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@html-eslint/eslint-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"description": "ESLint plugin for HTML",
|
|
6
6
|
"author": "yeonjuan",
|
|
@@ -40,16 +40,16 @@
|
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@eslint/plugin-kit": "^0.3.1",
|
|
43
|
-
"@html-eslint/parser": "^0.
|
|
44
|
-
"@html-eslint/template-parser": "^0.
|
|
45
|
-
"@html-eslint/template-syntax-parser": "^0.
|
|
43
|
+
"@html-eslint/parser": "^0.45.0",
|
|
44
|
+
"@html-eslint/template-parser": "^0.45.0",
|
|
45
|
+
"@html-eslint/template-syntax-parser": "^0.45.0"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"eslint": "^8.0.0 || ^9.0.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@eslint/core": "^0.14.0",
|
|
52
|
-
"@html-eslint/types": "^0.
|
|
52
|
+
"@html-eslint/types": "^0.45.0",
|
|
53
53
|
"@types/estree": "^0.0.47",
|
|
54
54
|
"es-html-parser": "0.3.0",
|
|
55
55
|
"eslint": "^9.27.0",
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"engines": {
|
|
60
60
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
|
61
61
|
},
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "39d3cc1773a4d999ba348ded5903d753a3f7d12c"
|
|
63
63
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indent.d.ts","sourceRoot":"","sources":["../../../lib/rules/indent/indent.js"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"indent.d.ts","sourceRoot":"","sources":["../../../lib/rules/indent/indent.js"],"names":[],"mappings":";;;wBAuDU,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;;qBAlD3B,OAAO,GAAG,IAAI;;SAEb,KAAK;WACL,OAAO;;;kBAEP,aAAa;;;gBAEb,UAAU,CAAC,KAAK,CAAC,GAAG,UAAU,CAAC,OAAO,CAAC;gBACvC,MAAM;gBACN,MAAM;;eAEP,KAAK,GAAG,MAAM;;;;;gCAd+B,aAAa;6BAD+C,oBAAoB;0BAChF,aAAa"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
declare namespace _exports {
|
|
2
|
+
export { AttributeChecker };
|
|
3
|
+
}
|
|
4
|
+
declare const _exports: RuleModule<[]>;
|
|
5
|
+
export = _exports;
|
|
6
|
+
type AttributeChecker = {
|
|
7
|
+
attr: string;
|
|
8
|
+
when: (node: Tag | ScriptTag) => boolean;
|
|
9
|
+
message: string;
|
|
10
|
+
};
|
|
11
|
+
import type { RuleModule } from "../types";
|
|
12
|
+
import type { Tag } from "@html-eslint/types";
|
|
13
|
+
import type { ScriptTag } from "@html-eslint/types";
|
|
14
|
+
//# sourceMappingURL=no-ineffective-attrs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"no-ineffective-attrs.d.ts","sourceRoot":"","sources":["../../lib/rules/no-ineffective-attrs.js"],"names":[],"mappings":";;;wBAiHU,WAAW,EAAE,CAAC;;wBA9GX;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,GAAG,SAAS,KAAK,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;CAAE;gCAF5D,UAAU;yBACN,oBAAoB;+BAApB,oBAAoB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"require-attrs.d.ts","sourceRoot":"","sources":["../../lib/rules/require-attrs.js"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"require-attrs.d.ts","sourceRoot":"","sources":["../../lib/rules/require-attrs.js"],"names":[],"mappings":";;;wBAuBU,WAAW,MAAM,EAAE,CAAC;;;SAlBhB,MAAM;UACN,MAAM;;;;gCAJ4B,UAAU"}
|
|
@@ -4,7 +4,9 @@ declare namespace _exports {
|
|
|
4
4
|
declare const _exports: RuleModule<[Option]>;
|
|
5
5
|
export = _exports;
|
|
6
6
|
type Option = {
|
|
7
|
-
priority?: string
|
|
7
|
+
priority?: (string | {
|
|
8
|
+
pattern: string;
|
|
9
|
+
})[] | undefined;
|
|
8
10
|
};
|
|
9
11
|
import type { RuleModule } from "../types";
|
|
10
12
|
//# sourceMappingURL=sort-attrs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sort-attrs.d.ts","sourceRoot":"","sources":["../../lib/rules/sort-attrs.js"],"names":[],"mappings":";;;wBAmBU,WAAW,CAAC,MAAM,CAAC,CAAC
|
|
1
|
+
{"version":3,"file":"sort-attrs.d.ts","sourceRoot":"","sources":["../../lib/rules/sort-attrs.js"],"names":[],"mappings":";;;wBAmBU,WAAW,CAAC,MAAM,CAAC,CAAC;;;;iBAdS,MAAM;;;gCAHL,UAAU"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../../lib/rules/utils/baseline.js"],"names":[],"mappings":"AAOA,
|
|
1
|
+
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../../lib/rules/utils/baseline.js"],"names":[],"mappings":"AAOA,2CAikBG;AACH,mDA4BG;AArmBH;;GAEG;AACH,4BAAsB,EAAE,CAAC;AACzB,2BAAqB,CAAC,CAAC;AACvB,6BAAuB,CAAC,CAAC"}
|
|
@@ -98,6 +98,12 @@ export function getTemplateTokens(tokens: AnyToken[]): ((CommentContent | Text)[
|
|
|
98
98
|
* @returns {boolean}
|
|
99
99
|
*/
|
|
100
100
|
export function hasTemplate(node: AttributeKey | AttributeValue | Text | CommentContent): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* @param {Tag | ScriptTag} node
|
|
103
|
+
* @param {string} attrName
|
|
104
|
+
* @returns {boolean}
|
|
105
|
+
*/
|
|
106
|
+
export function hasAttr(node: Tag | ScriptTag, attrName: string): boolean;
|
|
101
107
|
import type { Tag } from "@html-eslint/types";
|
|
102
108
|
import type { ScriptTag } from "@html-eslint/types";
|
|
103
109
|
import type { StyleTag } from "@html-eslint/types";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../lib/rules/utils/node.js"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,+BAJW,GAAG,GAAG,SAAS,GAAG,QAAQ,OAC1B,MAAM,GACJ,SAAS,GAAG,SAAS,CAMjC;
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../lib/rules/utils/node.js"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,+BAJW,GAAG,GAAG,SAAS,GAAG,QAAQ,OAC1B,MAAM,GACJ,SAAS,GAAG,SAAS,CAMjC;AAaD;;;;GAIG;AACH,wCAHW,GAAG,GAAG,SAAS,GAAG,QAAQ,GACxB,OAAO,CAInB;AAED;;;;GAIG;AACH,6CAHW,OAAO,GACL,OAAO,CAInB;AA+BD;;;;GAIG;AACH,uCAHW,IAAI,GAAG,cAAc,GACnB,IAAI,EAAE,CAwDlB;AAED;;;;;GAKG;AACH,sCAJW;IAAC,GAAG,EAAE,kBAAkB,CAAA;CAAC,SACzB;IAAC,GAAG,EAAE,kBAAkB,CAAA;CAAC,GACvB,kBAAkB,CAO9B;AA4DD;;;;GAIG;AACH,iCAJW,OAAO,aACP,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,GACxB,IAAI,GAAG,OAAO,CAiB1B;AA9ED;;;GAGG;AACH,4BAHW,QAAQ,GACN,IAAI,IAAI,GAAG,CAIvB;AAkBD;;;GAGG;AACH,gCAHW,QAAQ,GACN,IAAI,IAAI,OAAO,CAI3B;AAED;;;GAGG;AACH,6BAHW,QAAQ,GACN,IAAI,IAAI,IAAI,CAIxB;AAED;;;GAGG;AACH,6BAHW,QAAQ,GACN,IAAI,IAAI,IAAI,CAIxB;AAtCD;;;GAGG;AACH,+BAHW,QAAQ,GACN,IAAI,IAAI,SAAS,CAI7B;AAED;;;GAGG;AACH,8BAHW,QAAQ,GACN,IAAI,IAAI,QAAQ,CAI5B;AAnHD;;;;GAIG;AACH,8CAJW,CAAC,IAAI,GAAG,cAAc,CAAC,CAAC,OAAO,CAAC,SAChC,SAAS,GACP,OAAO,CAMnB;AAsID;;;GAGG;AACH,oCAHW,MAAM,GACJ,MAAM,EAAE,CAIpB;AA/JD;;;;;GAKG;AACH,wCAJW,SAAS,UACT,SAAS,GACP,OAAO,CAInB;AA+KD;;;;GAIG;AACH,0CAHW,QAAQ,EAAE,GACR,CAAC,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAaxD;AAlLD;;;GAGG;AACH,kCAHW,YAAY,GAAG,cAAc,GAAG,IAAI,GAAG,cAAc,GACnD,OAAO,CAInB;AAxDD;;;;GAIG;AACH,8BAJW,GAAG,GAAG,SAAS,YACf,MAAM,GACJ,OAAO,CAMnB;yBA3BqI,oBAAoB;+BAApB,oBAAoB;8BAApB,oBAAoB;+BAApB,oBAAoB;6BAApB,oBAAoB;0BAApB,oBAAoB;oCAApB,oBAAoB;0BACzH,aAAa;yBACxB,QAAQ;8BADG,aAAa;6BADwF,oBAAoB;8BAApB,oBAAoB;kCAApB,oBAAoB;oCAApB,oBAAoB"}
|