@html-eslint/eslint-plugin 0.50.0 → 0.52.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 (173) hide show
  1. package/lib/configs/recommended.js +8 -2
  2. package/lib/index.js +2 -6
  3. package/lib/languages/html-language.js +23 -15
  4. package/lib/languages/html-source-code.js +34 -26
  5. package/lib/languages/html-traversal-step.js +6 -10
  6. package/lib/rules/attrs-newline.js +9 -9
  7. package/lib/rules/class-spacing.js +148 -0
  8. package/lib/rules/element-newline.js +18 -21
  9. package/lib/rules/id-naming-convention.js +15 -12
  10. package/lib/rules/indent/indent-level.js +10 -17
  11. package/lib/rules/indent/indent.js +51 -56
  12. package/lib/rules/index.js +7 -1
  13. package/lib/rules/lowercase.js +11 -23
  14. package/lib/rules/max-element-depth.js +8 -10
  15. package/lib/rules/no-abstract-roles.js +8 -8
  16. package/lib/rules/no-accesskey-attrs.js +8 -8
  17. package/lib/rules/no-aria-hidden-body.js +2 -6
  18. package/lib/rules/no-aria-hidden-on-focusable.js +4 -5
  19. package/lib/rules/no-duplicate-attrs.js +12 -8
  20. package/lib/rules/no-duplicate-class.js +8 -14
  21. package/lib/rules/no-duplicate-id.js +9 -15
  22. package/lib/rules/no-duplicate-in-head.js +10 -17
  23. package/lib/rules/no-empty-headings.js +7 -8
  24. package/lib/rules/no-extra-spacing-attrs.js +13 -8
  25. package/lib/rules/no-extra-spacing-text.js +11 -14
  26. package/lib/rules/no-heading-inside-button.js +2 -6
  27. package/lib/rules/no-ineffective-attrs.js +14 -15
  28. package/lib/rules/no-inline-styles.js +2 -6
  29. package/lib/rules/no-invalid-entity.js +4 -8
  30. package/lib/rules/no-invalid-role.js +3 -10
  31. package/lib/rules/no-multiple-empty-lines.js +7 -7
  32. package/lib/rules/no-multiple-h1.js +4 -8
  33. package/lib/rules/no-nested-interactive.js +5 -9
  34. package/lib/rules/no-non-scalable-viewport.js +2 -6
  35. package/lib/rules/no-obsolete-attrs.js +83 -0
  36. package/lib/rules/no-obsolete-tags.js +2 -6
  37. package/lib/rules/no-positive-tabindex.js +8 -8
  38. package/lib/rules/no-restricted-attr-values.js +18 -21
  39. package/lib/rules/no-restricted-attrs.js +18 -22
  40. package/lib/rules/no-restricted-tags.js +17 -20
  41. package/lib/rules/no-script-style-type.js +7 -6
  42. package/lib/rules/no-skip-heading-levels.js +4 -8
  43. package/lib/rules/no-target-blank.js +3 -6
  44. package/lib/rules/no-trailing-spaces.js +7 -6
  45. package/lib/rules/no-whitespace-only-children.js +113 -0
  46. package/lib/rules/prefer-https.js +11 -14
  47. package/lib/rules/quotes.js +13 -16
  48. package/lib/rules/require-attrs.js +18 -17
  49. package/lib/rules/require-button-type.js +6 -5
  50. package/lib/rules/require-closing-tags.js +9 -12
  51. package/lib/rules/require-doctype.js +2 -6
  52. package/lib/rules/require-explicit-size.js +2 -5
  53. package/lib/rules/require-form-method.js +2 -6
  54. package/lib/rules/require-frame-title.js +2 -6
  55. package/lib/rules/require-img-alt.js +3 -6
  56. package/lib/rules/require-input-label.js +3 -5
  57. package/lib/rules/require-lang.js +2 -6
  58. package/lib/rules/require-li-container.js +2 -6
  59. package/lib/rules/require-meta-charset.js +6 -5
  60. package/lib/rules/require-meta-description.js +6 -5
  61. package/lib/rules/require-meta-viewport.js +6 -5
  62. package/lib/rules/require-open-graph-protocol.js +7 -8
  63. package/lib/rules/require-title.js +7 -5
  64. package/lib/rules/sort-attrs.js +13 -19
  65. package/lib/rules/use-baseline.js +8 -6
  66. package/lib/rules/utils/baseline.js +4 -6
  67. package/lib/rules/utils/naming.js +14 -7
  68. package/lib/rules/utils/node.js +63 -29
  69. package/lib/rules/utils/rule.js +1 -4
  70. package/lib/rules/utils/settings.js +9 -3
  71. package/lib/rules/utils/source-code.js +2 -6
  72. package/lib/rules/utils/template-literal.js +16 -7
  73. package/lib/rules/utils/visitors.js +4 -1
  74. package/lib/specs/index.js +5 -0
  75. package/lib/specs/obsolete-attrs.js +604 -0
  76. package/lib/types/ast.ts +5 -2
  77. package/package.json +6 -6
  78. package/types/configs/recommended.d.ts +2 -1
  79. package/types/configs/recommended.d.ts.map +1 -1
  80. package/types/index.d.ts +2 -6
  81. package/types/index.d.ts.map +1 -1
  82. package/types/languages/html-language.d.ts +12 -10
  83. package/types/languages/html-language.d.ts.map +1 -1
  84. package/types/languages/html-source-code.d.ts +13 -6
  85. package/types/languages/html-source-code.d.ts.map +1 -1
  86. package/types/languages/html-traversal-step.d.ts +5 -5
  87. package/types/languages/html-traversal-step.d.ts.map +1 -1
  88. package/types/rules/attrs-newline.d.ts +1 -1
  89. package/types/rules/attrs-newline.d.ts.map +1 -1
  90. package/types/rules/class-spacing.d.ts +4 -0
  91. package/types/rules/class-spacing.d.ts.map +1 -0
  92. package/types/rules/element-newline.d.ts +3 -3
  93. package/types/rules/element-newline.d.ts.map +1 -1
  94. package/types/rules/id-naming-convention.d.ts.map +1 -1
  95. package/types/rules/indent/indent-level.d.ts +10 -17
  96. package/types/rules/indent/indent-level.d.ts.map +1 -1
  97. package/types/rules/indent/indent.d.ts.map +1 -1
  98. package/types/rules/lowercase.d.ts.map +1 -1
  99. package/types/rules/max-element-depth.d.ts.map +1 -1
  100. package/types/rules/no-abstract-roles.d.ts.map +1 -1
  101. package/types/rules/no-accesskey-attrs.d.ts.map +1 -1
  102. package/types/rules/no-aria-hidden-body.d.ts.map +1 -1
  103. package/types/rules/no-aria-hidden-on-focusable.d.ts.map +1 -1
  104. package/types/rules/no-duplicate-attrs.d.ts.map +1 -1
  105. package/types/rules/no-duplicate-class.d.ts.map +1 -1
  106. package/types/rules/no-duplicate-id.d.ts.map +1 -1
  107. package/types/rules/no-duplicate-in-head.d.ts.map +1 -1
  108. package/types/rules/no-empty-headings.d.ts.map +1 -1
  109. package/types/rules/no-extra-spacing-attrs.d.ts.map +1 -1
  110. package/types/rules/no-extra-spacing-text.d.ts.map +1 -1
  111. package/types/rules/no-heading-inside-button.d.ts.map +1 -1
  112. package/types/rules/no-ineffective-attrs.d.ts.map +1 -1
  113. package/types/rules/no-inline-styles.d.ts.map +1 -1
  114. package/types/rules/no-invalid-entity.d.ts.map +1 -1
  115. package/types/rules/no-invalid-role.d.ts.map +1 -1
  116. package/types/rules/no-multiple-empty-lines.d.ts.map +1 -1
  117. package/types/rules/no-multiple-h1.d.ts.map +1 -1
  118. package/types/rules/no-nested-interactive.d.ts.map +1 -1
  119. package/types/rules/no-non-scalable-viewport.d.ts.map +1 -1
  120. package/types/rules/no-obsolete-attrs.d.ts +4 -0
  121. package/types/rules/no-obsolete-attrs.d.ts.map +1 -0
  122. package/types/rules/no-obsolete-tags.d.ts.map +1 -1
  123. package/types/rules/no-positive-tabindex.d.ts.map +1 -1
  124. package/types/rules/no-restricted-attr-values.d.ts.map +1 -1
  125. package/types/rules/no-restricted-attrs.d.ts.map +1 -1
  126. package/types/rules/no-restricted-tags.d.ts.map +1 -1
  127. package/types/rules/no-script-style-type.d.ts.map +1 -1
  128. package/types/rules/no-skip-heading-levels.d.ts.map +1 -1
  129. package/types/rules/no-target-blank.d.ts.map +1 -1
  130. package/types/rules/no-trailing-spaces.d.ts.map +1 -1
  131. package/types/rules/no-whitespace-only-children.d.ts +6 -0
  132. package/types/rules/no-whitespace-only-children.d.ts.map +1 -0
  133. package/types/rules/prefer-https.d.ts.map +1 -1
  134. package/types/rules/quotes.d.ts.map +1 -1
  135. package/types/rules/require-attrs.d.ts.map +1 -1
  136. package/types/rules/require-button-type.d.ts.map +1 -1
  137. package/types/rules/require-closing-tags.d.ts.map +1 -1
  138. package/types/rules/require-doctype.d.ts.map +1 -1
  139. package/types/rules/require-explicit-size.d.ts.map +1 -1
  140. package/types/rules/require-form-method.d.ts.map +1 -1
  141. package/types/rules/require-frame-title.d.ts.map +1 -1
  142. package/types/rules/require-img-alt.d.ts.map +1 -1
  143. package/types/rules/require-input-label.d.ts.map +1 -1
  144. package/types/rules/require-lang.d.ts.map +1 -1
  145. package/types/rules/require-li-container.d.ts.map +1 -1
  146. package/types/rules/require-meta-charset.d.ts.map +1 -1
  147. package/types/rules/require-meta-description.d.ts.map +1 -1
  148. package/types/rules/require-meta-viewport.d.ts.map +1 -1
  149. package/types/rules/require-open-graph-protocol.d.ts.map +1 -1
  150. package/types/rules/require-title.d.ts.map +1 -1
  151. package/types/rules/sort-attrs.d.ts.map +1 -1
  152. package/types/rules/use-baseline.d.ts.map +1 -1
  153. package/types/rules/utils/baseline.d.ts +1 -3
  154. package/types/rules/utils/baseline.d.ts.map +1 -1
  155. package/types/rules/utils/naming.d.ts +14 -7
  156. package/types/rules/utils/naming.d.ts.map +1 -1
  157. package/types/rules/utils/node.d.ts +11 -10
  158. package/types/rules/utils/node.d.ts.map +1 -1
  159. package/types/rules/utils/rule.d.ts +1 -4
  160. package/types/rules/utils/rule.d.ts.map +1 -1
  161. package/types/rules/utils/settings.d.ts +0 -1
  162. package/types/rules/utils/settings.d.ts.map +1 -1
  163. package/types/rules/utils/source-code.d.ts +2 -6
  164. package/types/rules/utils/source-code.d.ts.map +1 -1
  165. package/types/rules/utils/template-literal.d.ts +2 -1
  166. package/types/rules/utils/template-literal.d.ts.map +1 -1
  167. package/types/rules/utils/visitors.d.ts.map +1 -1
  168. package/types/specs/index.d.ts +3 -0
  169. package/types/specs/index.d.ts.map +1 -0
  170. package/types/specs/obsolete-attrs.d.ts +19 -0
  171. package/types/specs/obsolete-attrs.d.ts.map +1 -0
  172. package/types/types/ast.d.ts +5 -2
  173. package/types/types/ast.d.ts.map +1 -1
@@ -1,4 +1,4 @@
1
- /** @satisfies {import('eslint').Linter.Config['rules']} */
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
- /** @type {Record<string, typeof recommendedRules[keyof typeof recommendedRules]>} */
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 { Language, ParseResult, File, FileError, OkParseResult } from "@eslint/core";
3
- * @import { ParserOptions } from "@html-eslint/parser";
4
- * @import { AST } from "eslint";
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<{ LangOptions: ParserOptions; Code: ReturnType<typeof createHTMLSourceCode>; RootNode: AST.Program; Node: {}}>}
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 = /** @type {string} */ (file.body);
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: /** @type {string} */ (file.body),
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 {AST} from 'eslint';
3
- * @import {SourceLocation, DirectiveType} from '@eslint/plugin-kit';
4
- * @import {TraversalStep, SourceCode} from '@eslint/core';
5
- * @import {CommentContent, AnyHTMLNode} from '@html-eslint/types';
6
- * @import {BaseNode} from '../types';
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 {{ruleId: null | string, message: string; loc: SourceLocation}[]}
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, text: string, comments: CommentContent[]}} config
188
+ * @param {{ ast: AST.Program; text: string; comments: CommentContent[] }} config
188
189
  * @returns {TextSourceCodeBase<any> & {
189
- * getDisableDirectives(): { problems: {ruleId: null | string, message: string; loc: SourceLocation}[]; directives: Directive[]}
190
- * getInlineConfigNodes(): CommentContent[]
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 {AST} from 'eslint';
3
- * @import {AnyHTMLNode} from '@html-eslint/types';
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 {Array<any>} options.args
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 { RuleFixer, RuleModule } from '../types';
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,7 +68,9 @@ module.exports = {
70
68
  if (!shouldBeMultiline) return;
71
69
 
72
70
  /**
73
- * This doesn't do any indentation, so the result will look silly. Indentation should be covered by the `indent` rule
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) {
@@ -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 { Text, AnyNode, OpenTagEnd, CloseTag } from '@html-eslint/types';
3
- * @import { Line, RuleModule } from '../types';
4
- * @typedef { AnyNode | Line } AnyNodeOrLine
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 {Tag, ScriptTag, StyleTag} from "@html-eslint/types";
3
- * @import {RuleModule} from "../types";
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['type']]?: number}} IncLevelOptions
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
  }