@herb-tools/linter 0.8.9 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/{src/cli → cli}/argument-parser.js +15 -2
- package/dist/cli/argument-parser.js.map +1 -0
- package/dist/{src/cli → cli}/file-processor.js +155 -9
- package/dist/cli/file-processor.js.map +1 -0
- package/dist/cli/file-url.js +6 -0
- package/dist/cli/file-url.js.map +1 -0
- package/dist/cli/formatters/base-formatter.js.map +1 -0
- package/dist/{src/cli → cli}/formatters/detailed-formatter.js +16 -19
- package/dist/cli/formatters/detailed-formatter.js.map +1 -0
- package/dist/cli/formatters/github-actions-formatter.js.map +1 -0
- package/dist/cli/formatters/index.js.map +1 -0
- package/dist/cli/formatters/json-formatter.js.map +1 -0
- package/dist/cli/formatters/simple-formatter.js +54 -0
- package/dist/cli/formatters/simple-formatter.js.map +1 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/lint-worker.js +143 -0
- package/dist/cli/lint-worker.js.map +1 -0
- package/dist/cli/output-manager.js.map +1 -0
- package/dist/{src/cli → cli}/summary-reporter.js +13 -16
- package/dist/cli/summary-reporter.js.map +1 -0
- package/dist/{src/cli.js → cli.js} +5 -3
- package/dist/cli.js.map +1 -0
- package/dist/{src/custom-rule-loader.js → custom-rule-loader.js} +20 -4
- package/dist/custom-rule-loader.js.map +1 -0
- package/dist/herb-disable-comment-utils.js.map +1 -0
- package/dist/herb-lint.js +60648 -17513
- package/dist/herb-lint.js.map +1 -1
- package/dist/index.cjs +2621 -934
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2554 -873
- package/dist/index.js.map +1 -1
- package/dist/lint-worker.js +71462 -0
- package/dist/lint-worker.js.map +1 -0
- package/dist/linter-ignore.js.map +1 -0
- package/dist/{src/linter.js → linter.js} +89 -74
- package/dist/linter.js.map +1 -0
- package/dist/loader.cjs +31206 -7834
- package/dist/loader.cjs.map +1 -1
- package/dist/loader.js +31168 -7802
- package/dist/loader.js.map +1 -1
- package/dist/parse-cache.js +30 -0
- package/dist/parse-cache.js.map +1 -0
- package/dist/rules/actionview-no-silent-helper.js +45 -0
- package/dist/rules/actionview-no-silent-helper.js.map +1 -0
- package/dist/{src/rules → rules}/erb-comment-syntax.js +2 -2
- package/dist/rules/erb-comment-syntax.js.map +1 -0
- package/dist/{src/rules → rules}/erb-no-case-node-children.js +2 -2
- package/dist/rules/erb-no-case-node-children.js.map +1 -0
- package/dist/rules/erb-no-conditional-html-element.js +38 -0
- package/dist/rules/erb-no-conditional-html-element.js.map +1 -0
- package/dist/rules/erb-no-conditional-open-tag.js +24 -0
- package/dist/rules/erb-no-conditional-open-tag.js.map +1 -0
- package/dist/rules/erb-no-duplicate-branch-elements.js +245 -0
- package/dist/rules/erb-no-duplicate-branch-elements.js.map +1 -0
- package/dist/{src/rules → rules}/erb-no-empty-tags.js +2 -2
- package/dist/rules/erb-no-empty-tags.js.map +1 -0
- package/dist/{src/rules → rules}/erb-no-extra-newline.js +4 -21
- package/dist/rules/erb-no-extra-newline.js.map +1 -0
- package/dist/{src/rules → rules}/erb-no-extra-whitespace-inside-tags.js +39 -13
- package/dist/rules/erb-no-extra-whitespace-inside-tags.js.map +1 -0
- package/dist/rules/erb-no-inline-case-conditions.js +40 -0
- package/dist/rules/erb-no-inline-case-conditions.js.map +1 -0
- package/dist/rules/erb-no-instance-variables-in-partials.js +67 -0
- package/dist/rules/erb-no-instance-variables-in-partials.js.map +1 -0
- package/dist/rules/erb-no-interpolated-class-names.js +47 -0
- package/dist/rules/erb-no-interpolated-class-names.js.map +1 -0
- package/dist/rules/erb-no-javascript-tag-helper.js +34 -0
- package/dist/rules/erb-no-javascript-tag-helper.js.map +1 -0
- package/dist/{src/rules → rules}/erb-no-output-control-flow.js +9 -12
- package/dist/rules/erb-no-output-control-flow.js.map +1 -0
- package/dist/rules/erb-no-output-in-attribute-name.js +30 -0
- package/dist/rules/erb-no-output-in-attribute-name.js.map +1 -0
- package/dist/rules/erb-no-output-in-attribute-position.js +30 -0
- package/dist/rules/erb-no-output-in-attribute-position.js.map +1 -0
- package/dist/rules/erb-no-raw-output-in-attribute-value.js +35 -0
- package/dist/rules/erb-no-raw-output-in-attribute-value.js.map +1 -0
- package/dist/{src/rules → rules}/erb-no-silent-tag-in-attribute-name.js +2 -2
- package/dist/rules/erb-no-silent-tag-in-attribute-name.js.map +1 -0
- package/dist/rules/erb-no-statement-in-script.js +58 -0
- package/dist/rules/erb-no-statement-in-script.js.map +1 -0
- package/dist/rules/erb-no-then-in-control-flow.js +45 -0
- package/dist/rules/erb-no-then-in-control-flow.js.map +1 -0
- package/dist/rules/erb-no-trailing-whitespace.js +138 -0
- package/dist/rules/erb-no-trailing-whitespace.js.map +1 -0
- package/dist/rules/erb-no-unsafe-js-attribute.js +36 -0
- package/dist/rules/erb-no-unsafe-js-attribute.js.map +1 -0
- package/dist/rules/erb-no-unsafe-raw.js +63 -0
- package/dist/rules/erb-no-unsafe-raw.js.map +1 -0
- package/dist/rules/erb-no-unsafe-script-interpolation.js +54 -0
- package/dist/rules/erb-no-unsafe-script-interpolation.js.map +1 -0
- package/dist/{src/rules → rules}/erb-prefer-image-tag-helper.js +5 -4
- package/dist/rules/erb-prefer-image-tag-helper.js.map +1 -0
- package/dist/{src/rules → rules}/erb-require-trailing-newline.js +2 -2
- package/dist/rules/erb-require-trailing-newline.js.map +1 -0
- package/dist/{src/rules → rules}/erb-require-whitespace-inside-tags.js +39 -15
- package/dist/rules/erb-require-whitespace-inside-tags.js.map +1 -0
- package/dist/{src/rules → rules}/erb-right-trim.js +2 -2
- package/dist/rules/erb-right-trim.js.map +1 -0
- package/dist/{src/rules → rules}/erb-strict-locals-comment-syntax.js +5 -5
- package/dist/rules/erb-strict-locals-comment-syntax.js.map +1 -0
- package/dist/{src/rules → rules}/erb-strict-locals-required.js +2 -2
- package/dist/rules/erb-strict-locals-required.js.map +1 -0
- package/dist/rules/file-utils.js.map +1 -0
- package/dist/rules/herb-disable-comment-base.js.map +1 -0
- package/dist/{src/rules → rules}/herb-disable-comment-malformed.js +2 -2
- package/dist/rules/herb-disable-comment-malformed.js.map +1 -0
- package/dist/{src/rules → rules}/herb-disable-comment-missing-rules.js +2 -2
- package/dist/rules/herb-disable-comment-missing-rules.js.map +1 -0
- package/dist/{src/rules → rules}/herb-disable-comment-no-duplicate-rules.js +2 -2
- package/dist/rules/herb-disable-comment-no-duplicate-rules.js.map +1 -0
- package/dist/{src/rules → rules}/herb-disable-comment-no-redundant-all.js +2 -2
- package/dist/rules/herb-disable-comment-no-redundant-all.js.map +1 -0
- package/dist/{src/rules → rules}/herb-disable-comment-unnecessary.js +2 -2
- package/dist/rules/herb-disable-comment-unnecessary.js.map +1 -0
- package/dist/{src/rules → rules}/herb-disable-comment-valid-rule-name.js +2 -2
- package/dist/rules/herb-disable-comment-valid-rule-name.js.map +1 -0
- package/dist/rules/html-allowed-script-type.js +57 -0
- package/dist/rules/html-allowed-script-type.js.map +1 -0
- package/dist/rules/html-anchor-require-href.js +68 -0
- package/dist/rules/html-anchor-require-href.js.map +1 -0
- package/dist/{src/rules → rules}/html-aria-attribute-must-be-valid.js +3 -3
- package/dist/rules/html-aria-attribute-must-be-valid.js.map +1 -0
- package/dist/{src/rules → rules}/html-aria-label-is-well-formatted.js +3 -3
- package/dist/rules/html-aria-label-is-well-formatted.js.map +1 -0
- package/dist/{src/rules → rules}/html-aria-level-must-be-valid.js +3 -3
- package/dist/rules/html-aria-level-must-be-valid.js.map +1 -0
- package/dist/{src/rules → rules}/html-aria-role-heading-requires-level.js +5 -4
- package/dist/rules/html-aria-role-heading-requires-level.js.map +1 -0
- package/dist/{src/rules → rules}/html-aria-role-must-be-valid.js +3 -3
- package/dist/rules/html-aria-role-must-be-valid.js.map +1 -0
- package/dist/{src/rules → rules}/html-attribute-double-quotes.js +4 -4
- package/dist/rules/html-attribute-double-quotes.js.map +1 -0
- package/dist/{src/rules → rules}/html-attribute-equals-spacing.js +2 -2
- package/dist/rules/html-attribute-equals-spacing.js.map +1 -0
- package/dist/{src/rules → rules}/html-attribute-values-require-quotes.js +2 -2
- package/dist/rules/html-attribute-values-require-quotes.js.map +1 -0
- package/dist/{src/rules → rules}/html-avoid-both-disabled-and-aria-disabled.js +9 -9
- package/dist/rules/html-avoid-both-disabled-and-aria-disabled.js.map +1 -0
- package/dist/{src/rules → rules}/html-body-only-elements.js +5 -4
- package/dist/rules/html-body-only-elements.js.map +1 -0
- package/dist/{src/rules → rules}/html-boolean-attributes-no-value.js +4 -3
- package/dist/rules/html-boolean-attributes-no-value.js.map +1 -0
- package/dist/rules/html-details-has-summary.js +52 -0
- package/dist/rules/html-details-has-summary.js.map +1 -0
- package/dist/{src/rules → rules}/html-head-only-elements.js +6 -5
- package/dist/rules/html-head-only-elements.js.map +1 -0
- package/dist/{src/rules → rules}/html-iframe-has-title.js +8 -11
- package/dist/rules/html-iframe-has-title.js.map +1 -0
- package/dist/{src/rules → rules}/html-img-require-alt.js +11 -5
- package/dist/rules/html-img-require-alt.js.map +1 -0
- package/dist/{src/rules → rules}/html-input-require-autocomplete.js +7 -10
- package/dist/rules/html-input-require-autocomplete.js.map +1 -0
- package/dist/{src/rules → rules}/html-navigation-has-label.js +6 -5
- package/dist/rules/html-navigation-has-label.js.map +1 -0
- package/dist/rules/html-no-abstract-roles.js +29 -0
- package/dist/rules/html-no-abstract-roles.js.map +1 -0
- package/dist/rules/html-no-aria-hidden-on-body.js +42 -0
- package/dist/rules/html-no-aria-hidden-on-body.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-aria-hidden-on-focusable.js +6 -5
- package/dist/rules/html-no-aria-hidden-on-focusable.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-block-inside-inline.js +6 -9
- package/dist/rules/html-no-block-inside-inline.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-duplicate-attributes.js +4 -3
- package/dist/rules/html-no-duplicate-attributes.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-duplicate-ids.js +14 -11
- package/dist/rules/html-no-duplicate-ids.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-duplicate-meta-names.js +22 -20
- package/dist/rules/html-no-duplicate-meta-names.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-empty-attributes.js +2 -2
- package/dist/rules/html-no-empty-attributes.js.map +1 -0
- package/dist/rules/html-no-empty-headings.js +98 -0
- package/dist/rules/html-no-empty-headings.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-nested-links.js +23 -15
- package/dist/rules/html-no-nested-links.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-positive-tab-index.js +3 -3
- package/dist/rules/html-no-positive-tab-index.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-self-closing.js +4 -4
- package/dist/rules/html-no-self-closing.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-space-in-tag.js +4 -6
- package/dist/rules/html-no-space-in-tag.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-title-attribute.js +6 -5
- package/dist/rules/html-no-title-attribute.js.map +1 -0
- package/dist/{src/rules → rules}/html-no-underscores-in-attribute-names.js +2 -2
- package/dist/rules/html-no-underscores-in-attribute-names.js.map +1 -0
- package/dist/rules/html-require-closing-tags.js +29 -0
- package/dist/rules/html-require-closing-tags.js.map +1 -0
- package/dist/{src/rules → rules}/html-tag-name-lowercase.js +13 -9
- package/dist/rules/html-tag-name-lowercase.js.map +1 -0
- package/dist/{src/rules → rules}/index.js +19 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/{src/rules → rules}/parser-no-errors.js +3 -3
- package/dist/rules/parser-no-errors.js.map +1 -0
- package/dist/{src/rules → rules}/rule-utils.js +141 -219
- package/dist/rules/rule-utils.js.map +1 -0
- package/dist/rules/string-utils.js.map +1 -0
- package/dist/{src/rules → rules}/svg-tag-name-capitalization.js +7 -6
- package/dist/rules/svg-tag-name-capitalization.js.map +1 -0
- package/dist/rules/turbo-permanent-require-id.js +34 -0
- package/dist/rules/turbo-permanent-require-id.js.map +1 -0
- package/dist/{src/rules.js → rules.js} +56 -10
- package/dist/rules.js.map +1 -0
- package/dist/types/cli/argument-parser.d.ts +1 -0
- package/dist/types/cli/file-processor.d.ts +13 -0
- package/dist/types/cli/file-url.d.ts +1 -0
- package/dist/types/cli/index.d.ts +1 -0
- package/dist/types/cli/lint-worker.d.ts +34 -0
- package/dist/types/custom-rule-loader.d.ts +4 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/linter.d.ts +13 -6
- package/dist/types/parse-cache.d.ts +9 -0
- package/dist/types/{src/rules/html-aria-level-must-be-valid.d.ts → rules/actionview-no-silent-helper.d.ts} +4 -3
- package/dist/types/rules/erb-comment-syntax.d.ts +1 -1
- package/dist/types/rules/erb-no-case-node-children.d.ts +1 -1
- package/dist/types/{src/rules/herb-disable-comment-malformed.d.ts → rules/erb-no-conditional-html-element.d.ts} +3 -3
- package/dist/types/{src/rules/erb-prefer-image-tag-helper.d.ts → rules/erb-no-conditional-open-tag.d.ts} +3 -3
- package/dist/types/rules/erb-no-duplicate-branch-elements.d.ts +17 -0
- package/dist/types/rules/erb-no-empty-tags.d.ts +1 -1
- package/dist/types/rules/erb-no-extra-newline.d.ts +1 -1
- package/dist/types/rules/erb-no-extra-whitespace-inside-tags.d.ts +1 -1
- package/dist/types/{src/rules/html-no-duplicate-attributes.d.ts → rules/erb-no-inline-case-conditions.d.ts} +4 -3
- package/dist/types/rules/erb-no-instance-variables-in-partials.d.ts +10 -0
- package/dist/types/{src/rules/html-no-aria-hidden-on-focusable.d.ts → rules/erb-no-interpolated-class-names.d.ts} +2 -2
- package/dist/types/{src/rules/html-aria-attribute-must-be-valid.d.ts → rules/erb-no-javascript-tag-helper.d.ts} +2 -2
- package/dist/types/rules/erb-no-output-control-flow.d.ts +1 -1
- package/dist/types/{src/rules/erb-no-silent-tag-in-attribute-name.d.ts → rules/erb-no-output-in-attribute-name.d.ts} +2 -2
- package/dist/types/{src/rules/herb-disable-comment-missing-rules.d.ts → rules/erb-no-output-in-attribute-position.d.ts} +2 -2
- package/dist/types/{src/rules/erb-no-empty-tags.d.ts → rules/erb-no-raw-output-in-attribute-value.d.ts} +2 -2
- package/dist/types/rules/erb-no-silent-tag-in-attribute-name.d.ts +1 -1
- package/dist/types/{src/rules/html-navigation-has-label.d.ts → rules/erb-no-statement-in-script.d.ts} +2 -2
- package/dist/types/rules/erb-no-then-in-control-flow.d.ts +9 -0
- package/dist/types/rules/erb-no-trailing-whitespace.d.ts +19 -0
- package/dist/types/{src/rules/html-no-positive-tab-index.d.ts → rules/erb-no-unsafe-js-attribute.d.ts} +2 -2
- package/dist/types/{src/rules/erb-no-case-node-children.d.ts → rules/erb-no-unsafe-raw.d.ts} +2 -2
- package/dist/types/rules/erb-no-unsafe-script-interpolation.d.ts +8 -0
- package/dist/types/rules/erb-prefer-image-tag-helper.d.ts +1 -1
- package/dist/types/rules/erb-require-trailing-newline.d.ts +1 -1
- package/dist/types/rules/erb-require-whitespace-inside-tags.d.ts +1 -1
- package/dist/types/rules/erb-right-trim.d.ts +1 -1
- package/dist/types/rules/erb-strict-locals-comment-syntax.d.ts +1 -1
- package/dist/types/rules/erb-strict-locals-required.d.ts +1 -1
- package/dist/types/rules/herb-disable-comment-malformed.d.ts +1 -1
- package/dist/types/rules/herb-disable-comment-missing-rules.d.ts +1 -1
- package/dist/types/rules/herb-disable-comment-no-duplicate-rules.d.ts +1 -1
- package/dist/types/rules/herb-disable-comment-no-redundant-all.d.ts +1 -1
- package/dist/types/rules/herb-disable-comment-unnecessary.d.ts +1 -1
- package/dist/types/rules/herb-disable-comment-valid-rule-name.d.ts +1 -1
- package/dist/types/{src/rules/html-anchor-require-href.d.ts → rules/html-allowed-script-type.d.ts} +2 -2
- package/dist/types/rules/html-anchor-require-href.d.ts +3 -2
- package/dist/types/rules/html-aria-attribute-must-be-valid.d.ts +1 -1
- package/dist/types/rules/html-aria-label-is-well-formatted.d.ts +1 -1
- package/dist/types/rules/html-aria-level-must-be-valid.d.ts +1 -1
- package/dist/types/rules/html-aria-role-heading-requires-level.d.ts +1 -1
- package/dist/types/rules/html-aria-role-must-be-valid.d.ts +1 -1
- package/dist/types/rules/html-attribute-double-quotes.d.ts +1 -1
- package/dist/types/rules/html-attribute-equals-spacing.d.ts +1 -1
- package/dist/types/rules/html-attribute-values-require-quotes.d.ts +1 -1
- package/dist/types/rules/html-avoid-both-disabled-and-aria-disabled.d.ts +1 -1
- package/dist/types/rules/html-body-only-elements.d.ts +1 -1
- package/dist/types/rules/html-boolean-attributes-no-value.d.ts +1 -1
- package/dist/types/{src/rules/html-no-empty-attributes.d.ts → rules/html-details-has-summary.d.ts} +4 -3
- package/dist/types/rules/html-head-only-elements.d.ts +1 -1
- package/dist/types/rules/html-iframe-has-title.d.ts +1 -1
- package/dist/types/rules/html-img-require-alt.d.ts +1 -1
- package/dist/types/rules/html-input-require-autocomplete.d.ts +1 -1
- package/dist/types/rules/html-navigation-has-label.d.ts +1 -1
- package/dist/types/{src/rules/html-no-empty-headings.d.ts → rules/html-no-abstract-roles.d.ts} +2 -2
- package/dist/types/{src/rules/erb-no-output-control-flow.d.ts → rules/html-no-aria-hidden-on-body.d.ts} +3 -3
- package/dist/types/rules/html-no-aria-hidden-on-focusable.d.ts +1 -1
- package/dist/types/rules/html-no-block-inside-inline.d.ts +1 -1
- package/dist/types/rules/html-no-duplicate-attributes.d.ts +1 -1
- package/dist/types/rules/html-no-duplicate-ids.d.ts +1 -1
- package/dist/types/rules/html-no-duplicate-meta-names.d.ts +1 -1
- package/dist/types/rules/html-no-empty-attributes.d.ts +1 -1
- package/dist/types/rules/html-no-empty-headings.d.ts +1 -1
- package/dist/types/rules/html-no-nested-links.d.ts +1 -1
- package/dist/types/rules/html-no-positive-tab-index.d.ts +1 -1
- package/dist/types/rules/html-no-self-closing.d.ts +1 -1
- package/dist/types/rules/html-no-space-in-tag.d.ts +1 -1
- package/dist/types/rules/html-no-title-attribute.d.ts +1 -1
- package/dist/types/rules/html-no-underscores-in-attribute-names.d.ts +1 -1
- package/dist/types/{src/rules/html-body-only-elements.d.ts → rules/html-require-closing-tags.d.ts} +4 -3
- package/dist/types/rules/html-tag-name-lowercase.d.ts +1 -1
- package/dist/types/rules/index.d.ts +19 -0
- package/dist/types/rules/parser-no-errors.d.ts +1 -1
- package/dist/types/rules/rule-utils.d.ts +35 -88
- package/dist/types/rules/svg-tag-name-capitalization.d.ts +1 -1
- package/dist/types/{src/rules/html-aria-role-must-be-valid.d.ts → rules/turbo-permanent-require-id.d.ts} +2 -2
- package/dist/types/types.d.ts +25 -7
- package/dist/types/urls.d.ts +1 -0
- package/dist/{src/types.js → types.js} +53 -0
- package/dist/types.js.map +1 -0
- package/dist/urls.js +5 -0
- package/dist/urls.js.map +1 -0
- package/docs/rules/README.md +23 -2
- package/docs/rules/actionview-no-silent-helper.md +57 -0
- package/docs/rules/erb-no-conditional-html-element.md +90 -0
- package/docs/rules/erb-no-conditional-open-tag.md +130 -0
- package/docs/rules/erb-no-duplicate-branch-elements.md +98 -0
- package/docs/rules/erb-no-inline-case-conditions.md +85 -0
- package/docs/rules/erb-no-instance-variables-in-partials.md +43 -0
- package/docs/rules/erb-no-interpolated-class-names.md +57 -0
- package/docs/rules/erb-no-javascript-tag-helper.md +33 -0
- package/docs/rules/erb-no-output-in-attribute-name.md +38 -0
- package/docs/rules/erb-no-output-in-attribute-position.md +60 -0
- package/docs/rules/erb-no-raw-output-in-attribute-value.md +37 -0
- package/docs/rules/erb-no-statement-in-script.md +68 -0
- package/docs/rules/erb-no-then-in-control-flow.md +86 -0
- package/docs/rules/erb-no-trailing-whitespace.md +69 -0
- package/docs/rules/erb-no-unsafe-js-attribute.md +41 -0
- package/docs/rules/erb-no-unsafe-raw.md +47 -0
- package/docs/rules/erb-no-unsafe-script-interpolation.md +73 -0
- package/docs/rules/html-allowed-script-type.md +59 -0
- package/docs/rules/html-anchor-require-href.md +19 -6
- package/docs/rules/html-details-has-summary.md +46 -0
- package/docs/rules/html-img-require-alt.md +5 -3
- package/docs/rules/html-no-abstract-roles.md +74 -0
- package/docs/rules/html-no-aria-hidden-on-body.md +44 -0
- package/docs/rules/html-require-closing-tags.md +142 -0
- package/docs/rules/parser-no-errors.md +4 -17
- package/docs/rules/turbo-permanent-require-id.md +41 -0
- package/package.json +12 -11
- package/src/cli/argument-parser.ts +20 -2
- package/src/cli/file-processor.ts +189 -10
- package/src/cli/file-url.ts +6 -0
- package/src/cli/formatters/detailed-formatter.ts +19 -21
- package/src/cli/formatters/simple-formatter.ts +23 -13
- package/src/cli/index.ts +2 -0
- package/src/cli/lint-worker.ts +208 -0
- package/src/cli/summary-reporter.ts +14 -15
- package/src/cli.ts +5 -3
- package/src/custom-rule-loader.ts +20 -5
- package/src/herb-disable-comment-utils.ts +0 -3
- package/src/index.ts +1 -0
- package/src/linter.ts +98 -79
- package/src/parse-cache.ts +39 -0
- package/src/rules/actionview-no-silent-helper.ts +58 -0
- package/src/rules/erb-comment-syntax.ts +2 -2
- package/src/rules/erb-no-case-node-children.ts +2 -2
- package/src/rules/erb-no-conditional-html-element.ts +53 -0
- package/src/rules/erb-no-conditional-open-tag.ts +37 -0
- package/src/rules/erb-no-duplicate-branch-elements.ts +320 -0
- package/src/rules/erb-no-empty-tags.ts +2 -2
- package/src/rules/erb-no-extra-newline.ts +5 -25
- package/src/rules/erb-no-extra-whitespace-inside-tags.ts +45 -15
- package/src/rules/erb-no-inline-case-conditions.ts +54 -0
- package/src/rules/erb-no-instance-variables-in-partials.ts +101 -0
- package/src/rules/erb-no-interpolated-class-names.ts +65 -0
- package/src/rules/erb-no-javascript-tag-helper.ts +47 -0
- package/src/rules/erb-no-output-control-flow.ts +10 -10
- package/src/rules/erb-no-output-in-attribute-name.ts +39 -0
- package/src/rules/erb-no-output-in-attribute-position.ts +39 -0
- package/src/rules/erb-no-raw-output-in-attribute-value.ts +47 -0
- package/src/rules/erb-no-silent-tag-in-attribute-name.ts +2 -2
- package/src/rules/erb-no-statement-in-script.ts +82 -0
- package/src/rules/erb-no-then-in-control-flow.ts +62 -0
- package/src/rules/erb-no-trailing-whitespace.ts +187 -0
- package/src/rules/erb-no-unsafe-js-attribute.ts +47 -0
- package/src/rules/erb-no-unsafe-raw.ts +83 -0
- package/src/rules/erb-no-unsafe-script-interpolation.ts +76 -0
- package/src/rules/erb-prefer-image-tag-helper.ts +5 -4
- package/src/rules/erb-require-trailing-newline.ts +2 -2
- package/src/rules/erb-require-whitespace-inside-tags.ts +42 -18
- package/src/rules/erb-right-trim.ts +2 -2
- package/src/rules/erb-strict-locals-comment-syntax.ts +5 -5
- package/src/rules/erb-strict-locals-required.ts +2 -2
- package/src/rules/herb-disable-comment-malformed.ts +2 -2
- package/src/rules/herb-disable-comment-missing-rules.ts +2 -2
- package/src/rules/herb-disable-comment-no-duplicate-rules.ts +2 -2
- package/src/rules/herb-disable-comment-no-redundant-all.ts +2 -2
- package/src/rules/herb-disable-comment-unnecessary.ts +2 -2
- package/src/rules/herb-disable-comment-valid-rule-name.ts +2 -2
- package/src/rules/html-allowed-script-type.ts +84 -0
- package/src/rules/html-anchor-require-href.ts +73 -11
- package/src/rules/html-aria-attribute-must-be-valid.ts +3 -3
- package/src/rules/html-aria-label-is-well-formatted.ts +3 -3
- package/src/rules/html-aria-level-must-be-valid.ts +3 -3
- package/src/rules/html-aria-role-heading-requires-level.ts +5 -4
- package/src/rules/html-aria-role-must-be-valid.ts +3 -3
- package/src/rules/html-attribute-double-quotes.ts +4 -4
- package/src/rules/html-attribute-equals-spacing.ts +2 -2
- package/src/rules/html-attribute-values-require-quotes.ts +2 -2
- package/src/rules/html-avoid-both-disabled-and-aria-disabled.ts +10 -11
- package/src/rules/html-body-only-elements.ts +5 -4
- package/src/rules/html-boolean-attributes-no-value.ts +4 -3
- package/src/rules/html-details-has-summary.ts +69 -0
- package/src/rules/html-head-only-elements.ts +6 -5
- package/src/rules/html-iframe-has-title.ts +8 -11
- package/src/rules/html-img-require-alt.ts +16 -5
- package/src/rules/html-input-require-autocomplete.ts +7 -10
- package/src/rules/html-navigation-has-label.ts +6 -5
- package/src/rules/html-no-abstract-roles.ts +40 -0
- package/src/rules/html-no-aria-hidden-on-body.ts +58 -0
- package/src/rules/html-no-aria-hidden-on-focusable.ts +6 -5
- package/src/rules/html-no-block-inside-inline.ts +7 -13
- package/src/rules/html-no-duplicate-attributes.ts +4 -3
- package/src/rules/html-no-duplicate-ids.ts +16 -13
- package/src/rules/html-no-duplicate-meta-names.ts +20 -19
- package/src/rules/html-no-empty-attributes.ts +2 -2
- package/src/rules/html-no-empty-headings.ts +44 -58
- package/src/rules/html-no-nested-links.ts +25 -16
- package/src/rules/html-no-positive-tab-index.ts +3 -3
- package/src/rules/html-no-self-closing.ts +5 -5
- package/src/rules/html-no-space-in-tag.ts +5 -8
- package/src/rules/html-no-title-attribute.ts +6 -5
- package/src/rules/html-no-underscores-in-attribute-names.ts +2 -2
- package/src/rules/html-require-closing-tags.ts +41 -0
- package/src/rules/html-tag-name-lowercase.ts +14 -9
- package/src/rules/index.ts +19 -0
- package/src/rules/parser-no-errors.ts +3 -3
- package/src/rules/rule-utils.ts +162 -279
- package/src/rules/svg-tag-name-capitalization.ts +10 -10
- package/src/rules/turbo-permanent-require-id.ts +49 -0
- package/src/rules.ts +60 -10
- package/src/types.ts +76 -7
- package/src/urls.ts +5 -0
- package/dist/package.json +0 -65
- package/dist/src/cli/argument-parser.js.map +0 -1
- package/dist/src/cli/file-processor.js.map +0 -1
- package/dist/src/cli/formatters/base-formatter.js.map +0 -1
- package/dist/src/cli/formatters/detailed-formatter.js.map +0 -1
- package/dist/src/cli/formatters/github-actions-formatter.js.map +0 -1
- package/dist/src/cli/formatters/index.js.map +0 -1
- package/dist/src/cli/formatters/json-formatter.js.map +0 -1
- package/dist/src/cli/formatters/simple-formatter.js +0 -44
- package/dist/src/cli/formatters/simple-formatter.js.map +0 -1
- package/dist/src/cli/index.js.map +0 -1
- package/dist/src/cli/output-manager.js.map +0 -1
- package/dist/src/cli/summary-reporter.js.map +0 -1
- package/dist/src/cli.js.map +0 -1
- package/dist/src/custom-rule-loader.js.map +0 -1
- package/dist/src/herb-disable-comment-utils.js.map +0 -1
- package/dist/src/herb-lint.js +0 -5
- package/dist/src/herb-lint.js.map +0 -1
- package/dist/src/index.js +0 -5
- package/dist/src/index.js.map +0 -1
- package/dist/src/linter-ignore.js.map +0 -1
- package/dist/src/linter.js.map +0 -1
- package/dist/src/loader.js +0 -17
- package/dist/src/loader.js.map +0 -1
- package/dist/src/rules/erb-comment-syntax.js.map +0 -1
- package/dist/src/rules/erb-no-case-node-children.js.map +0 -1
- package/dist/src/rules/erb-no-empty-tags.js.map +0 -1
- package/dist/src/rules/erb-no-extra-newline.js.map +0 -1
- package/dist/src/rules/erb-no-extra-whitespace-inside-tags.js.map +0 -1
- package/dist/src/rules/erb-no-output-control-flow.js.map +0 -1
- package/dist/src/rules/erb-no-silent-tag-in-attribute-name.js.map +0 -1
- package/dist/src/rules/erb-prefer-image-tag-helper.js.map +0 -1
- package/dist/src/rules/erb-require-trailing-newline.js.map +0 -1
- package/dist/src/rules/erb-require-whitespace-inside-tags.js.map +0 -1
- package/dist/src/rules/erb-right-trim.js.map +0 -1
- package/dist/src/rules/erb-strict-locals-comment-syntax.js.map +0 -1
- package/dist/src/rules/erb-strict-locals-required.js.map +0 -1
- package/dist/src/rules/file-utils.js.map +0 -1
- package/dist/src/rules/herb-disable-comment-base.js.map +0 -1
- package/dist/src/rules/herb-disable-comment-malformed.js.map +0 -1
- package/dist/src/rules/herb-disable-comment-missing-rules.js.map +0 -1
- package/dist/src/rules/herb-disable-comment-no-duplicate-rules.js.map +0 -1
- package/dist/src/rules/herb-disable-comment-no-redundant-all.js.map +0 -1
- package/dist/src/rules/herb-disable-comment-unnecessary.js.map +0 -1
- package/dist/src/rules/herb-disable-comment-valid-rule-name.js.map +0 -1
- package/dist/src/rules/html-anchor-require-href.js +0 -32
- package/dist/src/rules/html-anchor-require-href.js.map +0 -1
- package/dist/src/rules/html-aria-attribute-must-be-valid.js.map +0 -1
- package/dist/src/rules/html-aria-label-is-well-formatted.js.map +0 -1
- package/dist/src/rules/html-aria-level-must-be-valid.js.map +0 -1
- package/dist/src/rules/html-aria-role-heading-requires-level.js.map +0 -1
- package/dist/src/rules/html-aria-role-must-be-valid.js.map +0 -1
- package/dist/src/rules/html-attribute-double-quotes.js.map +0 -1
- package/dist/src/rules/html-attribute-equals-spacing.js.map +0 -1
- package/dist/src/rules/html-attribute-values-require-quotes.js.map +0 -1
- package/dist/src/rules/html-avoid-both-disabled-and-aria-disabled.js.map +0 -1
- package/dist/src/rules/html-body-only-elements.js.map +0 -1
- package/dist/src/rules/html-boolean-attributes-no-value.js.map +0 -1
- package/dist/src/rules/html-head-only-elements.js.map +0 -1
- package/dist/src/rules/html-iframe-has-title.js.map +0 -1
- package/dist/src/rules/html-img-require-alt.js.map +0 -1
- package/dist/src/rules/html-input-require-autocomplete.js.map +0 -1
- package/dist/src/rules/html-navigation-has-label.js.map +0 -1
- package/dist/src/rules/html-no-aria-hidden-on-focusable.js.map +0 -1
- package/dist/src/rules/html-no-block-inside-inline.js.map +0 -1
- package/dist/src/rules/html-no-duplicate-attributes.js.map +0 -1
- package/dist/src/rules/html-no-duplicate-ids.js.map +0 -1
- package/dist/src/rules/html-no-duplicate-meta-names.js.map +0 -1
- package/dist/src/rules/html-no-empty-attributes.js.map +0 -1
- package/dist/src/rules/html-no-empty-headings.js +0 -115
- package/dist/src/rules/html-no-empty-headings.js.map +0 -1
- package/dist/src/rules/html-no-nested-links.js.map +0 -1
- package/dist/src/rules/html-no-positive-tab-index.js.map +0 -1
- package/dist/src/rules/html-no-self-closing.js.map +0 -1
- package/dist/src/rules/html-no-space-in-tag.js.map +0 -1
- package/dist/src/rules/html-no-title-attribute.js.map +0 -1
- package/dist/src/rules/html-no-underscores-in-attribute-names.js.map +0 -1
- package/dist/src/rules/html-tag-name-lowercase.js.map +0 -1
- package/dist/src/rules/index.js.map +0 -1
- package/dist/src/rules/parser-no-errors.js.map +0 -1
- package/dist/src/rules/rule-utils.js.map +0 -1
- package/dist/src/rules/string-utils.js.map +0 -1
- package/dist/src/rules/svg-tag-name-capitalization.js.map +0 -1
- package/dist/src/rules.js.map +0 -1
- package/dist/src/types.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/dist/types/src/cli/argument-parser.d.ts +0 -25
- package/dist/types/src/cli/file-processor.d.ts +0 -43
- package/dist/types/src/cli/formatters/base-formatter.d.ts +0 -6
- package/dist/types/src/cli/formatters/detailed-formatter.d.ts +0 -13
- package/dist/types/src/cli/formatters/github-actions-formatter.d.ts +0 -17
- package/dist/types/src/cli/formatters/index.d.ts +0 -5
- package/dist/types/src/cli/formatters/json-formatter.d.ts +0 -48
- package/dist/types/src/cli/formatters/simple-formatter.d.ts +0 -8
- package/dist/types/src/cli/index.d.ts +0 -5
- package/dist/types/src/cli/output-manager.d.ts +0 -32
- package/dist/types/src/cli/summary-reporter.d.ts +0 -28
- package/dist/types/src/cli.d.ts +0 -28
- package/dist/types/src/custom-rule-loader.d.ts +0 -62
- package/dist/types/src/herb-disable-comment-utils.d.ts +0 -69
- package/dist/types/src/herb-lint.d.ts +0 -2
- package/dist/types/src/index.d.ts +0 -4
- package/dist/types/src/linter-ignore.d.ts +0 -12
- package/dist/types/src/linter.d.ts +0 -133
- package/dist/types/src/loader.d.ts +0 -20
- package/dist/types/src/rules/erb-comment-syntax.d.ts +0 -14
- package/dist/types/src/rules/erb-no-extra-newline.d.ts +0 -14
- package/dist/types/src/rules/erb-no-extra-whitespace-inside-tags.d.ts +0 -18
- package/dist/types/src/rules/erb-require-trailing-newline.d.ts +0 -9
- package/dist/types/src/rules/erb-require-whitespace-inside-tags.d.ts +0 -18
- package/dist/types/src/rules/erb-right-trim.d.ts +0 -14
- package/dist/types/src/rules/erb-strict-locals-comment-syntax.d.ts +0 -9
- package/dist/types/src/rules/erb-strict-locals-required.d.ts +0 -9
- package/dist/types/src/rules/file-utils.d.ts +0 -13
- package/dist/types/src/rules/herb-disable-comment-base.d.ts +0 -37
- package/dist/types/src/rules/herb-disable-comment-no-duplicate-rules.d.ts +0 -8
- package/dist/types/src/rules/herb-disable-comment-no-redundant-all.d.ts +0 -8
- package/dist/types/src/rules/herb-disable-comment-unnecessary.d.ts +0 -8
- package/dist/types/src/rules/herb-disable-comment-valid-rule-name.d.ts +0 -8
- package/dist/types/src/rules/html-aria-label-is-well-formatted.d.ts +0 -8
- package/dist/types/src/rules/html-aria-role-heading-requires-level.d.ts +0 -8
- package/dist/types/src/rules/html-attribute-double-quotes.d.ts +0 -15
- package/dist/types/src/rules/html-attribute-equals-spacing.d.ts +0 -14
- package/dist/types/src/rules/html-attribute-values-require-quotes.d.ts +0 -15
- package/dist/types/src/rules/html-avoid-both-disabled-and-aria-disabled.d.ts +0 -8
- package/dist/types/src/rules/html-boolean-attributes-no-value.d.ts +0 -14
- package/dist/types/src/rules/html-head-only-elements.d.ts +0 -9
- package/dist/types/src/rules/html-iframe-has-title.d.ts +0 -8
- package/dist/types/src/rules/html-img-require-alt.d.ts +0 -8
- package/dist/types/src/rules/html-input-require-autocomplete.d.ts +0 -8
- package/dist/types/src/rules/html-no-block-inside-inline.d.ts +0 -8
- package/dist/types/src/rules/html-no-duplicate-ids.d.ts +0 -8
- package/dist/types/src/rules/html-no-duplicate-meta-names.d.ts +0 -9
- package/dist/types/src/rules/html-no-nested-links.d.ts +0 -8
- package/dist/types/src/rules/html-no-self-closing.d.ts +0 -16
- package/dist/types/src/rules/html-no-space-in-tag.d.ts +0 -16
- package/dist/types/src/rules/html-no-title-attribute.d.ts +0 -8
- package/dist/types/src/rules/html-no-underscores-in-attribute-names.d.ts +0 -8
- package/dist/types/src/rules/html-tag-name-lowercase.d.ts +0 -18
- package/dist/types/src/rules/index.d.ts +0 -54
- package/dist/types/src/rules/parser-no-errors.d.ts +0 -9
- package/dist/types/src/rules/rule-utils.d.ts +0 -351
- package/dist/types/src/rules/string-utils.d.ts +0 -15
- package/dist/types/src/rules/svg-tag-name-capitalization.d.ts +0 -16
- package/dist/types/src/rules.d.ts +0 -2
- package/dist/types/src/types.d.ts +0 -190
- /package/dist/{src/cli → cli}/formatters/base-formatter.js +0 -0
- /package/dist/{src/cli → cli}/formatters/github-actions-formatter.js +0 -0
- /package/dist/{src/cli → cli}/formatters/index.js +0 -0
- /package/dist/{src/cli → cli}/formatters/json-formatter.js +0 -0
- /package/dist/{src/cli → cli}/index.js +0 -0
- /package/dist/{src/cli → cli}/output-manager.js +0 -0
- /package/dist/{src/herb-disable-comment-utils.js → herb-disable-comment-utils.js} +0 -0
- /package/dist/{src/linter-ignore.js → linter-ignore.js} +0 -0
- /package/dist/{src/rules → rules}/file-utils.js +0 -0
- /package/dist/{src/rules → rules}/herb-disable-comment-base.js +0 -0
- /package/dist/{src/rules → rules}/string-utils.js +0 -0
package/src/linter.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import { Location } from "@herb-tools/core"
|
|
2
|
-
import { IdentityPrinter } from "@herb-tools/printer"
|
|
3
1
|
import picomatch from "picomatch"
|
|
4
2
|
|
|
3
|
+
import { Location } from "@herb-tools/core"
|
|
4
|
+
import { IdentityPrinter, IndentPrinter } from "@herb-tools/printer"
|
|
5
|
+
|
|
5
6
|
import { rules } from "./rules.js"
|
|
6
7
|
import { findNodeByLocation } from "./rules/rule-utils.js"
|
|
7
8
|
import { parseHerbDisableLine } from "./herb-disable-comment-utils.js"
|
|
8
9
|
import { hasLinterIgnoreDirective } from "./linter-ignore.js"
|
|
10
|
+
import { ParseCache } from "./parse-cache.js"
|
|
9
11
|
|
|
10
12
|
import { ParserNoErrorsRule } from "./rules/parser-no-errors.js"
|
|
13
|
+
|
|
11
14
|
import { DEFAULT_RULE_CONFIG } from "./types.js"
|
|
12
15
|
|
|
13
|
-
import type { RuleClass, Rule, ParserRule, LexerRule, SourceRule, LintResult, LintOffense, UnboundLintOffense, LintContext, AutofixResult } from "./types.js"
|
|
16
|
+
import type { RuleClass, ParserRuleClass, LexerRuleClass, SourceRuleClass, Rule, ParserRule, LexerRule, SourceRule, LintResult, LintOffense, UnboundLintOffense, LintContext, AutofixResult } from "./types.js"
|
|
14
17
|
import type { ParseResult, LexResult, HerbBackend } from "@herb-tools/core"
|
|
15
18
|
import type { RuleConfig, Config } from "@herb-tools/config"
|
|
16
19
|
|
|
@@ -45,9 +48,10 @@ export interface LinterOptions {
|
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
export class Linter {
|
|
48
|
-
|
|
51
|
+
public rules: RuleClass[]
|
|
49
52
|
protected allAvailableRules: RuleClass[]
|
|
50
53
|
protected herb: HerbBackend
|
|
54
|
+
protected parseCache: ParseCache
|
|
51
55
|
protected offenses: LintOffense[]
|
|
52
56
|
protected config?: Config
|
|
53
57
|
|
|
@@ -81,6 +85,7 @@ export class Linter {
|
|
|
81
85
|
*/
|
|
82
86
|
constructor(herb: HerbBackend, rules?: RuleClass[], config?: Config, allAvailableRules?: RuleClass[]) {
|
|
83
87
|
this.herb = herb
|
|
88
|
+
this.parseCache = new ParseCache(herb)
|
|
84
89
|
this.config = config
|
|
85
90
|
this.rules = rules !== undefined ? rules : this.getDefaultRules()
|
|
86
91
|
this.allAvailableRules = allAvailableRules !== undefined ? allAvailableRules : this.rules
|
|
@@ -104,10 +109,9 @@ export class Linter {
|
|
|
104
109
|
): RuleClass[] {
|
|
105
110
|
return allRules.filter(ruleClass => {
|
|
106
111
|
const instance = new ruleClass()
|
|
107
|
-
const ruleName = instance.name
|
|
108
112
|
|
|
109
113
|
const defaultEnabled = instance.defaultConfig?.enabled ?? DEFAULT_RULE_CONFIG.enabled
|
|
110
|
-
const userRuleConfig = userRulesConfig?.[ruleName]
|
|
114
|
+
const userRuleConfig = userRulesConfig?.[ruleClass.ruleName]
|
|
111
115
|
|
|
112
116
|
if (userRuleConfig !== undefined) {
|
|
113
117
|
return userRuleConfig.enabled !== false
|
|
@@ -156,18 +160,29 @@ export class Linter {
|
|
|
156
160
|
return this.rules.length
|
|
157
161
|
}
|
|
158
162
|
|
|
163
|
+
protected findRuleClass(ruleName: string): RuleClass | undefined {
|
|
164
|
+
return this.rules.find(ruleClass => ruleClass.ruleName === ruleName)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Type guard to check if a rule class is a LexerRule class
|
|
169
|
+
*/
|
|
170
|
+
protected isLexerRuleClass(ruleClass: RuleClass): ruleClass is LexerRuleClass {
|
|
171
|
+
return ruleClass.type === "lexer"
|
|
172
|
+
}
|
|
173
|
+
|
|
159
174
|
/**
|
|
160
|
-
* Type guard to check if a rule is a
|
|
175
|
+
* Type guard to check if a rule class is a SourceRule class
|
|
161
176
|
*/
|
|
162
|
-
protected
|
|
163
|
-
return
|
|
177
|
+
protected isSourceRuleClass(ruleClass: RuleClass): ruleClass is SourceRuleClass {
|
|
178
|
+
return ruleClass.type === "source"
|
|
164
179
|
}
|
|
165
180
|
|
|
166
181
|
/**
|
|
167
|
-
* Type guard to check if a rule is a
|
|
182
|
+
* Type guard to check if a rule class is a ParserRule class
|
|
168
183
|
*/
|
|
169
|
-
protected
|
|
170
|
-
return
|
|
184
|
+
protected isParserRuleClass(ruleClass: RuleClass): ruleClass is ParserRuleClass {
|
|
185
|
+
return ruleClass.type === "parser" || ruleClass.type === undefined
|
|
171
186
|
}
|
|
172
187
|
|
|
173
188
|
/**
|
|
@@ -175,19 +190,22 @@ export class Linter {
|
|
|
175
190
|
* Handles rule type checking (Lexer/Parser/Source) and isEnabled checks.
|
|
176
191
|
*/
|
|
177
192
|
private executeRule(
|
|
193
|
+
ruleClass: RuleClass,
|
|
178
194
|
rule: Rule,
|
|
179
195
|
parseResult: ParseResult,
|
|
180
196
|
lexResult: LexResult,
|
|
181
197
|
source: string,
|
|
182
198
|
context?: Partial<LintContext>
|
|
183
199
|
): UnboundLintOffense[] {
|
|
200
|
+
const ruleName = rule.ruleName
|
|
201
|
+
|
|
184
202
|
if (this.config && context?.fileName) {
|
|
185
|
-
if (!this.config.isRuleEnabledForPath(
|
|
203
|
+
if (!this.config.isRuleEnabledForPath(ruleName, context.fileName)) {
|
|
186
204
|
return []
|
|
187
205
|
}
|
|
188
206
|
}
|
|
189
207
|
|
|
190
|
-
if (context?.fileName && !this.config?.linter?.rules?.[
|
|
208
|
+
if (context?.fileName && !this.config?.linter?.rules?.[ruleName]?.exclude) {
|
|
191
209
|
const defaultExclude = rule.defaultConfig?.exclude ?? DEFAULT_RULE_CONFIG.exclude
|
|
192
210
|
|
|
193
211
|
if (defaultExclude && defaultExclude.length > 0) {
|
|
@@ -202,34 +220,40 @@ export class Linter {
|
|
|
202
220
|
let isEnabled = true
|
|
203
221
|
let ruleOffenses: UnboundLintOffense[]
|
|
204
222
|
|
|
205
|
-
if (this.
|
|
206
|
-
|
|
207
|
-
|
|
223
|
+
if (this.isLexerRuleClass(ruleClass)) {
|
|
224
|
+
const lexerRule = rule as LexerRule
|
|
225
|
+
|
|
226
|
+
if (lexerRule.isEnabled) {
|
|
227
|
+
isEnabled = lexerRule.isEnabled(lexResult, context)
|
|
208
228
|
}
|
|
209
229
|
|
|
210
230
|
if (isEnabled) {
|
|
211
|
-
ruleOffenses =
|
|
231
|
+
ruleOffenses = lexerRule.check(lexResult, context)
|
|
212
232
|
} else {
|
|
213
233
|
ruleOffenses = []
|
|
214
234
|
}
|
|
215
235
|
|
|
216
|
-
} else if (this.
|
|
217
|
-
|
|
218
|
-
|
|
236
|
+
} else if (this.isSourceRuleClass(ruleClass)) {
|
|
237
|
+
const sourceRule = rule as SourceRule
|
|
238
|
+
|
|
239
|
+
if (sourceRule.isEnabled) {
|
|
240
|
+
isEnabled = sourceRule.isEnabled(source, context)
|
|
219
241
|
}
|
|
220
242
|
|
|
221
243
|
if (isEnabled) {
|
|
222
|
-
ruleOffenses =
|
|
244
|
+
ruleOffenses = sourceRule.check(source, context)
|
|
223
245
|
} else {
|
|
224
246
|
ruleOffenses = []
|
|
225
247
|
}
|
|
226
248
|
} else {
|
|
227
|
-
|
|
228
|
-
|
|
249
|
+
const parserRule = rule as ParserRule
|
|
250
|
+
|
|
251
|
+
if (parserRule.isEnabled) {
|
|
252
|
+
isEnabled = parserRule.isEnabled(parseResult, context)
|
|
229
253
|
}
|
|
230
254
|
|
|
231
255
|
if (isEnabled) {
|
|
232
|
-
ruleOffenses =
|
|
256
|
+
ruleOffenses = parserRule.check(parseResult, context)
|
|
233
257
|
} else {
|
|
234
258
|
ruleOffenses = []
|
|
235
259
|
}
|
|
@@ -303,7 +327,7 @@ export class Linter {
|
|
|
303
327
|
let ignoredCount = 0
|
|
304
328
|
let wouldBeIgnoredCount = 0
|
|
305
329
|
|
|
306
|
-
const parseResult = this.
|
|
330
|
+
const parseResult = this.parseCache.get(source)
|
|
307
331
|
|
|
308
332
|
// Check for file-level ignore directive using visitor
|
|
309
333
|
if (hasLinterIgnoreDirective(parseResult)) {
|
|
@@ -323,22 +347,13 @@ export class Linter {
|
|
|
323
347
|
const herbDisableCache = new Map<number, string[]>()
|
|
324
348
|
|
|
325
349
|
if (hasParserErrors) {
|
|
326
|
-
const hasParserRule = this.
|
|
350
|
+
const hasParserRule = this.findRuleClass("parser-no-errors")
|
|
327
351
|
|
|
328
352
|
if (hasParserRule) {
|
|
329
353
|
const rule = new ParserNoErrorsRule()
|
|
330
354
|
const offenses = rule.check(parseResult)
|
|
331
355
|
this.offenses.push(...offenses)
|
|
332
356
|
}
|
|
333
|
-
|
|
334
|
-
return {
|
|
335
|
-
offenses: this.offenses,
|
|
336
|
-
errors: this.offenses.filter(o => o.severity === "error").length,
|
|
337
|
-
warnings: this.offenses.filter(o => o.severity === "warning").length,
|
|
338
|
-
info: this.offenses.filter(o => o.severity === "info").length,
|
|
339
|
-
hints: this.offenses.filter(o => o.severity === "hint").length,
|
|
340
|
-
ignored: 0
|
|
341
|
-
}
|
|
342
357
|
}
|
|
343
358
|
|
|
344
359
|
for (let i = 0; i < sourceLines.length; i++) {
|
|
@@ -352,24 +367,31 @@ export class Linter {
|
|
|
352
367
|
|
|
353
368
|
context = {
|
|
354
369
|
...context,
|
|
355
|
-
validRuleNames: this.getAvailableRules().map(
|
|
370
|
+
validRuleNames: this.getAvailableRules().map(ruleClass => ruleClass.ruleName),
|
|
356
371
|
ignoredOffensesByLine
|
|
357
372
|
}
|
|
358
373
|
|
|
359
|
-
const regularRules = this.rules.filter(
|
|
360
|
-
const rule = new RuleClass()
|
|
374
|
+
const regularRules = this.rules.filter(ruleClass => ruleClass.ruleName !== "herb-disable-comment-unnecessary")
|
|
361
375
|
|
|
362
|
-
|
|
363
|
-
|
|
376
|
+
for (const ruleClass of regularRules) {
|
|
377
|
+
const rule = new ruleClass()
|
|
378
|
+
const parserOptions = this.isParserRuleClass(ruleClass) ? (rule as ParserRule).parserOptions : {}
|
|
379
|
+
const parseResult = this.parseCache.get(source, parserOptions)
|
|
380
|
+
|
|
381
|
+
// Skip parser rules whose parse result has errors (parser-no-errors handled above)
|
|
382
|
+
// Skip lexer/source rules when the default parse has errors
|
|
383
|
+
if (this.isParserRuleClass(ruleClass)) {
|
|
384
|
+
if (parseResult.recursiveErrors().length > 0) continue
|
|
385
|
+
} else if (hasParserErrors) {
|
|
386
|
+
continue
|
|
387
|
+
}
|
|
364
388
|
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
const unboundOffenses = this.executeRule(rule, parseResult, lexResult, source, context)
|
|
368
|
-
const boundOffenses = this.bindSeverity(unboundOffenses, rule.name)
|
|
389
|
+
const unboundOffenses = this.executeRule(ruleClass, rule, parseResult, lexResult, source, context)
|
|
390
|
+
const boundOffenses = this.bindSeverity(unboundOffenses, ruleClass.ruleName)
|
|
369
391
|
|
|
370
392
|
const { kept, ignored, wouldBeIgnored } = this.filterOffenses(
|
|
371
393
|
boundOffenses,
|
|
372
|
-
|
|
394
|
+
ruleClass.ruleName,
|
|
373
395
|
ignoredOffensesByLine,
|
|
374
396
|
herbDisableCache,
|
|
375
397
|
context?.ignoreDisableComments
|
|
@@ -380,16 +402,13 @@ export class Linter {
|
|
|
380
402
|
this.offenses.push(...kept)
|
|
381
403
|
}
|
|
382
404
|
|
|
383
|
-
const unnecessaryRuleClass = this.
|
|
384
|
-
const rule = new RuleClass()
|
|
385
|
-
|
|
386
|
-
return rule.name === "herb-disable-comment-unnecessary"
|
|
387
|
-
})
|
|
405
|
+
const unnecessaryRuleClass = this.findRuleClass("herb-disable-comment-unnecessary")
|
|
388
406
|
|
|
389
407
|
if (unnecessaryRuleClass) {
|
|
390
408
|
const unnecessaryRule = new unnecessaryRuleClass() as ParserRule
|
|
409
|
+
const parseResult = this.parseCache.get(source, unnecessaryRule.parserOptions)
|
|
391
410
|
const unboundOffenses = unnecessaryRule.check(parseResult, context)
|
|
392
|
-
const boundOffenses = this.bindSeverity(unboundOffenses,
|
|
411
|
+
const boundOffenses = this.bindSeverity(unboundOffenses, unnecessaryRuleClass.ruleName)
|
|
393
412
|
|
|
394
413
|
this.offenses.push(...boundOffenses)
|
|
395
414
|
}
|
|
@@ -429,19 +448,16 @@ export class Linter {
|
|
|
429
448
|
* @returns Array of offenses with severity bound
|
|
430
449
|
*/
|
|
431
450
|
protected bindSeverity(unboundOffenses: UnboundLintOffense[], ruleName: string): LintOffense[] {
|
|
432
|
-
const
|
|
433
|
-
const instance = new rule()
|
|
434
|
-
return instance.name === ruleName
|
|
435
|
-
})
|
|
451
|
+
const ruleClass = this.findRuleClass(ruleName)
|
|
436
452
|
|
|
437
|
-
if (!
|
|
453
|
+
if (!ruleClass) {
|
|
438
454
|
return unboundOffenses.map(offense => ({
|
|
439
455
|
...offense,
|
|
440
456
|
severity: "error" as const
|
|
441
457
|
}))
|
|
442
458
|
}
|
|
443
459
|
|
|
444
|
-
const ruleInstance = new
|
|
460
|
+
const ruleInstance = new ruleClass()
|
|
445
461
|
const defaultSeverity = ruleInstance.defaultConfig?.severity ?? DEFAULT_RULE_CONFIG.severity
|
|
446
462
|
|
|
447
463
|
const userRuleConfig = this.config?.linter?.rules?.[ruleName]
|
|
@@ -449,7 +465,7 @@ export class Linter {
|
|
|
449
465
|
|
|
450
466
|
return unboundOffenses.map(offense => ({
|
|
451
467
|
...offense,
|
|
452
|
-
severity
|
|
468
|
+
severity: offense.severity ?? severity
|
|
453
469
|
}))
|
|
454
470
|
}
|
|
455
471
|
|
|
@@ -472,17 +488,13 @@ export class Linter {
|
|
|
472
488
|
const sourceOffenses: LintOffense[] = []
|
|
473
489
|
|
|
474
490
|
for (const offense of lintResult.offenses) {
|
|
475
|
-
const
|
|
476
|
-
const instance = new rule()
|
|
477
|
-
|
|
478
|
-
return instance.name === offense.rule
|
|
479
|
-
})
|
|
491
|
+
const ruleClass = this.findRuleClass(offense.rule)
|
|
480
492
|
|
|
481
|
-
if (!
|
|
493
|
+
if (!ruleClass) continue
|
|
482
494
|
|
|
483
|
-
if ((
|
|
495
|
+
if (this.isLexerRuleClass(ruleClass)) {
|
|
484
496
|
lexerOffenses.push(offense)
|
|
485
|
-
} else if ((
|
|
497
|
+
} else if (this.isSourceRuleClass(ruleClass)) {
|
|
486
498
|
sourceOffenses.push(offense)
|
|
487
499
|
} else {
|
|
488
500
|
parserOffenses.push(offense)
|
|
@@ -494,19 +506,20 @@ export class Linter {
|
|
|
494
506
|
const unfixed: LintOffense[] = []
|
|
495
507
|
|
|
496
508
|
if (parserOffenses.length > 0) {
|
|
497
|
-
const parseResult = this.
|
|
509
|
+
const parseResult = this.parseCache.get(currentSource)
|
|
510
|
+
let needsReindent = false
|
|
498
511
|
|
|
499
512
|
for (const offense of parserOffenses) {
|
|
500
|
-
const
|
|
513
|
+
const ruleClass = this.findRuleClass(offense.rule)
|
|
501
514
|
|
|
502
|
-
if (!
|
|
515
|
+
if (!ruleClass) {
|
|
503
516
|
unfixed.push(offense)
|
|
504
517
|
|
|
505
518
|
continue
|
|
506
519
|
}
|
|
507
520
|
|
|
508
|
-
const rule = new
|
|
509
|
-
const isUnsafe = (
|
|
521
|
+
const rule = new ruleClass() as ParserRule
|
|
522
|
+
const isUnsafe = (ruleClass as any).unsafeAutocorrectable === true || offense.autofixContext?.unsafe === true
|
|
510
523
|
|
|
511
524
|
if (!rule.autofix) {
|
|
512
525
|
unfixed.push(offense)
|
|
@@ -543,14 +556,21 @@ export class Linter {
|
|
|
543
556
|
|
|
544
557
|
if (fixedResult) {
|
|
545
558
|
fixed.push(offense)
|
|
559
|
+
|
|
560
|
+
if (this.isParserRuleClass(ruleClass) && ruleClass.reindentAfterAutofix === true) {
|
|
561
|
+
needsReindent = true
|
|
562
|
+
}
|
|
546
563
|
} else {
|
|
547
564
|
unfixed.push(offense)
|
|
548
565
|
}
|
|
549
566
|
}
|
|
550
567
|
|
|
551
568
|
if (fixed.length > 0) {
|
|
552
|
-
|
|
553
|
-
|
|
569
|
+
if (needsReindent) {
|
|
570
|
+
currentSource = new IndentPrinter().print(parseResult.value)
|
|
571
|
+
} else {
|
|
572
|
+
currentSource = new IdentityPrinter().print(parseResult.value)
|
|
573
|
+
}
|
|
554
574
|
}
|
|
555
575
|
}
|
|
556
576
|
|
|
@@ -564,15 +584,15 @@ export class Linter {
|
|
|
564
584
|
})
|
|
565
585
|
|
|
566
586
|
for (const offense of sortedSourceOffenses) {
|
|
567
|
-
const
|
|
587
|
+
const ruleClass = this.findRuleClass(offense.rule)
|
|
568
588
|
|
|
569
|
-
if (!
|
|
589
|
+
if (!ruleClass) {
|
|
570
590
|
unfixed.push(offense)
|
|
571
591
|
continue
|
|
572
592
|
}
|
|
573
593
|
|
|
574
|
-
const rule = new
|
|
575
|
-
const isUnsafe = (
|
|
594
|
+
const rule = new ruleClass() as SourceRule
|
|
595
|
+
const isUnsafe = (ruleClass as any).unsafeAutocorrectable === true || offense.autofixContext?.unsafe === true
|
|
576
596
|
|
|
577
597
|
if (!rule.autofix) {
|
|
578
598
|
unfixed.push(offense)
|
|
@@ -602,4 +622,3 @@ export class Linter {
|
|
|
602
622
|
}
|
|
603
623
|
}
|
|
604
624
|
}
|
|
605
|
-
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { DEFAULT_PARSER_OPTIONS } from "@herb-tools/core"
|
|
2
|
+
import { DEFAULT_LINTER_PARSER_OPTIONS } from "./types.js"
|
|
3
|
+
|
|
4
|
+
import type { ParseResult, HerbBackend, ParserOptions } from "@herb-tools/core"
|
|
5
|
+
|
|
6
|
+
export class ParseCache {
|
|
7
|
+
private herb: HerbBackend
|
|
8
|
+
private cache = new Map<string, ParseResult>()
|
|
9
|
+
|
|
10
|
+
constructor(herb: HerbBackend) {
|
|
11
|
+
this.herb = herb
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get(source: string, parserOptions: Partial<ParserOptions> = {}): ParseResult {
|
|
15
|
+
const effectiveOptions = this.resolveOptions(parserOptions)
|
|
16
|
+
const key = source + JSON.stringify(effectiveOptions)
|
|
17
|
+
|
|
18
|
+
let result = this.cache.get(key)
|
|
19
|
+
|
|
20
|
+
if (!result) {
|
|
21
|
+
result = this.herb.parse(source, effectiveOptions)
|
|
22
|
+
this.cache.set(key, result)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return result
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
clear(): void {
|
|
29
|
+
this.cache.clear()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
resolveOptions(parserOptions: Partial<ParserOptions>): ParserOptions {
|
|
33
|
+
return {
|
|
34
|
+
...DEFAULT_PARSER_OPTIONS,
|
|
35
|
+
...DEFAULT_LINTER_PARSER_OPTIONS,
|
|
36
|
+
...parserOptions
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ParserRule } from "../types.js"
|
|
2
|
+
import { BaseRuleVisitor } from "./rule-utils.js"
|
|
3
|
+
import { isERBOpenTagNode, isERBOutputNode } from "@herb-tools/core"
|
|
4
|
+
|
|
5
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
6
|
+
import type { ParseResult, HTMLElementNode, ParserOptions } from "@herb-tools/core"
|
|
7
|
+
|
|
8
|
+
class ActionViewNoSilentHelperVisitor extends BaseRuleVisitor {
|
|
9
|
+
visitHTMLElementNode(node: HTMLElementNode): void {
|
|
10
|
+
this.checkActionViewHelper(node)
|
|
11
|
+
super.visitHTMLElementNode(node)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
private checkActionViewHelper(node: HTMLElementNode): void {
|
|
15
|
+
if (!node.element_source || node.element_source === "HTML") return
|
|
16
|
+
if (!isERBOpenTagNode(node.open_tag)) return
|
|
17
|
+
if (isERBOutputNode(node.open_tag)) return
|
|
18
|
+
|
|
19
|
+
const tagOpening = node.open_tag.tag_opening?.value
|
|
20
|
+
|
|
21
|
+
if (!tagOpening) return
|
|
22
|
+
|
|
23
|
+
const helperName = node.element_source.includes("#")
|
|
24
|
+
? node.element_source.split("#").pop()
|
|
25
|
+
: node.element_source
|
|
26
|
+
|
|
27
|
+
this.addOffense(
|
|
28
|
+
`Avoid using \`${tagOpening} %>\` with \`${helperName}\`. Use \`<%= %>\` to ensure the helper's output is rendered.`,
|
|
29
|
+
node.open_tag.location,
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class ActionViewNoSilentHelperRule extends ParserRule {
|
|
35
|
+
static ruleName = "actionview-no-silent-helper"
|
|
36
|
+
|
|
37
|
+
get defaultConfig(): FullRuleConfig {
|
|
38
|
+
return {
|
|
39
|
+
enabled: true,
|
|
40
|
+
severity: "error"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get parserOptions(): Partial<ParserOptions> {
|
|
45
|
+
return {
|
|
46
|
+
track_whitespace: true,
|
|
47
|
+
action_view_helpers: true,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
52
|
+
const visitor = new ActionViewNoSilentHelperVisitor(this.ruleName, context)
|
|
53
|
+
|
|
54
|
+
visitor.visit(result.value)
|
|
55
|
+
|
|
56
|
+
return visitor.offenses
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -34,7 +34,7 @@ class ERBCommentSyntaxVisitor extends BaseRuleVisitor<ERBCommentSyntaxAutofixCon
|
|
|
34
34
|
|
|
35
35
|
export class ERBCommentSyntax extends ParserRule<ERBCommentSyntaxAutofixContext> {
|
|
36
36
|
static autocorrectable = true
|
|
37
|
-
|
|
37
|
+
static ruleName = "erb-comment-syntax"
|
|
38
38
|
|
|
39
39
|
get defaultConfig(): FullRuleConfig {
|
|
40
40
|
return {
|
|
@@ -44,7 +44,7 @@ export class ERBCommentSyntax extends ParserRule<ERBCommentSyntaxAutofixContext>
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense<ERBCommentSyntaxAutofixContext>[] {
|
|
47
|
-
const visitor = new ERBCommentSyntaxVisitor(this.
|
|
47
|
+
const visitor = new ERBCommentSyntaxVisitor(this.ruleName, context)
|
|
48
48
|
|
|
49
49
|
visitor.visit(result.value)
|
|
50
50
|
|
|
@@ -49,7 +49,7 @@ class ERBNoCaseNodeChildrenVisitor extends BaseRuleVisitor {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export class ERBNoCaseNodeChildrenRule extends ParserRule {
|
|
52
|
-
|
|
52
|
+
static ruleName = "erb-no-case-node-children"
|
|
53
53
|
|
|
54
54
|
get defaultConfig(): FullRuleConfig {
|
|
55
55
|
return {
|
|
@@ -59,7 +59,7 @@ export class ERBNoCaseNodeChildrenRule extends ParserRule {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
62
|
-
const visitor = new ERBNoCaseNodeChildrenVisitor(this.
|
|
62
|
+
const visitor = new ERBNoCaseNodeChildrenVisitor(this.ruleName, context)
|
|
63
63
|
|
|
64
64
|
visitor.visit(result.value)
|
|
65
65
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import dedent from "dedent"
|
|
2
|
+
|
|
3
|
+
import { ParserRule } from "../types.js"
|
|
4
|
+
import { BaseRuleVisitor } from "./rule-utils.js"
|
|
5
|
+
|
|
6
|
+
import type { ParseResult, HTMLConditionalElementNode } from "@herb-tools/core"
|
|
7
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
8
|
+
|
|
9
|
+
class ERBNoConditionalHTMLElementRuleVisitor extends BaseRuleVisitor {
|
|
10
|
+
visitHTMLConditionalElementNode(node: HTMLConditionalElementNode): void {
|
|
11
|
+
const tagName = node.tag_name?.value || "element"
|
|
12
|
+
const condition = node.condition || "condition"
|
|
13
|
+
|
|
14
|
+
const suggestion = dedent`
|
|
15
|
+
Consider using a \`capture\` block instead:
|
|
16
|
+
|
|
17
|
+
<% content = capture do %>
|
|
18
|
+
... your content here ...
|
|
19
|
+
<% end %>
|
|
20
|
+
|
|
21
|
+
<%= ${condition} ? content_tag(:${tagName}, content) : content %>
|
|
22
|
+
`
|
|
23
|
+
|
|
24
|
+
this.addOffense(
|
|
25
|
+
dedent`
|
|
26
|
+
Avoid opening and closing \`<${tagName}>\` tags in separate conditional blocks with the same condition. \
|
|
27
|
+
This pattern is difficult to read and maintain. ${suggestion}
|
|
28
|
+
`,
|
|
29
|
+
node.location,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
this.visitChildNodes(node)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class ERBNoConditionalHTMLElementRule extends ParserRule {
|
|
37
|
+
static ruleName = "erb-no-conditional-html-element"
|
|
38
|
+
|
|
39
|
+
get defaultConfig(): FullRuleConfig {
|
|
40
|
+
return {
|
|
41
|
+
enabled: true,
|
|
42
|
+
severity: "error"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
47
|
+
const visitor = new ERBNoConditionalHTMLElementRuleVisitor(this.ruleName, context)
|
|
48
|
+
|
|
49
|
+
visitor.visit(result.value)
|
|
50
|
+
|
|
51
|
+
return visitor.offenses
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ParserRule } from "../types.js"
|
|
2
|
+
import { BaseRuleVisitor } from "./rule-utils.js"
|
|
3
|
+
|
|
4
|
+
import type { ParseResult, HTMLConditionalOpenTagNode } from "@herb-tools/core"
|
|
5
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
6
|
+
|
|
7
|
+
class ERBNoConditionalOpenTagRuleVisitor extends BaseRuleVisitor {
|
|
8
|
+
visitHTMLConditionalOpenTagNode(node: HTMLConditionalOpenTagNode): void {
|
|
9
|
+
const tagName = node.tag_name?.value || "element"
|
|
10
|
+
|
|
11
|
+
this.addOffense(
|
|
12
|
+
`Avoid using ERB conditionals to split the open and closing tag of \`<${tagName}>\` element.`,
|
|
13
|
+
node.location,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
this.visitChildNodes(node)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class ERBNoConditionalOpenTagRule extends ParserRule {
|
|
21
|
+
static ruleName = "erb-no-conditional-open-tag"
|
|
22
|
+
|
|
23
|
+
get defaultConfig(): FullRuleConfig {
|
|
24
|
+
return {
|
|
25
|
+
enabled: true,
|
|
26
|
+
severity: "error"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
31
|
+
const visitor = new ERBNoConditionalOpenTagRuleVisitor(this.ruleName, context)
|
|
32
|
+
|
|
33
|
+
visitor.visit(result.value)
|
|
34
|
+
|
|
35
|
+
return visitor.offenses
|
|
36
|
+
}
|
|
37
|
+
}
|