@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.
Files changed (38) hide show
  1. package/lib/rules/id-naming-convention.js +1 -1
  2. package/lib/rules/indent/indent.js +4 -3
  3. package/lib/rules/index.js +2 -0
  4. package/lib/rules/lowercase.js +1 -1
  5. package/lib/rules/max-element-depth.js +1 -1
  6. package/lib/rules/no-abstract-roles.js +1 -1
  7. package/lib/rules/no-accesskey-attrs.js +1 -1
  8. package/lib/rules/no-aria-hidden-body.js +1 -1
  9. package/lib/rules/no-duplicate-attrs.js +1 -1
  10. package/lib/rules/no-duplicate-class.js +1 -1
  11. package/lib/rules/no-duplicate-id.js +1 -1
  12. package/lib/rules/no-extra-spacing-attrs.js +1 -1
  13. package/lib/rules/no-ineffective-attrs.js +184 -0
  14. package/lib/rules/no-obsolete-tags.js +1 -1
  15. package/lib/rules/require-attrs.js +9 -3
  16. package/lib/rules/require-button-type.js +2 -1
  17. package/lib/rules/require-doctype.js +1 -1
  18. package/lib/rules/require-img-alt.js +1 -1
  19. package/lib/rules/require-lang.js +1 -1
  20. package/lib/rules/require-li-container.js +1 -1
  21. package/lib/rules/require-meta-charset.js +1 -1
  22. package/lib/rules/require-meta-viewport.js +1 -1
  23. package/lib/rules/require-open-graph-protocol.js +1 -1
  24. package/lib/rules/require-title.js +1 -1
  25. package/lib/rules/sort-attrs.js +61 -7
  26. package/lib/rules/utils/baseline.js +4 -2
  27. package/lib/rules/utils/node.js +12 -0
  28. package/package.json +6 -6
  29. package/types/rules/indent/indent.d.ts.map +1 -1
  30. package/types/rules/no-ineffective-attrs.d.ts +14 -0
  31. package/types/rules/no-ineffective-attrs.d.ts.map +1 -0
  32. package/types/rules/require-attrs.d.ts +1 -0
  33. package/types/rules/require-attrs.d.ts.map +1 -1
  34. package/types/rules/sort-attrs.d.ts +3 -1
  35. package/types/rules/sort-attrs.d.ts.map +1 -1
  36. package/types/rules/utils/baseline.d.ts.map +1 -1
  37. package/types/rules/utils/node.d.ts +6 -0
  38. 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;
@@ -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
  };
@@ -21,7 +21,7 @@ module.exports = {
21
21
  type: "suggestion",
22
22
 
23
23
  docs: {
24
- description: "Enforce to use lowercase for tag and attribute names.",
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"),
@@ -22,7 +22,7 @@ module.exports = {
22
22
  type: "code",
23
23
 
24
24
  docs: {
25
- description: "Enforce element maximum depth",
25
+ description: "Enforce maximum element depth",
26
26
  category: RULE_CATEGORY.BEST_PRACTICE,
27
27
  recommended: false,
28
28
  url: getRuleUrl("max-element-depth"),
@@ -35,7 +35,7 @@ module.exports = {
35
35
  type: "code",
36
36
 
37
37
  docs: {
38
- description: "Disallow to use of abstract roles",
38
+ description: "Disallow use of abstract roles",
39
39
  category: RULE_CATEGORY.ACCESSIBILITY,
40
40
  recommended: false,
41
41
  url: getRuleUrl("no-abstract-roles"),
@@ -20,7 +20,7 @@ module.exports = {
20
20
  type: "code",
21
21
 
22
22
  docs: {
23
- description: "Disallow to use of accesskey attribute",
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 to use aria-hidden attributes on the `body` element.",
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 to use duplicate attributes",
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 to use duplicate class",
26
+ description: "Disallow duplicate class names",
27
27
  category: RULE_CATEGORY.BEST_PRACTICE,
28
28
  recommended: false,
29
29
  url: getRuleUrl("no-duplicate-class"),
@@ -25,7 +25,7 @@ module.exports = {
25
25
  type: "code",
26
26
 
27
27
  docs: {
28
- description: "Disallow to use duplicate id",
28
+ description: "Disallow duplicate id attributes",
29
29
  category: RULE_CATEGORY.BEST_PRACTICE,
30
30
  recommended: true,
31
31
  url: getRuleUrl("no-duplicate-id"),
@@ -36,7 +36,7 @@ module.exports = {
36
36
  type: "code",
37
37
 
38
38
  docs: {
39
- description: "Disallow an extra spacing around attributes",
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 to use obsolete elements in HTML5",
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
- messageId: MESSAGE_IDS.MISSING,
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
- messageId: MESSAGE_IDS.UNEXPECTED,
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: "Require use of button element with a valid type attribute.",
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"),
@@ -17,7 +17,7 @@ module.exports = {
17
17
  type: "code",
18
18
 
19
19
  docs: {
20
- description: "Require `<!DOCTYPE HTML>` in html,",
20
+ description: "Require `<!DOCTYPE HTML>` in HTML",
21
21
  category: RULE_CATEGORY.BEST_PRACTICE,
22
22
  recommended: true,
23
23
  url: getRuleUrl("require-doctype"),
@@ -23,7 +23,7 @@ module.exports = {
23
23
  type: "suggestion",
24
24
 
25
25
  docs: {
26
- description: "Require `alt` attribute at `<img>` tag",
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: "Require `lang` attribute at `<html>` tag",
23
+ description: "Require `lang` attribute on `<html>` tag",
24
24
  category: RULE_CATEGORY.SEO,
25
25
  recommended: true,
26
26
  url: getRuleUrl("require-lang"),
@@ -20,7 +20,7 @@ module.exports = {
20
20
  type: "code",
21
21
 
22
22
  docs: {
23
- description: "Enforce `<li>` to be in `<ul>`, `<ol>` or `<menu>`.",
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 to use `<meta charset="...">` in `<head>`',
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 to use `<meta name="viewport">` in `<head>`',
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 to use specified meta tags for open graph protocol.",
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><title/>` in the `<head><head/>`",
40
+ description: "Require `<title>` in the `<head>`",
41
41
  category: RULE_CATEGORY.SEO,
42
42
  recommended: true,
43
43
  url: getRuleUrl("require-title"),
@@ -3,7 +3,7 @@
3
3
  * @import {RuleFixer, RuleModule} from "../types";
4
4
  *
5
5
  * @typedef {Object} Option
6
- * @property {string[]} [Option.priority]
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
- type: "string",
41
- uniqueItems: true,
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 = priority.indexOf(keyA);
72
- const keyBReservedValue = priority.indexOf(keyB);
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.tick_marks", "5:2023"],
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", "10:2015"],
385
+ ["optgroup.disabled", "0:"],
384
386
  ["optgroup.label", "10:2015"],
385
387
  ["option", "10:2015"],
386
388
  ["option.disabled", "10:2015"],
@@ -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.44.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.0",
44
- "@html-eslint/template-parser": "^0.44.0",
45
- "@html-eslint/template-syntax-parser": "^0.44.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.44.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": "e75bca7964df38596359bdaef5f539149a206f29"
62
+ "gitHead": "39d3cc1773a4d999ba348ded5903d753a3f7d12c"
63
63
  }
@@ -1 +1 @@
1
- {"version":3,"file":"indent.d.ts","sourceRoot":"","sources":["../../../lib/rules/indent/indent.js"],"names":[],"mappings":";;;wBAsDU,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;;qBAjD3B,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;6BADyC,oBAAoB;0BAC1E,aAAa"}
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"}
@@ -7,6 +7,7 @@ type Option = {
7
7
  tag: string;
8
8
  attr: string;
9
9
  value?: string | undefined;
10
+ message?: string | undefined;
10
11
  };
11
12
  import type { RuleModule } from "../types";
12
13
  //# sourceMappingURL=require-attrs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"require-attrs.d.ts","sourceRoot":"","sources":["../../lib/rules/require-attrs.js"],"names":[],"mappings":";;;wBAsBU,WAAW,MAAM,EAAE,CAAC;;;SAjBhB,MAAM;UACN,MAAM;;;gCAJ4B,UAAU"}
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[] | undefined;
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;;;;;gCAjBU,UAAU"}
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,2CA+jBG;AACH,mDA4BG;AAnmBH;;GAEG;AACH,4BAAsB,EAAE,CAAC;AACzB,2BAAqB,CAAC,CAAC;AACvB,6BAAuB,CAAC,CAAC"}
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;AAED;;;;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;yBA/DqI,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"}
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"}