@herb-tools/linter 0.7.5 → 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 +26023 -3424
- package/dist/herb-lint.js.map +1 -1
- package/dist/index.cjs +5759 -1583
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5727 -1584
- 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 +38 -33
- 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 +107 -42
- 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 +31 -2
- package/dist/src/rules/erb-comment-syntax.js.map +1 -1
- 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 +69 -11
- package/dist/src/rules/erb-require-whitespace-inside-tags.js.map +1 -1
- package/dist/src/rules/erb-right-trim.js +26 -9
- package/dist/src/rules/erb-right-trim.js.map +1 -1
- 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 +19 -3
- 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} +44 -16
- 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 +12 -5
- 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 +12 -5
- 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 +19 -3
- 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 +12 -5
- 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 +12 -5
- 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 +19 -3
- 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 +16 -2
- 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 +5 -10
- 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 +46 -37
- 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 +134 -51
- 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 +53 -10
- 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 +94 -16
- package/src/rules/erb-right-trim.ts +45 -22
- 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 +23 -3
- package/src/rules/parser-no-errors.ts +8 -1
- package/src/rules/rule-utils.ts +248 -42
- package/src/rules/svg-tag-name-capitalization.ts +39 -6
- package/src/{default-rules.ts → rules.ts} +51 -15
- 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
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ParserRule } from "../types.js"
|
|
2
|
+
import { HerbDisableCommentParsedVisitor } from "./herb-disable-comment-base.js"
|
|
3
|
+
|
|
4
|
+
import { didyoumean } from "@herb-tools/core"
|
|
5
|
+
|
|
6
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
7
|
+
import type { ERBContentNode, ParseResult } from "@herb-tools/core"
|
|
8
|
+
import type { HerbDisableComment } from "../herb-disable-comment-utils.js"
|
|
9
|
+
|
|
10
|
+
class HerbDisableCommentValidRuleNameVisitor extends HerbDisableCommentParsedVisitor {
|
|
11
|
+
private validRuleNames: Set<string> = new Set()
|
|
12
|
+
private validRuleNamesList: string[] = []
|
|
13
|
+
|
|
14
|
+
constructor(ruleName: string, validRuleNames: string[], context?: Partial<LintContext>) {
|
|
15
|
+
super(ruleName, context)
|
|
16
|
+
|
|
17
|
+
this.validRuleNames = new Set([...validRuleNames, "all"])
|
|
18
|
+
this.validRuleNamesList = Array.from(this.validRuleNames)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protected checkParsedHerbDisable(node: ERBContentNode, _content: string, herbDisable: HerbDisableComment): void {
|
|
22
|
+
herbDisable.ruleNameDetails.forEach(ruleDetail => {
|
|
23
|
+
if (this.validRuleNames.has(ruleDetail.name)) return
|
|
24
|
+
|
|
25
|
+
const suggestion = didyoumean(ruleDetail.name, this.validRuleNamesList)
|
|
26
|
+
const message = suggestion
|
|
27
|
+
? `Unknown rule \`${ruleDetail.name}\`. Did you mean \`${suggestion}\`?`
|
|
28
|
+
: `Unknown rule \`${ruleDetail.name}\`.`
|
|
29
|
+
|
|
30
|
+
const location = this.createRuleNameLocation(node, ruleDetail)
|
|
31
|
+
this.addOffenseWithFallback(message, location, node)
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class HerbDisableCommentValidRuleNameRule extends ParserRule {
|
|
37
|
+
name = "herb-disable-comment-valid-rule-name"
|
|
38
|
+
|
|
39
|
+
get defaultConfig(): FullRuleConfig {
|
|
40
|
+
return {
|
|
41
|
+
enabled: true,
|
|
42
|
+
severity: "warning"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
47
|
+
const validRuleNames = context?.validRuleNames
|
|
48
|
+
|
|
49
|
+
if (!validRuleNames) return []
|
|
50
|
+
if (validRuleNames.length === 0) return []
|
|
51
|
+
|
|
52
|
+
const visitor = new HerbDisableCommentValidRuleNameVisitor(
|
|
53
|
+
this.name,
|
|
54
|
+
validRuleNames,
|
|
55
|
+
context
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
visitor.visit(result.value)
|
|
59
|
+
|
|
60
|
+
return visitor.offenses
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -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 AnchorRechireHrefVisitor extends BaseRuleVisitor {
|
|
@@ -21,7 +21,6 @@ class AnchorRechireHrefVisitor extends BaseRuleVisitor {
|
|
|
21
21
|
this.addOffense(
|
|
22
22
|
"Add an `href` attribute to `<a>` to ensure it is focusable and accessible.",
|
|
23
23
|
node.tag_name!.location,
|
|
24
|
-
"error",
|
|
25
24
|
)
|
|
26
25
|
}
|
|
27
26
|
}
|
|
@@ -30,7 +29,14 @@ class AnchorRechireHrefVisitor extends BaseRuleVisitor {
|
|
|
30
29
|
export class HTMLAnchorRequireHrefRule extends ParserRule {
|
|
31
30
|
name = "html-anchor-require-href"
|
|
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 AnchorRechireHrefVisitor(this.name, context)
|
|
35
41
|
|
|
36
42
|
visitor.visit(result.value)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ParserRule } from "../types.js"
|
|
2
2
|
import { ARIA_ATTRIBUTES, AttributeVisitorMixin, StaticAttributeStaticValueParams, StaticAttributeDynamicValueParams } from "./rule-utils.js"
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
5
5
|
import type { ParseResult, HTMLAttributeNode } from "@herb-tools/core"
|
|
6
6
|
|
|
7
7
|
class AriaAttributeMustBeValid extends AttributeVisitorMixin {
|
|
@@ -20,7 +20,6 @@ class AriaAttributeMustBeValid extends AttributeVisitorMixin {
|
|
|
20
20
|
this.addOffense(
|
|
21
21
|
`The attribute \`${attributeName}\` is not a valid ARIA attribute. ARIA attributes must match the WAI-ARIA specification.`,
|
|
22
22
|
attributeNode.location,
|
|
23
|
-
"error"
|
|
24
23
|
)
|
|
25
24
|
}
|
|
26
25
|
}
|
|
@@ -28,7 +27,14 @@ class AriaAttributeMustBeValid extends AttributeVisitorMixin {
|
|
|
28
27
|
export class HTMLAriaAttributeMustBeValid extends ParserRule {
|
|
29
28
|
name = "html-aria-attribute-must-be-valid"
|
|
30
29
|
|
|
31
|
-
|
|
30
|
+
get defaultConfig(): FullRuleConfig {
|
|
31
|
+
return {
|
|
32
|
+
enabled: true,
|
|
33
|
+
severity: "error"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
32
38
|
const visitor = new AriaAttributeMustBeValid(this.name, context)
|
|
33
39
|
|
|
34
40
|
visitor.visit(result.value)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ParserRule } from "../types.js"
|
|
2
2
|
import { AttributeVisitorMixin, 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 AriaLabelIsWellFormattedVisitor extends AttributeVisitorMixin {
|
|
@@ -12,7 +12,6 @@ class AriaLabelIsWellFormattedVisitor extends AttributeVisitorMixin {
|
|
|
12
12
|
this.addOffense(
|
|
13
13
|
"The `aria-label` attribute value text should not contain line breaks. Use concise, single-line descriptions.",
|
|
14
14
|
attributeNode.location,
|
|
15
|
-
"error"
|
|
16
15
|
)
|
|
17
16
|
|
|
18
17
|
return
|
|
@@ -22,7 +21,6 @@ class AriaLabelIsWellFormattedVisitor extends AttributeVisitorMixin {
|
|
|
22
21
|
this.addOffense(
|
|
23
22
|
"The `aria-label` attribute value should not be formatted like an ID. Use natural, sentence-case text instead.",
|
|
24
23
|
attributeNode.location,
|
|
25
|
-
"error"
|
|
26
24
|
)
|
|
27
25
|
|
|
28
26
|
return
|
|
@@ -32,7 +30,6 @@ class AriaLabelIsWellFormattedVisitor extends AttributeVisitorMixin {
|
|
|
32
30
|
this.addOffense(
|
|
33
31
|
"The `aria-label` attribute value text should be formatted like visual text. Use sentence case (capitalize the first letter).",
|
|
34
32
|
attributeNode.location,
|
|
35
|
-
"error"
|
|
36
33
|
)
|
|
37
34
|
}
|
|
38
35
|
}
|
|
@@ -49,7 +46,14 @@ class AriaLabelIsWellFormattedVisitor extends AttributeVisitorMixin {
|
|
|
49
46
|
export class HTMLAriaLabelIsWellFormattedRule extends ParserRule {
|
|
50
47
|
name = "html-aria-label-is-well-formatted"
|
|
51
48
|
|
|
52
|
-
|
|
49
|
+
get defaultConfig(): FullRuleConfig {
|
|
50
|
+
return {
|
|
51
|
+
enabled: true,
|
|
52
|
+
severity: "error"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
53
57
|
const visitor = new AriaLabelIsWellFormattedVisitor(this.name, context)
|
|
54
58
|
|
|
55
59
|
visitor.visit(result.value)
|
|
@@ -2,7 +2,7 @@ import { ParserRule } from "../types.js"
|
|
|
2
2
|
import { AttributeVisitorMixin, StaticAttributeStaticValueParams, StaticAttributeDynamicValueParams } from "./rule-utils.js"
|
|
3
3
|
import { getValidatableStaticContent, hasERBOutput, filterLiteralNodes, filterERBContentNodes, isERBOutputNode } from "@herb-tools/core"
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
6
6
|
import type { ParseResult, HTMLAttributeNode } from "@herb-tools/core"
|
|
7
7
|
|
|
8
8
|
class HTMLAriaLevelMustBeValidVisitor extends AttributeVisitorMixin {
|
|
@@ -65,7 +65,14 @@ class HTMLAriaLevelMustBeValidVisitor extends AttributeVisitorMixin {
|
|
|
65
65
|
export class HTMLAriaLevelMustBeValidRule extends ParserRule {
|
|
66
66
|
name = "html-aria-level-must-be-valid"
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
get defaultConfig(): FullRuleConfig {
|
|
69
|
+
return {
|
|
70
|
+
enabled: true,
|
|
71
|
+
severity: "error"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
69
76
|
const visitor = new HTMLAriaLevelMustBeValidVisitor(this.name, context)
|
|
70
77
|
|
|
71
78
|
visitor.visit(result.value)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ParserRule } from "../types.js"
|
|
2
2
|
import { AttributeVisitorMixin, getAttributeName, getAttributes, 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 AriaRoleHeadingRequiresLevel extends AttributeVisitorMixin {
|
|
@@ -15,7 +15,6 @@ class AriaRoleHeadingRequiresLevel extends AttributeVisitorMixin {
|
|
|
15
15
|
this.addOffense(
|
|
16
16
|
`Element with \`role="heading"\` must have an \`aria-level\` attribute.`,
|
|
17
17
|
attributeNode.location,
|
|
18
|
-
"error"
|
|
19
18
|
)
|
|
20
19
|
}
|
|
21
20
|
}
|
|
@@ -23,7 +22,14 @@ class AriaRoleHeadingRequiresLevel extends AttributeVisitorMixin {
|
|
|
23
22
|
export class HTMLAriaRoleHeadingRequiresLevelRule extends ParserRule {
|
|
24
23
|
name = "html-aria-role-heading-requires-level"
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
get defaultConfig(): FullRuleConfig {
|
|
26
|
+
return {
|
|
27
|
+
enabled: true,
|
|
28
|
+
severity: "error"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
27
33
|
const visitor = new AriaRoleHeadingRequiresLevel(this.name, context)
|
|
28
34
|
|
|
29
35
|
visitor.visit(result.value)
|
|
@@ -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
|
+
}
|