@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
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { BaseRuleVisitor } from "./rule-utils.js"
|
|
2
|
+
import { ParserRule } from "../types.js"
|
|
3
|
+
import { isWhitespaceNode, isLiteralNode, isHTMLTextNode, isCommentNode, isERBNode } from "@herb-tools/core"
|
|
4
|
+
import { IdentityPrinter } from "@herb-tools/printer"
|
|
5
|
+
|
|
6
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
7
|
+
import type { ParseResult, ERBCaseNode, ERBCaseMatchNode, Node } from "@herb-tools/core"
|
|
8
|
+
|
|
9
|
+
class ERBNoCaseNodeChildrenVisitor extends BaseRuleVisitor {
|
|
10
|
+
visitERBCaseNode(node: ERBCaseNode): void {
|
|
11
|
+
this.checkCaseNodeChildren(node, "when")
|
|
12
|
+
this.visitChildNodes(node)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
visitERBCaseMatchNode(node: ERBCaseMatchNode): void {
|
|
16
|
+
this.checkCaseNodeChildren(node, "in")
|
|
17
|
+
this.visitChildNodes(node)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private checkCaseNodeChildren(node: ERBCaseNode | ERBCaseMatchNode, type: string): void {
|
|
21
|
+
if (!node.children || node.children.length === 0) return
|
|
22
|
+
|
|
23
|
+
const caseCode = IdentityPrinter.printERBNode(node)
|
|
24
|
+
const firstCondition = node.conditions?.[0]
|
|
25
|
+
const conditionCode = firstCondition && isERBNode(firstCondition) ? IdentityPrinter.printERBNode(firstCondition) : `<% ${type} ... %>`
|
|
26
|
+
|
|
27
|
+
for (const child of node.children) {
|
|
28
|
+
if (!this.isAllowedContent(child)) {
|
|
29
|
+
const childCode = IdentityPrinter.print(child).trim()
|
|
30
|
+
|
|
31
|
+
this.addOffense(
|
|
32
|
+
`Do not place \`${childCode}\` between \`${caseCode}\` and \`${conditionCode}\`. Content here is not part of any branch and will not be rendered.`,
|
|
33
|
+
child.location,
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private isAllowedContent(node: Node): boolean {
|
|
40
|
+
if (isWhitespaceNode(node)) return true
|
|
41
|
+
if (isCommentNode(node)) return true
|
|
42
|
+
|
|
43
|
+
if (isLiteralNode(node) || isHTMLTextNode(node)) {
|
|
44
|
+
return /^\s*$/.test(node.content)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class ERBNoCaseNodeChildrenRule extends ParserRule {
|
|
52
|
+
name = "erb-no-case-node-children"
|
|
53
|
+
|
|
54
|
+
get defaultConfig(): FullRuleConfig {
|
|
55
|
+
return {
|
|
56
|
+
enabled: true,
|
|
57
|
+
severity: "error"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
62
|
+
const visitor = new ERBNoCaseNodeChildrenVisitor(this.name, context)
|
|
63
|
+
|
|
64
|
+
visitor.visit(result.value)
|
|
65
|
+
|
|
66
|
+
return visitor.offenses
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BaseRuleVisitor } 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 { ParseResult, ERBContentNode } from "@herb-tools/core"
|
|
6
6
|
|
|
7
7
|
class ERBNoEmptyTagsVisitor extends BaseRuleVisitor {
|
|
@@ -17,7 +17,6 @@ class ERBNoEmptyTagsVisitor extends BaseRuleVisitor {
|
|
|
17
17
|
this.addOffense(
|
|
18
18
|
"ERB tag should not be empty. Remove empty ERB tags or add content.",
|
|
19
19
|
node.location,
|
|
20
|
-
"error"
|
|
21
20
|
)
|
|
22
21
|
}
|
|
23
22
|
}
|
|
@@ -25,7 +24,14 @@ class ERBNoEmptyTagsVisitor extends BaseRuleVisitor {
|
|
|
25
24
|
export class ERBNoEmptyTagsRule extends ParserRule {
|
|
26
25
|
name = "erb-no-empty-tags"
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
get defaultConfig(): FullRuleConfig {
|
|
28
|
+
return {
|
|
29
|
+
enabled: true,
|
|
30
|
+
severity: "error"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
29
35
|
const visitor = new ERBNoEmptyTagsVisitor(this.name, context)
|
|
30
36
|
|
|
31
37
|
visitor.visit(result.value)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { BaseSourceRuleVisitor } from "./rule-utils.js"
|
|
2
|
+
import { SourceRule } from "../types.js"
|
|
3
|
+
import { Location, Position } from "@herb-tools/core"
|
|
4
|
+
|
|
5
|
+
import type { Node } from "@herb-tools/core"
|
|
6
|
+
import type { UnboundLintOffense, LintOffense, LintContext, BaseAutofixContext, FullRuleConfig } from "../types.js"
|
|
7
|
+
|
|
8
|
+
interface ERBNoExtraNewLineAutofixContext extends BaseAutofixContext {
|
|
9
|
+
startOffset: number
|
|
10
|
+
endOffset: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function positionFromOffset(source: string, offset: number): Position {
|
|
14
|
+
let line = 1
|
|
15
|
+
let column = 0
|
|
16
|
+
let currentOffset = 0
|
|
17
|
+
|
|
18
|
+
for (let i = 0; i < source.length && currentOffset < offset; i++) {
|
|
19
|
+
const char = source[i]
|
|
20
|
+
currentOffset++
|
|
21
|
+
if (char === "\n") {
|
|
22
|
+
line++
|
|
23
|
+
column = 0
|
|
24
|
+
} else {
|
|
25
|
+
column++
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return new Position(line, column)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class ERBNoExtraNewLineVisitor extends BaseSourceRuleVisitor<ERBNoExtraNewLineAutofixContext> {
|
|
33
|
+
protected visitSource(source: string): void {
|
|
34
|
+
if (source.length === 0) return
|
|
35
|
+
|
|
36
|
+
const regex = /\n{4,}/g
|
|
37
|
+
|
|
38
|
+
let match: RegExpExecArray | null
|
|
39
|
+
|
|
40
|
+
while ((match = regex.exec(source)) !== null) {
|
|
41
|
+
const startOffset = match.index + 3
|
|
42
|
+
const endOffset = match.index + match[0].length
|
|
43
|
+
const start = positionFromOffset(source, startOffset)
|
|
44
|
+
const end = positionFromOffset(source, endOffset)
|
|
45
|
+
const location = new Location(start, end)
|
|
46
|
+
|
|
47
|
+
const extraLines = match[0].length - 3
|
|
48
|
+
|
|
49
|
+
this.addOffense(
|
|
50
|
+
`Extra blank line detected. Remove ${extraLines} blank ${extraLines === 1 ? "line" : "lines"} to maintain consistent spacing (max 2 allowed).`,
|
|
51
|
+
location,
|
|
52
|
+
{
|
|
53
|
+
node: null as any as Node,
|
|
54
|
+
startOffset,
|
|
55
|
+
endOffset
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class ERBNoExtraNewLineRule extends SourceRule {
|
|
63
|
+
static autocorrectable = true
|
|
64
|
+
name = "erb-no-extra-newline"
|
|
65
|
+
|
|
66
|
+
get defaultConfig(): FullRuleConfig {
|
|
67
|
+
return {
|
|
68
|
+
enabled: true,
|
|
69
|
+
severity: "error"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
check(source: string, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
74
|
+
const visitor = new ERBNoExtraNewLineVisitor(this.name, context)
|
|
75
|
+
|
|
76
|
+
visitor.visit(source)
|
|
77
|
+
|
|
78
|
+
return visitor.offenses
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
autofix(offense: LintOffense<ERBNoExtraNewLineAutofixContext>, source: string, _context?: Partial<LintContext>): string | null {
|
|
82
|
+
if (!offense.autofixContext) return null
|
|
83
|
+
|
|
84
|
+
const { startOffset, endOffset } = offense.autofixContext
|
|
85
|
+
|
|
86
|
+
const before = source.substring(0, startOffset)
|
|
87
|
+
const after = source.substring(endOffset)
|
|
88
|
+
|
|
89
|
+
return before + after
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { ParserRule, BaseAutofixContext, Mutable } from "../types.js"
|
|
2
|
+
import { BaseRuleVisitor } from "./rule-utils.js"
|
|
3
|
+
|
|
4
|
+
import type { ParseResult, Token, ERBNode } from "@herb-tools/core"
|
|
5
|
+
import { Location } from "@herb-tools/core"
|
|
6
|
+
import type { UnboundLintOffense, LintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
7
|
+
|
|
8
|
+
interface ERBNoExtraWhitespaceAutofixContext extends BaseAutofixContext {
|
|
9
|
+
node: Mutable<ERBNode>
|
|
10
|
+
openTag: Token
|
|
11
|
+
closeTag: Token
|
|
12
|
+
content: string
|
|
13
|
+
fixType: "after-open" | "before-close" | "after-comment-equals"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class ERBNoExtraWhitespaceInsideTagsVisitor extends BaseRuleVisitor<ERBNoExtraWhitespaceAutofixContext> {
|
|
17
|
+
|
|
18
|
+
visitERBNode(node: ERBNode): void {
|
|
19
|
+
const openTag = node.tag_opening
|
|
20
|
+
const closeTag = node.tag_closing
|
|
21
|
+
const { value } = node.content ?? {}
|
|
22
|
+
|
|
23
|
+
if (!openTag || !closeTag || !value) return
|
|
24
|
+
|
|
25
|
+
if (this.hasExtraLeadingWhitespace(value)) {
|
|
26
|
+
this.reportWhitespace(node, openTag, closeTag, value, "start", 0, `Remove extra whitespace after \`${openTag.value}\`.`, "after-open")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (openTag.value === "<%#" && value.startsWith("=") && value.length > 1) {
|
|
30
|
+
const afterEquals = value.substring(1)
|
|
31
|
+
|
|
32
|
+
if (afterEquals.match(/^\s{2,}/) && !afterEquals.startsWith(" \n") && !afterEquals.startsWith("\n")) {
|
|
33
|
+
this.reportWhitespace(node, openTag, closeTag, value, "start", 1, `Remove extra whitespace after \`<%#=\`.`, "after-comment-equals")
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (this.hasExtraTrailingWhitespace(value)) {
|
|
38
|
+
this.reportWhitespace(node, openTag, closeTag, value, "end", 0, `Remove extra whitespace before \`${closeTag.value}\`.`, "before-close")
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private hasExtraLeadingWhitespace(content: string): boolean {
|
|
43
|
+
return content.startsWith(" ") && !content.startsWith(" \n")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private hasExtraTrailingWhitespace(content: string): boolean {
|
|
47
|
+
return !content.includes("\n") && /\s{2,}$/.test(content)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private getWhitespaceLocation(node: ERBNode, content: string, position: "start" | "end", offset: number = 0): Location {
|
|
51
|
+
const contentLocation = node.content!.location
|
|
52
|
+
|
|
53
|
+
if (position === "start") {
|
|
54
|
+
const match = content.substring(offset).match(/^\s+/)
|
|
55
|
+
const length = match ? match[0].length : 0
|
|
56
|
+
const startColumn = contentLocation.start.column + offset
|
|
57
|
+
|
|
58
|
+
return Location.from(
|
|
59
|
+
contentLocation.start.line,
|
|
60
|
+
startColumn,
|
|
61
|
+
contentLocation.start.line,
|
|
62
|
+
startColumn + length
|
|
63
|
+
)
|
|
64
|
+
} else {
|
|
65
|
+
const match = content.match(/\s+$/)
|
|
66
|
+
const length = match ? match[0].length : 0
|
|
67
|
+
|
|
68
|
+
return Location.from(
|
|
69
|
+
contentLocation.end.line,
|
|
70
|
+
contentLocation.end.column - length,
|
|
71
|
+
contentLocation.end.line,
|
|
72
|
+
contentLocation.end.column
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private reportWhitespace(
|
|
78
|
+
node: ERBNode,
|
|
79
|
+
openTag: Token,
|
|
80
|
+
closeTag: Token,
|
|
81
|
+
content: string,
|
|
82
|
+
position: "start" | "end",
|
|
83
|
+
offset: number,
|
|
84
|
+
message: string,
|
|
85
|
+
fixType: "after-open" | "before-close" | "after-comment-equals"
|
|
86
|
+
): void {
|
|
87
|
+
const location = this.getWhitespaceLocation(node, content, position, offset)
|
|
88
|
+
this.addOffense(message, location, {
|
|
89
|
+
node,
|
|
90
|
+
openTag,
|
|
91
|
+
closeTag,
|
|
92
|
+
content,
|
|
93
|
+
fixType
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export class ERBNoExtraWhitespaceRule extends ParserRule<ERBNoExtraWhitespaceAutofixContext> {
|
|
99
|
+
static autocorrectable = true
|
|
100
|
+
name = "erb-no-extra-whitespace-inside-tags"
|
|
101
|
+
|
|
102
|
+
get defaultConfig(): FullRuleConfig {
|
|
103
|
+
return {
|
|
104
|
+
enabled: true,
|
|
105
|
+
severity: "error"
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense<ERBNoExtraWhitespaceAutofixContext>[] {
|
|
110
|
+
const visitor = new ERBNoExtraWhitespaceInsideTagsVisitor(this.name, context)
|
|
111
|
+
|
|
112
|
+
visitor.visit(result.value)
|
|
113
|
+
|
|
114
|
+
return visitor.offenses
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
autofix(offense: LintOffense<ERBNoExtraWhitespaceAutofixContext>, result: ParseResult, _context?: Partial<LintContext>): ParseResult | null {
|
|
118
|
+
if (!offense.autofixContext) return null
|
|
119
|
+
|
|
120
|
+
const { node, fixType } = offense.autofixContext
|
|
121
|
+
if (!node.content) return null
|
|
122
|
+
|
|
123
|
+
const content = node.content.value
|
|
124
|
+
|
|
125
|
+
switch (fixType) {
|
|
126
|
+
case "before-close":
|
|
127
|
+
node.content.value = content.replace(/\s{2,}$/, " ")
|
|
128
|
+
break
|
|
129
|
+
|
|
130
|
+
case "after-open":
|
|
131
|
+
node.content.value = content.replace(/^\s{2,}/, " ")
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
case "after-comment-equals":
|
|
135
|
+
if (content.startsWith("=")) {
|
|
136
|
+
const afterEquals = content.substring(1)
|
|
137
|
+
node.content.value = "= " + afterEquals.replace(/^\s{2,}/, "")
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
break
|
|
141
|
+
default:
|
|
142
|
+
return null
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return result
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -2,7 +2,7 @@ import { BaseRuleVisitor } from "./rule-utils.js"
|
|
|
2
2
|
|
|
3
3
|
import type { ParseResult, ERBIfNode, ERBUnlessNode, ERBElseNode, ERBEndNode } from "@herb-tools/core"
|
|
4
4
|
import { ParserRule } from "../types.js"
|
|
5
|
-
import type {
|
|
5
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
6
6
|
|
|
7
7
|
class ERBNoOutputControlFlowRuleVisitor extends BaseRuleVisitor {
|
|
8
8
|
visitERBIfNode(node: ERBIfNode): void {
|
|
@@ -42,7 +42,6 @@ class ERBNoOutputControlFlowRuleVisitor extends BaseRuleVisitor {
|
|
|
42
42
|
this.addOffense(
|
|
43
43
|
`Control flow statements like \`${controlBlockType}\` should not be used with output tags. Use \`<% ${controlBlockType} ... %>\` instead.`,
|
|
44
44
|
openTag.location,
|
|
45
|
-
"error"
|
|
46
45
|
)
|
|
47
46
|
}
|
|
48
47
|
|
|
@@ -53,7 +52,14 @@ class ERBNoOutputControlFlowRuleVisitor extends BaseRuleVisitor {
|
|
|
53
52
|
export class ERBNoOutputControlFlowRule extends ParserRule {
|
|
54
53
|
name = "erb-no-output-control-flow"
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
get defaultConfig(): FullRuleConfig {
|
|
56
|
+
return {
|
|
57
|
+
enabled: true,
|
|
58
|
+
severity: "error"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
57
63
|
const visitor = new ERBNoOutputControlFlowRuleVisitor(this.name, context)
|
|
58
64
|
|
|
59
65
|
visitor.visit(result.value)
|
|
@@ -2,7 +2,7 @@ import { ParserRule } from "../types.js"
|
|
|
2
2
|
import { BaseRuleVisitor } from "./rule-utils.js"
|
|
3
3
|
import { filterERBContentNodes } from "@herb-tools/core"
|
|
4
4
|
|
|
5
|
-
import type {
|
|
5
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
6
6
|
import type { ParseResult, HTMLAttributeNameNode, ERBContentNode } from "@herb-tools/core"
|
|
7
7
|
|
|
8
8
|
class ERBNoSilentTagInAttributeNameVisitor extends BaseRuleVisitor {
|
|
@@ -14,7 +14,6 @@ class ERBNoSilentTagInAttributeNameVisitor extends BaseRuleVisitor {
|
|
|
14
14
|
this.addOffense(
|
|
15
15
|
`Remove silent ERB tag from HTML attribute name. Silent ERB tags (\`${node.tag_opening?.value}\`) do not output content and should not be used in attribute names.`,
|
|
16
16
|
node.location,
|
|
17
|
-
"error"
|
|
18
17
|
)
|
|
19
18
|
}
|
|
20
19
|
}
|
|
@@ -30,7 +29,14 @@ class ERBNoSilentTagInAttributeNameVisitor extends BaseRuleVisitor {
|
|
|
30
29
|
export class ERBNoSilentTagInAttributeNameRule extends ParserRule {
|
|
31
30
|
name = "erb-no-silent-tag-in-attribute-name"
|
|
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 ERBNoSilentTagInAttributeNameVisitor(this.name, context)
|
|
35
41
|
|
|
36
42
|
visitor.visit(result.value)
|
|
@@ -4,7 +4,7 @@ import { BaseRuleVisitor, getTagName, findAttributeByName, getAttributes } from
|
|
|
4
4
|
import { ERBToRubyStringPrinter } from "@herb-tools/printer"
|
|
5
5
|
import { filterNodes, ERBContentNode, LiteralNode, isNode } from "@herb-tools/core"
|
|
6
6
|
|
|
7
|
-
import type {
|
|
7
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
8
8
|
import type { HTMLOpenTagNode, HTMLAttributeValueNode, ParseResult } from "@herb-tools/core"
|
|
9
9
|
|
|
10
10
|
class ERBPreferImageTagHelperVisitor extends BaseRuleVisitor {
|
|
@@ -36,7 +36,6 @@ class ERBPreferImageTagHelperVisitor extends BaseRuleVisitor {
|
|
|
36
36
|
this.addOffense(
|
|
37
37
|
`Prefer \`image_tag\` helper over manual \`<img>\` with dynamic ERB expressions. Use \`<%= image_tag ${suggestedExpression}, alt: "..." %>\` instead.`,
|
|
38
38
|
srcAttribute.location,
|
|
39
|
-
"warning"
|
|
40
39
|
)
|
|
41
40
|
}
|
|
42
41
|
}
|
|
@@ -93,7 +92,14 @@ class ERBPreferImageTagHelperVisitor extends BaseRuleVisitor {
|
|
|
93
92
|
export class ERBPreferImageTagHelperRule extends ParserRule {
|
|
94
93
|
name = "erb-prefer-image-tag-helper"
|
|
95
94
|
|
|
96
|
-
|
|
95
|
+
get defaultConfig(): FullRuleConfig {
|
|
96
|
+
return {
|
|
97
|
+
enabled: true,
|
|
98
|
+
severity: "warning"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
97
103
|
const visitor = new ERBPreferImageTagHelperVisitor(this.name, context)
|
|
98
104
|
visitor.visit(result.value)
|
|
99
105
|
return visitor.offenses
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { SourceRule } from "../types.js"
|
|
2
|
+
import { BaseSourceRuleVisitor, createEndOfFileLocation } from "./rule-utils.js"
|
|
3
|
+
|
|
4
|
+
import type { UnboundLintOffense, LintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
5
|
+
|
|
6
|
+
class ERBRequireTrailingNewlineVisitor extends BaseSourceRuleVisitor {
|
|
7
|
+
protected visitSource(source: string): void {
|
|
8
|
+
if (source.length === 0) return
|
|
9
|
+
if (!this.context.fileName) return
|
|
10
|
+
|
|
11
|
+
if (!source.endsWith('\n')) {
|
|
12
|
+
this.addOffense(
|
|
13
|
+
"File must end with trailing newline.",
|
|
14
|
+
createEndOfFileLocation(source),
|
|
15
|
+
)
|
|
16
|
+
} else if (source.endsWith('\n\n')) {
|
|
17
|
+
this.addOffense(
|
|
18
|
+
"File must end with exactly one trailing newline.",
|
|
19
|
+
createEndOfFileLocation(source),
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ERBRequireTrailingNewlineRule extends SourceRule {
|
|
26
|
+
static autocorrectable = true
|
|
27
|
+
name = "erb-require-trailing-newline"
|
|
28
|
+
|
|
29
|
+
get defaultConfig(): FullRuleConfig {
|
|
30
|
+
return {
|
|
31
|
+
enabled: true,
|
|
32
|
+
severity: "error"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
check(source: string, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
37
|
+
const visitor = new ERBRequireTrailingNewlineVisitor(this.name, context)
|
|
38
|
+
|
|
39
|
+
visitor.visit(source)
|
|
40
|
+
|
|
41
|
+
return visitor.offenses
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
autofix(_offense: LintOffense, source: string, _context?: Partial<LintContext>): string | null {
|
|
45
|
+
return source.trimEnd() + "\n"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { isERBNode } from "@herb-tools/core";
|
|
3
|
-
import { ParserRule } from "../types.js"
|
|
4
|
-
import type { LintOffense, LintContext } from "../types.js"
|
|
1
|
+
import { ParserRule, BaseAutofixContext, Mutable } from "../types.js"
|
|
5
2
|
import { BaseRuleVisitor } from "./rule-utils.js"
|
|
6
3
|
|
|
7
|
-
|
|
4
|
+
import type { ParseResult, Token, ERBNode } from "@herb-tools/core"
|
|
5
|
+
import type { UnboundLintOffense, LintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
interface ERBRequireWhitespaceAutofixContext extends BaseAutofixContext {
|
|
8
|
+
node: Mutable<ERBNode>
|
|
9
|
+
openTag: Token
|
|
10
|
+
closeTag: Token
|
|
11
|
+
content: string
|
|
12
|
+
fixType: "after-open" | "before-close" | "after-comment-equals"
|
|
13
|
+
}
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
15
|
+
class RequireWhitespaceInsideTags extends BaseRuleVisitor<ERBRequireWhitespaceAutofixContext> {
|
|
16
|
+
|
|
17
|
+
visitERBNode(node: ERBNode): void {
|
|
18
18
|
const openTag = node.tag_opening
|
|
19
19
|
const closeTag = node.tag_closing
|
|
20
20
|
const content = node.content
|
|
@@ -26,25 +26,37 @@ class RequireWhitespaceInsideTags extends BaseRuleVisitor {
|
|
|
26
26
|
const value = content.value
|
|
27
27
|
|
|
28
28
|
if (openTag.value === "<%#") {
|
|
29
|
-
this.checkCommentTagWhitespace(openTag, closeTag, value)
|
|
29
|
+
this.checkCommentTagWhitespace(node, openTag, closeTag, value)
|
|
30
30
|
} else {
|
|
31
|
-
this.checkOpenTagWhitespace(openTag, value)
|
|
32
|
-
this.checkCloseTagWhitespace(closeTag, value)
|
|
31
|
+
this.checkOpenTagWhitespace(node, openTag, closeTag, value)
|
|
32
|
+
this.checkCloseTagWhitespace(node, openTag, closeTag, value)
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
private checkCommentTagWhitespace(openTag: Token, closeTag: Token, content: string): void {
|
|
36
|
+
private checkCommentTagWhitespace(node: ERBNode, openTag: Token, closeTag: Token, content: string): void {
|
|
37
37
|
if (!content.startsWith(" ") && !content.startsWith("\n") && !content.startsWith("=")) {
|
|
38
38
|
this.addOffense(
|
|
39
39
|
`Add whitespace after \`${openTag.value}\`.`,
|
|
40
40
|
openTag.location,
|
|
41
|
-
|
|
41
|
+
{
|
|
42
|
+
node,
|
|
43
|
+
openTag,
|
|
44
|
+
closeTag,
|
|
45
|
+
content,
|
|
46
|
+
fixType: "after-open"
|
|
47
|
+
}
|
|
42
48
|
)
|
|
43
49
|
} else if (content.startsWith("=") && content.length > 1 && !content[1].match(/\s/)) {
|
|
44
50
|
this.addOffense(
|
|
45
51
|
`Add whitespace after \`<%#=\`.`,
|
|
46
52
|
openTag.location,
|
|
47
|
-
|
|
53
|
+
{
|
|
54
|
+
node,
|
|
55
|
+
openTag,
|
|
56
|
+
closeTag,
|
|
57
|
+
content,
|
|
58
|
+
fixType: "after-comment-equals"
|
|
59
|
+
}
|
|
48
60
|
)
|
|
49
61
|
}
|
|
50
62
|
|
|
@@ -52,12 +64,18 @@ class RequireWhitespaceInsideTags extends BaseRuleVisitor {
|
|
|
52
64
|
this.addOffense(
|
|
53
65
|
`Add whitespace before \`${closeTag.value}\`.`,
|
|
54
66
|
closeTag.location,
|
|
55
|
-
|
|
67
|
+
{
|
|
68
|
+
node,
|
|
69
|
+
openTag,
|
|
70
|
+
closeTag,
|
|
71
|
+
content,
|
|
72
|
+
fixType: "before-close"
|
|
73
|
+
}
|
|
56
74
|
)
|
|
57
75
|
}
|
|
58
76
|
}
|
|
59
77
|
|
|
60
|
-
private checkOpenTagWhitespace(openTag: Token, content:string):void {
|
|
78
|
+
private checkOpenTagWhitespace(node: ERBNode, openTag: Token, closeTag: Token, content: string):void {
|
|
61
79
|
if (content.startsWith(" ") || content.startsWith("\n")) {
|
|
62
80
|
return
|
|
63
81
|
}
|
|
@@ -65,11 +83,17 @@ class RequireWhitespaceInsideTags extends BaseRuleVisitor {
|
|
|
65
83
|
this.addOffense(
|
|
66
84
|
`Add whitespace after \`${openTag.value}\`.`,
|
|
67
85
|
openTag.location,
|
|
68
|
-
|
|
86
|
+
{
|
|
87
|
+
node,
|
|
88
|
+
openTag,
|
|
89
|
+
closeTag,
|
|
90
|
+
content,
|
|
91
|
+
fixType: "after-open"
|
|
92
|
+
}
|
|
69
93
|
)
|
|
70
94
|
}
|
|
71
95
|
|
|
72
|
-
private checkCloseTagWhitespace(closeTag: Token, content:string):void {
|
|
96
|
+
private checkCloseTagWhitespace(node: ERBNode, openTag: Token, closeTag: Token, content: string):void {
|
|
73
97
|
if (content.endsWith(" ") || content.endsWith("\n")) {
|
|
74
98
|
return
|
|
75
99
|
}
|
|
@@ -77,17 +101,63 @@ class RequireWhitespaceInsideTags extends BaseRuleVisitor {
|
|
|
77
101
|
this.addOffense(
|
|
78
102
|
`Add whitespace before \`${closeTag.value}\`.`,
|
|
79
103
|
closeTag.location,
|
|
80
|
-
|
|
104
|
+
{
|
|
105
|
+
node,
|
|
106
|
+
openTag,
|
|
107
|
+
closeTag,
|
|
108
|
+
content,
|
|
109
|
+
fixType: "before-close"
|
|
110
|
+
}
|
|
81
111
|
)
|
|
82
112
|
}
|
|
83
113
|
}
|
|
84
114
|
|
|
85
|
-
export class ERBRequireWhitespaceRule extends ParserRule {
|
|
115
|
+
export class ERBRequireWhitespaceRule extends ParserRule<ERBRequireWhitespaceAutofixContext> {
|
|
116
|
+
static autocorrectable = true
|
|
86
117
|
name = "erb-require-whitespace-inside-tags"
|
|
87
118
|
|
|
88
|
-
|
|
119
|
+
get defaultConfig(): FullRuleConfig {
|
|
120
|
+
return {
|
|
121
|
+
enabled: true,
|
|
122
|
+
severity: "error"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense<ERBRequireWhitespaceAutofixContext>[] {
|
|
89
127
|
const visitor = new RequireWhitespaceInsideTags(this.name, context)
|
|
128
|
+
|
|
90
129
|
visitor.visit(result.value)
|
|
130
|
+
|
|
91
131
|
return visitor.offenses
|
|
92
132
|
}
|
|
133
|
+
|
|
134
|
+
autofix(offense: LintOffense<ERBRequireWhitespaceAutofixContext>, result: ParseResult, _context?: Partial<LintContext>): ParseResult | null {
|
|
135
|
+
if (!offense.autofixContext) return null
|
|
136
|
+
|
|
137
|
+
const { node, fixType } = offense.autofixContext
|
|
138
|
+
|
|
139
|
+
if (!node.content) return null
|
|
140
|
+
|
|
141
|
+
const content = node.content.value
|
|
142
|
+
|
|
143
|
+
if (fixType === "before-close") {
|
|
144
|
+
node.content.value = content + " "
|
|
145
|
+
|
|
146
|
+
return result
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (fixType === "after-open") {
|
|
150
|
+
node.content.value = " " + content
|
|
151
|
+
|
|
152
|
+
return result
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (fixType === "after-comment-equals" && content.startsWith("=")) {
|
|
156
|
+
node.content.value = "= " + content.substring(1)
|
|
157
|
+
|
|
158
|
+
return result
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return null
|
|
162
|
+
}
|
|
93
163
|
}
|