@html-eslint/eslint-plugin 0.9.0-alpha.0 → 0.11.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/configs/recommended.js +1 -0
  2. package/lib/constants/node-types.js +0 -5
  3. package/lib/constants/obsolete-tags.js +1 -1
  4. package/lib/constants/rule-category.js +0 -5
  5. package/lib/constants/void-elements.js +1 -1
  6. package/lib/rules/element-newline.js +29 -9
  7. package/lib/rules/id-naming-convention.js +16 -9
  8. package/lib/rules/indent.js +19 -7
  9. package/lib/rules/index.js +14 -2
  10. package/lib/rules/no-abstract-roles.js +62 -0
  11. package/lib/rules/no-accesskey-attrs.js +45 -0
  12. package/lib/rules/no-aria-hidden-body.js +46 -0
  13. package/lib/rules/no-duplicate-attrs.js +54 -0
  14. package/lib/rules/no-duplicate-id.js +7 -0
  15. package/lib/rules/no-extra-spacing-attrs.js +27 -5
  16. package/lib/rules/no-inline-styles.js +7 -0
  17. package/lib/rules/no-multiple-empty-lines.js +90 -0
  18. package/lib/rules/no-multiple-h1.js +7 -0
  19. package/lib/rules/no-non-scalable-viewport.js +7 -0
  20. package/lib/rules/no-obsolete-tags.js +7 -0
  21. package/lib/rules/no-positive-tabindex.js +7 -0
  22. package/lib/rules/no-skip-heading-levels.js +7 -0
  23. package/lib/rules/no-target-blank.js +7 -0
  24. package/lib/rules/quotes.js +30 -2
  25. package/lib/rules/require-button-type.js +58 -0
  26. package/lib/rules/require-closing-tags.js +11 -4
  27. package/lib/rules/require-doctype.js +7 -0
  28. package/lib/rules/require-frame-title.js +7 -0
  29. package/lib/rules/require-img-alt.js +13 -0
  30. package/lib/rules/require-lang.js +10 -4
  31. package/lib/rules/require-li-container.js +13 -1
  32. package/lib/rules/require-meta-charset.js +12 -1
  33. package/lib/rules/require-meta-description.js +15 -4
  34. package/lib/rules/require-meta-viewport.js +12 -8
  35. package/lib/rules/require-title.js +9 -3
  36. package/lib/rules/utils/node-utils.js +27 -5
  37. package/lib/types.d.ts +104 -33
  38. package/package.json +8 -4
@@ -13,5 +13,6 @@ module.exports = {
13
13
  "@html-eslint/quotes": "error",
14
14
  "@html-eslint/no-obsolete-tags": "error",
15
15
  "@html-eslint/require-closing-tags": "error",
16
+ "@html-eslint/no-duplicate-attrs": "error",
16
17
  },
17
18
  };
@@ -1,8 +1,3 @@
1
- /**
2
- * @typedef {import("../types").NodeTypes} NodeTypes
3
- */
4
-
5
- /** @type {NodeTypes} */
6
1
  module.exports = {
7
2
  PROGRAM: "Program",
8
3
  TEXT: "text",
@@ -1,5 +1,5 @@
1
1
  // https://html.spec.whatwg.org/#non-conforming-features
2
- /** @type {string[] */
2
+ /** @type {string[]} */
3
3
  module.exports = [
4
4
  "applet",
5
5
  "acronym",
@@ -1,8 +1,3 @@
1
- /**
2
- * @typedef {import("../types").RuleCategory} RuleCategory
3
- */
4
-
5
- /** @type {RuleCategory} */
6
1
  module.exports = {
7
2
  BEST_PRACTICE: "Best Practice",
8
3
  SEO: "SEO",
@@ -1,4 +1,4 @@
1
- /** @type {string[] */
1
+ /** @type {string[]} */
2
2
  module.exports = [
3
3
  "area",
4
4
  "base",
@@ -1,5 +1,7 @@
1
1
  /**
2
- * @typedef {import("../types").HTMLNode} HTMLNode
2
+ * @typedef {import("../types").ElementNode} ElementNode
3
+ * @typedef {import("../types").AnyNode} AnyNode
4
+ * @typedef {import("../types").Context} Context
3
5
  */
4
6
 
5
7
  const { RULE_CATEGORY, NODE_TYPES } = require("../constants");
@@ -29,9 +31,12 @@ module.exports = {
29
31
  },
30
32
  },
31
33
 
34
+ /**
35
+ * @param {Context} context
36
+ */
32
37
  create(context) {
33
- function checkSiblings(sibilings) {
34
- sibilings
38
+ function checkSiblings(siblings) {
39
+ siblings
35
40
  .filter((node) => node.type !== NODE_TYPES.TEXT && node.range[0])
36
41
  .forEach((current, index, arr) => {
37
42
  const after = arr[index + 1];
@@ -50,6 +55,10 @@ module.exports = {
50
55
  });
51
56
  }
52
57
 
58
+ /**
59
+ *
60
+ * @param {ElementNode['childNodes'][number]} node
61
+ */
53
62
  function checkChild(node) {
54
63
  const children = (node.childNodes || []).filter(
55
64
  (n) => !!n.range[0] && n.type !== NODE_TYPES.TEXT
@@ -57,31 +66,40 @@ module.exports = {
57
66
  const first = children[0];
58
67
  const last = children[children.length - 1];
59
68
  if (first) {
60
- if (isOnTheSameLine(node.startTag, first)) {
69
+ if (node.startTag && isOnTheSameLine(node.startTag, first)) {
61
70
  context.report({
62
71
  node: node.startTag,
63
72
  messageId: MESSAGE_IDS.EXPECT_NEW_LINE_AFTER,
64
73
  data: { tag: `<${node.tagName}>` },
65
74
  fix(fixer) {
66
- return fixer.insertTextAfter(node.startTag, "\n");
75
+ if (node.startTag) {
76
+ return fixer.insertTextAfter(node.startTag, "\n");
77
+ }
78
+ return null;
67
79
  },
68
80
  });
69
81
  }
70
82
  }
71
83
  if (last) {
72
- if (isOnTheSameLine(node.endTag, last)) {
84
+ if (node.endTag && isOnTheSameLine(node.endTag, last)) {
73
85
  context.report({
74
86
  node: node.endTag,
75
87
  messageId: MESSAGE_IDS.EXPECT_NEW_LINE_BEFORE,
76
88
  data: { tag: `</${node.tagName}>` },
77
89
  fix(fixer) {
78
- return fixer.insertTextBefore(node.endTag, "\n");
90
+ if (node.endTag) {
91
+ return fixer.insertTextBefore(node.endTag, "\n");
92
+ }
93
+ return null;
79
94
  },
80
95
  });
81
96
  }
82
97
  }
83
98
  }
84
99
  return {
100
+ /**
101
+ * @param {ElementNode} node
102
+ */
85
103
  "*"(node) {
86
104
  if (node.type !== NODE_TYPES.TEXT) {
87
105
  checkSiblings(node.childNodes || []);
@@ -94,13 +112,15 @@ module.exports = {
94
112
 
95
113
  /**
96
114
  * Checks whether two nodes are on the same line or not.
97
- * @param {HTMLNode} nodeBefore A node before
98
- * @param {HTMLNode} nodeAfter A node after
115
+ * @param {AnyNode} nodeBefore A node before
116
+ * @param {AnyNode} nodeAfter A node after
99
117
  * @returns {boolean} `true` if two nodes are on the same line, otherwise `false`.
100
118
  */
101
119
  function isOnTheSameLine(nodeBefore, nodeAfter) {
102
120
  if (nodeBefore && nodeAfter) {
121
+ // @ts-ignore
103
122
  if (nodeBefore.endTag) {
123
+ // @ts-ignore
104
124
  return nodeBefore.endTag.loc.end.line === nodeAfter.loc.start.line;
105
125
  }
106
126
  return nodeBefore.loc.start.line === nodeAfter.loc.start.line;
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @typedef {import("../types").Rule} Rule
3
+ */
4
+
1
5
  const { RULE_CATEGORY } = require("../constants");
2
6
  const { NamingUtils, NodeUtils } = require("./utils");
3
7
 
@@ -5,7 +9,7 @@ const MESSAGE_IDS = {
5
9
  WRONG: "wrong",
6
10
  };
7
11
 
8
- const CONEVNTIONS = {
12
+ const CONVENTIONS = {
9
13
  CAMEL_CASE: "camelCase",
10
14
  SNAKE_CASE: "snake_case",
11
15
  PASCAL_CASE: "PascalCase",
@@ -13,12 +17,15 @@ const CONEVNTIONS = {
13
17
  };
14
18
 
15
19
  const CONVENTION_CHECKERS = {
16
- [CONEVNTIONS.CAMEL_CASE]: NamingUtils.isCamelCase,
17
- [CONEVNTIONS.SNAKE_CASE]: NamingUtils.isSnakeCase,
18
- [CONEVNTIONS.PASCAL_CASE]: NamingUtils.isPascalCase,
19
- [CONEVNTIONS.KEBAB_CASE]: NamingUtils.isKebabCase,
20
+ [CONVENTIONS.CAMEL_CASE]: NamingUtils.isCamelCase,
21
+ [CONVENTIONS.SNAKE_CASE]: NamingUtils.isSnakeCase,
22
+ [CONVENTIONS.PASCAL_CASE]: NamingUtils.isPascalCase,
23
+ [CONVENTIONS.KEBAB_CASE]: NamingUtils.isKebabCase,
20
24
  };
21
25
 
26
+ /**
27
+ * @type {Rule}
28
+ */
22
29
  module.exports = {
23
30
  meta: {
24
31
  type: "code",
@@ -32,7 +39,7 @@ module.exports = {
32
39
  fixable: null,
33
40
  schema: [
34
41
  {
35
- enum: Object.values(CONEVNTIONS),
42
+ enum: Object.values(CONVENTIONS),
36
43
  },
37
44
  ],
38
45
  messages: {
@@ -45,14 +52,14 @@ module.exports = {
45
52
  const convention =
46
53
  context.options && context.options.length
47
54
  ? context.options[0]
48
- : CONEVNTIONS.SNAKE_CASE;
55
+ : CONVENTIONS.SNAKE_CASE;
49
56
 
50
- const checkNaminig = CONVENTION_CHECKERS[convention];
57
+ const checkNaming = CONVENTION_CHECKERS[convention];
51
58
 
52
59
  return {
53
60
  "*"(node) {
54
61
  const idAttr = NodeUtils.findAttr(node, "id");
55
- if (idAttr && idAttr.value && !checkNaminig(idAttr.value)) {
62
+ if (idAttr && idAttr.value && !checkNaming(idAttr.value)) {
56
63
  context.report({
57
64
  node: idAttr,
58
65
  data: {
@@ -1,7 +1,9 @@
1
- // @ts-check
2
1
  /**
3
- * @typedef {import("../types").HTMLNode} HTMLNode
2
+ * @typedef {import("../types").Rule} Rule
3
+ * @typedef {import("../types").ElementNode} ElementNode
4
4
  * @typedef {import("../types").AttrNode} AttrNode
5
+ * @typedef {import("../types").TagNode} TagNode
6
+ * @typedef {import("../types").AnyNode} AnyNode
5
7
  * @typedef {import("../types").BaseNode} BaseNode
6
8
  * @typedef {Object} IndentType
7
9
  * @property {"tab"} TAB
@@ -12,6 +14,7 @@
12
14
  */
13
15
 
14
16
  const { RULE_CATEGORY, NODE_TYPES } = require("../constants");
17
+ const { NodeUtils } = require("./utils");
15
18
 
16
19
  /** @type {MessageId} */
17
20
  const MESSAGE_ID = {
@@ -31,6 +34,9 @@ const IGNORING_NODES = [
31
34
  NODE_TYPES.XMP,
32
35
  ];
33
36
 
37
+ /**
38
+ * @type {Rule}
39
+ */
34
40
  module.exports = {
35
41
  meta: {
36
42
  type: "code",
@@ -60,7 +66,6 @@ module.exports = {
60
66
  "Expected indentation of {{expected}} but found {{actual}}.",
61
67
  },
62
68
  },
63
-
64
69
  create(context) {
65
70
  const sourceCode = context.getSourceCode();
66
71
  const indentLevel = new IndentLevel();
@@ -79,7 +84,7 @@ module.exports = {
79
84
 
80
85
  /**
81
86
  * @param {BaseNode} node
82
- * @param {HTMLNode} [nodeToReport]
87
+ * @param {BaseNode} [nodeToReport]
83
88
  */
84
89
  function checkIndent(node, nodeToReport) {
85
90
  const codeBefore = getLineCodeBefore(node);
@@ -133,7 +138,7 @@ module.exports = {
133
138
  }
134
139
 
135
140
  /**
136
- * @param {BaseNode} startTag
141
+ * @param {AnyNode} startTag
137
142
  */
138
143
  function checkEndOfStartTag(startTag) {
139
144
  const start = startTag.range[1] - 1;
@@ -160,7 +165,7 @@ module.exports = {
160
165
  let nodesToIgnoreChildren = [];
161
166
  return {
162
167
  /**
163
- * @param {HTMLNode} node
168
+ * @param {ElementNode} node
164
169
  */
165
170
  "*"(node) {
166
171
  if (IGNORING_NODES.includes(node.type)) {
@@ -186,7 +191,11 @@ module.exports = {
186
191
  }
187
192
  });
188
193
 
189
- if (node.lineNodes && node.lineNodes.length) {
194
+ if (
195
+ (NodeUtils.isTextNode(node) || NodeUtils.isCommentNode(node)) &&
196
+ node.lineNodes &&
197
+ node.lineNodes.length
198
+ ) {
190
199
  if (!node.startTag) {
191
200
  indentLevel.down();
192
201
  }
@@ -219,6 +228,9 @@ module.exports = {
219
228
  };
220
229
 
221
230
  function getIndentTypeAndSize(options) {
231
+ /**
232
+ * @type {IndentType['SPACE'] | IndentType['TAB']}
233
+ */
222
234
  let indentType = INDENT_TYPES.SPACE;
223
235
  let indentSize = 4;
224
236
  if (options.length) {
@@ -12,7 +12,7 @@ const indent = require("./indent");
12
12
  const requireLiContainer = require("./require-li-container");
13
13
  const quotes = require("./quotes");
14
14
  const idNamingConvention = require("./id-naming-convention");
15
- const noObsoluteTags = require("./no-obsolete-tags");
15
+ const noObsoleteTags = require("./no-obsolete-tags");
16
16
  const requireClosingTags = require("./require-closing-tags");
17
17
  const requireMetaDescription = require("./require-meta-description");
18
18
  const requireFrameTitle = require("./require-frame-title");
@@ -21,6 +21,12 @@ const noPositiveTabindex = require("./no-positive-tabindex");
21
21
  const requireMetaViewport = require("./require-meta-viewport");
22
22
  const requireMetaCharset = require("./require-meta-charset");
23
23
  const noTargetBlank = require("./no-target-blank");
24
+ const noDuplicateAttrs = require("./no-duplicate-attrs");
25
+ const noAbstractRoles = require("./no-abstract-roles");
26
+ const requireButtonType = require("./require-button-type");
27
+ const noAriaHiddenBody = require("./no-aria-hidden-body");
28
+ const noMultipleEmptyLines = require("./no-multiple-empty-lines");
29
+ const noAccesskeyAttrs = require("./no-accesskey-attrs");
24
30
 
25
31
  module.exports = {
26
32
  "require-lang": requireLang,
@@ -37,7 +43,7 @@ module.exports = {
37
43
  indent: indent,
38
44
  quotes: quotes,
39
45
  "id-naming-convention": idNamingConvention,
40
- "no-obsolete-tags": noObsoluteTags,
46
+ "no-obsolete-tags": noObsoleteTags,
41
47
  "require-closing-tags": requireClosingTags,
42
48
  "require-meta-description": requireMetaDescription,
43
49
  "require-frame-title": requireFrameTitle,
@@ -46,4 +52,10 @@ module.exports = {
46
52
  "require-meta-viewport": requireMetaViewport,
47
53
  "require-meta-charset": requireMetaCharset,
48
54
  "no-target-blank": noTargetBlank,
55
+ "no-duplicate-attrs": noDuplicateAttrs,
56
+ "no-abstract-roles": noAbstractRoles,
57
+ "require-button-type": requireButtonType,
58
+ "no-aria-hidden-body": noAriaHiddenBody,
59
+ "no-multiple-empty-lines": noMultipleEmptyLines,
60
+ "no-accesskey-attrs": noAccesskeyAttrs,
49
61
  };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * @typedef {import("../types").Rule} Rule
3
+ */
4
+
5
+ const { RULE_CATEGORY } = require("../constants");
6
+ const { NodeUtils } = require("./utils");
7
+
8
+ const MESSAGE_IDS = {
9
+ UNEXPECTED: "unexpected",
10
+ };
11
+
12
+ const ABSTRACT_ROLE_SET = new Set([
13
+ "command",
14
+ "composite",
15
+ "input",
16
+ "landmark",
17
+ "range",
18
+ "roletype",
19
+ "section",
20
+ "sectionhead",
21
+ "select",
22
+ "structure",
23
+ "widget",
24
+ "window",
25
+ ]);
26
+
27
+ /**
28
+ * @type {Rule}
29
+ */
30
+ module.exports = {
31
+ meta: {
32
+ type: "code",
33
+
34
+ docs: {
35
+ description: "Disallow to use of abstract roles",
36
+ category: RULE_CATEGORY.ACCESSIBILITY,
37
+ recommended: false,
38
+ },
39
+
40
+ fixable: null,
41
+ schema: [],
42
+ messages: {
43
+ [MESSAGE_IDS.UNEXPECTED]: "Unexpected use of an abstract role.",
44
+ },
45
+ },
46
+
47
+ create(context) {
48
+ return {
49
+ "*"(node) {
50
+ const roleAttr = NodeUtils.findAttr(node, "role");
51
+ if (roleAttr) {
52
+ if (ABSTRACT_ROLE_SET.has(roleAttr.value)) {
53
+ context.report({
54
+ messageId: MESSAGE_IDS.UNEXPECTED,
55
+ node: roleAttr,
56
+ });
57
+ }
58
+ }
59
+ },
60
+ };
61
+ },
62
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @typedef {import("../types").Rule} Rule
3
+ */
4
+
5
+ const { RULE_CATEGORY } = require("../constants");
6
+ const { NodeUtils } = require("./utils");
7
+
8
+ const MESSAGE_IDS = {
9
+ UNEXPECTED: "unexpected",
10
+ };
11
+
12
+ /**
13
+ * @type {Rule}
14
+ */
15
+ module.exports = {
16
+ meta: {
17
+ type: "code",
18
+
19
+ docs: {
20
+ description: "Disallow to use of accesskey attribute",
21
+ category: RULE_CATEGORY.ACCESSIBILITY,
22
+ recommended: false,
23
+ },
24
+
25
+ fixable: null,
26
+ schema: [],
27
+ messages: {
28
+ [MESSAGE_IDS.UNEXPECTED]: "Unexpected use of accesskey attribute.",
29
+ },
30
+ },
31
+
32
+ create(context) {
33
+ return {
34
+ "*"(node) {
35
+ const accessKeyAttr = NodeUtils.findAttr(node, "accesskey");
36
+ if (accessKeyAttr) {
37
+ context.report({
38
+ node: accessKeyAttr,
39
+ messageId: MESSAGE_IDS.UNEXPECTED,
40
+ });
41
+ }
42
+ },
43
+ };
44
+ },
45
+ };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @typedef {import("../types").Rule} Rule
3
+ */
4
+
5
+ const { RULE_CATEGORY } = require("../constants");
6
+ const { NodeUtils } = require("./utils");
7
+
8
+ const MESSAGE_IDS = {
9
+ UNEXPECTED: "unexpected",
10
+ };
11
+
12
+ /**
13
+ * @type {Rule}
14
+ */
15
+ module.exports = {
16
+ meta: {
17
+ type: "code",
18
+
19
+ docs: {
20
+ description:
21
+ "Disallow to use aria-hidden attributes on the `body` element.",
22
+ category: RULE_CATEGORY.ACCESSIBILITY,
23
+ recommended: false,
24
+ },
25
+
26
+ fixable: null,
27
+ schema: [],
28
+ messages: {
29
+ [MESSAGE_IDS.UNEXPECTED]: "Unexpected aria-hidden on body tag.",
30
+ },
31
+ },
32
+
33
+ create(context) {
34
+ return {
35
+ Body(node) {
36
+ const ariaHiddenAttr = NodeUtils.findAttr(node, "aria-hidden");
37
+ if (ariaHiddenAttr && ariaHiddenAttr.value !== "false") {
38
+ context.report({
39
+ node: ariaHiddenAttr,
40
+ messageId: MESSAGE_IDS.UNEXPECTED,
41
+ });
42
+ }
43
+ },
44
+ };
45
+ },
46
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @typedef {import("../types").Rule} Rule
3
+ */
4
+
5
+ const { RULE_CATEGORY } = require("../constants");
6
+
7
+ const MESSAGE_IDS = {
8
+ DUPLICATE_ATTRS: "duplicateAttrs",
9
+ };
10
+
11
+ /**
12
+ * @type {Rule}
13
+ */
14
+ module.exports = {
15
+ meta: {
16
+ type: "code",
17
+
18
+ docs: {
19
+ description: "Disallow to use duplicate attributes",
20
+ category: RULE_CATEGORY.BEST_PRACTICE,
21
+ recommended: true,
22
+ },
23
+
24
+ fixable: null,
25
+ schema: [],
26
+ messages: {
27
+ [MESSAGE_IDS.DUPLICATE_ATTRS]:
28
+ "The attribute '{{attrName}}' is duplicated.",
29
+ },
30
+ },
31
+
32
+ create(context) {
33
+ return {
34
+ "*"(node) {
35
+ if (Array.isArray(node.attrs)) {
36
+ const attrsSet = new Set();
37
+ node.attrs.forEach((attr) => {
38
+ if (attrsSet.has(attr.name)) {
39
+ context.report({
40
+ node: node.startTag,
41
+ data: {
42
+ attrName: attr.name,
43
+ },
44
+ messageId: MESSAGE_IDS.DUPLICATE_ATTRS,
45
+ });
46
+ } else {
47
+ attrsSet.add(attr.name);
48
+ }
49
+ });
50
+ }
51
+ },
52
+ };
53
+ },
54
+ };
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @typedef {import("../types").Rule} Rule
3
+ */
4
+
1
5
  const { RULE_CATEGORY } = require("../constants");
2
6
  const { NodeUtils } = require("./utils");
3
7
 
@@ -5,6 +9,9 @@ const MESSAGE_IDS = {
5
9
  DUPLICATE_ID: "duplicateId",
6
10
  };
7
11
 
12
+ /**
13
+ * @type {Rule}
14
+ */
8
15
  module.exports = {
9
16
  meta: {
10
17
  type: "code",
@@ -1,11 +1,21 @@
1
+ /**
2
+ * @typedef {import("../types").Rule} Rule
3
+ * @typedef {import("../types").AttrNode} AttrNode
4
+ * @typedef {import("../types").TagNode} TagNode
5
+ * @typedef {import("../types").ElementNode} ElementNode
6
+ */
7
+
1
8
  const { RULE_CATEGORY } = require("../constants");
2
9
 
3
10
  const MESSAGE_IDS = {
4
- EXTRA_BETWEEN: "unexpectBetween",
5
- EXTRA_AFTER: "unexpectAfter",
6
- EXTRA_BEFORE: "unexpectBefore",
11
+ EXTRA_BETWEEN: "unexpectedBetween",
12
+ EXTRA_AFTER: "unexpectedAfter",
13
+ EXTRA_BEFORE: "unexpectedBefore",
7
14
  };
8
15
 
16
+ /**
17
+ * @type {Rule}
18
+ */
9
19
  module.exports = {
10
20
  meta: {
11
21
  type: "code",
@@ -25,6 +35,9 @@ module.exports = {
25
35
  },
26
36
  },
27
37
  create(context) {
38
+ /**
39
+ * @param {AttrNode[]} attrs
40
+ */
28
41
  function checkExtraSpacesBetweenAttrs(attrs) {
29
42
  attrs.forEach((current, index, attrs) => {
30
43
  if (index >= attrs.length - 1) {
@@ -50,9 +63,13 @@ module.exports = {
50
63
  }
51
64
  });
52
65
  }
66
+ /**
67
+ * @param {TagNode} startTag
68
+ * @param {AttrNode} lastAttr
69
+ */
53
70
  function checkExtraSpaceAfter(startTag, lastAttr) {
54
71
  if (startTag.loc.end.line !== lastAttr.loc.end.line) {
55
- // skip the attribute on the diffrent line with the start tag
72
+ // skip the attribute on the different line with the start tag
56
73
  return;
57
74
  }
58
75
  const spacesBetween = startTag.loc.end.column - lastAttr.loc.end.column;
@@ -72,9 +89,14 @@ module.exports = {
72
89
  });
73
90
  }
74
91
  }
92
+
93
+ /**
94
+ * @param {ElementNode} node
95
+ * @param {AttrNode} firstAttr
96
+ */
75
97
  function checkExtraSpaceBefore(node, firstAttr) {
76
98
  if (node.loc.start.line !== firstAttr.loc.start.line) {
77
- // skip the attribute on the diffrent line with the start tag
99
+ // skip the attribute on the different line with the start tag
78
100
  return;
79
101
  }
80
102
  const nodeLength = node.tagName.length;
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @typedef {import("../types").Rule} Rule
3
+ */
4
+
1
5
  const { RULE_CATEGORY } = require("../constants");
2
6
  const { NodeUtils } = require("./utils");
3
7
 
@@ -5,6 +9,9 @@ const MESSAGE_IDS = {
5
9
  INLINE_STYLE: "unexpectedInlineStyle",
6
10
  };
7
11
 
12
+ /**
13
+ * @type {Rule}
14
+ */
8
15
  module.exports = {
9
16
  meta: {
10
17
  type: "code",