@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
|
@@ -15,56 +15,34 @@ Headings relay the structure of a webpage and provide a meaningful, hierarchical
|
|
|
15
15
|
### ✅ Good
|
|
16
16
|
|
|
17
17
|
```erb
|
|
18
|
-
<
|
|
19
|
-
```
|
|
18
|
+
<h1>Heading Content</h1>
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
<h*><span>Text</span><h*>
|
|
23
|
-
```
|
|
20
|
+
<h1><span>Text</span></h1>
|
|
24
21
|
|
|
25
|
-
```erb
|
|
26
22
|
<div role="heading" aria-level="1">Heading Content</div>
|
|
27
|
-
```
|
|
28
23
|
|
|
29
|
-
|
|
30
|
-
<h* aria-hidden="true">Heading Content</h*>
|
|
31
|
-
```
|
|
24
|
+
<h1 aria-hidden="true">Heading Content</h1>
|
|
32
25
|
|
|
33
|
-
|
|
34
|
-
<h* hidden>Heading Content</h*>
|
|
26
|
+
<h1 hidden>Heading Content</h1>
|
|
35
27
|
```
|
|
36
28
|
|
|
37
29
|
### 🚫 Bad
|
|
38
30
|
|
|
39
31
|
```erb
|
|
40
32
|
<h1></h1>
|
|
41
|
-
```
|
|
42
33
|
|
|
43
|
-
```erb
|
|
44
34
|
<h2></h2>
|
|
45
|
-
```
|
|
46
35
|
|
|
47
|
-
```erb
|
|
48
36
|
<h3></h3>
|
|
49
|
-
```
|
|
50
37
|
|
|
51
|
-
```erb
|
|
52
38
|
<h4></h4>
|
|
53
|
-
```
|
|
54
39
|
|
|
55
|
-
```erb
|
|
56
40
|
<h5></h5>
|
|
57
|
-
```
|
|
58
41
|
|
|
59
|
-
```erb
|
|
60
42
|
<h6></h6>
|
|
61
|
-
```
|
|
62
43
|
|
|
63
|
-
```erb
|
|
64
44
|
<div role="heading" aria-level="1"></div>
|
|
65
|
-
```
|
|
66
45
|
|
|
67
|
-
```erb
|
|
68
46
|
<h1><span aria-hidden="true">Inaccessible text</span></h1>
|
|
69
47
|
```
|
|
70
48
|
|
|
@@ -41,8 +41,7 @@ The recommended approach is to structure your HTML in the correct tab order and
|
|
|
41
41
|
|
|
42
42
|
<button tabindex="2">Second in tab order</button>
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
<input tabindex="5" type="text">
|
|
44
|
+
<input tabindex="5" type="text" autocomplete="off">
|
|
46
45
|
|
|
47
46
|
<button tabindex="10">Submit</button>
|
|
48
47
|
```
|
|
@@ -22,6 +22,21 @@ Self-closing syntax is an XHTML artifact. In HTML:
|
|
|
22
22
|
Removing the slash ensures HTML5-compliant, cleaner markup and avoids mixing
|
|
23
23
|
XHTML and HTML styles.
|
|
24
24
|
|
|
25
|
+
## Exception for Email Templates
|
|
26
|
+
|
|
27
|
+
::: info
|
|
28
|
+
This rule is automatically disabled for ActionMailer view files (paths matching `**/views/**/*_mailer/**/*`) because older email clients, particularly legacy versions of Microsoft Outlook, require self-closing syntax for proper rendering of void elements like `<br />`.
|
|
29
|
+
:::
|
|
30
|
+
|
|
31
|
+
If you want to enable this rule for email templates as well, you can override the default exclusion in your `.herb.yml` configuration:
|
|
32
|
+
|
|
33
|
+
```yaml [.herb.yml]
|
|
34
|
+
linter:
|
|
35
|
+
rules:
|
|
36
|
+
html-no-self-closing:
|
|
37
|
+
exclude: [] # Override to enable for all files including ActionMailer views
|
|
38
|
+
```
|
|
39
|
+
|
|
25
40
|
## Examples
|
|
26
41
|
|
|
27
42
|
### ✅ Good
|
|
@@ -33,7 +48,7 @@ XHTML and HTML styles.
|
|
|
33
48
|
<custom-element></custom-element>
|
|
34
49
|
|
|
35
50
|
<img src="/logo.png" alt="Logo">
|
|
36
|
-
<input type="text">
|
|
51
|
+
<input type="text" autocomplete="off">
|
|
37
52
|
<br>
|
|
38
53
|
<hr>
|
|
39
54
|
```
|
|
@@ -51,7 +66,7 @@ XHTML and HTML styles.
|
|
|
51
66
|
|
|
52
67
|
<img src="/logo.png" alt="Logo" />
|
|
53
68
|
|
|
54
|
-
<input type="text" />
|
|
69
|
+
<input type="text" autocomplete="off" />
|
|
55
70
|
|
|
56
71
|
<br />
|
|
57
72
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Linter Rule: Enforce consistent spacing within HTML tags
|
|
2
|
+
|
|
3
|
+
**Rule:** `html-no-space-in-tag`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Enforce consistent spacing within HTML opening and closing tags. This rule ensures:
|
|
8
|
+
- Exactly one space between tag name and first attribute
|
|
9
|
+
- Exactly one space between attributes
|
|
10
|
+
- No extra spaces before the closing `>` in non-self-closing tags
|
|
11
|
+
- Exactly one space before `/>` in self-closing tags
|
|
12
|
+
- No whitespace in closing tags (e.g., `</div>`)
|
|
13
|
+
- Consistent indentation in multiline tags
|
|
14
|
+
|
|
15
|
+
## Rationale
|
|
16
|
+
|
|
17
|
+
Consistent spacing within HTML tags improves code readability and maintainability. Extra or missing spaces can make templates harder to scan and can indicate formatting inconsistencies across a codebase. This rule enforces a canonical style that is both readable and machine-parseable.
|
|
18
|
+
|
|
19
|
+
Self-closing tags (`<img />`, `<br />`) should have exactly one space before the `/>` to maintain visual consistency with HTML5 and JSX conventions.
|
|
20
|
+
|
|
21
|
+
## Examples
|
|
22
|
+
|
|
23
|
+
### ✅ Good
|
|
24
|
+
|
|
25
|
+
```erb
|
|
26
|
+
<div class="foo"></div>
|
|
27
|
+
|
|
28
|
+
<img src="/logo.png" alt="Logo">
|
|
29
|
+
|
|
30
|
+
<input class="foo" name="bar">
|
|
31
|
+
|
|
32
|
+
<div class="foo" data-x="bar"></div>
|
|
33
|
+
|
|
34
|
+
<div
|
|
35
|
+
class="foo"
|
|
36
|
+
data-x="bar"
|
|
37
|
+
>
|
|
38
|
+
foo
|
|
39
|
+
</div>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 🚫 Bad
|
|
43
|
+
|
|
44
|
+
```erb
|
|
45
|
+
<div class="foo"></div>
|
|
46
|
+
|
|
47
|
+
<div class="foo" ></div>
|
|
48
|
+
|
|
49
|
+
<img alt="Logo" src="/logo.png">
|
|
50
|
+
|
|
51
|
+
<div class="foo" data-x="bar"></div>
|
|
52
|
+
|
|
53
|
+
<div
|
|
54
|
+
class="foo"
|
|
55
|
+
data-x="bar"
|
|
56
|
+
>
|
|
57
|
+
foo
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div >
|
|
61
|
+
</ div>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## References
|
|
65
|
+
|
|
66
|
+
- [Inspiration: ERB Lint `SpaceInHtmlTag` rule](https://github.com/shopify/erb_lint)
|
|
@@ -33,7 +33,7 @@ This rule allows `title` on `<iframe>` and `<link>` elements where it serves spe
|
|
|
33
33
|
<button aria-label="Close dialog">×</button>
|
|
34
34
|
|
|
35
35
|
<!-- Use aria-describedby for additional context -->
|
|
36
|
-
<input type="password" aria-describedby="pwd-help">
|
|
36
|
+
<input type="password" aria-describedby="pwd-help" autocomplete="off">
|
|
37
37
|
<div id="pwd-help">Password must be at least 8 characters</div>
|
|
38
38
|
|
|
39
39
|
<!-- Exceptions: title allowed on iframe and links -->
|
|
@@ -52,7 +52,7 @@ This rule allows `title` on `<iframe>` and `<link>` elements where it serves spe
|
|
|
52
52
|
<span title="Required field">*</span>
|
|
53
53
|
|
|
54
54
|
<!-- Don't use title on form elements -->
|
|
55
|
-
<input type="text" title="Enter your name">
|
|
55
|
+
<input type="text" title="Enter your name" autocomplete="off">
|
|
56
56
|
|
|
57
57
|
<select title="Choose your country">
|
|
58
58
|
<option>US</option>
|
|
@@ -23,7 +23,7 @@ Underscores in attribute names violate the HTML specification and are not suppor
|
|
|
23
23
|
```html
|
|
24
24
|
<div data-user-id="123"></div>
|
|
25
25
|
|
|
26
|
-
<img aria-label="Close">
|
|
26
|
+
<img aria-label="Close" alt="Close">
|
|
27
27
|
|
|
28
28
|
<div data-<%= key %>-attribute="value"></div>
|
|
29
29
|
```
|
|
@@ -33,7 +33,7 @@ Underscores in attribute names violate the HTML specification and are not suppor
|
|
|
33
33
|
```html
|
|
34
34
|
<div data_user_id="123"></div>
|
|
35
35
|
|
|
36
|
-
<img aria_label="Close">
|
|
36
|
+
<img aria_label="Close" alt="Close">
|
|
37
37
|
|
|
38
38
|
<div data-<%= key %>_attribute="value"></div>
|
|
39
39
|
```
|
|
@@ -33,7 +33,7 @@ This rule does not apply to child elements within `<svg>` tags, as SVG element n
|
|
|
33
33
|
```erb
|
|
34
34
|
<div class="container"></div>
|
|
35
35
|
|
|
36
|
-
<input type="text" name="username">
|
|
36
|
+
<input type="text" name="username" autocomplete="off">
|
|
37
37
|
|
|
38
38
|
<span>Label</span>
|
|
39
39
|
|
|
@@ -45,7 +45,7 @@ This rule does not apply to child elements within `<svg>` tags, as SVG element n
|
|
|
45
45
|
```erb
|
|
46
46
|
<DIV class="container"></DIV>
|
|
47
47
|
|
|
48
|
-
<Input type="text" name="username">
|
|
48
|
+
<Input type="text" name="username" autocomplete="off">
|
|
49
49
|
|
|
50
50
|
<Span>Label</Span>
|
|
51
51
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herb-tools/linter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "HTML+ERB linter for validating HTML structure and enforcing best practices",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://herb-tools.dev",
|
|
@@ -36,13 +36,21 @@
|
|
|
36
36
|
"types": "./dist/types/src/cli.d.ts",
|
|
37
37
|
"require": "./dist/src/cli.js",
|
|
38
38
|
"default": "./dist/src/cli.js"
|
|
39
|
+
},
|
|
40
|
+
"./loader": {
|
|
41
|
+
"types": "./dist/types/loader.d.ts",
|
|
42
|
+
"import": "./dist/loader.js",
|
|
43
|
+
"require": "./dist/loader.cjs",
|
|
44
|
+
"default": "./dist/loader.js"
|
|
39
45
|
}
|
|
40
46
|
},
|
|
41
47
|
"dependencies": {
|
|
42
|
-
"@herb-tools/
|
|
43
|
-
"@herb-tools/
|
|
44
|
-
"@herb-tools/
|
|
45
|
-
"@herb-tools/
|
|
48
|
+
"@herb-tools/config": "0.8.0",
|
|
49
|
+
"@herb-tools/core": "0.8.0",
|
|
50
|
+
"@herb-tools/highlighter": "0.8.0",
|
|
51
|
+
"@herb-tools/node-wasm": "0.8.0",
|
|
52
|
+
"@herb-tools/printer": "0.8.0",
|
|
53
|
+
"@herb-tools/rewriter": "0.8.0",
|
|
46
54
|
"glob": "^11.0.3"
|
|
47
55
|
},
|
|
48
56
|
"files": [
|
|
@@ -1,50 +1,58 @@
|
|
|
1
1
|
import dedent from "dedent"
|
|
2
2
|
|
|
3
3
|
import { parseArgs } from "util"
|
|
4
|
-
import { statSync } from "fs"
|
|
5
|
-
import { join } from "path"
|
|
6
|
-
|
|
7
4
|
import { Herb } from "@herb-tools/node-wasm"
|
|
8
5
|
|
|
9
6
|
import { THEME_NAMES, DEFAULT_THEME } from "@herb-tools/highlighter"
|
|
10
7
|
import type { ThemeInput } from "@herb-tools/highlighter"
|
|
11
8
|
|
|
12
|
-
import { name, version } from "../../package.json"
|
|
9
|
+
import { name, version, dependencies } from "../../package.json"
|
|
13
10
|
|
|
14
11
|
export type FormatOption = "simple" | "detailed" | "json"
|
|
15
12
|
|
|
16
13
|
export interface ParsedArguments {
|
|
17
|
-
|
|
14
|
+
patterns: string[]
|
|
15
|
+
configFile?: string
|
|
18
16
|
formatOption: FormatOption
|
|
19
17
|
showTiming: boolean
|
|
20
18
|
theme: ThemeInput
|
|
21
19
|
wrapLines: boolean
|
|
22
20
|
truncateLines: boolean
|
|
23
21
|
useGitHubActions: boolean
|
|
22
|
+
fix: boolean
|
|
23
|
+
ignoreDisableComments: boolean
|
|
24
|
+
force: boolean
|
|
25
|
+
init: boolean
|
|
26
|
+
loadCustomRules: boolean
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
export class ArgumentParser {
|
|
27
30
|
private readonly usage = dedent`
|
|
28
|
-
Usage: herb-lint [
|
|
31
|
+
Usage: herb-lint [files|directories|glob-patterns...] [options]
|
|
29
32
|
|
|
30
33
|
Arguments:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
directory Directory to lint (automatically appends **/*.html.erb)
|
|
34
|
+
files Files, directories, or glob patterns to lint (defaults to configured extensions in .herb.yml)
|
|
35
|
+
Multiple arguments are supported (e.g., herb-lint file1.erb file2.erb dir/ "**/*.erb")
|
|
34
36
|
|
|
35
37
|
Options:
|
|
36
|
-
-h, --help
|
|
37
|
-
-v, --version
|
|
38
|
-
--
|
|
39
|
-
--
|
|
40
|
-
--
|
|
41
|
-
--
|
|
42
|
-
--
|
|
43
|
-
--
|
|
44
|
-
--
|
|
45
|
-
--
|
|
46
|
-
--
|
|
47
|
-
--
|
|
38
|
+
-h, --help show help
|
|
39
|
+
-v, --version show version
|
|
40
|
+
--init create a .herb.yml configuration file in the current directory
|
|
41
|
+
-c, --config-file <path> explicitly specify path to .herb.yml config file
|
|
42
|
+
--force force linting even if disabled in .herb.yml
|
|
43
|
+
--fix automatically fix auto-correctable offenses
|
|
44
|
+
--ignore-disable-comments report offenses even when suppressed with <%# herb:disable %> comments
|
|
45
|
+
--format output format (simple|detailed|json) [default: detailed]
|
|
46
|
+
--simple use simple output format (shortcut for --format simple)
|
|
47
|
+
--json use JSON output format (shortcut for --format json)
|
|
48
|
+
--github enable GitHub Actions annotations (combines with --format)
|
|
49
|
+
--no-github disable GitHub Actions annotations (even in GitHub Actions environment)
|
|
50
|
+
--no-custom-rules disable loading custom rules from project (custom rules are loaded by default from .herb/rules/**/*.{mjs,js})
|
|
51
|
+
--theme syntax highlighting theme (${THEME_NAMES.join("|")}) or path to custom theme file [default: ${DEFAULT_THEME}]
|
|
52
|
+
--no-color disable colored output
|
|
53
|
+
--no-timing hide timing information
|
|
54
|
+
--no-wrap-lines disable line wrapping
|
|
55
|
+
--truncate-lines enable line truncation (mutually exclusive with line wrapping)
|
|
48
56
|
`
|
|
49
57
|
|
|
50
58
|
parse(argv: string[]): ParsedArguments {
|
|
@@ -53,6 +61,11 @@ export class ArgumentParser {
|
|
|
53
61
|
options: {
|
|
54
62
|
help: { type: "boolean", short: "h" },
|
|
55
63
|
version: { type: "boolean", short: "v" },
|
|
64
|
+
init: { type: "boolean" },
|
|
65
|
+
"config-file": { type: "string", short: "c" },
|
|
66
|
+
force: { type: "boolean" },
|
|
67
|
+
fix: { type: "boolean" },
|
|
68
|
+
"ignore-disable-comments": { type: "boolean" },
|
|
56
69
|
format: { type: "string" },
|
|
57
70
|
simple: { type: "boolean" },
|
|
58
71
|
json: { type: "boolean" },
|
|
@@ -62,7 +75,8 @@ export class ArgumentParser {
|
|
|
62
75
|
"no-color": { type: "boolean" },
|
|
63
76
|
"no-timing": { type: "boolean" },
|
|
64
77
|
"no-wrap-lines": { type: "boolean" },
|
|
65
|
-
"truncate-lines": { type: "boolean" }
|
|
78
|
+
"truncate-lines": { type: "boolean" },
|
|
79
|
+
"no-custom-rules": { type: "boolean" }
|
|
66
80
|
},
|
|
67
81
|
allowPositionals: true
|
|
68
82
|
})
|
|
@@ -74,7 +88,9 @@ export class ArgumentParser {
|
|
|
74
88
|
|
|
75
89
|
if (values.version) {
|
|
76
90
|
console.log("Versions:")
|
|
77
|
-
console.log(` ${name}@${version}
|
|
91
|
+
console.log(` ${name}@${version}`)
|
|
92
|
+
console.log(` @herb-tools/printer@${dependencies["@herb-tools/printer"]}`)
|
|
93
|
+
console.log(` ${Herb.version}`.split(", ").join("\n "))
|
|
78
94
|
process.exit(0)
|
|
79
95
|
}
|
|
80
96
|
|
|
@@ -120,23 +136,18 @@ export class ArgumentParser {
|
|
|
120
136
|
}
|
|
121
137
|
|
|
122
138
|
const theme = values.theme || DEFAULT_THEME
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
139
|
+
const patterns = this.getFilePatterns(positionals)
|
|
140
|
+
const fix = values.fix || false
|
|
141
|
+
const force = !!values.force
|
|
142
|
+
const ignoreDisableComments = values["ignore-disable-comments"] || false
|
|
143
|
+
const configFile = values["config-file"]
|
|
144
|
+
const init = values.init || false
|
|
145
|
+
const loadCustomRules = !values["no-custom-rules"]
|
|
146
|
+
|
|
147
|
+
return { patterns, configFile, formatOption, showTiming, theme, wrapLines, truncateLines, useGitHubActions, fix, ignoreDisableComments, force, init, loadCustomRules }
|
|
126
148
|
}
|
|
127
149
|
|
|
128
|
-
private
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
try {
|
|
132
|
-
const stat = statSync(pattern)
|
|
133
|
-
if (stat.isDirectory()) {
|
|
134
|
-
pattern = join(pattern, "**/*.html.erb")
|
|
135
|
-
}
|
|
136
|
-
} catch {
|
|
137
|
-
// Not a file/directory, treat as glob pattern
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return pattern
|
|
150
|
+
private getFilePatterns(positionals: string[]): string[] {
|
|
151
|
+
return positionals
|
|
141
152
|
}
|
|
142
153
|
}
|
|
@@ -1,26 +1,42 @@
|
|
|
1
|
-
import { readFileSync } from "fs"
|
|
2
|
-
import { resolve } from "path"
|
|
3
1
|
import { Herb } from "@herb-tools/node-wasm"
|
|
4
2
|
import { Linter } from "../linter.js"
|
|
3
|
+
import { loadCustomRules } from "../loader.js"
|
|
4
|
+
import { Config } from "@herb-tools/config"
|
|
5
|
+
|
|
6
|
+
import { readFileSync, writeFileSync } from "fs"
|
|
7
|
+
import { resolve } from "path"
|
|
5
8
|
import { colorize } from "@herb-tools/highlighter"
|
|
9
|
+
|
|
6
10
|
import type { Diagnostic } from "@herb-tools/core"
|
|
7
11
|
import type { FormatOption } from "./argument-parser.js"
|
|
12
|
+
import type { HerbConfigOptions } from "@herb-tools/config"
|
|
8
13
|
|
|
9
14
|
export interface ProcessedFile {
|
|
10
15
|
filename: string
|
|
11
16
|
offense: Diagnostic
|
|
12
17
|
content: string
|
|
18
|
+
autocorrectable?: boolean
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
export interface ProcessingContext {
|
|
16
22
|
projectPath?: string
|
|
17
23
|
pattern?: string
|
|
24
|
+
fix?: boolean
|
|
25
|
+
ignoreDisableComments?: boolean
|
|
26
|
+
linterConfig?: HerbConfigOptions['linter']
|
|
27
|
+
config?: Config
|
|
28
|
+
loadCustomRules?: boolean
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
export interface ProcessingResult {
|
|
21
32
|
totalErrors: number
|
|
22
33
|
totalWarnings: number
|
|
34
|
+
totalInfo: number
|
|
35
|
+
totalHints: number
|
|
36
|
+
totalIgnored: number
|
|
37
|
+
totalWouldBeIgnored?: number
|
|
23
38
|
filesWithOffenses: number
|
|
39
|
+
filesFixed: number
|
|
24
40
|
ruleCount: number
|
|
25
41
|
allOffenses: ProcessedFile[]
|
|
26
42
|
ruleOffenses: Map<string, { count: number, files: Set<string> }>
|
|
@@ -29,55 +45,146 @@ export interface ProcessingResult {
|
|
|
29
45
|
|
|
30
46
|
export class FileProcessor {
|
|
31
47
|
private linter: Linter | null = null
|
|
48
|
+
private customRulesLoaded: boolean = false
|
|
49
|
+
|
|
50
|
+
private isRuleAutocorrectable(ruleName: string): boolean {
|
|
51
|
+
if (!this.linter) return false
|
|
52
|
+
|
|
53
|
+
const RuleClass = (this.linter as any).rules.find((rule: any) => {
|
|
54
|
+
const instance = new rule()
|
|
55
|
+
|
|
56
|
+
return instance.name === ruleName
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
if (!RuleClass) return false
|
|
60
|
+
|
|
61
|
+
return RuleClass.autocorrectable === true
|
|
62
|
+
}
|
|
32
63
|
|
|
33
64
|
async processFiles(files: string[], formatOption: FormatOption = 'detailed', context?: ProcessingContext): Promise<ProcessingResult> {
|
|
34
65
|
let totalErrors = 0
|
|
35
66
|
let totalWarnings = 0
|
|
67
|
+
let totalInfo = 0
|
|
68
|
+
let totalHints = 0
|
|
69
|
+
let totalIgnored = 0
|
|
70
|
+
let totalWouldBeIgnored = 0
|
|
36
71
|
let filesWithOffenses = 0
|
|
72
|
+
let filesFixed = 0
|
|
37
73
|
let ruleCount = 0
|
|
74
|
+
|
|
38
75
|
const allOffenses: ProcessedFile[] = []
|
|
39
76
|
const ruleOffenses = new Map<string, { count: number, files: Set<string> }>()
|
|
40
77
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
78
|
+
if (!this.linter) {
|
|
79
|
+
let customRules = undefined
|
|
80
|
+
let customRuleInfo: Array<{ name: string, path: string }> = []
|
|
81
|
+
let customRuleWarnings: string[] = []
|
|
82
|
+
|
|
83
|
+
if (context?.loadCustomRules && !this.customRulesLoaded) {
|
|
84
|
+
try {
|
|
85
|
+
const result = await loadCustomRules({
|
|
86
|
+
baseDir: context.projectPath,
|
|
87
|
+
silent: formatOption === 'json'
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
customRules = result.rules
|
|
91
|
+
customRuleInfo = result.ruleInfo
|
|
92
|
+
customRuleWarnings = result.warnings
|
|
93
|
+
|
|
94
|
+
this.customRulesLoaded = true
|
|
95
|
+
|
|
96
|
+
if (customRules.length > 0 && formatOption !== 'json') {
|
|
97
|
+
const ruleText = customRules.length === 1 ? 'rule' : 'rules'
|
|
98
|
+
console.log(colorize(`\nLoaded ${customRules.length} custom ${ruleText}:`, "green"))
|
|
99
|
+
|
|
100
|
+
for (const { name, path } of customRuleInfo) {
|
|
101
|
+
const relativePath = context.projectPath ? path.replace(context.projectPath + '/', '') : path
|
|
102
|
+
console.log(colorize(` • ${name}`, "cyan") + colorize(` (${relativePath})`, "dim"))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (customRuleWarnings.length > 0) {
|
|
106
|
+
console.log()
|
|
107
|
+
for (const warning of customRuleWarnings) {
|
|
108
|
+
console.warn(colorize(` ⚠ ${warning}`, "yellow"))
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log()
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (formatOption !== 'json') {
|
|
116
|
+
console.warn(colorize(`Warning: Failed to load custom rules: ${error}`, "yellow"))
|
|
52
117
|
}
|
|
53
118
|
}
|
|
54
|
-
|
|
55
|
-
for (const error of parseResult.errors) {
|
|
56
|
-
allOffenses.push({ filename, offense: error, content })
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
totalErrors++
|
|
60
|
-
filesWithOffenses++
|
|
61
|
-
continue
|
|
62
119
|
}
|
|
63
120
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
121
|
+
this.linter = Linter.from(Herb, context?.config, customRules)
|
|
122
|
+
}
|
|
67
123
|
|
|
68
|
-
|
|
124
|
+
for (const filename of files) {
|
|
125
|
+
const filePath = context?.projectPath ? resolve(context.projectPath, filename) : resolve(filename)
|
|
126
|
+
let content = readFileSync(filePath, "utf-8")
|
|
127
|
+
|
|
128
|
+
const lintResult = this.linter.lint(content, {
|
|
129
|
+
fileName: filename,
|
|
130
|
+
ignoreDisableComments: context?.ignoreDisableComments
|
|
131
|
+
})
|
|
69
132
|
|
|
70
133
|
if (ruleCount === 0) {
|
|
71
134
|
ruleCount = this.linter.getRuleCount()
|
|
72
135
|
}
|
|
73
136
|
|
|
74
|
-
if (lintResult.offenses.length
|
|
137
|
+
if (context?.fix && lintResult.offenses.length > 0) {
|
|
138
|
+
const autofixResult = this.linter.autofix(content, {
|
|
139
|
+
fileName: filename,
|
|
140
|
+
ignoreDisableComments: context?.ignoreDisableComments
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
if (autofixResult.fixed.length > 0) {
|
|
144
|
+
writeFileSync(filePath, autofixResult.source, "utf-8")
|
|
145
|
+
|
|
146
|
+
filesFixed++
|
|
147
|
+
|
|
148
|
+
if (formatOption !== 'json') {
|
|
149
|
+
console.log(`${colorize("✓", "brightGreen")} ${colorize(filename, "cyan")} - ${colorize(`Fixed ${autofixResult.fixed.length} offense(s)`, "green")}`)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
content = autofixResult.source
|
|
154
|
+
|
|
155
|
+
for (const offense of autofixResult.unfixed) {
|
|
156
|
+
allOffenses.push({
|
|
157
|
+
filename,
|
|
158
|
+
offense: offense,
|
|
159
|
+
content,
|
|
160
|
+
autocorrectable: this.isRuleAutocorrectable(offense.rule)
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
const ruleData = ruleOffenses.get(offense.rule) || { count: 0, files: new Set() }
|
|
164
|
+
ruleData.count++
|
|
165
|
+
ruleData.files.add(filename)
|
|
166
|
+
ruleOffenses.set(offense.rule, ruleData)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (autofixResult.unfixed.length > 0) {
|
|
170
|
+
totalErrors += autofixResult.unfixed.filter(offense => offense.severity === "error").length
|
|
171
|
+
totalWarnings += autofixResult.unfixed.filter(offense => offense.severity === "warning").length
|
|
172
|
+
totalInfo += autofixResult.unfixed.filter(offense => offense.severity === "info").length
|
|
173
|
+
totalHints += autofixResult.unfixed.filter(offense => offense.severity === "hint").length
|
|
174
|
+
filesWithOffenses++
|
|
175
|
+
}
|
|
176
|
+
} else if (lintResult.offenses.length === 0) {
|
|
75
177
|
if (files.length === 1 && formatOption !== 'json') {
|
|
76
178
|
console.log(`${colorize("✓", "brightGreen")} ${colorize(filename, "cyan")} - ${colorize("No issues found", "green")}`)
|
|
77
179
|
}
|
|
78
180
|
} else {
|
|
79
181
|
for (const offense of lintResult.offenses) {
|
|
80
|
-
allOffenses.push({
|
|
182
|
+
allOffenses.push({
|
|
183
|
+
filename,
|
|
184
|
+
offense: offense,
|
|
185
|
+
content,
|
|
186
|
+
autocorrectable: this.isRuleAutocorrectable(offense.rule)
|
|
187
|
+
})
|
|
81
188
|
|
|
82
189
|
const ruleData = ruleOffenses.get(offense.rule) || { count: 0, files: new Set() }
|
|
83
190
|
ruleData.count++
|
|
@@ -87,10 +194,34 @@ export class FileProcessor {
|
|
|
87
194
|
|
|
88
195
|
totalErrors += lintResult.errors
|
|
89
196
|
totalWarnings += lintResult.warnings
|
|
197
|
+
totalInfo += lintResult.offenses.filter(o => o.severity === "info").length
|
|
198
|
+
totalHints += lintResult.offenses.filter(o => o.severity === "hint").length
|
|
90
199
|
filesWithOffenses++
|
|
91
200
|
}
|
|
201
|
+
totalIgnored += lintResult.ignored
|
|
202
|
+
if (lintResult.wouldBeIgnored) {
|
|
203
|
+
totalWouldBeIgnored += lintResult.wouldBeIgnored
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const result: ProcessingResult = {
|
|
208
|
+
totalErrors,
|
|
209
|
+
totalWarnings,
|
|
210
|
+
totalInfo,
|
|
211
|
+
totalHints,
|
|
212
|
+
totalIgnored,
|
|
213
|
+
filesWithOffenses,
|
|
214
|
+
filesFixed,
|
|
215
|
+
ruleCount,
|
|
216
|
+
allOffenses,
|
|
217
|
+
ruleOffenses,
|
|
218
|
+
context
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (totalWouldBeIgnored > 0) {
|
|
222
|
+
result.totalWouldBeIgnored = totalWouldBeIgnored
|
|
92
223
|
}
|
|
93
224
|
|
|
94
|
-
return
|
|
225
|
+
return result
|
|
95
226
|
}
|
|
96
227
|
}
|