@herb-tools/linter 0.7.4 → 0.8.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 +253 -13
- package/dist/herb-lint.js +26087 -3414
- package/dist/herb-lint.js.map +1 -1
- package/dist/index.cjs +5783 -1568
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5749 -1569
- package/dist/index.js.map +1 -1
- package/dist/loader.cjs +17010 -0
- package/dist/loader.cjs.map +1 -0
- package/dist/loader.js +16879 -0
- package/dist/loader.js.map +1 -0
- package/dist/package.json +13 -5
- package/dist/src/cli/argument-parser.js +42 -35
- package/dist/src/cli/argument-parser.js.map +1 -1
- package/dist/src/cli/file-processor.js +124 -23
- package/dist/src/cli/file-processor.js.map +1 -1
- package/dist/src/cli/formatters/detailed-formatter.js +18 -3
- package/dist/src/cli/formatters/detailed-formatter.js.map +1 -1
- package/dist/src/cli/formatters/github-actions-formatter.js +15 -1
- package/dist/src/cli/formatters/github-actions-formatter.js.map +1 -1
- package/dist/src/cli/formatters/json-formatter.js +3 -0
- package/dist/src/cli/formatters/json-formatter.js.map +1 -1
- package/dist/src/cli/formatters/simple-formatter.js +20 -7
- package/dist/src/cli/formatters/simple-formatter.js.map +1 -1
- package/dist/src/cli/output-manager.js +22 -3
- package/dist/src/cli/output-manager.js.map +1 -1
- package/dist/src/cli/summary-reporter.js +26 -3
- package/dist/src/cli/summary-reporter.js.map +1 -1
- package/dist/src/cli.js +109 -43
- package/dist/src/cli.js.map +1 -1
- package/dist/src/custom-rule-loader.js +139 -0
- package/dist/src/custom-rule-loader.js.map +1 -0
- package/dist/src/herb-disable-comment-utils.js +129 -0
- package/dist/src/herb-disable-comment-utils.js.map +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/linter.js +369 -34
- package/dist/src/linter.js.map +1 -1
- package/dist/src/loader.js +17 -0
- package/dist/src/loader.js.map +1 -0
- package/dist/src/rules/erb-comment-syntax.js +48 -0
- package/dist/src/rules/erb-comment-syntax.js.map +1 -0
- package/dist/src/rules/erb-no-case-node-children.js +52 -0
- package/dist/src/rules/erb-no-case-node-children.js.map +1 -0
- package/dist/src/rules/erb-no-empty-tags.js +7 -1
- package/dist/src/rules/erb-no-empty-tags.js.map +1 -1
- package/dist/src/rules/erb-no-extra-newline.js +65 -0
- package/dist/src/rules/erb-no-extra-newline.js.map +1 -0
- package/dist/src/rules/erb-no-extra-whitespace-inside-tags.js +95 -0
- package/dist/src/rules/erb-no-extra-whitespace-inside-tags.js.map +1 -0
- package/dist/src/rules/erb-no-output-control-flow.js +7 -1
- package/dist/src/rules/erb-no-output-control-flow.js.map +1 -1
- package/dist/src/rules/erb-no-silent-tag-in-attribute-name.js +7 -1
- package/dist/src/rules/erb-no-silent-tag-in-attribute-name.js.map +1 -1
- package/dist/src/rules/erb-prefer-image-tag-helper.js +7 -1
- package/dist/src/rules/erb-prefer-image-tag-helper.js.map +1 -1
- package/dist/src/rules/erb-require-trailing-newline.js +35 -0
- package/dist/src/rules/erb-require-trailing-newline.js.map +1 -0
- package/dist/src/rules/erb-require-whitespace-inside-tags.js +70 -20
- package/dist/src/rules/erb-require-whitespace-inside-tags.js.map +1 -1
- package/dist/src/rules/erb-right-trim.js +45 -0
- package/dist/src/rules/erb-right-trim.js.map +1 -0
- package/dist/src/rules/herb-disable-comment-base.js +51 -0
- package/dist/src/rules/herb-disable-comment-base.js.map +1 -0
- package/dist/src/rules/herb-disable-comment-malformed.js +51 -0
- package/dist/src/rules/herb-disable-comment-malformed.js.map +1 -0
- package/dist/src/rules/herb-disable-comment-missing-rules.js +29 -0
- package/dist/src/rules/herb-disable-comment-missing-rules.js.map +1 -0
- package/dist/src/rules/herb-disable-comment-no-duplicate-rules.js +32 -0
- package/dist/src/rules/herb-disable-comment-no-duplicate-rules.js.map +1 -0
- package/dist/src/rules/herb-disable-comment-no-redundant-all.js +31 -0
- package/dist/src/rules/herb-disable-comment-no-redundant-all.js.map +1 -0
- package/dist/src/rules/herb-disable-comment-unnecessary.js +65 -0
- package/dist/src/rules/herb-disable-comment-unnecessary.js.map +1 -0
- package/dist/src/rules/herb-disable-comment-valid-rule-name.js +44 -0
- package/dist/src/rules/herb-disable-comment-valid-rule-name.js.map +1 -0
- package/dist/src/rules/html-anchor-require-href.js +7 -1
- package/dist/src/rules/html-anchor-require-href.js.map +1 -1
- package/dist/src/rules/html-aria-attribute-must-be-valid.js +7 -1
- package/dist/src/rules/html-aria-attribute-must-be-valid.js.map +1 -1
- package/dist/src/rules/html-aria-label-is-well-formatted.js +9 -3
- package/dist/src/rules/html-aria-label-is-well-formatted.js.map +1 -1
- package/dist/src/rules/html-aria-level-must-be-valid.js +6 -0
- package/dist/src/rules/html-aria-level-must-be-valid.js.map +1 -1
- package/dist/src/rules/html-aria-role-heading-requires-level.js +7 -1
- package/dist/src/rules/html-aria-role-heading-requires-level.js.map +1 -1
- package/dist/src/rules/html-aria-role-must-be-valid.js +7 -1
- package/dist/src/rules/html-aria-role-must-be-valid.js.map +1 -1
- package/dist/src/rules/html-attribute-double-quotes.js +29 -2
- package/dist/src/rules/html-attribute-double-quotes.js.map +1 -1
- package/dist/src/rules/html-attribute-equals-spacing.js +18 -2
- package/dist/src/rules/html-attribute-equals-spacing.js.map +1 -1
- package/dist/src/rules/html-attribute-values-require-quotes.js +39 -3
- package/dist/src/rules/html-attribute-values-require-quotes.js.map +1 -1
- package/dist/src/rules/html-avoid-both-disabled-and-aria-disabled.js +7 -1
- package/dist/src/rules/html-avoid-both-disabled-and-aria-disabled.js.map +1 -1
- package/dist/src/rules/html-body-only-elements.js +46 -0
- package/dist/src/rules/html-body-only-elements.js.map +1 -0
- package/dist/src/rules/html-boolean-attributes-no-value.js +18 -1
- package/dist/src/rules/html-boolean-attributes-no-value.js.map +1 -1
- package/dist/src/rules/html-head-only-elements.js +51 -0
- package/dist/src/rules/html-head-only-elements.js.map +1 -0
- package/dist/src/rules/html-iframe-has-title.js +8 -2
- package/dist/src/rules/html-iframe-has-title.js.map +1 -1
- package/dist/src/rules/html-img-require-alt.js +7 -1
- package/dist/src/rules/html-img-require-alt.js.map +1 -1
- package/dist/src/rules/html-input-require-autocomplete.js +70 -0
- package/dist/src/rules/html-input-require-autocomplete.js.map +1 -0
- package/dist/src/rules/html-navigation-has-label.js +7 -1
- package/dist/src/rules/html-navigation-has-label.js.map +1 -1
- package/dist/src/rules/html-no-aria-hidden-on-focusable.js +7 -1
- package/dist/src/rules/html-no-aria-hidden-on-focusable.js.map +1 -1
- package/dist/src/rules/html-no-block-inside-inline.js +7 -1
- package/dist/src/rules/html-no-block-inside-inline.js.map +1 -1
- package/dist/src/rules/html-no-duplicate-attributes.js +7 -1
- package/dist/src/rules/html-no-duplicate-attributes.js.map +1 -1
- package/dist/src/rules/html-no-duplicate-ids.js +9 -3
- package/dist/src/rules/html-no-duplicate-ids.js.map +1 -1
- package/dist/src/rules/html-no-duplicate-meta-names.js +136 -0
- package/dist/src/rules/html-no-duplicate-meta-names.js.map +1 -0
- package/dist/src/rules/html-no-empty-attributes.js +45 -7
- package/dist/src/rules/html-no-empty-attributes.js.map +1 -1
- package/dist/src/rules/html-no-empty-headings.js +7 -6
- package/dist/src/rules/html-no-empty-headings.js.map +1 -1
- package/dist/src/rules/html-no-nested-links.js +7 -1
- package/dist/src/rules/html-no-nested-links.js.map +1 -1
- package/dist/src/rules/html-no-positive-tab-index.js +7 -1
- package/dist/src/rules/html-no-positive-tab-index.js.map +1 -1
- package/dist/src/rules/html-no-self-closing.js +48 -3
- package/dist/src/rules/html-no-self-closing.js.map +1 -1
- package/dist/src/rules/html-no-space-in-tag.js +173 -0
- package/dist/src/rules/html-no-space-in-tag.js.map +1 -0
- package/dist/src/rules/html-no-title-attribute.js +7 -1
- package/dist/src/rules/html-no-title-attribute.js.map +1 -1
- package/dist/src/rules/html-no-underscores-in-attribute-names.js +7 -1
- package/dist/src/rules/html-no-underscores-in-attribute-names.js.map +1 -1
- package/dist/src/rules/html-tag-name-lowercase.js +23 -5
- package/dist/src/rules/html-tag-name-lowercase.js.map +1 -1
- package/dist/src/rules/index.js +20 -2
- package/dist/src/rules/index.js.map +1 -1
- package/dist/src/rules/parser-no-errors.js +6 -0
- package/dist/src/rules/parser-no-errors.js.map +1 -1
- package/dist/src/rules/rule-utils.js +211 -31
- package/dist/src/rules/rule-utils.js.map +1 -1
- package/dist/src/rules/svg-tag-name-capitalization.js +22 -2
- package/dist/src/rules/svg-tag-name-capitalization.js.map +1 -1
- package/dist/src/{default-rules.js → rules.js} +46 -14
- package/dist/src/rules.js.map +1 -0
- package/dist/src/types.js +34 -1
- package/dist/src/types.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/cli/argument-parser.d.ts +8 -2
- package/dist/types/cli/file-processor.d.ts +15 -0
- package/dist/types/cli/formatters/json-formatter.d.ts +6 -0
- package/dist/types/cli/formatters/simple-formatter.d.ts +1 -0
- package/dist/types/cli/summary-reporter.d.ts +6 -0
- package/dist/types/cli.d.ts +9 -4
- package/dist/types/custom-rule-loader.d.ts +62 -0
- package/dist/types/herb-disable-comment-utils.d.ts +69 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/linter.d.ts +99 -3
- package/dist/types/loader.d.ts +20 -0
- package/dist/types/rules/erb-comment-syntax.d.ts +14 -0
- package/dist/types/rules/erb-no-case-node-children.d.ts +8 -0
- package/dist/types/rules/erb-no-empty-tags.d.ts +3 -2
- package/dist/types/rules/erb-no-extra-newline.d.ts +14 -0
- package/dist/types/rules/erb-no-extra-whitespace-inside-tags.d.ts +18 -0
- package/dist/types/rules/erb-no-output-control-flow.d.ts +3 -2
- package/dist/types/rules/erb-no-silent-tag-in-attribute-name.d.ts +3 -2
- package/dist/types/rules/erb-prefer-image-tag-helper.d.ts +3 -2
- package/dist/types/rules/erb-require-trailing-newline.d.ts +9 -0
- package/dist/types/rules/erb-require-whitespace-inside-tags.d.ts +16 -5
- package/dist/types/rules/erb-right-trim.d.ts +14 -0
- package/dist/types/rules/herb-disable-comment-base.d.ts +37 -0
- package/dist/types/rules/herb-disable-comment-malformed.d.ts +8 -0
- package/dist/types/rules/herb-disable-comment-missing-rules.d.ts +8 -0
- package/dist/types/rules/herb-disable-comment-no-duplicate-rules.d.ts +8 -0
- package/dist/types/rules/herb-disable-comment-no-redundant-all.d.ts +8 -0
- package/dist/types/rules/herb-disable-comment-unnecessary.d.ts +8 -0
- package/dist/types/rules/herb-disable-comment-valid-rule-name.d.ts +8 -0
- package/dist/types/rules/html-anchor-require-href.d.ts +3 -2
- package/dist/types/rules/html-aria-attribute-must-be-valid.d.ts +3 -2
- package/dist/types/rules/html-aria-label-is-well-formatted.d.ts +3 -2
- package/dist/types/rules/html-aria-level-must-be-valid.d.ts +3 -2
- package/dist/types/rules/html-aria-role-heading-requires-level.d.ts +3 -2
- package/dist/types/rules/html-aria-role-must-be-valid.d.ts +3 -2
- package/dist/types/rules/html-attribute-double-quotes.d.ts +13 -5
- package/dist/types/rules/html-attribute-equals-spacing.d.ts +12 -5
- package/dist/types/rules/html-attribute-values-require-quotes.d.ts +13 -5
- package/dist/types/rules/html-avoid-both-disabled-and-aria-disabled.d.ts +3 -2
- package/dist/types/rules/html-body-only-elements.d.ts +9 -0
- package/dist/types/rules/html-boolean-attributes-no-value.d.ts +12 -5
- package/dist/types/rules/html-head-only-elements.d.ts +9 -0
- package/dist/types/rules/html-iframe-has-title.d.ts +3 -2
- package/dist/types/rules/html-img-require-alt.d.ts +3 -2
- package/dist/types/rules/html-input-require-autocomplete.d.ts +8 -0
- package/dist/types/rules/html-navigation-has-label.d.ts +3 -2
- package/dist/types/rules/html-no-aria-hidden-on-focusable.d.ts +3 -2
- package/dist/types/rules/html-no-block-inside-inline.d.ts +3 -2
- package/dist/types/rules/html-no-duplicate-attributes.d.ts +3 -2
- package/dist/types/rules/html-no-duplicate-ids.d.ts +3 -2
- package/dist/types/rules/html-no-duplicate-meta-names.d.ts +9 -0
- package/dist/types/rules/html-no-empty-attributes.d.ts +3 -2
- package/dist/types/rules/html-no-empty-headings.d.ts +3 -2
- package/dist/types/rules/html-no-nested-links.d.ts +3 -2
- package/dist/types/rules/html-no-positive-tab-index.d.ts +3 -2
- package/dist/types/rules/html-no-self-closing.d.ts +14 -5
- package/dist/types/rules/html-no-space-in-tag.d.ts +16 -0
- package/dist/types/rules/html-no-title-attribute.d.ts +3 -2
- package/dist/types/rules/html-no-underscores-in-attribute-names.d.ts +3 -2
- package/dist/types/rules/html-tag-name-lowercase.d.ts +16 -6
- package/dist/types/rules/index.d.ts +20 -2
- package/dist/types/rules/parser-no-errors.d.ts +2 -1
- package/dist/types/rules/rule-utils.d.ts +72 -25
- package/dist/types/rules/svg-tag-name-capitalization.d.ts +13 -4
- package/dist/types/rules.d.ts +2 -0
- package/dist/types/src/cli/argument-parser.d.ts +8 -2
- package/dist/types/src/cli/file-processor.d.ts +15 -0
- package/dist/types/src/cli/formatters/json-formatter.d.ts +6 -0
- package/dist/types/src/cli/formatters/simple-formatter.d.ts +1 -0
- package/dist/types/src/cli/summary-reporter.d.ts +6 -0
- package/dist/types/src/cli.d.ts +9 -4
- package/dist/types/src/custom-rule-loader.d.ts +62 -0
- package/dist/types/src/herb-disable-comment-utils.d.ts +69 -0
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/linter.d.ts +99 -3
- package/dist/types/src/loader.d.ts +20 -0
- package/dist/types/src/rules/erb-comment-syntax.d.ts +14 -0
- package/dist/types/src/rules/erb-no-case-node-children.d.ts +8 -0
- package/dist/types/src/rules/erb-no-empty-tags.d.ts +3 -2
- package/dist/types/src/rules/erb-no-extra-newline.d.ts +14 -0
- package/dist/types/src/rules/erb-no-extra-whitespace-inside-tags.d.ts +18 -0
- package/dist/types/src/rules/erb-no-output-control-flow.d.ts +3 -2
- package/dist/types/src/rules/erb-no-silent-tag-in-attribute-name.d.ts +3 -2
- package/dist/types/src/rules/erb-prefer-image-tag-helper.d.ts +3 -2
- package/dist/types/src/rules/erb-require-trailing-newline.d.ts +9 -0
- package/dist/types/src/rules/erb-require-whitespace-inside-tags.d.ts +16 -5
- package/dist/types/src/rules/erb-right-trim.d.ts +14 -0
- package/dist/types/src/rules/herb-disable-comment-base.d.ts +37 -0
- package/dist/types/src/rules/herb-disable-comment-malformed.d.ts +8 -0
- package/dist/types/src/rules/herb-disable-comment-missing-rules.d.ts +8 -0
- package/dist/types/src/rules/herb-disable-comment-no-duplicate-rules.d.ts +8 -0
- package/dist/types/src/rules/herb-disable-comment-no-redundant-all.d.ts +8 -0
- package/dist/types/src/rules/herb-disable-comment-unnecessary.d.ts +8 -0
- package/dist/types/src/rules/herb-disable-comment-valid-rule-name.d.ts +8 -0
- package/dist/types/src/rules/html-anchor-require-href.d.ts +3 -2
- package/dist/types/src/rules/html-aria-attribute-must-be-valid.d.ts +3 -2
- package/dist/types/src/rules/html-aria-label-is-well-formatted.d.ts +3 -2
- package/dist/types/src/rules/html-aria-level-must-be-valid.d.ts +3 -2
- package/dist/types/src/rules/html-aria-role-heading-requires-level.d.ts +3 -2
- package/dist/types/src/rules/html-aria-role-must-be-valid.d.ts +3 -2
- package/dist/types/src/rules/html-attribute-double-quotes.d.ts +13 -5
- package/dist/types/src/rules/html-attribute-equals-spacing.d.ts +12 -5
- package/dist/types/src/rules/html-attribute-values-require-quotes.d.ts +13 -5
- package/dist/types/src/rules/html-avoid-both-disabled-and-aria-disabled.d.ts +3 -2
- package/dist/types/src/rules/html-body-only-elements.d.ts +9 -0
- package/dist/types/src/rules/html-boolean-attributes-no-value.d.ts +12 -5
- package/dist/types/src/rules/html-head-only-elements.d.ts +9 -0
- package/dist/types/src/rules/html-iframe-has-title.d.ts +3 -2
- package/dist/types/src/rules/html-img-require-alt.d.ts +3 -2
- package/dist/types/src/rules/html-input-require-autocomplete.d.ts +8 -0
- package/dist/types/src/rules/html-navigation-has-label.d.ts +3 -2
- package/dist/types/src/rules/html-no-aria-hidden-on-focusable.d.ts +3 -2
- package/dist/types/src/rules/html-no-block-inside-inline.d.ts +3 -2
- package/dist/types/src/rules/html-no-duplicate-attributes.d.ts +3 -2
- package/dist/types/src/rules/html-no-duplicate-ids.d.ts +3 -2
- package/dist/types/src/rules/html-no-duplicate-meta-names.d.ts +9 -0
- package/dist/types/src/rules/html-no-empty-attributes.d.ts +3 -2
- package/dist/types/src/rules/html-no-empty-headings.d.ts +3 -2
- package/dist/types/src/rules/html-no-nested-links.d.ts +3 -2
- package/dist/types/src/rules/html-no-positive-tab-index.d.ts +3 -2
- package/dist/types/src/rules/html-no-self-closing.d.ts +14 -5
- package/dist/types/src/rules/html-no-space-in-tag.d.ts +16 -0
- package/dist/types/src/rules/html-no-title-attribute.d.ts +3 -2
- package/dist/types/src/rules/html-no-underscores-in-attribute-names.d.ts +3 -2
- package/dist/types/src/rules/html-tag-name-lowercase.d.ts +16 -6
- package/dist/types/src/rules/index.d.ts +20 -2
- package/dist/types/src/rules/parser-no-errors.d.ts +2 -1
- package/dist/types/src/rules/rule-utils.d.ts +72 -25
- package/dist/types/src/rules/svg-tag-name-capitalization.d.ts +13 -4
- package/dist/types/src/rules.d.ts +2 -0
- package/dist/types/src/types.d.ts +102 -11
- package/dist/types/types.d.ts +102 -11
- package/docs/rules/README.md +19 -3
- package/docs/rules/erb-comment-syntax.md +44 -0
- package/docs/rules/erb-no-case-node-children.md +50 -0
- package/docs/rules/erb-no-extra-newline.md +74 -0
- package/docs/rules/erb-no-extra-whitespace-inside-tags.md +39 -0
- package/docs/rules/{erb-requires-trailing-newline.md → erb-require-trailing-newline.md} +1 -1
- package/docs/rules/erb-right-trim.md +52 -0
- package/docs/rules/herb-disable-comment-malformed.md +45 -0
- package/docs/rules/herb-disable-comment-missing-rules.md +60 -0
- package/docs/rules/herb-disable-comment-no-duplicate-rules.md +49 -0
- package/docs/rules/herb-disable-comment-no-redundant-all.md +53 -0
- package/docs/rules/herb-disable-comment-unnecessary.md +44 -0
- package/docs/rules/herb-disable-comment-valid-rule-name.md +41 -0
- package/docs/rules/html-aria-attribute-must-be-valid.md +2 -5
- package/docs/rules/html-aria-label-is-well-formatted.md +1 -1
- package/docs/rules/html-attribute-double-quotes.md +2 -2
- package/docs/rules/html-attribute-equals-spacing.md +2 -2
- package/docs/rules/html-attribute-values-require-quotes.md +3 -3
- package/docs/rules/html-avoid-both-disabled-and-aria-disabled.md +2 -2
- package/docs/rules/html-body-only-elements.md +99 -0
- package/docs/rules/html-boolean-attributes-no-value.md +2 -2
- package/docs/rules/html-head-only-elements.md +81 -0
- package/docs/rules/html-input-require-autocomplete.md +64 -0
- package/docs/rules/html-no-aria-hidden-on-focusable.md +2 -2
- package/docs/rules/html-no-duplicate-attributes.md +2 -2
- package/docs/rules/html-no-duplicate-meta-names.md +64 -0
- package/docs/rules/html-no-empty-attributes.md +3 -3
- package/docs/rules/html-no-empty-headings.md +4 -26
- package/docs/rules/html-no-positive-tab-index.md +1 -2
- package/docs/rules/html-no-self-closing.md +17 -2
- package/docs/rules/html-no-space-in-tag.md +66 -0
- package/docs/rules/html-no-title-attribute.md +2 -2
- package/docs/rules/html-no-underscores-in-attribute-names.md +2 -2
- package/docs/rules/html-tag-name-lowercase.md +2 -2
- package/package.json +13 -5
- package/src/cli/argument-parser.ts +50 -39
- package/src/cli/file-processor.ts +159 -28
- package/src/cli/formatters/detailed-formatter.ts +21 -3
- package/src/cli/formatters/github-actions-formatter.ts +17 -1
- package/src/cli/formatters/json-formatter.ts +9 -0
- package/src/cli/formatters/simple-formatter.ts +24 -8
- package/src/cli/output-manager.ts +23 -3
- package/src/cli/summary-reporter.ts +40 -3
- package/src/cli.ts +137 -52
- package/src/custom-rule-loader.ts +189 -0
- package/src/herb-disable-comment-utils.ts +175 -0
- package/src/index.ts +2 -0
- package/src/linter.ts +501 -36
- package/src/loader.ts +30 -0
- package/src/rules/erb-comment-syntax.ts +73 -0
- package/src/rules/erb-no-case-node-children.ts +68 -0
- package/src/rules/erb-no-empty-tags.ts +9 -3
- package/src/rules/erb-no-extra-newline.ts +91 -0
- package/src/rules/erb-no-extra-whitespace-inside-tags.ts +147 -0
- package/src/rules/erb-no-output-control-flow.ts +9 -3
- package/src/rules/erb-no-silent-tag-in-attribute-name.ts +9 -3
- package/src/rules/erb-prefer-image-tag-helper.ts +9 -3
- package/src/rules/erb-require-trailing-newline.ts +47 -0
- package/src/rules/erb-require-whitespace-inside-tags.ts +96 -26
- package/src/rules/erb-right-trim.ts +67 -0
- package/src/rules/herb-disable-comment-base.ts +76 -0
- package/src/rules/herb-disable-comment-malformed.ts +66 -0
- package/src/rules/herb-disable-comment-missing-rules.ts +41 -0
- package/src/rules/herb-disable-comment-no-duplicate-rules.ts +46 -0
- package/src/rules/herb-disable-comment-no-redundant-all.ts +40 -0
- package/src/rules/herb-disable-comment-unnecessary.ts +103 -0
- package/src/rules/herb-disable-comment-valid-rule-name.ts +62 -0
- package/src/rules/html-anchor-require-href.ts +9 -3
- package/src/rules/html-aria-attribute-must-be-valid.ts +9 -3
- package/src/rules/html-aria-label-is-well-formatted.ts +9 -5
- package/src/rules/html-aria-level-must-be-valid.ts +9 -2
- package/src/rules/html-aria-role-heading-requires-level.ts +9 -3
- package/src/rules/html-aria-role-must-be-valid.ts +9 -3
- package/src/rules/html-attribute-double-quotes.ts +42 -8
- package/src/rules/html-attribute-equals-spacing.ts +31 -7
- package/src/rules/html-attribute-values-require-quotes.ts +56 -10
- package/src/rules/html-avoid-both-disabled-and-aria-disabled.ts +9 -3
- package/src/rules/html-body-only-elements.ts +60 -0
- package/src/rules/html-boolean-attributes-no-value.ts +31 -6
- package/src/rules/html-head-only-elements.ts +65 -0
- package/src/rules/html-iframe-has-title.ts +9 -4
- package/src/rules/html-img-require-alt.ts +10 -4
- package/src/rules/html-input-require-autocomplete.ts +85 -0
- package/src/rules/html-navigation-has-label.ts +9 -3
- package/src/rules/html-no-aria-hidden-on-focusable.ts +9 -3
- package/src/rules/html-no-block-inside-inline.ts +9 -3
- package/src/rules/html-no-duplicate-attributes.ts +9 -3
- package/src/rules/html-no-duplicate-ids.ts +11 -7
- package/src/rules/html-no-duplicate-meta-names.ts +188 -0
- package/src/rules/html-no-empty-attributes.ts +58 -10
- package/src/rules/html-no-empty-headings.ts +10 -8
- package/src/rules/html-no-nested-links.ts +10 -4
- package/src/rules/html-no-positive-tab-index.ts +9 -3
- package/src/rules/html-no-self-closing.ts +69 -9
- package/src/rules/html-no-space-in-tag.ts +221 -0
- package/src/rules/html-no-title-attribute.ts +9 -3
- package/src/rules/html-no-underscores-in-attribute-names.ts +12 -4
- package/src/rules/html-tag-name-lowercase.ts +41 -10
- package/src/rules/index.ts +24 -2
- package/src/rules/parser-no-errors.ts +8 -1
- package/src/rules/rule-utils.ts +250 -44
- package/src/rules/svg-tag-name-capitalization.ts +39 -6
- package/src/{default-rules.ts → rules.ts} +53 -13
- package/src/types.ts +133 -15
- package/dist/src/default-rules.js.map +0 -1
- package/dist/src/rules/erb-requires-trailing-newline.js +0 -22
- package/dist/src/rules/erb-requires-trailing-newline.js.map +0 -1
- package/dist/types/default-rules.d.ts +0 -2
- package/dist/types/rules/erb-requires-trailing-newline.d.ts +0 -6
- package/dist/types/src/default-rules.d.ts +0 -2
- package/dist/types/src/rules/erb-requires-trailing-newline.d.ts +0 -6
- package/src/rules/erb-requires-trailing-newline.ts +0 -29
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ParserRule } from "../types.js"
|
|
2
2
|
import { AttributeVisitorMixin, VALID_ARIA_ROLES, StaticAttributeStaticValueParams } from "./rule-utils.js"
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
5
5
|
import type { ParseResult } from "@herb-tools/core"
|
|
6
6
|
|
|
7
7
|
class AriaRoleMustBeValid extends AttributeVisitorMixin {
|
|
@@ -13,7 +13,6 @@ class AriaRoleMustBeValid extends AttributeVisitorMixin {
|
|
|
13
13
|
this.addOffense(
|
|
14
14
|
`The \`role\` attribute must be a valid ARIA role. Role \`${attributeValue}\` is not recognized.`,
|
|
15
15
|
attributeNode.location,
|
|
16
|
-
"error"
|
|
17
16
|
)
|
|
18
17
|
}
|
|
19
18
|
}
|
|
@@ -21,7 +20,14 @@ class AriaRoleMustBeValid extends AttributeVisitorMixin {
|
|
|
21
20
|
export class HTMLAriaRoleMustBeValidRule extends ParserRule {
|
|
22
21
|
name = "html-aria-role-must-be-valid"
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
get defaultConfig(): FullRuleConfig {
|
|
24
|
+
return {
|
|
25
|
+
enabled: true,
|
|
26
|
+
severity: "error"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
25
31
|
const visitor = new AriaRoleMustBeValid(this.name, context)
|
|
26
32
|
|
|
27
33
|
visitor.visit(result.value)
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { ParserRule } from "../types.js"
|
|
1
|
+
import { ParserRule, BaseAutofixContext, Mutable } from "../types.js"
|
|
2
2
|
import { AttributeVisitorMixin, StaticAttributeStaticValueParams, StaticAttributeDynamicValueParams, getAttributeValueQuoteType, hasAttributeValue } from "./rule-utils.js"
|
|
3
3
|
import { filterLiteralNodes } from "@herb-tools/core"
|
|
4
4
|
|
|
5
|
-
import type { LintOffense, LintContext } from "../types.js"
|
|
6
|
-
import type { ParseResult } from "@herb-tools/core"
|
|
5
|
+
import type { UnboundLintOffense, LintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
6
|
+
import type { ParseResult, HTMLAttributeNode } from "@herb-tools/core"
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
interface AttributeDoubleQuotesAutofixContext extends BaseAutofixContext {
|
|
9
|
+
node: Mutable<HTMLAttributeNode>
|
|
10
|
+
valueContent: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class AttributeDoubleQuotesVisitor extends AttributeVisitorMixin<AttributeDoubleQuotesAutofixContext> {
|
|
9
14
|
protected checkStaticAttributeStaticValue({ attributeName, attributeValue, attributeNode }: StaticAttributeStaticValueParams) {
|
|
10
15
|
if (!hasAttributeValue(attributeNode)) return
|
|
11
16
|
if (getAttributeValueQuoteType(attributeNode) !== "single") return
|
|
@@ -14,7 +19,10 @@ class AttributeDoubleQuotesVisitor extends AttributeVisitorMixin {
|
|
|
14
19
|
this.addOffense(
|
|
15
20
|
`Attribute \`${attributeName}\` uses single quotes. Prefer double quotes for HTML attribute values: \`${attributeName}="${attributeValue}"\`.`,
|
|
16
21
|
attributeNode.value!.location,
|
|
17
|
-
|
|
22
|
+
{
|
|
23
|
+
node: attributeNode,
|
|
24
|
+
valueContent: attributeValue
|
|
25
|
+
}
|
|
18
26
|
)
|
|
19
27
|
}
|
|
20
28
|
|
|
@@ -26,19 +34,45 @@ class AttributeDoubleQuotesVisitor extends AttributeVisitorMixin {
|
|
|
26
34
|
this.addOffense(
|
|
27
35
|
`Attribute \`${attributeName}\` uses single quotes. Prefer double quotes for HTML attribute values: \`${attributeName}="${combinedValue}"\`.`,
|
|
28
36
|
attributeNode.value!.location,
|
|
29
|
-
|
|
37
|
+
{
|
|
38
|
+
node: attributeNode,
|
|
39
|
+
valueContent: combinedValue || ""
|
|
40
|
+
}
|
|
30
41
|
)
|
|
31
42
|
}
|
|
32
43
|
}
|
|
33
44
|
|
|
34
|
-
export class HTMLAttributeDoubleQuotesRule extends ParserRule {
|
|
45
|
+
export class HTMLAttributeDoubleQuotesRule extends ParserRule<AttributeDoubleQuotesAutofixContext> {
|
|
46
|
+
static autocorrectable = true
|
|
35
47
|
name = "html-attribute-double-quotes"
|
|
36
48
|
|
|
37
|
-
|
|
49
|
+
get defaultConfig(): FullRuleConfig {
|
|
50
|
+
return {
|
|
51
|
+
enabled: true,
|
|
52
|
+
severity: "warning"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense<AttributeDoubleQuotesAutofixContext>[] {
|
|
38
57
|
const visitor = new AttributeDoubleQuotesVisitor(this.name, context)
|
|
39
58
|
|
|
40
59
|
visitor.visit(result.value)
|
|
41
60
|
|
|
42
61
|
return visitor.offenses
|
|
43
62
|
}
|
|
63
|
+
|
|
64
|
+
autofix(offense: LintOffense<AttributeDoubleQuotesAutofixContext>, result: ParseResult, _context?: Partial<LintContext>): ParseResult | null {
|
|
65
|
+
if (!offense.autofixContext) return null
|
|
66
|
+
|
|
67
|
+
const { node: { value } } = offense.autofixContext
|
|
68
|
+
|
|
69
|
+
if (!value) return null
|
|
70
|
+
if (!value.open_quote) return null
|
|
71
|
+
if (!value.close_quote) return null
|
|
72
|
+
|
|
73
|
+
value.open_quote.value = '"'
|
|
74
|
+
value.close_quote.value = '"'
|
|
75
|
+
|
|
76
|
+
return result
|
|
77
|
+
}
|
|
44
78
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { BaseRuleVisitor } from "./rule-utils.js"
|
|
2
|
-
import { ParserRule } from "../types.js"
|
|
2
|
+
import { ParserRule, BaseAutofixContext, Mutable } from "../types.js"
|
|
3
3
|
|
|
4
|
-
import type { LintOffense, LintContext } from "../types.js"
|
|
4
|
+
import type { UnboundLintOffense, LintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
5
5
|
import type { ParseResult, HTMLAttributeNode } from "@herb-tools/core"
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
interface AttributeEqualsSpacingAutofixContext extends BaseAutofixContext {
|
|
8
|
+
node: Mutable<HTMLAttributeNode>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
class HTMLAttributeEqualsSpacingVisitor extends BaseRuleVisitor<AttributeEqualsSpacingAutofixContext> {
|
|
8
12
|
visitHTMLAttributeNode(attribute: HTMLAttributeNode): void {
|
|
9
13
|
if (!attribute.equals || !attribute.name || !attribute.value) {
|
|
10
14
|
return
|
|
@@ -14,7 +18,7 @@ class HTMLAttributeEqualsSpacingVisitor extends BaseRuleVisitor {
|
|
|
14
18
|
this.addOffense(
|
|
15
19
|
"Remove whitespace before `=` in HTML attribute",
|
|
16
20
|
attribute.equals.location,
|
|
17
|
-
|
|
21
|
+
{ node: attribute }
|
|
18
22
|
)
|
|
19
23
|
}
|
|
20
24
|
|
|
@@ -22,20 +26,40 @@ class HTMLAttributeEqualsSpacingVisitor extends BaseRuleVisitor {
|
|
|
22
26
|
this.addOffense(
|
|
23
27
|
"Remove whitespace after `=` in HTML attribute",
|
|
24
28
|
attribute.equals.location,
|
|
25
|
-
|
|
29
|
+
{ node: attribute }
|
|
26
30
|
)
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
export class HTMLAttributeEqualsSpacingRule extends ParserRule {
|
|
35
|
+
export class HTMLAttributeEqualsSpacingRule extends ParserRule<AttributeEqualsSpacingAutofixContext> {
|
|
36
|
+
static autocorrectable = true
|
|
32
37
|
name = "html-attribute-equals-spacing"
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
get defaultConfig(): FullRuleConfig {
|
|
40
|
+
return {
|
|
41
|
+
enabled: true,
|
|
42
|
+
severity: "error"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense<AttributeEqualsSpacingAutofixContext>[] {
|
|
35
47
|
const visitor = new HTMLAttributeEqualsSpacingVisitor(this.name, context)
|
|
36
48
|
|
|
37
49
|
visitor.visit(result.value)
|
|
38
50
|
|
|
39
51
|
return visitor.offenses
|
|
40
52
|
}
|
|
53
|
+
|
|
54
|
+
autofix(offense: LintOffense<AttributeEqualsSpacingAutofixContext>, result: ParseResult, _context?: Partial<LintContext>): ParseResult | null {
|
|
55
|
+
if (!offense.autofixContext) return null
|
|
56
|
+
|
|
57
|
+
const { node: { equals } } = offense.autofixContext
|
|
58
|
+
|
|
59
|
+
if (!equals) return null
|
|
60
|
+
|
|
61
|
+
equals.value = "="
|
|
62
|
+
|
|
63
|
+
return result
|
|
64
|
+
}
|
|
41
65
|
}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Token, Location } from "@herb-tools/core"
|
|
2
|
+
import { ParserRule, BaseAutofixContext, Mutable } from "../types.js"
|
|
2
3
|
import { AttributeVisitorMixin, StaticAttributeStaticValueParams, StaticAttributeDynamicValueParams } from "./rule-utils.js"
|
|
3
4
|
|
|
4
|
-
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
-
import type { HTMLAttributeNode,
|
|
5
|
+
import type { UnboundLintOffense, LintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
6
|
+
import type { HTMLAttributeNode, ParseResult, } from "@herb-tools/core"
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
interface AttributeValuesRequireQuotesAutofixContext extends BaseAutofixContext {
|
|
9
|
+
node: Mutable<HTMLAttributeNode>
|
|
10
|
+
unquotedValue: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class AttributeValuesRequireQuotesVisitor extends AttributeVisitorMixin<AttributeValuesRequireQuotesAutofixContext> {
|
|
8
14
|
protected checkStaticAttributeStaticValue({ attributeName, attributeValue, attributeNode }: StaticAttributeStaticValueParams): void {
|
|
9
15
|
if (this.hasAttributeValue(attributeNode)) return
|
|
10
16
|
if (this.isQuoted(attributeNode)) return
|
|
@@ -12,7 +18,10 @@ class AttributeValuesRequireQuotesVisitor extends AttributeVisitorMixin {
|
|
|
12
18
|
this.addOffense(
|
|
13
19
|
`Attribute value should be quoted: \`${attributeName}="${attributeValue}"\`. Always wrap attribute values in quotes.`,
|
|
14
20
|
attributeNode.value!.location,
|
|
15
|
-
|
|
21
|
+
{
|
|
22
|
+
node: attributeNode,
|
|
23
|
+
unquotedValue: attributeValue
|
|
24
|
+
}
|
|
16
25
|
)
|
|
17
26
|
}
|
|
18
27
|
|
|
@@ -23,7 +32,10 @@ class AttributeValuesRequireQuotesVisitor extends AttributeVisitorMixin {
|
|
|
23
32
|
this.addOffense(
|
|
24
33
|
`Attribute value should be quoted: \`${attributeName}="${combinedValue}"\`. Always wrap attribute values in quotes.`,
|
|
25
34
|
attributeNode.value!.location,
|
|
26
|
-
|
|
35
|
+
{
|
|
36
|
+
node: attributeNode,
|
|
37
|
+
unquotedValue: combinedValue || ""
|
|
38
|
+
}
|
|
27
39
|
)
|
|
28
40
|
}
|
|
29
41
|
|
|
@@ -32,20 +44,54 @@ class AttributeValuesRequireQuotesVisitor extends AttributeVisitorMixin {
|
|
|
32
44
|
}
|
|
33
45
|
|
|
34
46
|
private isQuoted(attributeNode: HTMLAttributeNode): boolean {
|
|
35
|
-
const valueNode = attributeNode.value
|
|
47
|
+
const valueNode = attributeNode.value
|
|
36
48
|
|
|
37
|
-
return valueNode.quoted
|
|
49
|
+
return valueNode ? valueNode.quoted : false
|
|
38
50
|
}
|
|
39
51
|
}
|
|
40
52
|
|
|
41
|
-
export class HTMLAttributeValuesRequireQuotesRule extends ParserRule {
|
|
53
|
+
export class HTMLAttributeValuesRequireQuotesRule extends ParserRule<AttributeValuesRequireQuotesAutofixContext> {
|
|
54
|
+
static autocorrectable = true
|
|
42
55
|
name = "html-attribute-values-require-quotes"
|
|
43
56
|
|
|
44
|
-
|
|
57
|
+
get defaultConfig(): FullRuleConfig {
|
|
58
|
+
return {
|
|
59
|
+
enabled: true,
|
|
60
|
+
severity: "error"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense<AttributeValuesRequireQuotesAutofixContext>[] {
|
|
45
65
|
const visitor = new AttributeValuesRequireQuotesVisitor(this.name, context)
|
|
46
66
|
|
|
47
67
|
visitor.visit(result.value)
|
|
48
68
|
|
|
49
69
|
return visitor.offenses
|
|
50
70
|
}
|
|
71
|
+
|
|
72
|
+
autofix(offense: LintOffense<AttributeValuesRequireQuotesAutofixContext>, result: ParseResult, _context?: Partial<LintContext>): ParseResult | null {
|
|
73
|
+
if (!offense.autofixContext) return null
|
|
74
|
+
|
|
75
|
+
const { node: { value } } = offense.autofixContext
|
|
76
|
+
|
|
77
|
+
if (!value) return null
|
|
78
|
+
|
|
79
|
+
const quote = Token.from({ type: "TOKEN_QUOTE", value: '"', location: Location.zero, range: [0, 0] })
|
|
80
|
+
|
|
81
|
+
if (value.open_quote) {
|
|
82
|
+
value.open_quote.value = '"'
|
|
83
|
+
} else {
|
|
84
|
+
value.open_quote = quote
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (value.close_quote) {
|
|
88
|
+
value.close_quote.value = '"'
|
|
89
|
+
} else {
|
|
90
|
+
value.close_quote = quote
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
value.quoted = true
|
|
94
|
+
|
|
95
|
+
return result
|
|
96
|
+
}
|
|
51
97
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ParserRule } from "../types.js"
|
|
2
2
|
import { BaseRuleVisitor, getTagName, hasAttribute, getAttributes, findAttributeByName } from "./rule-utils.js"
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
5
5
|
import type { HTMLOpenTagNode, HTMLAttributeValueNode, ParseResult, Node } from "@herb-tools/core"
|
|
6
6
|
|
|
7
7
|
const ELEMENTS_WITH_NATIVE_DISABLED_ATTRIBUTE_SUPPORT = new Set([
|
|
@@ -32,7 +32,6 @@ class AvoidBothDisabledAndAriaDisabledVisitor extends BaseRuleVisitor {
|
|
|
32
32
|
this.addOffense(
|
|
33
33
|
"aria-disabled may be used in place of native HTML disabled to allow tab-focus on an otherwise ignored element. Setting both attributes is contradictory and confusing. Choose either disabled or aria-disabled, not both.",
|
|
34
34
|
node.tag_name!.location,
|
|
35
|
-
"error"
|
|
36
35
|
)
|
|
37
36
|
}
|
|
38
37
|
}
|
|
@@ -56,7 +55,14 @@ class AvoidBothDisabledAndAriaDisabledVisitor extends BaseRuleVisitor {
|
|
|
56
55
|
export class HTMLAvoidBothDisabledAndAriaDisabledRule extends ParserRule {
|
|
57
56
|
name = "html-avoid-both-disabled-and-aria-disabled"
|
|
58
57
|
|
|
59
|
-
|
|
58
|
+
get defaultConfig(): FullRuleConfig {
|
|
59
|
+
return {
|
|
60
|
+
enabled: true,
|
|
61
|
+
severity: "error"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
60
66
|
const visitor = new AvoidBothDisabledAndAriaDisabledVisitor(this.name, context)
|
|
61
67
|
|
|
62
68
|
visitor.visit(result.value)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ParserRule } from "../types.js"
|
|
2
|
+
import { BaseRuleVisitor, getTagName, isBodyOnlyTag } from "./rule-utils.js"
|
|
3
|
+
|
|
4
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
5
|
+
import type { HTMLElementNode, ParseResult } from "@herb-tools/core"
|
|
6
|
+
|
|
7
|
+
class HTMLBodyOnlyElementsVisitor extends BaseRuleVisitor {
|
|
8
|
+
private elementStack: string[] = []
|
|
9
|
+
|
|
10
|
+
visitHTMLElementNode(node: HTMLElementNode): void {
|
|
11
|
+
const tagName = getTagName(node.open_tag)?.toLowerCase()
|
|
12
|
+
if (!tagName) return
|
|
13
|
+
|
|
14
|
+
this.checkBodyOnlyElement(node, tagName)
|
|
15
|
+
|
|
16
|
+
this.elementStack.push(tagName)
|
|
17
|
+
this.visitChildNodes(node)
|
|
18
|
+
this.elementStack.pop()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private checkBodyOnlyElement(node: HTMLElementNode, tagName: string): void {
|
|
22
|
+
if (this.insideBody) return
|
|
23
|
+
if (!this.insideHead) return
|
|
24
|
+
if (!isBodyOnlyTag(tagName)) return
|
|
25
|
+
|
|
26
|
+
this.addOffense(
|
|
27
|
+
`Element \`<${tagName}>\` must be placed inside the \`<body>\` tag.`,
|
|
28
|
+
node.location,
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private get insideBody(): boolean {
|
|
33
|
+
return this.elementStack.includes("body")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private get insideHead(): boolean {
|
|
37
|
+
return this.elementStack.includes("head")
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class HTMLBodyOnlyElementsRule extends ParserRule {
|
|
42
|
+
static autocorrectable = false
|
|
43
|
+
name = "html-body-only-elements"
|
|
44
|
+
|
|
45
|
+
get defaultConfig(): FullRuleConfig {
|
|
46
|
+
return {
|
|
47
|
+
enabled: true,
|
|
48
|
+
severity: "error",
|
|
49
|
+
exclude: ["**/*.xml", "**/*.xml.erb"]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
54
|
+
const visitor = new HTMLBodyOnlyElementsVisitor(this.name, context)
|
|
55
|
+
|
|
56
|
+
visitor.visit(result.value)
|
|
57
|
+
|
|
58
|
+
return visitor.offenses
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { ParserRule } from "../types.js"
|
|
1
|
+
import { ParserRule, BaseAutofixContext, Mutable } from "../types.js"
|
|
2
2
|
import { AttributeVisitorMixin, StaticAttributeStaticValueParams, StaticAttributeDynamicValueParams, isBooleanAttribute, hasAttributeValue } from "./rule-utils.js"
|
|
3
3
|
import { IdentityPrinter } from "@herb-tools/printer"
|
|
4
4
|
|
|
5
|
-
import type { LintOffense, LintContext } from "../types.js"
|
|
5
|
+
import type { UnboundLintOffense, LintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
6
6
|
import type { ParseResult, HTMLAttributeNode } from "@herb-tools/core"
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
interface BooleanAttributeAutofixContext extends BaseAutofixContext {
|
|
9
|
+
node: Mutable<HTMLAttributeNode>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class BooleanAttributesNoValueVisitor extends AttributeVisitorMixin<BooleanAttributeAutofixContext> {
|
|
9
13
|
protected checkStaticAttributeStaticValue({ originalAttributeName, attributeNode }: StaticAttributeStaticValueParams) {
|
|
10
14
|
this.checkAttribute(originalAttributeName, attributeNode)
|
|
11
15
|
}
|
|
@@ -21,19 +25,40 @@ class BooleanAttributesNoValueVisitor extends AttributeVisitorMixin {
|
|
|
21
25
|
this.addOffense(
|
|
22
26
|
`Boolean attribute \`${IdentityPrinter.print(attributeNode.name)}\` should not have a value. Use \`${attributeName.toLowerCase()}\` instead of \`${IdentityPrinter.print(attributeNode)}\`.`,
|
|
23
27
|
attributeNode.value!.location,
|
|
24
|
-
|
|
28
|
+
{
|
|
29
|
+
node: attributeNode
|
|
30
|
+
}
|
|
25
31
|
)
|
|
26
32
|
}
|
|
27
33
|
}
|
|
28
34
|
|
|
29
|
-
export class HTMLBooleanAttributesNoValueRule extends ParserRule {
|
|
35
|
+
export class HTMLBooleanAttributesNoValueRule extends ParserRule<BooleanAttributeAutofixContext> {
|
|
36
|
+
static autocorrectable = true
|
|
30
37
|
name = "html-boolean-attributes-no-value"
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
get defaultConfig(): FullRuleConfig {
|
|
40
|
+
return {
|
|
41
|
+
enabled: true,
|
|
42
|
+
severity: "error"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense<BooleanAttributeAutofixContext>[] {
|
|
33
47
|
const visitor = new BooleanAttributesNoValueVisitor(this.name, context)
|
|
34
48
|
|
|
35
49
|
visitor.visit(result.value)
|
|
36
50
|
|
|
37
51
|
return visitor.offenses
|
|
38
52
|
}
|
|
53
|
+
|
|
54
|
+
autofix(offense: LintOffense<BooleanAttributeAutofixContext>, result: ParseResult, _context?: Partial<LintContext>): ParseResult | null {
|
|
55
|
+
if (!offense.autofixContext) return null
|
|
56
|
+
|
|
57
|
+
const { node } = offense.autofixContext
|
|
58
|
+
|
|
59
|
+
node.equals = null
|
|
60
|
+
node.value = null
|
|
61
|
+
|
|
62
|
+
return result
|
|
63
|
+
}
|
|
39
64
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ParserRule } from "../types"
|
|
2
|
+
import { BaseRuleVisitor, getTagName, isHeadOnlyTag } from "./rule-utils"
|
|
3
|
+
|
|
4
|
+
import type { ParseResult, HTMLElementNode } from "@herb-tools/core"
|
|
5
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types"
|
|
6
|
+
|
|
7
|
+
class HeadOnlyElementsVisitor extends BaseRuleVisitor {
|
|
8
|
+
private elementStack: string[] = []
|
|
9
|
+
|
|
10
|
+
visitHTMLElementNode(node: HTMLElementNode): void {
|
|
11
|
+
const tagName = getTagName(node)?.toLowerCase()
|
|
12
|
+
if (!tagName) return
|
|
13
|
+
|
|
14
|
+
this.checkHeadOnlyElement(node, tagName)
|
|
15
|
+
|
|
16
|
+
this.elementStack.push(tagName)
|
|
17
|
+
this.visitChildNodes(node)
|
|
18
|
+
this.elementStack.pop()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private checkHeadOnlyElement(node: HTMLElementNode, tagName: string): void {
|
|
22
|
+
if (this.insideHead) return
|
|
23
|
+
if (!this.insideBody) return
|
|
24
|
+
if (!isHeadOnlyTag(tagName)) return
|
|
25
|
+
if (tagName === "title" && this.insideSVG) return
|
|
26
|
+
|
|
27
|
+
this.addOffense(
|
|
28
|
+
`Element \`<${tagName}>\` must be placed inside the \`<head>\` tag.`,
|
|
29
|
+
node.location,
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private get insideHead(): boolean {
|
|
34
|
+
return this.elementStack.includes("head")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private get insideBody(): boolean {
|
|
38
|
+
return this.elementStack.includes("body")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private get insideSVG(): boolean {
|
|
42
|
+
return this.elementStack.includes("svg")
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class HTMLHeadOnlyElementsRule extends ParserRule {
|
|
47
|
+
static autocorrectable = false
|
|
48
|
+
name = "html-head-only-elements"
|
|
49
|
+
|
|
50
|
+
get defaultConfig(): FullRuleConfig {
|
|
51
|
+
return {
|
|
52
|
+
enabled: true,
|
|
53
|
+
severity: "error",
|
|
54
|
+
exclude: ["**/*.xml", "**/*.xml.erb"]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
59
|
+
const visitor = new HeadOnlyElementsVisitor(this.name, context)
|
|
60
|
+
|
|
61
|
+
visitor.visit(result.value)
|
|
62
|
+
|
|
63
|
+
return visitor.offenses
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ParserRule } from "../types.js"
|
|
2
2
|
import { BaseRuleVisitor, getTagName, getAttribute, getAttributeValue } from "./rule-utils.js"
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
5
5
|
import type { HTMLOpenTagNode, ParseResult } from "@herb-tools/core"
|
|
6
6
|
|
|
7
7
|
class IframeHasTitleVisitor extends BaseRuleVisitor {
|
|
@@ -31,7 +31,6 @@ class IframeHasTitleVisitor extends BaseRuleVisitor {
|
|
|
31
31
|
this.addOffense(
|
|
32
32
|
"`<iframe>` elements must have a `title` attribute that describes the content of the frame for screen reader users.",
|
|
33
33
|
node.location,
|
|
34
|
-
"error"
|
|
35
34
|
)
|
|
36
35
|
|
|
37
36
|
return
|
|
@@ -43,7 +42,6 @@ class IframeHasTitleVisitor extends BaseRuleVisitor {
|
|
|
43
42
|
this.addOffense(
|
|
44
43
|
"`<iframe>` elements must have a `title` attribute that describes the content of the frame for screen reader users.",
|
|
45
44
|
node.location,
|
|
46
|
-
"error"
|
|
47
45
|
)
|
|
48
46
|
}
|
|
49
47
|
}
|
|
@@ -52,7 +50,14 @@ class IframeHasTitleVisitor extends BaseRuleVisitor {
|
|
|
52
50
|
export class HTMLIframeHasTitleRule extends ParserRule {
|
|
53
51
|
name = "html-iframe-has-title"
|
|
54
52
|
|
|
55
|
-
|
|
53
|
+
get defaultConfig(): FullRuleConfig {
|
|
54
|
+
return {
|
|
55
|
+
enabled: true,
|
|
56
|
+
severity: "error"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
56
61
|
const visitor = new IframeHasTitleVisitor(this.name, context)
|
|
57
62
|
|
|
58
63
|
visitor.visit(result.value)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BaseRuleVisitor, getTagName, hasAttribute } from "./rule-utils.js"
|
|
2
2
|
|
|
3
3
|
import { ParserRule } from "../types.js"
|
|
4
|
-
import type {
|
|
4
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
5
5
|
import type { HTMLOpenTagNode, ParseResult } from "@herb-tools/core"
|
|
6
6
|
|
|
7
7
|
class ImgRequireAltVisitor extends BaseRuleVisitor {
|
|
@@ -20,8 +20,7 @@ class ImgRequireAltVisitor extends BaseRuleVisitor {
|
|
|
20
20
|
if (!hasAttribute(node, "alt")) {
|
|
21
21
|
this.addOffense(
|
|
22
22
|
'Missing required `alt` attribute on `<img>` tag. Add `alt=""` for decorative images or `alt="description"` for informative images.',
|
|
23
|
-
node.tag_name!.location
|
|
24
|
-
"error"
|
|
23
|
+
node.tag_name!.location
|
|
25
24
|
)
|
|
26
25
|
}
|
|
27
26
|
}
|
|
@@ -30,7 +29,14 @@ class ImgRequireAltVisitor extends BaseRuleVisitor {
|
|
|
30
29
|
export class HTMLImgRequireAltRule extends ParserRule {
|
|
31
30
|
name = "html-img-require-alt"
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
get defaultConfig(): FullRuleConfig {
|
|
33
|
+
return {
|
|
34
|
+
enabled: true,
|
|
35
|
+
severity: "error"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
34
40
|
const visitor = new ImgRequireAltVisitor(this.name, context)
|
|
35
41
|
visitor.visit(result.value)
|
|
36
42
|
return visitor.offenses
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { getTagName } from "@herb-tools/core"
|
|
2
|
+
import { BaseRuleVisitor, getAttribute, getAttributeValue, getStaticAttributeValueContent } from "./rule-utils.js"
|
|
3
|
+
import { ParserRule } from "../types.js"
|
|
4
|
+
|
|
5
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
6
|
+
import type { ParseResult, HTMLOpenTagNode } from "@herb-tools/core"
|
|
7
|
+
|
|
8
|
+
class HTMLInputRequireAutocompleteVisitor extends BaseRuleVisitor {
|
|
9
|
+
readonly HTML_INPUT_TYPES_REQUIRING_AUTOCOMPLETE = new Set([
|
|
10
|
+
"color",
|
|
11
|
+
"date",
|
|
12
|
+
"datetime-local",
|
|
13
|
+
"email",
|
|
14
|
+
"month",
|
|
15
|
+
"number",
|
|
16
|
+
"password",
|
|
17
|
+
"range",
|
|
18
|
+
"search",
|
|
19
|
+
"tel",
|
|
20
|
+
"text",
|
|
21
|
+
"time",
|
|
22
|
+
"url",
|
|
23
|
+
"week",
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
visitHTMLOpenTagNode(node: HTMLOpenTagNode): void {
|
|
27
|
+
this.checkInputTag(node)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private checkInputTag(node: HTMLOpenTagNode): void {
|
|
31
|
+
if (!this.isInputTag(node) || this.hasAutocomplete(node)) return
|
|
32
|
+
|
|
33
|
+
const typeAttribute = getAttribute(node, "type");
|
|
34
|
+
if (!typeAttribute) return
|
|
35
|
+
|
|
36
|
+
const typeValue = getStaticAttributeValueContent(typeAttribute)
|
|
37
|
+
if (!typeValue) return
|
|
38
|
+
|
|
39
|
+
if (!this.HTML_INPUT_TYPES_REQUIRING_AUTOCOMPLETE.has(typeValue)) return
|
|
40
|
+
|
|
41
|
+
this.addOffense(
|
|
42
|
+
"Add an `autocomplete` attribute to improve form accessibility. Use a specific value (e.g., `autocomplete=\"email\"`), `autocomplete=\"on\"` for defaults, or `autocomplete=\"off\"` to disable.",
|
|
43
|
+
node.location
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private hasAutocomplete(node: HTMLOpenTagNode) {
|
|
48
|
+
const autocompleteAttribute = getAttribute(node, "autocomplete");
|
|
49
|
+
if (!autocompleteAttribute) return false
|
|
50
|
+
|
|
51
|
+
const autocompleteValue = getAttributeValue(autocompleteAttribute)
|
|
52
|
+
if (!autocompleteValue) return false
|
|
53
|
+
|
|
54
|
+
return true
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private isInputTag(node: HTMLOpenTagNode) {
|
|
58
|
+
const tagName = getTagName(node);
|
|
59
|
+
|
|
60
|
+
if (tagName === "input") {
|
|
61
|
+
return true
|
|
62
|
+
} else {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class HTMLInputRequireAutocompleteRule extends ParserRule {
|
|
69
|
+
name = "html-input-require-autocomplete"
|
|
70
|
+
|
|
71
|
+
get defaultConfig(): FullRuleConfig {
|
|
72
|
+
return {
|
|
73
|
+
enabled: true,
|
|
74
|
+
severity: "error"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
79
|
+
const visitor = new HTMLInputRequireAutocompleteVisitor(this.name, context)
|
|
80
|
+
|
|
81
|
+
visitor.visit(result.value)
|
|
82
|
+
|
|
83
|
+
return visitor.offenses
|
|
84
|
+
}
|
|
85
|
+
}
|