@herb-tools/linter 0.4.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 (175) hide show
  1. package/README.md +34 -0
  2. package/bin/herb-lint +3 -0
  3. package/dist/herb-lint.js +16505 -0
  4. package/dist/herb-lint.js.map +1 -0
  5. package/dist/index.cjs +834 -0
  6. package/dist/index.cjs.map +1 -0
  7. package/dist/index.js +820 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/package.json +49 -0
  10. package/dist/src/cli/argument-parser.js +96 -0
  11. package/dist/src/cli/argument-parser.js.map +1 -0
  12. package/dist/src/cli/file-processor.js +58 -0
  13. package/dist/src/cli/file-processor.js.map +1 -0
  14. package/dist/src/cli/formatters/base-formatter.js +3 -0
  15. package/dist/src/cli/formatters/base-formatter.js.map +1 -0
  16. package/dist/src/cli/formatters/detailed-formatter.js +62 -0
  17. package/dist/src/cli/formatters/detailed-formatter.js.map +1 -0
  18. package/dist/src/cli/formatters/index.js +4 -0
  19. package/dist/src/cli/formatters/index.js.map +1 -0
  20. package/dist/src/cli/formatters/simple-formatter.js +31 -0
  21. package/dist/src/cli/formatters/simple-formatter.js.map +1 -0
  22. package/dist/src/cli/index.js +5 -0
  23. package/dist/src/cli/index.js.map +1 -0
  24. package/dist/src/cli/summary-reporter.js +96 -0
  25. package/dist/src/cli/summary-reporter.js.map +1 -0
  26. package/dist/src/cli.js +50 -0
  27. package/dist/src/cli.js.map +1 -0
  28. package/dist/src/default-rules.js +31 -0
  29. package/dist/src/default-rules.js.map +1 -0
  30. package/dist/src/herb-lint.js +5 -0
  31. package/dist/src/herb-lint.js.map +1 -0
  32. package/dist/src/index.js +4 -0
  33. package/dist/src/index.js.map +1 -0
  34. package/dist/src/linter.js +39 -0
  35. package/dist/src/linter.js.map +1 -0
  36. package/dist/src/rules/erb-no-empty-tags.js +23 -0
  37. package/dist/src/rules/erb-no-empty-tags.js.map +1 -0
  38. package/dist/src/rules/erb-no-output-control-flow.js +47 -0
  39. package/dist/src/rules/erb-no-output-control-flow.js.map +1 -0
  40. package/dist/src/rules/erb-require-whitespace-inside-tags.js +43 -0
  41. package/dist/src/rules/erb-require-whitespace-inside-tags.js.map +1 -0
  42. package/dist/src/rules/html-anchor-require-href.js +25 -0
  43. package/dist/src/rules/html-anchor-require-href.js.map +1 -0
  44. package/dist/src/rules/html-aria-role-heading-requires-level.js +26 -0
  45. package/dist/src/rules/html-aria-role-heading-requires-level.js.map +1 -0
  46. package/dist/src/rules/html-attribute-double-quotes.js +21 -0
  47. package/dist/src/rules/html-attribute-double-quotes.js.map +1 -0
  48. package/dist/src/rules/html-attribute-values-require-quotes.js +22 -0
  49. package/dist/src/rules/html-attribute-values-require-quotes.js.map +1 -0
  50. package/dist/src/rules/html-boolean-attributes-no-value.js +19 -0
  51. package/dist/src/rules/html-boolean-attributes-no-value.js.map +1 -0
  52. package/dist/src/rules/html-img-require-alt.js +29 -0
  53. package/dist/src/rules/html-img-require-alt.js.map +1 -0
  54. package/dist/src/rules/html-no-block-inside-inline.js +59 -0
  55. package/dist/src/rules/html-no-block-inside-inline.js.map +1 -0
  56. package/dist/src/rules/html-no-duplicate-attributes.js +43 -0
  57. package/dist/src/rules/html-no-duplicate-attributes.js.map +1 -0
  58. package/dist/src/rules/html-no-empty-headings.js +148 -0
  59. package/dist/src/rules/html-no-empty-headings.js.map +1 -0
  60. package/dist/src/rules/html-no-nested-links.js +45 -0
  61. package/dist/src/rules/html-no-nested-links.js.map +1 -0
  62. package/dist/src/rules/html-tag-name-lowercase.js +39 -0
  63. package/dist/src/rules/html-tag-name-lowercase.js.map +1 -0
  64. package/dist/src/rules/index.js +13 -0
  65. package/dist/src/rules/index.js.map +1 -0
  66. package/dist/src/rules/rule-utils.js +198 -0
  67. package/dist/src/rules/rule-utils.js.map +1 -0
  68. package/dist/src/types.js +2 -0
  69. package/dist/src/types.js.map +1 -0
  70. package/dist/tsconfig.tsbuildinfo +1 -0
  71. package/dist/types/cli/argument-parser.d.ts +14 -0
  72. package/dist/types/cli/file-processor.d.ts +21 -0
  73. package/dist/types/cli/formatters/base-formatter.d.ts +6 -0
  74. package/dist/types/cli/formatters/detailed-formatter.d.ts +13 -0
  75. package/dist/types/cli/formatters/index.d.ts +3 -0
  76. package/dist/types/cli/formatters/simple-formatter.d.ts +7 -0
  77. package/dist/types/cli/summary-reporter.d.ts +22 -0
  78. package/dist/types/cli.d.ts +6 -0
  79. package/dist/types/default-rules.d.ts +2 -0
  80. package/dist/types/herb-lint.d.ts +2 -0
  81. package/dist/types/index.d.ts +3 -0
  82. package/dist/types/linter.d.ts +18 -0
  83. package/dist/types/rules/erb-no-empty-tags.d.ts +6 -0
  84. package/dist/types/rules/erb-no-output-control-flow.d.ts +6 -0
  85. package/dist/types/rules/erb-require-whitespace-inside-tags.d.ts +6 -0
  86. package/dist/types/rules/html-anchor-require-href.d.ts +6 -0
  87. package/dist/types/rules/html-aria-role-heading-requires-level.d.ts +6 -0
  88. package/dist/types/rules/html-attribute-double-quotes.d.ts +6 -0
  89. package/dist/types/rules/html-attribute-values-require-quotes.d.ts +6 -0
  90. package/dist/types/rules/html-boolean-attributes-no-value.d.ts +6 -0
  91. package/dist/types/rules/html-img-require-alt.d.ts +6 -0
  92. package/dist/types/rules/html-no-block-inside-inline.d.ts +6 -0
  93. package/dist/types/rules/html-no-duplicate-attributes.d.ts +6 -0
  94. package/dist/types/rules/html-no-empty-headings.d.ts +6 -0
  95. package/dist/types/rules/html-no-nested-links.d.ts +6 -0
  96. package/dist/types/rules/html-tag-name-lowercase.d.ts +6 -0
  97. package/dist/types/rules/index.d.ts +12 -0
  98. package/dist/types/rules/rule-utils.d.ts +89 -0
  99. package/dist/types/src/cli/argument-parser.d.ts +14 -0
  100. package/dist/types/src/cli/file-processor.d.ts +21 -0
  101. package/dist/types/src/cli/formatters/base-formatter.d.ts +6 -0
  102. package/dist/types/src/cli/formatters/detailed-formatter.d.ts +13 -0
  103. package/dist/types/src/cli/formatters/index.d.ts +3 -0
  104. package/dist/types/src/cli/formatters/simple-formatter.d.ts +7 -0
  105. package/dist/types/src/cli/index.d.ts +4 -0
  106. package/dist/types/src/cli/summary-reporter.d.ts +22 -0
  107. package/dist/types/src/cli.d.ts +6 -0
  108. package/dist/types/src/default-rules.d.ts +2 -0
  109. package/dist/types/src/herb-lint.d.ts +2 -0
  110. package/dist/types/src/index.d.ts +3 -0
  111. package/dist/types/src/linter.d.ts +18 -0
  112. package/dist/types/src/rules/erb-no-empty-tags.d.ts +6 -0
  113. package/dist/types/src/rules/erb-no-output-control-flow.d.ts +6 -0
  114. package/dist/types/src/rules/erb-require-whitespace-inside-tags.d.ts +6 -0
  115. package/dist/types/src/rules/html-anchor-require-href.d.ts +6 -0
  116. package/dist/types/src/rules/html-aria-role-heading-requires-level.d.ts +6 -0
  117. package/dist/types/src/rules/html-attribute-double-quotes.d.ts +6 -0
  118. package/dist/types/src/rules/html-attribute-values-require-quotes.d.ts +6 -0
  119. package/dist/types/src/rules/html-boolean-attributes-no-value.d.ts +6 -0
  120. package/dist/types/src/rules/html-img-require-alt.d.ts +6 -0
  121. package/dist/types/src/rules/html-no-block-inside-inline.d.ts +6 -0
  122. package/dist/types/src/rules/html-no-duplicate-attributes.d.ts +6 -0
  123. package/dist/types/src/rules/html-no-empty-headings.d.ts +6 -0
  124. package/dist/types/src/rules/html-no-nested-links.d.ts +6 -0
  125. package/dist/types/src/rules/html-tag-name-lowercase.d.ts +6 -0
  126. package/dist/types/src/rules/index.d.ts +12 -0
  127. package/dist/types/src/rules/rule-utils.d.ts +89 -0
  128. package/dist/types/src/types.d.ts +26 -0
  129. package/dist/types/types.d.ts +26 -0
  130. package/docs/rules/README.md +39 -0
  131. package/docs/rules/erb-no-empty-tags.md +38 -0
  132. package/docs/rules/erb-no-output-control-flow.md +45 -0
  133. package/docs/rules/erb-require-whitespace-inside-tags.md +43 -0
  134. package/docs/rules/html-anchor-require-href.md +32 -0
  135. package/docs/rules/html-aria-role-heading-requires-level.md +34 -0
  136. package/docs/rules/html-attribute-double-quotes.md +43 -0
  137. package/docs/rules/html-attribute-values-require-quotes.md +43 -0
  138. package/docs/rules/html-boolean-attributes-no-value.md +39 -0
  139. package/docs/rules/html-img-require-alt.md +44 -0
  140. package/docs/rules/html-no-block-inside-inline.md +66 -0
  141. package/docs/rules/html-no-duplicate-attributes.md +35 -0
  142. package/docs/rules/html-no-empty-headings.md +78 -0
  143. package/docs/rules/html-no-nested-links.md +44 -0
  144. package/docs/rules/html-tag-name-lowercase.md +44 -0
  145. package/package.json +49 -0
  146. package/src/cli/argument-parser.ts +125 -0
  147. package/src/cli/file-processor.ts +86 -0
  148. package/src/cli/formatters/base-formatter.ts +11 -0
  149. package/src/cli/formatters/detailed-formatter.ts +74 -0
  150. package/src/cli/formatters/index.ts +3 -0
  151. package/src/cli/formatters/simple-formatter.ts +40 -0
  152. package/src/cli/index.ts +4 -0
  153. package/src/cli/summary-reporter.ts +127 -0
  154. package/src/cli.ts +60 -0
  155. package/src/default-rules.ts +33 -0
  156. package/src/herb-lint.ts +6 -0
  157. package/src/index.ts +3 -0
  158. package/src/linter.ts +50 -0
  159. package/src/rules/erb-no-empty-tags.ts +34 -0
  160. package/src/rules/erb-no-output-control-flow.ts +61 -0
  161. package/src/rules/erb-require-whitespace-inside-tags.ts +61 -0
  162. package/src/rules/html-anchor-require-href.ts +39 -0
  163. package/src/rules/html-aria-role-heading-requires-level.ts +44 -0
  164. package/src/rules/html-attribute-double-quotes.ts +28 -0
  165. package/src/rules/html-attribute-values-require-quotes.ts +30 -0
  166. package/src/rules/html-boolean-attributes-no-value.ts +27 -0
  167. package/src/rules/html-img-require-alt.ts +42 -0
  168. package/src/rules/html-no-block-inside-inline.ts +84 -0
  169. package/src/rules/html-no-duplicate-attributes.ts +59 -0
  170. package/src/rules/html-no-empty-headings.ts +185 -0
  171. package/src/rules/html-no-nested-links.ts +65 -0
  172. package/src/rules/html-tag-name-lowercase.ts +50 -0
  173. package/src/rules/index.ts +12 -0
  174. package/src/rules/rule-utils.ts +257 -0
  175. package/src/types.ts +32 -0
@@ -0,0 +1,4 @@
1
+ export * from "./linter.js";
2
+ export * from "./rules/index.js";
3
+ export * from "./types.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA"}
@@ -0,0 +1,39 @@
1
+ import { defaultRules } from "./default-rules.js";
2
+ export class Linter {
3
+ rules;
4
+ offenses;
5
+ /**
6
+ * Creates a new Linter instance.
7
+ * @param rules - Array of rule classes (not instances) to use. If not provided, uses default rules.
8
+ */
9
+ constructor(rules) {
10
+ this.rules = rules !== undefined ? rules : this.getDefaultRules();
11
+ this.offenses = [];
12
+ }
13
+ /**
14
+ * Returns the default set of rule classes used by the linter.
15
+ * @returns Array of rule classes
16
+ */
17
+ getDefaultRules() {
18
+ return defaultRules;
19
+ }
20
+ getRuleCount() {
21
+ return this.rules.length;
22
+ }
23
+ lint(document) {
24
+ this.offenses = [];
25
+ for (const Rule of this.rules) {
26
+ const rule = new Rule();
27
+ const ruleOffenses = rule.check(document);
28
+ this.offenses.push(...ruleOffenses);
29
+ }
30
+ const errors = this.offenses.filter(offense => offense.severity === "error").length;
31
+ const warnings = this.offenses.filter(offense => offense.severity === "warning").length;
32
+ return {
33
+ offenses: this.offenses,
34
+ errors,
35
+ warnings
36
+ };
37
+ }
38
+ }
39
+ //# sourceMappingURL=linter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linter.js","sourceRoot":"","sources":["../../src/linter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAKjD,MAAM,OAAO,MAAM;IACT,KAAK,CAAa;IAClB,QAAQ,CAAe;IAE/B;;;OAGG;IACH,YAAY,KAAmB;QAC7B,IAAI,CAAC,KAAK,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,EAAE,CAAA;QACjE,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAA;IACpB,CAAC;IAED;;;OAGG;IACK,eAAe;QACrB,OAAO,YAAY,CAAA;IACrB,CAAC;IAED,YAAY;QACV,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAA;IAC1B,CAAC;IAED,IAAI,CAAC,QAAsB;QACzB,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAA;QAElB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAA;YACvB,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;YAEzC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,CAAA;QACrC,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM,CAAA;QACnF,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM,CAAA;QAEvF,OAAO;YACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,MAAM;YACN,QAAQ;SACT,CAAA;IACH,CAAC;CACF"}
@@ -0,0 +1,23 @@
1
+ import { BaseRuleVisitor } from "./rule-utils.js";
2
+ class ERBNoEmptyTagsVisitor extends BaseRuleVisitor {
3
+ visitERBContentNode(node) {
4
+ this.visitChildNodes(node);
5
+ const { content, tag_closing } = node;
6
+ if (!content)
7
+ return;
8
+ if (tag_closing?.value === "")
9
+ return;
10
+ if (content.value.trim().length > 0)
11
+ return;
12
+ this.addOffense("ERB tag should not be empty. Remove empty ERB tags or add content.", node.location, "error");
13
+ }
14
+ }
15
+ export class ERBNoEmptyTagsRule {
16
+ name = "erb-no-empty-tags";
17
+ check(node) {
18
+ const visitor = new ERBNoEmptyTagsVisitor(this.name);
19
+ visitor.visit(node);
20
+ return visitor.offenses;
21
+ }
22
+ }
23
+ //# sourceMappingURL=erb-no-empty-tags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"erb-no-empty-tags.js","sourceRoot":"","sources":["../../../src/rules/erb-no-empty-tags.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAKjD,MAAM,qBAAsB,SAAQ,eAAe;IACjD,mBAAmB,CAAC,IAAoB;QACtC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;QAE1B,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,IAAI,CAAA;QAErC,IAAI,CAAC,OAAO;YAAE,OAAM;QACpB,IAAI,WAAW,EAAE,KAAK,KAAK,EAAE;YAAE,OAAM;QACrC,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;YAAE,OAAM;QAE3C,IAAI,CAAC,UAAU,CACb,oEAAoE,EACpE,IAAI,CAAC,QAAQ,EACb,OAAO,CACR,CAAA;IACH,CAAC;CACF;AAED,MAAM,OAAO,kBAAkB;IAC7B,IAAI,GAAG,mBAAmB,CAAA;IAE1B,KAAK,CAAC,IAAU;QACd,MAAM,OAAO,GAAG,IAAI,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAEpD,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAEnB,OAAO,OAAO,CAAC,QAAQ,CAAA;IACzB,CAAC;CACF"}
@@ -0,0 +1,47 @@
1
+ import { BaseRuleVisitor } from "./rule-utils.js";
2
+ class ERBNoOutputControlFlowRuleVisitor extends BaseRuleVisitor {
3
+ visitERBIfNode(node) {
4
+ this.checkOutputControlFlow(node);
5
+ this.visitChildNodes(node);
6
+ }
7
+ visitERBUnlessNode(node) {
8
+ this.checkOutputControlFlow(node);
9
+ this.visitChildNodes(node);
10
+ }
11
+ visitERBElseNode(node) {
12
+ this.checkOutputControlFlow(node);
13
+ this.visitChildNodes(node);
14
+ }
15
+ visitERBEndNode(node) {
16
+ this.checkOutputControlFlow(node);
17
+ this.visitChildNodes(node);
18
+ }
19
+ checkOutputControlFlow(controlBlock) {
20
+ const openTag = controlBlock.tag_opening;
21
+ if (!openTag) {
22
+ return;
23
+ }
24
+ if (openTag.value === "<%=") {
25
+ let controlBlockType = controlBlock.type;
26
+ if (controlBlock.type === "AST_ERB_IF_NODE")
27
+ controlBlockType = "if";
28
+ if (controlBlock.type === "AST_ERB_ELSE_NODE")
29
+ controlBlockType = "else";
30
+ if (controlBlock.type === "AST_ERB_END_NODE")
31
+ controlBlockType = "end";
32
+ if (controlBlock.type === "AST_ERB_UNLESS_NODE")
33
+ controlBlockType = "unless";
34
+ this.addOffense(`Control flow statements like \`${controlBlockType}\` should not be used with output tags. Use \`<% ${controlBlockType} ... %>\` instead.`, openTag.location, "error");
35
+ }
36
+ return;
37
+ }
38
+ }
39
+ export class ERBNoOutputControlFlowRule {
40
+ name = "erb-no-output-control-flow";
41
+ check(node) {
42
+ const visitor = new ERBNoOutputControlFlowRuleVisitor(this.name);
43
+ visitor.visit(node);
44
+ return visitor.offenses;
45
+ }
46
+ }
47
+ //# sourceMappingURL=erb-no-output-control-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"erb-no-output-control-flow.js","sourceRoot":"","sources":["../../../src/rules/erb-no-output-control-flow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAKjD,MAAM,iCAAkC,SAAQ,eAAe;IAC7D,cAAc,CAAC,IAAe;QAC5B,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;IAC5B,CAAC;IAED,kBAAkB,CAAC,IAAmB;QACpC,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;IAC5B,CAAC;IAED,gBAAgB,CAAC,IAAiB;QAChC,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;IAC5B,CAAC;IAED,eAAe,CAAC,IAAgB;QAC9B,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;IAC5B,CAAC;IAEO,sBAAsB,CAAC,YAAkE;QAC/F,MAAM,OAAO,GAAG,YAAY,CAAC,WAAW,CAAC;QACzC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAM;QACR,CAAC;QAED,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK,EAAC,CAAC;YAC3B,IAAI,gBAAgB,GAAW,YAAY,CAAC,IAAI,CAAA;YAEhD,IAAI,YAAY,CAAC,IAAI,KAAK,iBAAiB;gBAAE,gBAAgB,GAAG,IAAI,CAAA;YACpE,IAAI,YAAY,CAAC,IAAI,KAAK,mBAAmB;gBAAE,gBAAgB,GAAG,MAAM,CAAA;YACxE,IAAI,YAAY,CAAC,IAAI,KAAK,kBAAkB;gBAAE,gBAAgB,GAAG,KAAK,CAAA;YACtE,IAAI,YAAY,CAAC,IAAI,KAAK,qBAAqB;gBAAE,gBAAgB,GAAG,QAAQ,CAAA;YAE5E,IAAI,CAAC,UAAU,CACb,kCAAkC,gBAAgB,oDAAoD,gBAAgB,oBAAoB,EAC1I,OAAO,CAAC,QAAQ,EAChB,OAAO,CACR,CAAA;QACH,CAAC;QAED,OAAM;IACR,CAAC;CACF;AAED,MAAM,OAAO,0BAA0B;IACrC,IAAI,GAAG,4BAA4B,CAAA;IACnC,KAAK,CAAC,IAAU;QACd,MAAM,OAAO,GAAG,IAAI,iCAAiC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAEhE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAEnB,OAAO,OAAO,CAAC,QAAQ,CAAA;IACzB,CAAC;CACF"}
@@ -0,0 +1,43 @@
1
+ import { isERBNode } from "@herb-tools/core";
2
+ import { BaseRuleVisitor } from "./rule-utils.js";
3
+ class RequireWhitespaceInsideTags extends BaseRuleVisitor {
4
+ visitChildNodes(node) {
5
+ this.checkWhitespace(node);
6
+ super.visitChildNodes(node);
7
+ }
8
+ checkWhitespace(node) {
9
+ if (!isERBNode(node)) {
10
+ return;
11
+ }
12
+ const openTag = node.tag_opening;
13
+ const closeTag = node.tag_closing;
14
+ const content = node.content;
15
+ if (!openTag || !closeTag || !content) {
16
+ return;
17
+ }
18
+ const value = content.value;
19
+ this.checkOpenTagWhitespace(openTag, value);
20
+ this.checkCloseTagWhitespace(closeTag, value);
21
+ }
22
+ checkOpenTagWhitespace(openTag, content) {
23
+ if (content.startsWith(" ") || content.startsWith("\n")) {
24
+ return;
25
+ }
26
+ this.addOffense(`Add whitespace after \`${openTag.value}\`.`, openTag.location, "error");
27
+ }
28
+ checkCloseTagWhitespace(closeTag, content) {
29
+ if (content.endsWith(" ") || content.endsWith("\n")) {
30
+ return;
31
+ }
32
+ this.addOffense(`Add whitespace before \`${closeTag.value}\`.`, closeTag.location, "error");
33
+ }
34
+ }
35
+ export class ERBRequireWhitespaceRule {
36
+ name = "erb-require-whitespace-inside-tags";
37
+ check(node) {
38
+ const visitor = new RequireWhitespaceInsideTags(this.name);
39
+ visitor.visit(node);
40
+ return visitor.offenses;
41
+ }
42
+ }
43
+ //# sourceMappingURL=erb-require-whitespace-inside-tags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"erb-require-whitespace-inside-tags.js","sourceRoot":"","sources":["../../../src/rules/erb-require-whitespace-inside-tags.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEjD,MAAM,2BAA4B,SAAQ,eAAe;IAEvD,eAAe,CAAC,IAAU;QACxB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;QAC1B,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,CAAA;IAC7B,CAAC;IAEO,eAAe,CAAC,IAAU;QAChC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,OAAM;QACR,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAA;QAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW,CAAA;QACjC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;QAE5B,IAAI,CAAC,OAAO,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC;YACtC,OAAM;QACR,CAAC;QAED,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAA;QAE3B,IAAI,CAAC,sBAAsB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QAC3C,IAAI,CAAC,uBAAuB,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;IAC/C,CAAC;IAEO,sBAAsB,CAAC,OAAc,EAAE,OAAc;QAC3D,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACxD,OAAM;QACR,CAAC;QACD,IAAI,CAAC,UAAU,CACb,0BAA0B,OAAO,CAAC,KAAK,KAAK,EAC5C,OAAO,CAAC,QAAQ,EAChB,OAAO,CACR,CAAA;IACH,CAAC;IAEO,uBAAuB,CAAC,QAAe,EAAE,OAAc;QAC7D,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACpD,OAAM;QACR,CAAC;QACD,IAAI,CAAC,UAAU,CACb,2BAA2B,QAAQ,CAAC,KAAK,KAAK,EAC9C,QAAQ,CAAC,QAAQ,EACjB,OAAO,CACR,CAAA;IACH,CAAC;CACF;AAED,MAAM,OAAO,wBAAwB;IACnC,IAAI,GAAG,oCAAoC,CAAA;IAC3C,KAAK,CAAC,IAAU;QACd,MAAM,OAAO,GAAG,IAAI,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC1D,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACnB,OAAO,OAAO,CAAC,QAAQ,CAAA;IACzB,CAAC;CACF"}
@@ -0,0 +1,25 @@
1
+ import { BaseRuleVisitor, getTagName, hasAttribute } from "./rule-utils.js";
2
+ class AnchorRechireHrefVisitor extends BaseRuleVisitor {
3
+ visitHTMLOpenTagNode(node) {
4
+ this.checkATag(node);
5
+ super.visitHTMLOpenTagNode(node);
6
+ }
7
+ checkATag(node) {
8
+ const tagName = getTagName(node);
9
+ if (tagName !== "a") {
10
+ return;
11
+ }
12
+ if (!hasAttribute(node, "href")) {
13
+ this.addOffense("Add an `href` attribute to `<a>` to ensure it is focusable and accessible.", node.tag_name.location, "error");
14
+ }
15
+ }
16
+ }
17
+ export class HTMLAnchorRequireHrefRule {
18
+ name = "html-anchor-require-href";
19
+ check(node) {
20
+ const visitor = new AnchorRechireHrefVisitor(this.name);
21
+ visitor.visit(node);
22
+ return visitor.offenses;
23
+ }
24
+ }
25
+ //# sourceMappingURL=html-anchor-require-href.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-anchor-require-href.js","sourceRoot":"","sources":["../../../src/rules/html-anchor-require-href.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAK3E,MAAM,wBAAyB,SAAQ,eAAe;IACpD,oBAAoB,CAAC,IAAqB;QACxC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;QACpB,KAAK,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAA;IAClC,CAAC;IAEO,SAAS,CAAC,IAAqB;QACrC,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;QAEhC,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;YACpB,OAAM;QACR,CAAC;QAED,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;YAChC,IAAI,CAAC,UAAU,CACb,4EAA4E,EAC5E,IAAI,CAAC,QAAS,CAAC,QAAQ,EACvB,OAAO,CACR,CAAA;QACH,CAAC;IACH,CAAC;CACF;AAED,MAAM,OAAO,yBAAyB;IACpC,IAAI,GAAG,0BAA0B,CAAA;IAEjC,KAAK,CAAC,IAAU;QACd,MAAM,OAAO,GAAG,IAAI,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAEvD,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAEnB,OAAO,OAAO,CAAC,QAAQ,CAAA;IACzB,CAAC;CACF"}
@@ -0,0 +1,26 @@
1
+ import { AttributeVisitorMixin, getAttributeName, getAttributes } from "./rule-utils.js";
2
+ class AriaRoleHeadingRequiresLevel extends AttributeVisitorMixin {
3
+ // We want to check 2 attributes here:
4
+ // 1. role="heading"
5
+ // 2. aria-level (which must be present if role="heading")
6
+ checkAttribute(attributeName, attributeValue, attributeNode, parentNode) {
7
+ if (!(attributeName === "role" && attributeValue === "heading")) {
8
+ return;
9
+ }
10
+ const allAttributes = getAttributes(parentNode);
11
+ // If we have a role="heading", we must check for aria-level
12
+ const ariaLevelAttr = allAttributes.find(attr => getAttributeName(attr) === "aria-level");
13
+ if (!ariaLevelAttr) {
14
+ this.addOffense(`Element with \`role="heading"\` must have an \`aria-level\` attribute.`, attributeNode.location, "error");
15
+ }
16
+ }
17
+ }
18
+ export class HTMLAriaRoleHeadingRequiresLevelRule {
19
+ name = "html-aria-role-heading-requires-level";
20
+ check(node) {
21
+ const visitor = new AriaRoleHeadingRequiresLevel(this.name);
22
+ visitor.visit(node);
23
+ return visitor.offenses;
24
+ }
25
+ }
26
+ //# sourceMappingURL=html-aria-role-heading-requires-level.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-aria-role-heading-requires-level.js","sourceRoot":"","sources":["../../../src/rules/html-aria-role-heading-requires-level.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAKxF,MAAM,4BAA6B,SAAQ,qBAAqB;IAE9D,sCAAsC;IACtC,oBAAoB;IACpB,0DAA0D;IAC1D,cAAc,CACZ,aAAqB,EACrB,cAA6B,EAC7B,aAAgC,EAChC,UAAkD;QAGlD,IAAI,CAAC,CAAC,aAAa,KAAK,MAAM,IAAI,cAAc,KAAK,SAAS,CAAC,EAAE,CAAC;YAChE,OAAM;QACR,CAAC;QAED,MAAM,aAAa,GAAG,aAAa,CAAC,UAAU,CAAC,CAAA;QAE/C,4DAA4D;QAC5D,MAAM,aAAa,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,YAAY,CAAC,CAAA;QACzF,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,IAAI,CAAC,UAAU,CACb,wEAAwE,EACxE,aAAa,CAAC,QAAQ,EACtB,OAAO,CACR,CAAA;QACH,CAAC;IACH,CAAC;CACF;AAED,MAAM,OAAO,oCAAoC;IAC/C,IAAI,GAAG,uCAAuC,CAAA;IAE9C,KAAK,CAAC,IAAU;QACd,MAAM,OAAO,GAAG,IAAI,4BAA4B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3D,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACnB,OAAO,OAAO,CAAC,QAAQ,CAAA;IACzB,CAAC;CACF"}
@@ -0,0 +1,21 @@
1
+ import { AttributeVisitorMixin, getAttributeValueQuoteType, hasAttributeValue } from "./rule-utils.js";
2
+ class AttributeDoubleQuotesVisitor extends AttributeVisitorMixin {
3
+ checkAttribute(attributeName, attributeValue, attributeNode) {
4
+ if (!hasAttributeValue(attributeNode))
5
+ return;
6
+ if (getAttributeValueQuoteType(attributeNode) !== "single")
7
+ return;
8
+ if (attributeValue?.includes('"'))
9
+ return; // Single quotes acceptable when value contains double quotes
10
+ this.addOffense(`Attribute \`${attributeName}\` uses single quotes. Prefer double quotes for HTML attribute values: \`${attributeName}="value"\`.`, attributeNode.value.location, "warning");
11
+ }
12
+ }
13
+ export class HTMLAttributeDoubleQuotesRule {
14
+ name = "html-attribute-double-quotes";
15
+ check(node) {
16
+ const visitor = new AttributeDoubleQuotesVisitor(this.name);
17
+ visitor.visit(node);
18
+ return visitor.offenses;
19
+ }
20
+ }
21
+ //# sourceMappingURL=html-attribute-double-quotes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-attribute-double-quotes.js","sourceRoot":"","sources":["../../../src/rules/html-attribute-double-quotes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAKtG,MAAM,4BAA6B,SAAQ,qBAAqB;IACpD,cAAc,CAAC,aAAqB,EAAE,cAA6B,EAAE,aAAgC;QAC7G,IAAI,CAAC,iBAAiB,CAAC,aAAa,CAAC;YAAE,OAAM;QAC7C,IAAI,0BAA0B,CAAC,aAAa,CAAC,KAAK,QAAQ;YAAE,OAAM;QAClE,IAAI,cAAc,EAAE,QAAQ,CAAC,GAAG,CAAC;YAAE,OAAM,CAAC,6DAA6D;QAEvG,IAAI,CAAC,UAAU,CACb,eAAe,aAAa,4EAA4E,aAAa,aAAa,EAClI,aAAa,CAAC,KAAM,CAAC,QAAQ,EAC7B,SAAS,CACV,CAAA;IACH,CAAC;CACF;AAED,MAAM,OAAO,6BAA6B;IACxC,IAAI,GAAG,8BAA8B,CAAA;IAErC,KAAK,CAAC,IAAU;QACd,MAAM,OAAO,GAAG,IAAI,4BAA4B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3D,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACnB,OAAO,OAAO,CAAC,QAAQ,CAAA;IACzB,CAAC;CACF"}
@@ -0,0 +1,22 @@
1
+ import { AttributeVisitorMixin } from "./rule-utils.js";
2
+ class AttributeValuesRequireQuotesVisitor extends AttributeVisitorMixin {
3
+ checkAttribute(attributeName, _attributeValue, attributeNode) {
4
+ if (attributeNode.value?.type !== "AST_HTML_ATTRIBUTE_VALUE_NODE")
5
+ return;
6
+ const valueNode = attributeNode.value;
7
+ if (valueNode.quoted)
8
+ return;
9
+ this.addOffense(
10
+ // TODO: print actual attribute value in message
11
+ `Attribute value should be quoted: \`${attributeName}="value"\`. Always wrap attribute values in quotes.`, valueNode.location, "error");
12
+ }
13
+ }
14
+ export class HTMLAttributeValuesRequireQuotesRule {
15
+ name = "html-attribute-values-require-quotes";
16
+ check(node) {
17
+ const visitor = new AttributeValuesRequireQuotesVisitor(this.name);
18
+ visitor.visit(node);
19
+ return visitor.offenses;
20
+ }
21
+ }
22
+ //# sourceMappingURL=html-attribute-values-require-quotes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-attribute-values-require-quotes.js","sourceRoot":"","sources":["../../../src/rules/html-attribute-values-require-quotes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAKvD,MAAM,mCAAoC,SAAQ,qBAAqB;IAC3D,cAAc,CAAC,aAAqB,EAAE,eAA8B,EAAE,aAAgC;QAC9G,IAAI,aAAa,CAAC,KAAK,EAAE,IAAI,KAAK,+BAA+B;YAAE,OAAM;QAEzE,MAAM,SAAS,GAAG,aAAa,CAAC,KAA+B,CAAA;QAC/D,IAAI,SAAS,CAAC,MAAM;YAAE,OAAM;QAE5B,IAAI,CAAC,UAAU;QACb,gDAAgD;QAChD,uCAAuC,aAAa,qDAAqD,EACzG,SAAS,CAAC,QAAQ,EAClB,OAAO,CACR,CAAA;IACH,CAAC;CACF;AAED,MAAM,OAAO,oCAAoC;IAC/C,IAAI,GAAG,sCAAsC,CAAA;IAE7C,KAAK,CAAC,IAAU;QACd,MAAM,OAAO,GAAG,IAAI,mCAAmC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAClE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACnB,OAAO,OAAO,CAAC,QAAQ,CAAA;IACzB,CAAC;CACF"}
@@ -0,0 +1,19 @@
1
+ import { AttributeVisitorMixin, isBooleanAttribute, hasAttributeValue } from "./rule-utils.js";
2
+ class BooleanAttributesNoValueVisitor extends AttributeVisitorMixin {
3
+ checkAttribute(attributeName, _attributeValue, attributeNode) {
4
+ if (!isBooleanAttribute(attributeName))
5
+ return;
6
+ if (!hasAttributeValue(attributeNode))
7
+ return;
8
+ this.addOffense(`Boolean attribute \`${attributeName}\` should not have a value. Use \`${attributeName}\` instead of \`${attributeName}="${attributeName}"\`.`, attributeNode.value.location, "error");
9
+ }
10
+ }
11
+ export class HTMLBooleanAttributesNoValueRule {
12
+ name = "html-boolean-attributes-no-value";
13
+ check(node) {
14
+ const visitor = new BooleanAttributesNoValueVisitor(this.name);
15
+ visitor.visit(node);
16
+ return visitor.offenses;
17
+ }
18
+ }
19
+ //# sourceMappingURL=html-boolean-attributes-no-value.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-boolean-attributes-no-value.js","sourceRoot":"","sources":["../../../src/rules/html-boolean-attributes-no-value.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAK9F,MAAM,+BAAgC,SAAQ,qBAAqB;IACvD,cAAc,CAAC,aAAqB,EAAE,eAA8B,EAAE,aAAgC;QAC9G,IAAI,CAAC,kBAAkB,CAAC,aAAa,CAAC;YAAE,OAAM;QAC9C,IAAI,CAAC,iBAAiB,CAAC,aAAa,CAAC;YAAE,OAAM;QAE7C,IAAI,CAAC,UAAU,CACb,uBAAuB,aAAa,qCAAqC,aAAa,mBAAmB,aAAa,KAAK,aAAa,MAAM,EAC9I,aAAa,CAAC,KAAM,CAAC,QAAQ,EAC7B,OAAO,CACR,CAAA;IACH,CAAC;CACF;AAED,MAAM,OAAO,gCAAgC;IAC3C,IAAI,GAAG,kCAAkC,CAAA;IAEzC,KAAK,CAAC,IAAU;QACd,MAAM,OAAO,GAAG,IAAI,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC9D,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACnB,OAAO,OAAO,CAAC,QAAQ,CAAA;IACzB,CAAC;CACF"}
@@ -0,0 +1,29 @@
1
+ import { BaseRuleVisitor, getTagName, hasAttribute } from "./rule-utils.js";
2
+ class ImgRequireAltVisitor extends BaseRuleVisitor {
3
+ visitHTMLOpenTagNode(node) {
4
+ this.checkImgTag(node);
5
+ super.visitHTMLOpenTagNode(node);
6
+ }
7
+ visitHTMLSelfCloseTagNode(node) {
8
+ this.checkImgTag(node);
9
+ super.visitHTMLSelfCloseTagNode(node);
10
+ }
11
+ checkImgTag(node) {
12
+ const tagName = getTagName(node);
13
+ if (tagName !== "img") {
14
+ return;
15
+ }
16
+ if (!hasAttribute(node, "alt")) {
17
+ this.addOffense('Missing required `alt` attribute on `<img>` tag. Add `alt=""` for decorative images or `alt="description"` for informative images.', node.tag_name.location, "error");
18
+ }
19
+ }
20
+ }
21
+ export class HTMLImgRequireAltRule {
22
+ name = "html-img-require-alt";
23
+ check(node) {
24
+ const visitor = new ImgRequireAltVisitor(this.name);
25
+ visitor.visit(node);
26
+ return visitor.offenses;
27
+ }
28
+ }
29
+ //# sourceMappingURL=html-img-require-alt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-img-require-alt.js","sourceRoot":"","sources":["../../../src/rules/html-img-require-alt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAK3E,MAAM,oBAAqB,SAAQ,eAAe;IAChD,oBAAoB,CAAC,IAAqB;QACxC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;QACtB,KAAK,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAA;IAClC,CAAC;IAED,yBAAyB,CAAC,IAA0B;QAClD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;QACtB,KAAK,CAAC,yBAAyB,CAAC,IAAI,CAAC,CAAA;IACvC,CAAC;IAEO,WAAW,CAAC,IAA4C;QAC9D,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,CAAA;QAEhC,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;YACtB,OAAM;QACR,CAAC;QAED,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;YAC/B,IAAI,CAAC,UAAU,CACb,oIAAoI,EACpI,IAAI,CAAC,QAAS,CAAC,QAAQ,EACvB,OAAO,CACR,CAAA;QACH,CAAC;IACH,CAAC;CACF;AAED,MAAM,OAAO,qBAAqB;IAChC,IAAI,GAAG,sBAAsB,CAAA;IAE7B,KAAK,CAAC,IAAU;QACd,MAAM,OAAO,GAAG,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACnD,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACnB,OAAO,OAAO,CAAC,QAAQ,CAAA;IACzB,CAAC;CACF"}
@@ -0,0 +1,59 @@
1
+ import { BaseRuleVisitor, isInlineElement, isBlockElement } from "./rule-utils.js";
2
+ class BlockInsideInlineVisitor extends BaseRuleVisitor {
3
+ inlineStack = [];
4
+ isValidHTMLOpenTag(node) {
5
+ return !!(node.open_tag && node.open_tag.type === "AST_HTML_OPEN_TAG_NODE");
6
+ }
7
+ getElementType(tagName) {
8
+ const isInline = isInlineElement(tagName);
9
+ const isBlock = isBlockElement(tagName);
10
+ const isUnknown = !isInline && !isBlock;
11
+ return { isInline, isBlock, isUnknown };
12
+ }
13
+ addViolationMessage(tagName, isBlock, openTag) {
14
+ const parentInline = this.inlineStack[this.inlineStack.length - 1];
15
+ const elementType = isBlock ? "Block-level" : "Unknown";
16
+ this.addOffense(`${elementType} element \`<${tagName}>\` cannot be placed inside inline element \`<${parentInline}>\`.`, openTag.tag_name.location, "error");
17
+ }
18
+ visitInlineElement(node, tagName) {
19
+ this.inlineStack.push(tagName);
20
+ super.visitHTMLElementNode(node);
21
+ this.inlineStack.pop();
22
+ }
23
+ visitBlockElement(node) {
24
+ const savedStack = [...this.inlineStack];
25
+ this.inlineStack = [];
26
+ super.visitHTMLElementNode(node);
27
+ this.inlineStack = savedStack;
28
+ }
29
+ visitHTMLElementNode(node) {
30
+ if (!this.isValidHTMLOpenTag(node)) {
31
+ super.visitHTMLElementNode(node);
32
+ return;
33
+ }
34
+ const openTag = node.open_tag;
35
+ const tagName = openTag.tag_name?.value.toLowerCase();
36
+ if (!tagName) {
37
+ super.visitHTMLElementNode(node);
38
+ return;
39
+ }
40
+ const { isInline, isBlock, isUnknown } = this.getElementType(tagName);
41
+ if ((isBlock || isUnknown) && this.inlineStack.length > 0) {
42
+ this.addViolationMessage(tagName, isBlock, openTag);
43
+ }
44
+ if (isInline) {
45
+ this.visitInlineElement(node, tagName);
46
+ return;
47
+ }
48
+ this.visitBlockElement(node);
49
+ }
50
+ }
51
+ export class HTMLNoBlockInsideInlineRule {
52
+ name = "html-no-block-inside-inline";
53
+ check(node) {
54
+ const visitor = new BlockInsideInlineVisitor(this.name);
55
+ visitor.visit(node);
56
+ return visitor.offenses;
57
+ }
58
+ }
59
+ //# sourceMappingURL=html-no-block-inside-inline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-no-block-inside-inline.js","sourceRoot":"","sources":["../../../src/rules/html-no-block-inside-inline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { BaseRuleVisitor, forEachAttribute } from "./rule-utils.js";
2
+ class NoDuplicateAttributesVisitor extends BaseRuleVisitor {
3
+ visitHTMLOpenTagNode(node) {
4
+ this.checkDuplicateAttributes(node);
5
+ super.visitHTMLOpenTagNode(node);
6
+ }
7
+ visitHTMLSelfCloseTagNode(node) {
8
+ this.checkDuplicateAttributes(node);
9
+ super.visitHTMLSelfCloseTagNode(node);
10
+ }
11
+ checkDuplicateAttributes(node) {
12
+ const attributeNames = new Map();
13
+ forEachAttribute(node, (attributeNode) => {
14
+ if (attributeNode.name?.type !== "AST_HTML_ATTRIBUTE_NAME_NODE")
15
+ return;
16
+ const nameNode = attributeNode.name;
17
+ if (!nameNode.name)
18
+ return;
19
+ const attributeName = nameNode.name.value.toLowerCase(); // HTML attributes are case-insensitive
20
+ if (!attributeNames.has(attributeName)) {
21
+ attributeNames.set(attributeName, []);
22
+ }
23
+ attributeNames.get(attributeName).push(nameNode);
24
+ });
25
+ for (const [attributeName, nameNodes] of attributeNames) {
26
+ if (nameNodes.length > 1) {
27
+ for (let i = 1; i < nameNodes.length; i++) {
28
+ const nameNode = nameNodes[i];
29
+ this.addOffense(`Duplicate attribute \`${attributeName}\` found on tag. Remove the duplicate occurrence.`, nameNode.location, "error");
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
35
+ export class HTMLNoDuplicateAttributesRule {
36
+ name = "html-no-duplicate-attributes";
37
+ check(node) {
38
+ const visitor = new NoDuplicateAttributesVisitor(this.name);
39
+ visitor.visit(node);
40
+ return visitor.offenses;
41
+ }
42
+ }
43
+ //# sourceMappingURL=html-no-duplicate-attributes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-no-duplicate-attributes.js","sourceRoot":"","sources":["../../../src/rules/html-no-duplicate-attributes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAKnE,MAAM,4BAA6B,SAAQ,eAAe;IACxD,oBAAoB,CAAC,IAAqB;QACxC,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAA;QACnC,KAAK,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAA;IAClC,CAAC;IAED,yBAAyB,CAAC,IAA0B;QAClD,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAA;QACnC,KAAK,CAAC,yBAAyB,CAAC,IAAI,CAAC,CAAA;IACvC,CAAC;IAEO,wBAAwB,CAAC,IAA4C;QAC3E,MAAM,cAAc,GAAG,IAAI,GAAG,EAAmC,CAAA;QAEjE,gBAAgB,CAAC,IAAI,EAAE,CAAC,aAAa,EAAE,EAAE;YACvC,IAAI,aAAa,CAAC,IAAI,EAAE,IAAI,KAAK,8BAA8B;gBAAE,OAAM;YAEvE,MAAM,QAAQ,GAAG,aAAa,CAAC,IAA6B,CAAA;YAC5D,IAAI,CAAC,QAAQ,CAAC,IAAI;gBAAE,OAAM;YAE1B,MAAM,aAAa,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAA,CAAC,uCAAuC;YAE/F,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;gBACvC,cAAc,CAAC,GAAG,CAAC,aAAa,EAAE,EAAE,CAAC,CAAA;YACvC,CAAC;YAED,cAAc,CAAC,GAAG,CAAC,aAAa,CAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACnD,CAAC,CAAC,CAAA;QAEF,KAAK,MAAM,CAAC,aAAa,EAAE,SAAS,CAAC,IAAI,cAAc,EAAE,CAAC;YACxD,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC1C,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;oBAE7B,IAAI,CAAC,UAAU,CACb,yBAAyB,aAAa,mDAAmD,EACzF,QAAQ,CAAC,QAAQ,EACjB,OAAO,CACR,CAAA;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAED,MAAM,OAAO,6BAA6B;IACxC,IAAI,GAAG,8BAA8B,CAAA;IAErC,KAAK,CAAC,IAAU;QACd,MAAM,OAAO,GAAG,IAAI,4BAA4B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3D,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACnB,OAAO,OAAO,CAAC,QAAQ,CAAA;IACzB,CAAC;CACF"}
@@ -0,0 +1,148 @@
1
+ import { BaseRuleVisitor, getTagName, getAttributes, findAttributeByName, getAttributeValue, HEADING_TAGS } from "./rule-utils.js";
2
+ class NoEmptyHeadingsVisitor extends BaseRuleVisitor {
3
+ visitHTMLElementNode(node) {
4
+ this.checkHeadingElement(node);
5
+ super.visitHTMLElementNode(node);
6
+ }
7
+ visitHTMLSelfCloseTagNode(node) {
8
+ this.checkSelfClosingHeading(node);
9
+ super.visitHTMLSelfCloseTagNode(node);
10
+ }
11
+ checkHeadingElement(node) {
12
+ if (!node.open_tag || node.open_tag.type !== "AST_HTML_OPEN_TAG_NODE") {
13
+ return;
14
+ }
15
+ const openTag = node.open_tag;
16
+ const tagName = getTagName(openTag);
17
+ if (!tagName) {
18
+ return;
19
+ }
20
+ const isStandardHeading = HEADING_TAGS.has(tagName);
21
+ const isAriaHeading = this.hasHeadingRole(openTag);
22
+ if (!isStandardHeading && !isAriaHeading) {
23
+ return;
24
+ }
25
+ if (this.isEmptyHeading(node)) {
26
+ const elementDescription = isStandardHeading
27
+ ? `\`<${tagName}>\``
28
+ : `\`<${tagName} role="heading">\``;
29
+ this.addOffense(`Heading element ${elementDescription} must not be empty. Provide accessible text content for screen readers and SEO.`, node.location, "error");
30
+ }
31
+ }
32
+ checkSelfClosingHeading(node) {
33
+ const tagName = getTagName(node);
34
+ if (!tagName) {
35
+ return;
36
+ }
37
+ // Check if it's a standard heading tag (h1-h6) or has role="heading"
38
+ const isStandardHeading = HEADING_TAGS.has(tagName);
39
+ const isAriaHeading = this.hasHeadingRole(node);
40
+ if (!isStandardHeading && !isAriaHeading) {
41
+ return;
42
+ }
43
+ // Self-closing headings are always empty
44
+ const elementDescription = isStandardHeading
45
+ ? `\`<${tagName}>\``
46
+ : `\`<${tagName} role="heading">\``;
47
+ this.addOffense(`Heading element ${elementDescription} must not be empty. Provide accessible text content for screen readers and SEO.`, node.tag_name.location, "error");
48
+ }
49
+ isEmptyHeading(node) {
50
+ if (!node.body || node.body.length === 0) {
51
+ return true;
52
+ }
53
+ // Check if all content is just whitespace or inaccessible
54
+ let hasAccessibleContent = false;
55
+ for (const child of node.body) {
56
+ if (child.type === "AST_LITERAL_NODE") {
57
+ const literalNode = child;
58
+ if (literalNode.content.trim().length > 0) {
59
+ hasAccessibleContent = true;
60
+ break;
61
+ }
62
+ }
63
+ else if (child.type === "AST_HTML_TEXT_NODE") {
64
+ const textNode = child;
65
+ if (textNode.content.trim().length > 0) {
66
+ hasAccessibleContent = true;
67
+ break;
68
+ }
69
+ }
70
+ else if (child.type === "AST_HTML_ELEMENT_NODE") {
71
+ const elementNode = child;
72
+ // Check if this element is accessible (not aria-hidden="true")
73
+ if (this.isElementAccessible(elementNode)) {
74
+ hasAccessibleContent = true;
75
+ break;
76
+ }
77
+ }
78
+ else {
79
+ // If there's any non-literal/non-text/non-element content (like ERB), consider it accessible
80
+ hasAccessibleContent = true;
81
+ break;
82
+ }
83
+ }
84
+ return !hasAccessibleContent;
85
+ }
86
+ hasHeadingRole(node) {
87
+ const attributes = getAttributes(node);
88
+ const roleAttribute = findAttributeByName(attributes, "role");
89
+ if (!roleAttribute) {
90
+ return false;
91
+ }
92
+ const roleValue = getAttributeValue(roleAttribute);
93
+ return roleValue === "heading";
94
+ }
95
+ isElementAccessible(node) {
96
+ // Check if the element has aria-hidden="true"
97
+ if (!node.open_tag || node.open_tag.type !== "AST_HTML_OPEN_TAG_NODE") {
98
+ return true;
99
+ }
100
+ const openTag = node.open_tag;
101
+ const attributes = getAttributes(openTag);
102
+ const ariaHiddenAttribute = findAttributeByName(attributes, "aria-hidden");
103
+ if (ariaHiddenAttribute) {
104
+ const ariaHiddenValue = getAttributeValue(ariaHiddenAttribute);
105
+ if (ariaHiddenValue === "true") {
106
+ return false;
107
+ }
108
+ }
109
+ // Recursively check if the element has any accessible content
110
+ if (!node.body || node.body.length === 0) {
111
+ return false;
112
+ }
113
+ for (const child of node.body) {
114
+ if (child.type === "AST_LITERAL_NODE") {
115
+ const literalNode = child;
116
+ if (literalNode.content.trim().length > 0) {
117
+ return true;
118
+ }
119
+ }
120
+ else if (child.type === "AST_HTML_TEXT_NODE") {
121
+ const textNode = child;
122
+ if (textNode.content.trim().length > 0) {
123
+ return true;
124
+ }
125
+ }
126
+ else if (child.type === "AST_HTML_ELEMENT_NODE") {
127
+ const elementNode = child;
128
+ if (this.isElementAccessible(elementNode)) {
129
+ return true;
130
+ }
131
+ }
132
+ else {
133
+ // If there's any non-literal/non-text/non-element content (like ERB), consider it accessible
134
+ return true;
135
+ }
136
+ }
137
+ return false;
138
+ }
139
+ }
140
+ export class HTMLNoEmptyHeadingsRule {
141
+ name = "html-no-empty-headings";
142
+ check(node) {
143
+ const visitor = new NoEmptyHeadingsVisitor(this.name);
144
+ visitor.visit(node);
145
+ return visitor.offenses;
146
+ }
147
+ }
148
+ //# sourceMappingURL=html-no-empty-headings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html-no-empty-headings.js","sourceRoot":"","sources":["../../../src/rules/html-no-empty-headings.ts"],"names":[],"mappings":"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"}