@herb-tools/linter 0.8.9 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/{src/cli → cli}/argument-parser.js +15 -2
- package/dist/cli/argument-parser.js.map +1 -0
- package/dist/{src/cli → cli}/file-processor.js +155 -9
- package/dist/cli/file-processor.js.map +1 -0
- package/dist/cli/file-url.js +6 -0
- package/dist/cli/file-url.js.map +1 -0
- package/dist/cli/formatters/base-formatter.js.map +1 -0
- package/dist/{src/cli → cli}/formatters/detailed-formatter.js +16 -19
- package/dist/cli/formatters/detailed-formatter.js.map +1 -0
- package/dist/cli/formatters/github-actions-formatter.js.map +1 -0
- package/dist/cli/formatters/index.js.map +1 -0
- package/dist/cli/formatters/json-formatter.js.map +1 -0
- package/dist/cli/formatters/simple-formatter.js +54 -0
- package/dist/cli/formatters/simple-formatter.js.map +1 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/lint-worker.js +143 -0
- package/dist/cli/lint-worker.js.map +1 -0
- package/dist/cli/output-manager.js.map +1 -0
- package/dist/{src/cli → cli}/summary-reporter.js +13 -16
- package/dist/cli/summary-reporter.js.map +1 -0
- package/dist/{src/cli.js → cli.js} +5 -3
- package/dist/cli.js.map +1 -0
- package/dist/{src/custom-rule-loader.js → custom-rule-loader.js} +20 -4
- package/dist/custom-rule-loader.js.map +1 -0
- package/dist/herb-disable-comment-utils.js.map +1 -0
- package/dist/herb-lint.js +60648 -17513
- package/dist/herb-lint.js.map +1 -1
- package/dist/index.cjs +2621 -934
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2554 -873
- package/dist/index.js.map +1 -1
- package/dist/lint-worker.js +71462 -0
- package/dist/lint-worker.js.map +1 -0
- package/dist/linter-ignore.js.map +1 -0
- package/dist/{src/linter.js → linter.js} +89 -74
- package/dist/linter.js.map +1 -0
- package/dist/loader.cjs +31206 -7834
- package/dist/loader.cjs.map +1 -1
- package/dist/loader.js +31168 -7802
- package/dist/loader.js.map +1 -1
- package/dist/parse-cache.js +30 -0
- package/dist/parse-cache.js.map +1 -0
- package/dist/rules/actionview-no-silent-helper.js +45 -0
- package/dist/rules/actionview-no-silent-helper.js.map +1 -0
- package/dist/{src/rules → rules}/erb-comment-syntax.js +2 -2
- package/dist/rules/erb-comment-syntax.js.map +1 -0
- package/dist/{src/rules → rules}/erb-no-case-node-children.js +2 -2
- package/dist/rules/erb-no-case-node-children.js.map +1 -0
- package/dist/rules/erb-no-conditional-html-element.js +38 -0
- package/dist/rules/erb-no-conditional-html-element.js.map +1 -0
- package/dist/rules/erb-no-conditional-open-tag.js +24 -0
- package/dist/rules/erb-no-conditional-open-tag.js.map +1 -0
- package/dist/rules/erb-no-duplicate-branch-elements.js +245 -0
- package/dist/rules/erb-no-duplicate-branch-elements.js.map +1 -0
- package/dist/{src/rules → rules}/erb-no-empty-tags.js +2 -2
- package/dist/rules/erb-no-empty-tags.js.map +1 -0
- package/dist/{src/rules → rules}/erb-no-extra-newline.js +4 -21
- package/dist/rules/erb-no-extra-newline.js.map +1 -0
- package/dist/{src/rules → rules}/erb-no-extra-whitespace-inside-tags.js +39 -13
- package/dist/rules/erb-no-extra-whitespace-inside-tags.js.map +1 -0
- package/dist/rules/erb-no-inline-case-conditions.js +40 -0
- package/dist/rules/erb-no-inline-case-conditions.js.map +1 -0
- package/dist/rules/erb-no-instance-variables-in-partials.js +67 -0
- package/dist/rules/erb-no-instance-variables-in-partials.js.map +1 -0
- package/dist/rules/erb-no-interpolated-class-names.js +47 -0
- package/dist/rules/erb-no-interpolated-class-names.js.map +1 -0
- package/dist/rules/erb-no-javascript-tag-helper.js +34 -0
- package/dist/rules/erb-no-javascript-tag-helper.js.map +1 -0
- package/dist/{src/rules → rules}/erb-no-output-control-flow.js +9 -12
- package/dist/rules/erb-no-output-control-flow.js.map +1 -0
- package/dist/rules/erb-no-output-in-attribute-name.js +30 -0
- package/dist/rules/erb-no-output-in-attribute-name.js.map +1 -0
- package/dist/rules/erb-no-output-in-attribute-position.js +30 -0
- package/dist/rules/erb-no-output-in-attribute-position.js.map +1 -0
- package/dist/rules/erb-no-raw-output-in-attribute-value.js +35 -0
- package/dist/rules/erb-no-raw-output-in-attribute-value.js.map +1 -0
- package/dist/{src/rules → rules}/erb-no-silent-tag-in-attribute-name.js +2 -2
- package/dist/rules/erb-no-silent-tag-in-attribute-name.js.map +1 -0
- package/dist/rules/erb-no-statement-in-script.js +58 -0
- package/dist/rules/erb-no-statement-in-script.js.map +1 -0
- package/dist/rules/erb-no-then-in-control-flow.js +45 -0
- package/dist/rules/erb-no-then-in-control-flow.js.map +1 -0
- package/dist/rules/erb-no-trailing-whitespace.js +138 -0
- package/dist/rules/erb-no-trailing-whitespace.js.map +1 -0
- package/dist/rules/erb-no-unsafe-js-attribute.js +36 -0
- package/dist/rules/erb-no-unsafe-js-attribute.js.map +1 -0
- package/dist/rules/erb-no-unsafe-raw.js +63 -0
- package/dist/rules/erb-no-unsafe-raw.js.map +1 -0
- package/dist/rules/erb-no-unsafe-script-interpolation.js +54 -0
- package/dist/rules/erb-no-unsafe-script-interpolation.js.map +1 -0
- package/dist/{src/rules → rules}/erb-prefer-image-tag-helper.js +5 -4
- package/dist/rules/erb-prefer-image-tag-helper.js.map +1 -0
- package/dist/{src/rules → rules}/erb-require-trailing-newline.js +2 -2
- package/dist/rules/erb-require-trailing-newline.js.map +1 -0
- package/dist/{src/rules → rules}/erb-require-whitespace-inside-tags.js +39 -15
- package/dist/rules/erb-require-whitespace-inside-tags.js.map +1 -0
- package/dist/{src/rules → rules}/erb-right-trim.js +2 -2
- package/dist/rules/erb-right-trim.js.map +1 -0
- package/dist/{src/rules → rules}/erb-strict-locals-comment-syntax.js +5 -5
- package/dist/rules/erb-strict-locals-comment-syntax.js.map +1 -0
- package/dist/{src/rules → rules}/erb-strict-locals-required.js +2 -2
- package/dist/rules/erb-strict-locals-required.js.map +1 -0
- package/dist/rules/file-utils.js.map +1 -0
- package/dist/rules/herb-disable-comment-base.js.map +1 -0
- package/dist/{src/rules → rules}/herb-disable-comment-malformed.js +2 -2
- package/dist/rules/herb-disable-comment-malformed.js.map +1 -0
- package/dist/{src/rules → rules}/herb-disable-comment-missing-rules.js +2 -2
- package/dist/rules/herb-disable-comment-missing-rules.js.map +1 -0
- package/dist/{src/rules → rules}/herb-disable-comment-no-duplicate-rules.js +2 -2
- package/dist/rules/herb-disable-comment-no-duplicate-rules.js.map +1 -0
- package/dist/{src/rules → rules}/herb-disable-comment-no-redundant-all.js +2 -2
- package/dist/rules/herb-disable-comment-no-redundant-all.js.map +1 -0
- package/dist/{src/rules → rules}/herb-disable-comment-unnecessary.js +2 -2
- package/dist/rules/herb-disable-comment-unnecessary.js.map +1 -0
- package/dist/{src/rules → rules}/herb-disable-comment-valid-rule-name.js +2 -2
- package/dist/rules/herb-disable-comment-valid-rule-name.js.map +1 -0
- package/dist/rules/html-allowed-script-type.js +57 -0
- package/dist/rules/html-allowed-script-type.js.map +1 -0
- package/dist/rules/html-anchor-require-href.js +68 -0
- package/dist/rules/html-anchor-require-href.js.map +1 -0
- package/dist/{src/rules → rules}/html-aria-attribute-must-be-valid.js +3 -3
- package/dist/rules/html-aria-attribute-must-be-valid.js.map +1 -0
- package/dist/{src/rules → rules}/html-aria-label-is-well-formatted.js +3 -3
- package/dist/rules/html-aria-label-is-well-formatted.js.map +1 -0
- package/dist/{src/rules → rules}/html-aria-level-must-be-valid.js +3 -3
- package/dist/rules/html-aria-level-must-be-valid.js.map +1 -0
- package/dist/{src/rules → rules}/html-aria-role-heading-requires-level.js +5 -4
- package/dist/rules/html-aria-role-heading-requires-level.js.map +1 -0
- package/dist/{src/rules → rules}/html-aria-role-must-be-valid.js +3 -3
- package/dist/rules/html-aria-role-must-be-valid.js.map +1 -0
- package/dist/{src/rules → rules}/html-attribute-double-quotes.js +4 -4
- package/dist/rules/html-attribute-double-quotes.js.map +1 -0
- package/dist/{src/rules → rules}/html-attribute-equals-spacing.js +2 -2
- package/dist/rules/html-attribute-equals-spacing.js.map +1 -0
- package/dist/{src/rules → rules}/html-attribute-values-require-quotes.js +2 -2
- package/dist/rules/html-attribute-values-require-quotes.js.map +1 -0
- package/dist/{src/rules → rules}/html-avoid-both-disabled-and-aria-disabled.js +9 -9
- package/dist/rules/html-avoid-both-disabled-and-aria-disabled.js.map +1 -0
- package/dist/{src/rules → rules}/html-body-only-elements.js +5 -4
- package/dist/rules/html-body-only-elements.js.map +1 -0
- package/dist/{src/rules → rules}/html-boolean-attributes-no-value.js +4 -3
- package/dist/rules/html-boolean-attributes-no-value.js.map +1 -0
- package/dist/rules/html-details-has-summary.js +52 -0
- package/dist/rules/html-details-has-summary.js.map +1 -0
- package/dist/{src/rules → rules}/html-head-only-elements.js +6 -5
- package/dist/rules/html-head-only-elements.js.map +1 -0
- package/dist/{src/rules → rules}/html-iframe-has-title.js +8 -11
- package/dist/rules/html-iframe-has-title.js.map +1 -0
- package/dist/{src/rules → rules}/html-img-require-alt.js +11 -5
- package/dist/rules/html-img-require-alt.js.map +1 -0
- package/dist/{src/rules → rules}/html-input-require-autocomplete.js +7 -10
- package/dist/rules/html-input-require-autocomplete.js.map +1 -0
- package/dist/{src/rules → rules}/html-navigation-has-label.js +6 -5
- package/dist/rules/html-navigation-has-label.js.map +1 -0
- package/dist/rules/html-no-abstract-roles.js +29 -0
- package/dist/rules/html-no-abstract-roles.js.map +1 -0
- package/dist/rules/html-no-aria-hidden-on-body.js +42 -0
- package/dist/rules/html-no-aria-hidden-on-body.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-aria-hidden-on-focusable.js +6 -5
- package/dist/rules/html-no-aria-hidden-on-focusable.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-block-inside-inline.js +6 -9
- package/dist/rules/html-no-block-inside-inline.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-duplicate-attributes.js +4 -3
- package/dist/rules/html-no-duplicate-attributes.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-duplicate-ids.js +14 -11
- package/dist/rules/html-no-duplicate-ids.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-duplicate-meta-names.js +22 -20
- package/dist/rules/html-no-duplicate-meta-names.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-empty-attributes.js +2 -2
- package/dist/rules/html-no-empty-attributes.js.map +1 -0
- package/dist/rules/html-no-empty-headings.js +98 -0
- package/dist/rules/html-no-empty-headings.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-nested-links.js +23 -15
- package/dist/rules/html-no-nested-links.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-positive-tab-index.js +3 -3
- package/dist/rules/html-no-positive-tab-index.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-self-closing.js +4 -4
- package/dist/rules/html-no-self-closing.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-space-in-tag.js +4 -6
- package/dist/rules/html-no-space-in-tag.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-title-attribute.js +6 -5
- package/dist/rules/html-no-title-attribute.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-underscores-in-attribute-names.js +2 -2
- package/dist/rules/html-no-underscores-in-attribute-names.js.map +1 -0
- package/dist/rules/html-require-closing-tags.js +29 -0
- package/dist/rules/html-require-closing-tags.js.map +1 -0
- package/dist/{src/rules → rules}/html-tag-name-lowercase.js +13 -9
- package/dist/rules/html-tag-name-lowercase.js.map +1 -0
- package/dist/{src/rules → rules}/index.js +19 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/{src/rules → rules}/parser-no-errors.js +3 -3
- package/dist/rules/parser-no-errors.js.map +1 -0
- package/dist/{src/rules → rules}/rule-utils.js +141 -219
- package/dist/rules/rule-utils.js.map +1 -0
- package/dist/rules/string-utils.js.map +1 -0
- package/dist/{src/rules → rules}/svg-tag-name-capitalization.js +7 -6
- package/dist/rules/svg-tag-name-capitalization.js.map +1 -0
- package/dist/rules/turbo-permanent-require-id.js +34 -0
- package/dist/rules/turbo-permanent-require-id.js.map +1 -0
- package/dist/{src/rules.js → rules.js} +56 -10
- package/dist/rules.js.map +1 -0
- package/dist/types/cli/argument-parser.d.ts +1 -0
- package/dist/types/cli/file-processor.d.ts +13 -0
- package/dist/types/cli/file-url.d.ts +1 -0
- package/dist/types/cli/index.d.ts +1 -0
- package/dist/types/cli/lint-worker.d.ts +34 -0
- package/dist/types/custom-rule-loader.d.ts +4 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/linter.d.ts +13 -6
- package/dist/types/parse-cache.d.ts +9 -0
- package/dist/types/{src/rules/html-aria-level-must-be-valid.d.ts → rules/actionview-no-silent-helper.d.ts} +4 -3
- package/dist/types/rules/erb-comment-syntax.d.ts +1 -1
- package/dist/types/rules/erb-no-case-node-children.d.ts +1 -1
- package/dist/types/{src/rules/herb-disable-comment-malformed.d.ts → rules/erb-no-conditional-html-element.d.ts} +3 -3
- package/dist/types/{src/rules/erb-prefer-image-tag-helper.d.ts → rules/erb-no-conditional-open-tag.d.ts} +3 -3
- package/dist/types/rules/erb-no-duplicate-branch-elements.d.ts +17 -0
- package/dist/types/rules/erb-no-empty-tags.d.ts +1 -1
- package/dist/types/rules/erb-no-extra-newline.d.ts +1 -1
- package/dist/types/rules/erb-no-extra-whitespace-inside-tags.d.ts +1 -1
- package/dist/types/{src/rules/html-no-duplicate-attributes.d.ts → rules/erb-no-inline-case-conditions.d.ts} +4 -3
- package/dist/types/rules/erb-no-instance-variables-in-partials.d.ts +10 -0
- package/dist/types/{src/rules/html-no-aria-hidden-on-focusable.d.ts → rules/erb-no-interpolated-class-names.d.ts} +2 -2
- package/dist/types/{src/rules/html-aria-attribute-must-be-valid.d.ts → rules/erb-no-javascript-tag-helper.d.ts} +2 -2
- package/dist/types/rules/erb-no-output-control-flow.d.ts +1 -1
- package/dist/types/{src/rules/erb-no-silent-tag-in-attribute-name.d.ts → rules/erb-no-output-in-attribute-name.d.ts} +2 -2
- package/dist/types/{src/rules/herb-disable-comment-missing-rules.d.ts → rules/erb-no-output-in-attribute-position.d.ts} +2 -2
- package/dist/types/{src/rules/erb-no-empty-tags.d.ts → rules/erb-no-raw-output-in-attribute-value.d.ts} +2 -2
- package/dist/types/rules/erb-no-silent-tag-in-attribute-name.d.ts +1 -1
- package/dist/types/{src/rules/html-navigation-has-label.d.ts → rules/erb-no-statement-in-script.d.ts} +2 -2
- package/dist/types/rules/erb-no-then-in-control-flow.d.ts +9 -0
- package/dist/types/rules/erb-no-trailing-whitespace.d.ts +19 -0
- package/dist/types/{src/rules/html-no-positive-tab-index.d.ts → rules/erb-no-unsafe-js-attribute.d.ts} +2 -2
- package/dist/types/{src/rules/erb-no-case-node-children.d.ts → rules/erb-no-unsafe-raw.d.ts} +2 -2
- package/dist/types/rules/erb-no-unsafe-script-interpolation.d.ts +8 -0
- package/dist/types/rules/erb-prefer-image-tag-helper.d.ts +1 -1
- package/dist/types/rules/erb-require-trailing-newline.d.ts +1 -1
- package/dist/types/rules/erb-require-whitespace-inside-tags.d.ts +1 -1
- package/dist/types/rules/erb-right-trim.d.ts +1 -1
- package/dist/types/rules/erb-strict-locals-comment-syntax.d.ts +1 -1
- package/dist/types/rules/erb-strict-locals-required.d.ts +1 -1
- package/dist/types/rules/herb-disable-comment-malformed.d.ts +1 -1
- package/dist/types/rules/herb-disable-comment-missing-rules.d.ts +1 -1
- package/dist/types/rules/herb-disable-comment-no-duplicate-rules.d.ts +1 -1
- package/dist/types/rules/herb-disable-comment-no-redundant-all.d.ts +1 -1
- package/dist/types/rules/herb-disable-comment-unnecessary.d.ts +1 -1
- package/dist/types/rules/herb-disable-comment-valid-rule-name.d.ts +1 -1
- package/dist/types/{src/rules/html-anchor-require-href.d.ts → rules/html-allowed-script-type.d.ts} +2 -2
- package/dist/types/rules/html-anchor-require-href.d.ts +3 -2
- package/dist/types/rules/html-aria-attribute-must-be-valid.d.ts +1 -1
- package/dist/types/rules/html-aria-label-is-well-formatted.d.ts +1 -1
- package/dist/types/rules/html-aria-level-must-be-valid.d.ts +1 -1
- package/dist/types/rules/html-aria-role-heading-requires-level.d.ts +1 -1
- package/dist/types/rules/html-aria-role-must-be-valid.d.ts +1 -1
- package/dist/types/rules/html-attribute-double-quotes.d.ts +1 -1
- package/dist/types/rules/html-attribute-equals-spacing.d.ts +1 -1
- package/dist/types/rules/html-attribute-values-require-quotes.d.ts +1 -1
- package/dist/types/rules/html-avoid-both-disabled-and-aria-disabled.d.ts +1 -1
- package/dist/types/rules/html-body-only-elements.d.ts +1 -1
- package/dist/types/rules/html-boolean-attributes-no-value.d.ts +1 -1
- package/dist/types/{src/rules/html-no-empty-attributes.d.ts → rules/html-details-has-summary.d.ts} +4 -3
- package/dist/types/rules/html-head-only-elements.d.ts +1 -1
- package/dist/types/rules/html-iframe-has-title.d.ts +1 -1
- package/dist/types/rules/html-img-require-alt.d.ts +1 -1
- package/dist/types/rules/html-input-require-autocomplete.d.ts +1 -1
- package/dist/types/rules/html-navigation-has-label.d.ts +1 -1
- package/dist/types/{src/rules/html-no-empty-headings.d.ts → rules/html-no-abstract-roles.d.ts} +2 -2
- package/dist/types/{src/rules/erb-no-output-control-flow.d.ts → rules/html-no-aria-hidden-on-body.d.ts} +3 -3
- package/dist/types/rules/html-no-aria-hidden-on-focusable.d.ts +1 -1
- package/dist/types/rules/html-no-block-inside-inline.d.ts +1 -1
- package/dist/types/rules/html-no-duplicate-attributes.d.ts +1 -1
- package/dist/types/rules/html-no-duplicate-ids.d.ts +1 -1
- package/dist/types/rules/html-no-duplicate-meta-names.d.ts +1 -1
- package/dist/types/rules/html-no-empty-attributes.d.ts +1 -1
- package/dist/types/rules/html-no-empty-headings.d.ts +1 -1
- package/dist/types/rules/html-no-nested-links.d.ts +1 -1
- package/dist/types/rules/html-no-positive-tab-index.d.ts +1 -1
- package/dist/types/rules/html-no-self-closing.d.ts +1 -1
- package/dist/types/rules/html-no-space-in-tag.d.ts +1 -1
- package/dist/types/rules/html-no-title-attribute.d.ts +1 -1
- package/dist/types/rules/html-no-underscores-in-attribute-names.d.ts +1 -1
- package/dist/types/{src/rules/html-body-only-elements.d.ts → rules/html-require-closing-tags.d.ts} +4 -3
- package/dist/types/rules/html-tag-name-lowercase.d.ts +1 -1
- package/dist/types/rules/index.d.ts +19 -0
- package/dist/types/rules/parser-no-errors.d.ts +1 -1
- package/dist/types/rules/rule-utils.d.ts +35 -88
- package/dist/types/rules/svg-tag-name-capitalization.d.ts +1 -1
- package/dist/types/{src/rules/html-aria-role-must-be-valid.d.ts → rules/turbo-permanent-require-id.d.ts} +2 -2
- package/dist/types/types.d.ts +25 -7
- package/dist/types/urls.d.ts +1 -0
- package/dist/{src/types.js → types.js} +53 -0
- package/dist/types.js.map +1 -0
- package/dist/urls.js +5 -0
- package/dist/urls.js.map +1 -0
- package/docs/rules/README.md +23 -2
- package/docs/rules/actionview-no-silent-helper.md +57 -0
- package/docs/rules/erb-no-conditional-html-element.md +90 -0
- package/docs/rules/erb-no-conditional-open-tag.md +130 -0
- package/docs/rules/erb-no-duplicate-branch-elements.md +98 -0
- package/docs/rules/erb-no-inline-case-conditions.md +85 -0
- package/docs/rules/erb-no-instance-variables-in-partials.md +43 -0
- package/docs/rules/erb-no-interpolated-class-names.md +57 -0
- package/docs/rules/erb-no-javascript-tag-helper.md +33 -0
- package/docs/rules/erb-no-output-in-attribute-name.md +38 -0
- package/docs/rules/erb-no-output-in-attribute-position.md +60 -0
- package/docs/rules/erb-no-raw-output-in-attribute-value.md +37 -0
- package/docs/rules/erb-no-statement-in-script.md +68 -0
- package/docs/rules/erb-no-then-in-control-flow.md +86 -0
- package/docs/rules/erb-no-trailing-whitespace.md +69 -0
- package/docs/rules/erb-no-unsafe-js-attribute.md +41 -0
- package/docs/rules/erb-no-unsafe-raw.md +47 -0
- package/docs/rules/erb-no-unsafe-script-interpolation.md +73 -0
- package/docs/rules/html-allowed-script-type.md +59 -0
- package/docs/rules/html-anchor-require-href.md +19 -6
- package/docs/rules/html-details-has-summary.md +46 -0
- package/docs/rules/html-img-require-alt.md +5 -3
- package/docs/rules/html-no-abstract-roles.md +74 -0
- package/docs/rules/html-no-aria-hidden-on-body.md +44 -0
- package/docs/rules/html-require-closing-tags.md +142 -0
- package/docs/rules/parser-no-errors.md +4 -17
- package/docs/rules/turbo-permanent-require-id.md +41 -0
- package/package.json +12 -11
- package/src/cli/argument-parser.ts +20 -2
- package/src/cli/file-processor.ts +189 -10
- package/src/cli/file-url.ts +6 -0
- package/src/cli/formatters/detailed-formatter.ts +19 -21
- package/src/cli/formatters/simple-formatter.ts +23 -13
- package/src/cli/index.ts +2 -0
- package/src/cli/lint-worker.ts +208 -0
- package/src/cli/summary-reporter.ts +14 -15
- package/src/cli.ts +5 -3
- package/src/custom-rule-loader.ts +20 -5
- package/src/herb-disable-comment-utils.ts +0 -3
- package/src/index.ts +1 -0
- package/src/linter.ts +98 -79
- package/src/parse-cache.ts +39 -0
- package/src/rules/actionview-no-silent-helper.ts +58 -0
- package/src/rules/erb-comment-syntax.ts +2 -2
- package/src/rules/erb-no-case-node-children.ts +2 -2
- package/src/rules/erb-no-conditional-html-element.ts +53 -0
- package/src/rules/erb-no-conditional-open-tag.ts +37 -0
- package/src/rules/erb-no-duplicate-branch-elements.ts +320 -0
- package/src/rules/erb-no-empty-tags.ts +2 -2
- package/src/rules/erb-no-extra-newline.ts +5 -25
- package/src/rules/erb-no-extra-whitespace-inside-tags.ts +45 -15
- package/src/rules/erb-no-inline-case-conditions.ts +54 -0
- package/src/rules/erb-no-instance-variables-in-partials.ts +101 -0
- package/src/rules/erb-no-interpolated-class-names.ts +65 -0
- package/src/rules/erb-no-javascript-tag-helper.ts +47 -0
- package/src/rules/erb-no-output-control-flow.ts +10 -10
- package/src/rules/erb-no-output-in-attribute-name.ts +39 -0
- package/src/rules/erb-no-output-in-attribute-position.ts +39 -0
- package/src/rules/erb-no-raw-output-in-attribute-value.ts +47 -0
- package/src/rules/erb-no-silent-tag-in-attribute-name.ts +2 -2
- package/src/rules/erb-no-statement-in-script.ts +82 -0
- package/src/rules/erb-no-then-in-control-flow.ts +62 -0
- package/src/rules/erb-no-trailing-whitespace.ts +187 -0
- package/src/rules/erb-no-unsafe-js-attribute.ts +47 -0
- package/src/rules/erb-no-unsafe-raw.ts +83 -0
- package/src/rules/erb-no-unsafe-script-interpolation.ts +76 -0
- package/src/rules/erb-prefer-image-tag-helper.ts +5 -4
- package/src/rules/erb-require-trailing-newline.ts +2 -2
- package/src/rules/erb-require-whitespace-inside-tags.ts +42 -18
- package/src/rules/erb-right-trim.ts +2 -2
- package/src/rules/erb-strict-locals-comment-syntax.ts +5 -5
- package/src/rules/erb-strict-locals-required.ts +2 -2
- package/src/rules/herb-disable-comment-malformed.ts +2 -2
- package/src/rules/herb-disable-comment-missing-rules.ts +2 -2
- package/src/rules/herb-disable-comment-no-duplicate-rules.ts +2 -2
- package/src/rules/herb-disable-comment-no-redundant-all.ts +2 -2
- package/src/rules/herb-disable-comment-unnecessary.ts +2 -2
- package/src/rules/herb-disable-comment-valid-rule-name.ts +2 -2
- package/src/rules/html-allowed-script-type.ts +84 -0
- package/src/rules/html-anchor-require-href.ts +73 -11
- package/src/rules/html-aria-attribute-must-be-valid.ts +3 -3
- package/src/rules/html-aria-label-is-well-formatted.ts +3 -3
- package/src/rules/html-aria-level-must-be-valid.ts +3 -3
- package/src/rules/html-aria-role-heading-requires-level.ts +5 -4
- package/src/rules/html-aria-role-must-be-valid.ts +3 -3
- package/src/rules/html-attribute-double-quotes.ts +4 -4
- package/src/rules/html-attribute-equals-spacing.ts +2 -2
- package/src/rules/html-attribute-values-require-quotes.ts +2 -2
- package/src/rules/html-avoid-both-disabled-and-aria-disabled.ts +10 -11
- package/src/rules/html-body-only-elements.ts +5 -4
- package/src/rules/html-boolean-attributes-no-value.ts +4 -3
- package/src/rules/html-details-has-summary.ts +69 -0
- package/src/rules/html-head-only-elements.ts +6 -5
- package/src/rules/html-iframe-has-title.ts +8 -11
- package/src/rules/html-img-require-alt.ts +16 -5
- package/src/rules/html-input-require-autocomplete.ts +7 -10
- package/src/rules/html-navigation-has-label.ts +6 -5
- package/src/rules/html-no-abstract-roles.ts +40 -0
- package/src/rules/html-no-aria-hidden-on-body.ts +58 -0
- package/src/rules/html-no-aria-hidden-on-focusable.ts +6 -5
- package/src/rules/html-no-block-inside-inline.ts +7 -13
- package/src/rules/html-no-duplicate-attributes.ts +4 -3
- package/src/rules/html-no-duplicate-ids.ts +16 -13
- package/src/rules/html-no-duplicate-meta-names.ts +20 -19
- package/src/rules/html-no-empty-attributes.ts +2 -2
- package/src/rules/html-no-empty-headings.ts +44 -58
- package/src/rules/html-no-nested-links.ts +25 -16
- package/src/rules/html-no-positive-tab-index.ts +3 -3
- package/src/rules/html-no-self-closing.ts +5 -5
- package/src/rules/html-no-space-in-tag.ts +5 -8
- package/src/rules/html-no-title-attribute.ts +6 -5
- package/src/rules/html-no-underscores-in-attribute-names.ts +2 -2
- package/src/rules/html-require-closing-tags.ts +41 -0
- package/src/rules/html-tag-name-lowercase.ts +14 -9
- package/src/rules/index.ts +19 -0
- package/src/rules/parser-no-errors.ts +3 -3
- package/src/rules/rule-utils.ts +162 -279
- package/src/rules/svg-tag-name-capitalization.ts +10 -10
- package/src/rules/turbo-permanent-require-id.ts +49 -0
- package/src/rules.ts +60 -10
- package/src/types.ts +76 -7
- package/src/urls.ts +5 -0
- package/dist/package.json +0 -65
- package/dist/src/cli/argument-parser.js.map +0 -1
- package/dist/src/cli/file-processor.js.map +0 -1
- package/dist/src/cli/formatters/base-formatter.js.map +0 -1
- package/dist/src/cli/formatters/detailed-formatter.js.map +0 -1
- package/dist/src/cli/formatters/github-actions-formatter.js.map +0 -1
- package/dist/src/cli/formatters/index.js.map +0 -1
- package/dist/src/cli/formatters/json-formatter.js.map +0 -1
- package/dist/src/cli/formatters/simple-formatter.js +0 -44
- package/dist/src/cli/formatters/simple-formatter.js.map +0 -1
- package/dist/src/cli/index.js.map +0 -1
- package/dist/src/cli/output-manager.js.map +0 -1
- package/dist/src/cli/summary-reporter.js.map +0 -1
- package/dist/src/cli.js.map +0 -1
- package/dist/src/custom-rule-loader.js.map +0 -1
- package/dist/src/herb-disable-comment-utils.js.map +0 -1
- package/dist/src/herb-lint.js +0 -5
- package/dist/src/herb-lint.js.map +0 -1
- package/dist/src/index.js +0 -5
- package/dist/src/index.js.map +0 -1
- package/dist/src/linter-ignore.js.map +0 -1
- package/dist/src/linter.js.map +0 -1
- package/dist/src/loader.js +0 -17
- package/dist/src/loader.js.map +0 -1
- package/dist/src/rules/erb-comment-syntax.js.map +0 -1
- package/dist/src/rules/erb-no-case-node-children.js.map +0 -1
- package/dist/src/rules/erb-no-empty-tags.js.map +0 -1
- package/dist/src/rules/erb-no-extra-newline.js.map +0 -1
- package/dist/src/rules/erb-no-extra-whitespace-inside-tags.js.map +0 -1
- package/dist/src/rules/erb-no-output-control-flow.js.map +0 -1
- package/dist/src/rules/erb-no-silent-tag-in-attribute-name.js.map +0 -1
- package/dist/src/rules/erb-prefer-image-tag-helper.js.map +0 -1
- package/dist/src/rules/erb-require-trailing-newline.js.map +0 -1
- package/dist/src/rules/erb-require-whitespace-inside-tags.js.map +0 -1
- package/dist/src/rules/erb-right-trim.js.map +0 -1
- package/dist/src/rules/erb-strict-locals-comment-syntax.js.map +0 -1
- package/dist/src/rules/erb-strict-locals-required.js.map +0 -1
- package/dist/src/rules/file-utils.js.map +0 -1
- package/dist/src/rules/herb-disable-comment-base.js.map +0 -1
- package/dist/src/rules/herb-disable-comment-malformed.js.map +0 -1
- package/dist/src/rules/herb-disable-comment-missing-rules.js.map +0 -1
- package/dist/src/rules/herb-disable-comment-no-duplicate-rules.js.map +0 -1
- package/dist/src/rules/herb-disable-comment-no-redundant-all.js.map +0 -1
- package/dist/src/rules/herb-disable-comment-unnecessary.js.map +0 -1
- package/dist/src/rules/herb-disable-comment-valid-rule-name.js.map +0 -1
- package/dist/src/rules/html-anchor-require-href.js +0 -32
- package/dist/src/rules/html-anchor-require-href.js.map +0 -1
- package/dist/src/rules/html-aria-attribute-must-be-valid.js.map +0 -1
- package/dist/src/rules/html-aria-label-is-well-formatted.js.map +0 -1
- package/dist/src/rules/html-aria-level-must-be-valid.js.map +0 -1
- package/dist/src/rules/html-aria-role-heading-requires-level.js.map +0 -1
- package/dist/src/rules/html-aria-role-must-be-valid.js.map +0 -1
- package/dist/src/rules/html-attribute-double-quotes.js.map +0 -1
- package/dist/src/rules/html-attribute-equals-spacing.js.map +0 -1
- package/dist/src/rules/html-attribute-values-require-quotes.js.map +0 -1
- package/dist/src/rules/html-avoid-both-disabled-and-aria-disabled.js.map +0 -1
- package/dist/src/rules/html-body-only-elements.js.map +0 -1
- package/dist/src/rules/html-boolean-attributes-no-value.js.map +0 -1
- package/dist/src/rules/html-head-only-elements.js.map +0 -1
- package/dist/src/rules/html-iframe-has-title.js.map +0 -1
- package/dist/src/rules/html-img-require-alt.js.map +0 -1
- package/dist/src/rules/html-input-require-autocomplete.js.map +0 -1
- package/dist/src/rules/html-navigation-has-label.js.map +0 -1
- package/dist/src/rules/html-no-aria-hidden-on-focusable.js.map +0 -1
- package/dist/src/rules/html-no-block-inside-inline.js.map +0 -1
- package/dist/src/rules/html-no-duplicate-attributes.js.map +0 -1
- package/dist/src/rules/html-no-duplicate-ids.js.map +0 -1
- package/dist/src/rules/html-no-duplicate-meta-names.js.map +0 -1
- package/dist/src/rules/html-no-empty-attributes.js.map +0 -1
- package/dist/src/rules/html-no-empty-headings.js +0 -115
- package/dist/src/rules/html-no-empty-headings.js.map +0 -1
- package/dist/src/rules/html-no-nested-links.js.map +0 -1
- package/dist/src/rules/html-no-positive-tab-index.js.map +0 -1
- package/dist/src/rules/html-no-self-closing.js.map +0 -1
- package/dist/src/rules/html-no-space-in-tag.js.map +0 -1
- package/dist/src/rules/html-no-title-attribute.js.map +0 -1
- package/dist/src/rules/html-no-underscores-in-attribute-names.js.map +0 -1
- package/dist/src/rules/html-tag-name-lowercase.js.map +0 -1
- package/dist/src/rules/index.js.map +0 -1
- package/dist/src/rules/parser-no-errors.js.map +0 -1
- package/dist/src/rules/rule-utils.js.map +0 -1
- package/dist/src/rules/string-utils.js.map +0 -1
- package/dist/src/rules/svg-tag-name-capitalization.js.map +0 -1
- package/dist/src/rules.js.map +0 -1
- package/dist/src/types.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/dist/types/src/cli/argument-parser.d.ts +0 -25
- package/dist/types/src/cli/file-processor.d.ts +0 -43
- package/dist/types/src/cli/formatters/base-formatter.d.ts +0 -6
- package/dist/types/src/cli/formatters/detailed-formatter.d.ts +0 -13
- package/dist/types/src/cli/formatters/github-actions-formatter.d.ts +0 -17
- package/dist/types/src/cli/formatters/index.d.ts +0 -5
- package/dist/types/src/cli/formatters/json-formatter.d.ts +0 -48
- package/dist/types/src/cli/formatters/simple-formatter.d.ts +0 -8
- package/dist/types/src/cli/index.d.ts +0 -5
- package/dist/types/src/cli/output-manager.d.ts +0 -32
- package/dist/types/src/cli/summary-reporter.d.ts +0 -28
- package/dist/types/src/cli.d.ts +0 -28
- package/dist/types/src/custom-rule-loader.d.ts +0 -62
- package/dist/types/src/herb-disable-comment-utils.d.ts +0 -69
- package/dist/types/src/herb-lint.d.ts +0 -2
- package/dist/types/src/index.d.ts +0 -4
- package/dist/types/src/linter-ignore.d.ts +0 -12
- package/dist/types/src/linter.d.ts +0 -133
- package/dist/types/src/loader.d.ts +0 -20
- package/dist/types/src/rules/erb-comment-syntax.d.ts +0 -14
- package/dist/types/src/rules/erb-no-extra-newline.d.ts +0 -14
- package/dist/types/src/rules/erb-no-extra-whitespace-inside-tags.d.ts +0 -18
- package/dist/types/src/rules/erb-require-trailing-newline.d.ts +0 -9
- package/dist/types/src/rules/erb-require-whitespace-inside-tags.d.ts +0 -18
- package/dist/types/src/rules/erb-right-trim.d.ts +0 -14
- package/dist/types/src/rules/erb-strict-locals-comment-syntax.d.ts +0 -9
- package/dist/types/src/rules/erb-strict-locals-required.d.ts +0 -9
- package/dist/types/src/rules/file-utils.d.ts +0 -13
- package/dist/types/src/rules/herb-disable-comment-base.d.ts +0 -37
- package/dist/types/src/rules/herb-disable-comment-no-duplicate-rules.d.ts +0 -8
- package/dist/types/src/rules/herb-disable-comment-no-redundant-all.d.ts +0 -8
- package/dist/types/src/rules/herb-disable-comment-unnecessary.d.ts +0 -8
- package/dist/types/src/rules/herb-disable-comment-valid-rule-name.d.ts +0 -8
- package/dist/types/src/rules/html-aria-label-is-well-formatted.d.ts +0 -8
- package/dist/types/src/rules/html-aria-role-heading-requires-level.d.ts +0 -8
- package/dist/types/src/rules/html-attribute-double-quotes.d.ts +0 -15
- package/dist/types/src/rules/html-attribute-equals-spacing.d.ts +0 -14
- package/dist/types/src/rules/html-attribute-values-require-quotes.d.ts +0 -15
- package/dist/types/src/rules/html-avoid-both-disabled-and-aria-disabled.d.ts +0 -8
- package/dist/types/src/rules/html-boolean-attributes-no-value.d.ts +0 -14
- package/dist/types/src/rules/html-head-only-elements.d.ts +0 -9
- package/dist/types/src/rules/html-iframe-has-title.d.ts +0 -8
- package/dist/types/src/rules/html-img-require-alt.d.ts +0 -8
- package/dist/types/src/rules/html-input-require-autocomplete.d.ts +0 -8
- package/dist/types/src/rules/html-no-block-inside-inline.d.ts +0 -8
- package/dist/types/src/rules/html-no-duplicate-ids.d.ts +0 -8
- package/dist/types/src/rules/html-no-duplicate-meta-names.d.ts +0 -9
- package/dist/types/src/rules/html-no-nested-links.d.ts +0 -8
- package/dist/types/src/rules/html-no-self-closing.d.ts +0 -16
- package/dist/types/src/rules/html-no-space-in-tag.d.ts +0 -16
- package/dist/types/src/rules/html-no-title-attribute.d.ts +0 -8
- package/dist/types/src/rules/html-no-underscores-in-attribute-names.d.ts +0 -8
- package/dist/types/src/rules/html-tag-name-lowercase.d.ts +0 -18
- package/dist/types/src/rules/index.d.ts +0 -54
- package/dist/types/src/rules/parser-no-errors.d.ts +0 -9
- package/dist/types/src/rules/rule-utils.d.ts +0 -351
- package/dist/types/src/rules/string-utils.d.ts +0 -15
- package/dist/types/src/rules/svg-tag-name-capitalization.d.ts +0 -16
- package/dist/types/src/rules.d.ts +0 -2
- package/dist/types/src/types.d.ts +0 -190
- /package/dist/{src/cli → cli}/formatters/base-formatter.js +0 -0
- /package/dist/{src/cli → cli}/formatters/github-actions-formatter.js +0 -0
- /package/dist/{src/cli → cli}/formatters/index.js +0 -0
- /package/dist/{src/cli → cli}/formatters/json-formatter.js +0 -0
- /package/dist/{src/cli → cli}/index.js +0 -0
- /package/dist/{src/cli → cli}/output-manager.js +0 -0
- /package/dist/{src/herb-disable-comment-utils.js → herb-disable-comment-utils.js} +0 -0
- /package/dist/{src/linter-ignore.js → linter-ignore.js} +0 -0
- /package/dist/{src/rules → rules}/file-utils.js +0 -0
- /package/dist/{src/rules → rules}/herb-disable-comment-base.js +0 -0
- /package/dist/{src/rules → rules}/string-utils.js +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { BaseRuleVisitor
|
|
1
|
+
import { getTagLocalName, getStaticAttributeValue, getAttribute, getAttributeValue } from "@herb-tools/core"
|
|
2
|
+
import { BaseRuleVisitor } from "./rule-utils.js"
|
|
3
3
|
import { ParserRule } from "../types.js"
|
|
4
4
|
|
|
5
5
|
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
@@ -30,10 +30,7 @@ class HTMLInputRequireAutocompleteVisitor extends BaseRuleVisitor {
|
|
|
30
30
|
private checkInputTag(node: HTMLOpenTagNode): void {
|
|
31
31
|
if (!this.isInputTag(node) || this.hasAutocomplete(node)) return
|
|
32
32
|
|
|
33
|
-
const
|
|
34
|
-
if (!typeAttribute) return
|
|
35
|
-
|
|
36
|
-
const typeValue = getStaticAttributeValueContent(typeAttribute)
|
|
33
|
+
const typeValue = getStaticAttributeValue(node, "type")
|
|
37
34
|
if (!typeValue) return
|
|
38
35
|
|
|
39
36
|
if (!this.HTML_INPUT_TYPES_REQUIRING_AUTOCOMPLETE.has(typeValue)) return
|
|
@@ -55,7 +52,7 @@ class HTMLInputRequireAutocompleteVisitor extends BaseRuleVisitor {
|
|
|
55
52
|
}
|
|
56
53
|
|
|
57
54
|
private isInputTag(node: HTMLOpenTagNode) {
|
|
58
|
-
const tagName =
|
|
55
|
+
const tagName = getTagLocalName(node);
|
|
59
56
|
|
|
60
57
|
if (tagName === "input") {
|
|
61
58
|
return true
|
|
@@ -66,17 +63,17 @@ class HTMLInputRequireAutocompleteVisitor extends BaseRuleVisitor {
|
|
|
66
63
|
}
|
|
67
64
|
|
|
68
65
|
export class HTMLInputRequireAutocompleteRule extends ParserRule {
|
|
69
|
-
|
|
66
|
+
static ruleName = "html-input-require-autocomplete"
|
|
70
67
|
|
|
71
68
|
get defaultConfig(): FullRuleConfig {
|
|
72
69
|
return {
|
|
73
70
|
enabled: true,
|
|
74
|
-
severity: "
|
|
71
|
+
severity: "warning"
|
|
75
72
|
}
|
|
76
73
|
}
|
|
77
74
|
|
|
78
75
|
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
79
|
-
const visitor = new HTMLInputRequireAutocompleteVisitor(this.
|
|
76
|
+
const visitor = new HTMLInputRequireAutocompleteVisitor(this.ruleName, context)
|
|
80
77
|
|
|
81
78
|
visitor.visit(result.value)
|
|
82
79
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ParserRule } from "../types.js"
|
|
2
|
-
import { BaseRuleVisitor
|
|
2
|
+
import { BaseRuleVisitor } from "./rule-utils.js"
|
|
3
|
+
import { hasAttribute, getAttributeValue, findAttributeByName, getAttributes, getTagLocalName } from "@herb-tools/core"
|
|
3
4
|
|
|
4
5
|
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
5
6
|
import type { HTMLOpenTagNode, ParseResult } from "@herb-tools/core"
|
|
@@ -11,7 +12,7 @@ class NavigationHasLabelVisitor extends BaseRuleVisitor {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
private checkNavigationElement(node: HTMLOpenTagNode): void {
|
|
14
|
-
const tagName =
|
|
15
|
+
const tagName = getTagLocalName(node)
|
|
15
16
|
const isNavElement = tagName === "nav"
|
|
16
17
|
const hasNavigationRole = this.hasRoleNavigation(node)
|
|
17
18
|
|
|
@@ -51,17 +52,17 @@ class NavigationHasLabelVisitor extends BaseRuleVisitor {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
export class HTMLNavigationHasLabelRule extends ParserRule {
|
|
54
|
-
|
|
55
|
+
static ruleName = "html-navigation-has-label"
|
|
55
56
|
|
|
56
57
|
get defaultConfig(): FullRuleConfig {
|
|
57
58
|
return {
|
|
58
59
|
enabled: false,
|
|
59
|
-
severity: "
|
|
60
|
+
severity: "warning"
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
64
|
-
const visitor = new NavigationHasLabelVisitor(this.
|
|
65
|
+
const visitor = new NavigationHasLabelVisitor(this.ruleName, context)
|
|
65
66
|
|
|
66
67
|
visitor.visit(result.value)
|
|
67
68
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ParserRule } from "../types.js"
|
|
2
|
+
import { AttributeVisitorMixin, ABSTRACT_ARIA_ROLES, StaticAttributeStaticValueParams } from "./rule-utils.js"
|
|
3
|
+
|
|
4
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
5
|
+
import type { ParseResult } from "@herb-tools/core"
|
|
6
|
+
|
|
7
|
+
class NoAbstractRolesVisitor extends AttributeVisitorMixin {
|
|
8
|
+
protected checkStaticAttributeStaticValue({ attributeName, attributeValue, attributeNode }: StaticAttributeStaticValueParams): void {
|
|
9
|
+
if (attributeName !== "role") return
|
|
10
|
+
if (!attributeValue) return
|
|
11
|
+
|
|
12
|
+
const normalizedValue = attributeValue.toLowerCase()
|
|
13
|
+
|
|
14
|
+
if (!ABSTRACT_ARIA_ROLES.has(normalizedValue)) return
|
|
15
|
+
|
|
16
|
+
this.addOffense(
|
|
17
|
+
`The \`role\` attribute must not use abstract ARIA role \`${attributeValue}\`. Abstract roles are not meant to be used directly.`,
|
|
18
|
+
attributeNode.location,
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class HTMLNoAbstractRolesRule extends ParserRule {
|
|
24
|
+
static ruleName = "html-no-abstract-roles"
|
|
25
|
+
|
|
26
|
+
get defaultConfig(): FullRuleConfig {
|
|
27
|
+
return {
|
|
28
|
+
enabled: true,
|
|
29
|
+
severity: "warning"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
34
|
+
const visitor = new NoAbstractRolesVisitor(this.ruleName, context)
|
|
35
|
+
|
|
36
|
+
visitor.visit(result.value)
|
|
37
|
+
|
|
38
|
+
return visitor.offenses
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ParserRule } from "../types.js"
|
|
2
|
+
import { BaseRuleVisitor } from "./rule-utils.js"
|
|
3
|
+
import { hasAttribute, getAttributeValue, findAttributeByName, getAttributes, getTagLocalName } from "@herb-tools/core"
|
|
4
|
+
|
|
5
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
6
|
+
import type { HTMLOpenTagNode, ParseResult } from "@herb-tools/core"
|
|
7
|
+
|
|
8
|
+
class NoAriaHiddenBodyVisitor extends BaseRuleVisitor {
|
|
9
|
+
visitHTMLOpenTagNode(node: HTMLOpenTagNode): void {
|
|
10
|
+
this.checkAriaHiddenOnBody(node)
|
|
11
|
+
super.visitHTMLOpenTagNode(node)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
private checkAriaHiddenOnBody(node: HTMLOpenTagNode): void {
|
|
15
|
+
const tagName = getTagLocalName(node)
|
|
16
|
+
|
|
17
|
+
if (tagName !== "body") return
|
|
18
|
+
|
|
19
|
+
if (this.hasAriaHidden(node)) {
|
|
20
|
+
this.addOffense(
|
|
21
|
+
"The `aria-hidden` attribute should never be present on the `<body>` element, as it hides the entire document from assistive technology users.",
|
|
22
|
+
node.tag_name!.location,
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private hasAriaHidden(node: HTMLOpenTagNode): boolean {
|
|
28
|
+
if (!hasAttribute(node, "aria-hidden")) return false
|
|
29
|
+
|
|
30
|
+
const attributes = getAttributes(node)
|
|
31
|
+
const ariaHiddenAttr = findAttributeByName(attributes, "aria-hidden")
|
|
32
|
+
|
|
33
|
+
if (!ariaHiddenAttr) return false
|
|
34
|
+
|
|
35
|
+
const value = getAttributeValue(ariaHiddenAttr)
|
|
36
|
+
|
|
37
|
+
return value === null || value === "" || value === "true"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class HTMLNoAriaHiddenOnBodyRule extends ParserRule {
|
|
42
|
+
static ruleName = "html-no-aria-hidden-on-body"
|
|
43
|
+
|
|
44
|
+
get defaultConfig(): FullRuleConfig {
|
|
45
|
+
return {
|
|
46
|
+
enabled: true,
|
|
47
|
+
severity: "warning"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
52
|
+
const visitor = new NoAriaHiddenBodyVisitor(this.ruleName, context)
|
|
53
|
+
|
|
54
|
+
visitor.visit(result.value)
|
|
55
|
+
|
|
56
|
+
return visitor.offenses
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ParserRule } from "../types.js"
|
|
2
|
-
import { BaseRuleVisitor
|
|
2
|
+
import { BaseRuleVisitor } from "./rule-utils.js"
|
|
3
|
+
import { hasAttribute, getAttributeValue, findAttributeByName, getAttributes, getTagLocalName } from "@herb-tools/core"
|
|
3
4
|
|
|
4
5
|
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
5
6
|
import type { HTMLOpenTagNode, ParseResult } from "@herb-tools/core"
|
|
@@ -37,7 +38,7 @@ class NoAriaHiddenOnFocusableVisitor extends BaseRuleVisitor {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
private isFocusable(node: HTMLOpenTagNode): boolean {
|
|
40
|
-
const tagName =
|
|
41
|
+
const tagName = getTagLocalName(node)
|
|
41
42
|
if (!tagName) return false
|
|
42
43
|
|
|
43
44
|
const tabIndexValue = this.getTabIndexValue(node)
|
|
@@ -77,17 +78,17 @@ class NoAriaHiddenOnFocusableVisitor extends BaseRuleVisitor {
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
export class HTMLNoAriaHiddenOnFocusableRule extends ParserRule {
|
|
80
|
-
|
|
81
|
+
static ruleName = "html-no-aria-hidden-on-focusable"
|
|
81
82
|
|
|
82
83
|
get defaultConfig(): FullRuleConfig {
|
|
83
84
|
return {
|
|
84
85
|
enabled: true,
|
|
85
|
-
severity: "
|
|
86
|
+
severity: "warning"
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
90
|
-
const visitor = new NoAriaHiddenOnFocusableVisitor(this.
|
|
91
|
+
const visitor = new NoAriaHiddenOnFocusableVisitor(this.ruleName, context)
|
|
91
92
|
|
|
92
93
|
visitor.visit(result.value)
|
|
93
94
|
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import { BaseRuleVisitor, isInlineElement, isBlockElement } from "./rule-utils.js"
|
|
2
|
-
|
|
3
2
|
import { ParserRule } from "../types.js"
|
|
3
|
+
import { isHTMLOpenTagNode } from "@herb-tools/core"
|
|
4
|
+
|
|
4
5
|
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
5
6
|
import type { HTMLOpenTagNode, HTMLElementNode, ParseResult } from "@herb-tools/core"
|
|
6
7
|
|
|
7
8
|
class BlockInsideInlineVisitor extends BaseRuleVisitor {
|
|
8
9
|
private inlineStack: string[] = []
|
|
9
10
|
|
|
10
|
-
private isValidHTMLOpenTag(node: HTMLElementNode): boolean {
|
|
11
|
-
return !!(node.open_tag && node.open_tag.type === "AST_HTML_OPEN_TAG_NODE")
|
|
12
|
-
}
|
|
13
|
-
|
|
14
11
|
private getElementType(tagName: string): { isInline: boolean; isBlock: boolean; isUnknown: boolean } {
|
|
15
12
|
const isInline = isInlineElement(tagName)
|
|
16
13
|
const isBlock = isBlockElement(tagName)
|
|
@@ -43,25 +40,22 @@ class BlockInsideInlineVisitor extends BaseRuleVisitor {
|
|
|
43
40
|
}
|
|
44
41
|
|
|
45
42
|
visitHTMLElementNode(node: HTMLElementNode): void {
|
|
46
|
-
if (!
|
|
43
|
+
if (!isHTMLOpenTagNode(node.open_tag)) {
|
|
47
44
|
super.visitHTMLElementNode(node)
|
|
48
|
-
|
|
49
45
|
return
|
|
50
46
|
}
|
|
51
47
|
|
|
52
|
-
const
|
|
53
|
-
const tagName = openTag.tag_name?.value.toLowerCase()
|
|
48
|
+
const tagName = node.open_tag.tag_name?.value.toLowerCase()
|
|
54
49
|
|
|
55
50
|
if (!tagName) {
|
|
56
51
|
super.visitHTMLElementNode(node)
|
|
57
|
-
|
|
58
52
|
return
|
|
59
53
|
}
|
|
60
54
|
|
|
61
55
|
const { isInline, isBlock, isUnknown } = this.getElementType(tagName)
|
|
62
56
|
|
|
63
57
|
if ((isBlock || isUnknown) && this.inlineStack.length > 0) {
|
|
64
|
-
this.addOffenseMessage(tagName, isBlock,
|
|
58
|
+
this.addOffenseMessage(tagName, isBlock, node.open_tag)
|
|
65
59
|
}
|
|
66
60
|
|
|
67
61
|
if (isInline) {
|
|
@@ -74,7 +68,7 @@ class BlockInsideInlineVisitor extends BaseRuleVisitor {
|
|
|
74
68
|
}
|
|
75
69
|
|
|
76
70
|
export class HTMLNoBlockInsideInlineRule extends ParserRule {
|
|
77
|
-
|
|
71
|
+
static ruleName = "html-no-block-inside-inline"
|
|
78
72
|
|
|
79
73
|
get defaultConfig(): FullRuleConfig {
|
|
80
74
|
return {
|
|
@@ -84,7 +78,7 @@ export class HTMLNoBlockInsideInlineRule extends ParserRule {
|
|
|
84
78
|
}
|
|
85
79
|
|
|
86
80
|
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
87
|
-
const visitor = new BlockInsideInlineVisitor(this.
|
|
81
|
+
const visitor = new BlockInsideInlineVisitor(this.ruleName, context)
|
|
88
82
|
visitor.visit(result.value)
|
|
89
83
|
return visitor.offenses
|
|
90
84
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ParserRule, BaseAutofixContext } from "../types.js"
|
|
2
|
-
import { ControlFlowTrackingVisitor, ControlFlowType
|
|
2
|
+
import { ControlFlowTrackingVisitor, ControlFlowType } from "./rule-utils.js"
|
|
3
|
+
import { getAttributeName } from "@herb-tools/core"
|
|
3
4
|
|
|
4
5
|
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
5
6
|
import type { HTMLOpenTagNode, HTMLAttributeNode, ParseResult, Location } from "@herb-tools/core"
|
|
@@ -162,7 +163,7 @@ class NoDuplicateAttributesVisitor extends ControlFlowTrackingVisitor<
|
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
export class HTMLNoDuplicateAttributesRule extends ParserRule {
|
|
165
|
-
|
|
166
|
+
static ruleName = "html-no-duplicate-attributes"
|
|
166
167
|
|
|
167
168
|
get defaultConfig(): FullRuleConfig {
|
|
168
169
|
return {
|
|
@@ -172,7 +173,7 @@ export class HTMLNoDuplicateAttributesRule extends ParserRule {
|
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
175
|
-
const visitor = new NoDuplicateAttributesVisitor(this.
|
|
176
|
+
const visitor = new NoDuplicateAttributesVisitor(this.ruleName, context)
|
|
176
177
|
|
|
177
178
|
visitor.visit(result.value)
|
|
178
179
|
|
|
@@ -93,40 +93,41 @@ class NoDuplicateIdsVisitor extends ControlFlowTrackingVisitor<BaseAutofixContex
|
|
|
93
93
|
return getStaticAttributeName(attributeNode.name) === "id"
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
private extractIdValue(attributeNode: HTMLAttributeNode): { identifier: string; shouldTrackDuplicates: boolean } | null {
|
|
96
|
+
private extractIdValue(attributeNode: HTMLAttributeNode): { identifier: string; shouldTrackDuplicates: boolean; isDynamic: boolean } | null {
|
|
97
97
|
const valueNodes = attributeNode.value?.children || []
|
|
98
|
+
const isDynamic = hasERBOutput(valueNodes)
|
|
98
99
|
|
|
99
|
-
if (
|
|
100
|
+
if (isDynamic && this.isInControlFlow && this.currentControlFlowType === ControlFlowType.LOOP) {
|
|
100
101
|
return null
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
const identifier = isEffectivelyStatic(valueNodes) ? getValidatableStaticContent(valueNodes) : OutputPrinter.print(valueNodes)
|
|
104
105
|
if (!identifier) return null
|
|
105
106
|
|
|
106
|
-
return { identifier, shouldTrackDuplicates: true }
|
|
107
|
+
return { identifier, shouldTrackDuplicates: true, isDynamic }
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
private isWhitespaceOnlyId(identifier: string): boolean {
|
|
110
111
|
return identifier !== '' && identifier.trim() === ''
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
private processIdDuplicate(idValue: { identifier: string; shouldTrackDuplicates: boolean }, attributeNode: HTMLAttributeNode): void {
|
|
114
|
-
const { identifier, shouldTrackDuplicates } = idValue
|
|
114
|
+
private processIdDuplicate(idValue: { identifier: string; shouldTrackDuplicates: boolean; isDynamic: boolean }, attributeNode: HTMLAttributeNode): void {
|
|
115
|
+
const { identifier, shouldTrackDuplicates, isDynamic } = idValue
|
|
115
116
|
|
|
116
117
|
if (!shouldTrackDuplicates) return
|
|
117
118
|
|
|
118
119
|
if (this.isInControlFlow) {
|
|
119
|
-
this.handleControlFlowId(identifier, attributeNode)
|
|
120
|
+
this.handleControlFlowId(identifier, attributeNode, isDynamic)
|
|
120
121
|
} else {
|
|
121
122
|
this.handleGlobalId(identifier, attributeNode)
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
private handleControlFlowId(identifier: string, attributeNode: HTMLAttributeNode): void {
|
|
126
|
+
private handleControlFlowId(identifier: string, attributeNode: HTMLAttributeNode, isDynamic: boolean): void {
|
|
126
127
|
if (this.currentControlFlowType === ControlFlowType.LOOP) {
|
|
127
128
|
this.handleLoopId(identifier, attributeNode)
|
|
128
129
|
} else {
|
|
129
|
-
this.handleConditionalId(identifier, attributeNode)
|
|
130
|
+
this.handleConditionalId(identifier, attributeNode, isDynamic)
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
this.currentBranchIds.add(identifier)
|
|
@@ -145,18 +146,20 @@ class NoDuplicateIdsVisitor extends ControlFlowTrackingVisitor<BaseAutofixContex
|
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
148
|
|
|
148
|
-
private handleConditionalId(identifier: string, attributeNode: HTMLAttributeNode): void {
|
|
149
|
+
private handleConditionalId(identifier: string, attributeNode: HTMLAttributeNode, isDynamic: boolean): void {
|
|
149
150
|
if (this.currentBranchIds.has(identifier)) {
|
|
150
151
|
this.addSameBranchOffense(identifier, attributeNode.location)
|
|
151
152
|
return
|
|
152
153
|
}
|
|
153
154
|
|
|
154
|
-
if (this.documentIds.has(identifier)) {
|
|
155
|
+
if (!isDynamic && this.documentIds.has(identifier)) {
|
|
155
156
|
this.addDuplicateIdOffense(identifier, attributeNode.location)
|
|
156
157
|
return
|
|
157
158
|
}
|
|
158
159
|
|
|
159
|
-
|
|
160
|
+
if (!isDynamic) {
|
|
161
|
+
this.controlFlowIds.add(identifier)
|
|
162
|
+
}
|
|
160
163
|
}
|
|
161
164
|
|
|
162
165
|
private handleGlobalId(identifier: string, attributeNode: HTMLAttributeNode): void {
|
|
@@ -199,7 +202,7 @@ class NoDuplicateIdsVisitor extends ControlFlowTrackingVisitor<BaseAutofixContex
|
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
export class HTMLNoDuplicateIdsRule extends ParserRule {
|
|
202
|
-
|
|
205
|
+
static ruleName = "html-no-duplicate-ids"
|
|
203
206
|
|
|
204
207
|
get defaultConfig(): FullRuleConfig {
|
|
205
208
|
return {
|
|
@@ -209,7 +212,7 @@ export class HTMLNoDuplicateIdsRule extends ParserRule {
|
|
|
209
212
|
}
|
|
210
213
|
|
|
211
214
|
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
212
|
-
const visitor = new NoDuplicateIdsVisitor(this.
|
|
215
|
+
const visitor = new NoDuplicateIdsVisitor(this.ruleName, context)
|
|
213
216
|
|
|
214
217
|
visitor.visit(result.value)
|
|
215
218
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isHTMLElementNode } from "@herb-tools/core"
|
|
2
|
-
import {
|
|
1
|
+
import { isHTMLElementNode, isHTMLOpenTagNode, getAttributeName, getAttributeValue, forEachAttribute } from "@herb-tools/core"
|
|
2
|
+
import { getTagLocalName } from "@herb-tools/core"
|
|
3
3
|
|
|
4
4
|
import { ControlFlowTrackingVisitor, ControlFlowType } from "./rule-utils"
|
|
5
5
|
import { ParserRule, BaseAutofixContext } from "../types"
|
|
@@ -30,7 +30,7 @@ class HTMLNoDuplicateMetaNamesVisitor extends ControlFlowTrackingVisitor<BaseAut
|
|
|
30
30
|
private controlFlowMetas: MetaTag[] = []
|
|
31
31
|
|
|
32
32
|
visitHTMLElementNode(node: HTMLElementNode): void {
|
|
33
|
-
const tagName =
|
|
33
|
+
const tagName = getTagLocalName(node)
|
|
34
34
|
if (!tagName) return
|
|
35
35
|
|
|
36
36
|
if (tagName === "head") {
|
|
@@ -104,20 +104,21 @@ class HTMLNoDuplicateMetaNamesVisitor extends ControlFlowTrackingVisitor<BaseAut
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
private extractAttributes(node: HTMLElementNode, metaTag: MetaTag): void {
|
|
107
|
-
if (isHTMLElementNode(node)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
107
|
+
if (!isHTMLElementNode(node)) return
|
|
108
|
+
if (!isHTMLOpenTagNode(node.open_tag)) return
|
|
109
|
+
|
|
110
|
+
forEachAttribute(node.open_tag, (attributeNode: HTMLAttributeNode) => {
|
|
111
|
+
const name = getAttributeName(attributeNode)
|
|
112
|
+
const value = getAttributeValue(attributeNode)?.trim()
|
|
113
|
+
|
|
114
|
+
if (name === "name" && value) {
|
|
115
|
+
metaTag.nameValue = value
|
|
116
|
+
} else if (name === "http-equiv" && value) {
|
|
117
|
+
metaTag.httpEquivValue = value
|
|
118
|
+
} else if (name === "media" && value) {
|
|
119
|
+
metaTag.mediaValue = value
|
|
120
|
+
}
|
|
121
|
+
})
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
private handleControlFlowMeta(metaTag: MetaTag): void {
|
|
@@ -182,7 +183,7 @@ class HTMLNoDuplicateMetaNamesVisitor extends ControlFlowTrackingVisitor<BaseAut
|
|
|
182
183
|
|
|
183
184
|
export class HTMLNoDuplicateMetaNamesRule extends ParserRule {
|
|
184
185
|
static autocorrectable = false
|
|
185
|
-
|
|
186
|
+
static ruleName = "html-no-duplicate-meta-names"
|
|
186
187
|
|
|
187
188
|
get defaultConfig(): FullRuleConfig {
|
|
188
189
|
return {
|
|
@@ -192,7 +193,7 @@ export class HTMLNoDuplicateMetaNamesRule extends ParserRule {
|
|
|
192
193
|
}
|
|
193
194
|
|
|
194
195
|
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
195
|
-
const visitor = new HTMLNoDuplicateMetaNamesVisitor(this.
|
|
196
|
+
const visitor = new HTMLNoDuplicateMetaNamesVisitor(this.ruleName, context)
|
|
196
197
|
|
|
197
198
|
visitor.visit(result.value)
|
|
198
199
|
|
|
@@ -120,7 +120,7 @@ class NoEmptyAttributesVisitor extends AttributeVisitorMixin {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
export class HTMLNoEmptyAttributesRule extends ParserRule {
|
|
123
|
-
|
|
123
|
+
static ruleName = "html-no-empty-attributes"
|
|
124
124
|
|
|
125
125
|
get defaultConfig(): FullRuleConfig {
|
|
126
126
|
return {
|
|
@@ -130,7 +130,7 @@ export class HTMLNoEmptyAttributesRule extends ParserRule {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
133
|
-
const visitor = new NoEmptyAttributesVisitor(this.
|
|
133
|
+
const visitor = new NoEmptyAttributesVisitor(this.ruleName, context)
|
|
134
134
|
|
|
135
135
|
visitor.visit(result.value)
|
|
136
136
|
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { BaseRuleVisitor, getTagName, getAttributes, findAttributeByName, getAttributeValue, HEADING_TAGS } from "./rule-utils.js"
|
|
2
|
-
import { isHTMLOpenTagNode, isLiteralNode, isHTMLTextNode, isHTMLElementNode } from "@herb-tools/core"
|
|
3
|
-
|
|
4
1
|
import { ParserRule } from "../types.js"
|
|
2
|
+
import { BaseRuleVisitor, HEADING_TAGS } from "./rule-utils.js"
|
|
3
|
+
import { getTagLocalName } from "@herb-tools/core"
|
|
4
|
+
import { isLiteralNode, isHTMLTextNode, isHTMLElementNode, isERBOutputNode, isERBControlFlowNode, getStaticAttributeValue } from "@herb-tools/core"
|
|
5
5
|
|
|
6
|
+
import type { Node } from "@herb-tools/core"
|
|
6
7
|
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
7
|
-
import type { HTMLElementNode,
|
|
8
|
+
import type { HTMLElementNode, ParseResult } from "@herb-tools/core"
|
|
8
9
|
|
|
9
10
|
class NoEmptyHeadingsVisitor extends BaseRuleVisitor {
|
|
10
11
|
visitHTMLElementNode(node: HTMLElementNode): void {
|
|
11
|
-
const tagName =
|
|
12
|
+
const tagName = getTagLocalName(node)
|
|
12
13
|
if (tagName === "template") return
|
|
13
14
|
|
|
14
15
|
this.checkHeadingElement(node)
|
|
@@ -16,14 +17,11 @@ class NoEmptyHeadingsVisitor extends BaseRuleVisitor {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
private checkHeadingElement(node: HTMLElementNode): void {
|
|
19
|
-
|
|
20
|
-
if (!isHTMLOpenTagNode(node.open_tag)) return
|
|
21
|
-
|
|
22
|
-
const tagName = getTagName(node.open_tag)
|
|
20
|
+
const tagName = getTagLocalName(node)
|
|
23
21
|
if (!tagName) return
|
|
24
22
|
|
|
25
23
|
const isStandardHeading = HEADING_TAGS.has(tagName)
|
|
26
|
-
const isAriaHeading = this.hasHeadingRole(node
|
|
24
|
+
const isAriaHeading = this.hasHeadingRole(node)
|
|
27
25
|
|
|
28
26
|
if (!isStandardHeading && !isAriaHeading) {
|
|
29
27
|
return
|
|
@@ -46,90 +44,78 @@ class NoEmptyHeadingsVisitor extends BaseRuleVisitor {
|
|
|
46
44
|
return true
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
|
|
47
|
+
return !this.hasAccessibleContent(node.body)
|
|
48
|
+
}
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
private hasAccessibleContent(nodes: Node[]): boolean {
|
|
51
|
+
for (const child of nodes) {
|
|
52
|
+
if (isLiteralNode(child) || isHTMLTextNode(child)) {
|
|
53
53
|
if (child.content.trim().length > 0) {
|
|
54
|
-
|
|
55
|
-
break
|
|
54
|
+
return true
|
|
56
55
|
}
|
|
57
56
|
} else if (isHTMLElementNode(child)) {
|
|
58
57
|
if (this.isElementAccessible(child)) {
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
return true
|
|
59
|
+
}
|
|
60
|
+
} else if (isERBOutputNode(child)) {
|
|
61
|
+
return true
|
|
62
|
+
} else if (isERBControlFlowNode(child)) {
|
|
63
|
+
if (this.hasAccessibleContentInControlFlow(child)) {
|
|
64
|
+
return true
|
|
61
65
|
}
|
|
62
|
-
} else {
|
|
63
|
-
hasAccessibleContent = true
|
|
64
|
-
break
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
return
|
|
69
|
+
return false
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
private
|
|
72
|
-
const
|
|
73
|
-
const roleAttribute = findAttributeByName(attributes, "role")
|
|
72
|
+
private hasAccessibleContentInControlFlow(node: Node): boolean {
|
|
73
|
+
const nodeWithStatements = node as { statements?: Node[], body?: Node[], subsequent?: Node }
|
|
74
74
|
|
|
75
|
-
if (
|
|
76
|
-
return
|
|
75
|
+
if (nodeWithStatements.statements && this.hasAccessibleContent(nodeWithStatements.statements)) {
|
|
76
|
+
return true
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
if (nodeWithStatements.body && this.hasAccessibleContent(nodeWithStatements.body)) {
|
|
80
|
+
return true
|
|
81
|
+
}
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
if (nodeWithStatements.subsequent) {
|
|
84
|
+
return this.hasAccessibleContentInControlFlow(nodeWithStatements.subsequent)
|
|
85
|
+
}
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
private hasHeadingRole(node: HTMLElementNode): boolean {
|
|
91
|
+
return getStaticAttributeValue(node, "role") === "heading"
|
|
92
|
+
}
|
|
92
93
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
private isElementAccessible(node: HTMLElementNode): boolean {
|
|
95
|
+
if (getStaticAttributeValue(node, "aria-hidden") === "true") {
|
|
96
|
+
return false
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
if (!node.body || node.body.length === 0) {
|
|
99
100
|
return false
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
|
|
103
|
-
if (isLiteralNode(child) || isHTMLTextNode(child)) {
|
|
104
|
-
if (child.content.trim().length > 0) {
|
|
105
|
-
return true
|
|
106
|
-
}
|
|
107
|
-
} else if (isHTMLElementNode(child)) {
|
|
108
|
-
if (this.isElementAccessible(child)) {
|
|
109
|
-
return true
|
|
110
|
-
}
|
|
111
|
-
} else {
|
|
112
|
-
// If there's any non-literal/non-text/non-element content (like ERB), consider it accessible
|
|
113
|
-
return true
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return false
|
|
103
|
+
return this.hasAccessibleContent(node.body)
|
|
118
104
|
}
|
|
119
105
|
}
|
|
120
106
|
|
|
121
107
|
export class HTMLNoEmptyHeadingsRule extends ParserRule {
|
|
122
|
-
|
|
108
|
+
static ruleName = "html-no-empty-headings"
|
|
123
109
|
|
|
124
110
|
get defaultConfig(): FullRuleConfig {
|
|
125
111
|
return {
|
|
126
112
|
enabled: true,
|
|
127
|
-
severity: "
|
|
113
|
+
severity: "warning"
|
|
128
114
|
}
|
|
129
115
|
}
|
|
130
116
|
|
|
131
117
|
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
132
|
-
const visitor = new NoEmptyHeadingsVisitor(this.
|
|
118
|
+
const visitor = new NoEmptyHeadingsVisitor(this.ruleName, context)
|
|
133
119
|
visitor.visit(result.value)
|
|
134
120
|
return visitor.offenses
|
|
135
121
|
}
|