@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/rules/rule-utils.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Visitor,
|
|
3
3
|
Location,
|
|
4
|
-
|
|
5
|
-
hasDynamicAttributeName as hasNodeDynamicAttributeName,
|
|
6
|
-
getCombinedAttributeName,
|
|
4
|
+
Position,
|
|
7
5
|
hasERBOutput,
|
|
8
|
-
getStaticContentFromNodes,
|
|
9
|
-
hasStaticContent,
|
|
10
6
|
isEffectivelyStatic,
|
|
11
|
-
getValidatableStaticContent
|
|
7
|
+
getValidatableStaticContent,
|
|
8
|
+
getAttributeName,
|
|
9
|
+
getStaticAttributeValue,
|
|
10
|
+
hasDynamicAttributeNameOnAttribute as hasDynamicAttributeName,
|
|
11
|
+
getCombinedAttributeNameString,
|
|
12
|
+
getAttributeValueNodes,
|
|
13
|
+
getAttributeValue,
|
|
14
|
+
forEachAttribute,
|
|
12
15
|
} from "@herb-tools/core"
|
|
13
16
|
|
|
14
17
|
import type {
|
|
15
|
-
ERBContentNode,
|
|
16
18
|
HTMLAttributeNameNode,
|
|
17
19
|
HTMLAttributeNode,
|
|
18
|
-
HTMLAttributeValueNode,
|
|
19
20
|
HTMLElementNode,
|
|
20
21
|
HTMLOpenTagNode,
|
|
21
|
-
LiteralNode,
|
|
22
22
|
LexResult,
|
|
23
23
|
Token,
|
|
24
24
|
Node
|
|
@@ -27,7 +27,7 @@ import type {
|
|
|
27
27
|
import { DEFAULT_LINT_CONTEXT } from "../types.js"
|
|
28
28
|
|
|
29
29
|
import type * as Nodes from "@herb-tools/core"
|
|
30
|
-
import type { UnboundLintOffense, LintContext, BaseAutofixContext } from "../types.js"
|
|
30
|
+
import type { UnboundLintOffense, LintContext, LintSeverity, BaseAutofixContext } from "../types.js"
|
|
31
31
|
|
|
32
32
|
export enum ControlFlowType {
|
|
33
33
|
CONDITIONAL,
|
|
@@ -53,7 +53,7 @@ export abstract class BaseRuleVisitor<TAutofixContext extends BaseAutofixContext
|
|
|
53
53
|
* Helper method to create an unbound lint offense (without severity).
|
|
54
54
|
* The Linter will bind severity based on the rule's config.
|
|
55
55
|
*/
|
|
56
|
-
protected createOffense(message: string, location: Location, autofixContext?: TAutofixContext): UnboundLintOffense<TAutofixContext> {
|
|
56
|
+
protected createOffense(message: string, location: Location, autofixContext?: TAutofixContext, severity?: LintSeverity): UnboundLintOffense<TAutofixContext> {
|
|
57
57
|
return {
|
|
58
58
|
rule: this.ruleName,
|
|
59
59
|
code: this.ruleName,
|
|
@@ -61,14 +61,15 @@ export abstract class BaseRuleVisitor<TAutofixContext extends BaseAutofixContext
|
|
|
61
61
|
message,
|
|
62
62
|
location,
|
|
63
63
|
autofixContext,
|
|
64
|
+
severity,
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
/**
|
|
68
69
|
* Helper method to add an offense to the offenses array
|
|
69
70
|
*/
|
|
70
|
-
protected addOffense(message: string, location: Location, autofixContext?: TAutofixContext): void {
|
|
71
|
-
this.offenses.push(this.createOffense(message, location, autofixContext))
|
|
71
|
+
protected addOffense(message: string, location: Location, autofixContext?: TAutofixContext, severity?: LintSeverity): void {
|
|
72
|
+
this.offenses.push(this.createOffense(message, location, autofixContext, severity))
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -162,236 +163,6 @@ export abstract class ControlFlowTrackingVisitor<TAutofixContext extends BaseAut
|
|
|
162
163
|
protected abstract onExitBranch(stateToRestore: TBranchState): void
|
|
163
164
|
}
|
|
164
165
|
|
|
165
|
-
/**
|
|
166
|
-
* Gets attributes from an HTMLOpenTagNode
|
|
167
|
-
*/
|
|
168
|
-
export function getAttributes(node: HTMLOpenTagNode): HTMLAttributeNode[] {
|
|
169
|
-
return node.children.filter(node => node.type === "AST_HTML_ATTRIBUTE_NODE") as HTMLAttributeNode[]
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Gets the tag name from an HTML tag node (lowercased)
|
|
174
|
-
*/
|
|
175
|
-
export function getTagName(node: HTMLElementNode | HTMLOpenTagNode | null | undefined): string | null {
|
|
176
|
-
if (!node) return null
|
|
177
|
-
|
|
178
|
-
return node.tag_name?.value.toLowerCase() || null
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Gets the attribute name from an HTMLAttributeNode (lowercased)
|
|
183
|
-
* Returns null if the attribute name contains dynamic content (ERB)
|
|
184
|
-
*/
|
|
185
|
-
export function getAttributeName(attributeNode: HTMLAttributeNode, lowercase = true): string | null {
|
|
186
|
-
if (attributeNode.name?.type === "AST_HTML_ATTRIBUTE_NAME_NODE") {
|
|
187
|
-
const nameNode = attributeNode.name as HTMLAttributeNameNode
|
|
188
|
-
const staticName = getStaticAttributeName(nameNode)
|
|
189
|
-
|
|
190
|
-
if (!lowercase) return staticName
|
|
191
|
-
|
|
192
|
-
return staticName ? staticName.toLowerCase() : null
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return null
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Checks if an attribute has a dynamic (ERB-containing) name
|
|
200
|
-
*/
|
|
201
|
-
export function hasDynamicAttributeName(attributeNode: HTMLAttributeNode): boolean {
|
|
202
|
-
if (attributeNode.name?.type === "AST_HTML_ATTRIBUTE_NAME_NODE") {
|
|
203
|
-
const nameNode = attributeNode.name as HTMLAttributeNameNode
|
|
204
|
-
return hasNodeDynamicAttributeName(nameNode)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return false
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Gets the combined string representation of an attribute name (for debugging)
|
|
212
|
-
* This includes both static content and ERB syntax
|
|
213
|
-
*/
|
|
214
|
-
export function getCombinedAttributeNameString(attributeNode: HTMLAttributeNode): string {
|
|
215
|
-
if (attributeNode.name?.type === "AST_HTML_ATTRIBUTE_NAME_NODE") {
|
|
216
|
-
const nameNode = attributeNode.name as HTMLAttributeNameNode
|
|
217
|
-
|
|
218
|
-
return getCombinedAttributeName(nameNode)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return ""
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Checks if an attribute value contains only static content (no ERB)
|
|
226
|
-
*/
|
|
227
|
-
export function hasStaticAttributeValue(attributeNode: HTMLAttributeNode): boolean {
|
|
228
|
-
const valueNode = attributeNode.value as HTMLAttributeValueNode | null
|
|
229
|
-
|
|
230
|
-
if (!valueNode?.children) return false
|
|
231
|
-
|
|
232
|
-
return valueNode.children.every(child => child.type === "AST_LITERAL_NODE")
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Checks if an attribute value contains dynamic content (ERB)
|
|
237
|
-
*/
|
|
238
|
-
export function hasDynamicAttributeValue(attributeNode: HTMLAttributeNode): boolean {
|
|
239
|
-
const valueNode = attributeNode.value as HTMLAttributeValueNode | null
|
|
240
|
-
|
|
241
|
-
if (!valueNode?.children) return false
|
|
242
|
-
|
|
243
|
-
return valueNode.children.some(child => child.type === "AST_ERB_CONTENT_NODE")
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Gets the static string value of an attribute (returns null if it contains ERB)
|
|
248
|
-
*/
|
|
249
|
-
export function getStaticAttributeValue(attributeNode: HTMLAttributeNode): string | null {
|
|
250
|
-
if (!hasStaticAttributeValue(attributeNode)) return null
|
|
251
|
-
|
|
252
|
-
const valueNode = attributeNode.value as HTMLAttributeValueNode
|
|
253
|
-
|
|
254
|
-
const result = valueNode.children
|
|
255
|
-
?.filter(child => child.type === "AST_LITERAL_NODE")
|
|
256
|
-
.map(child => (child as LiteralNode).content)
|
|
257
|
-
.join("") || ""
|
|
258
|
-
|
|
259
|
-
return result
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Gets the value nodes array for dynamic inspection
|
|
264
|
-
*/
|
|
265
|
-
export function getAttributeValueNodes(attributeNode: HTMLAttributeNode): Node[] {
|
|
266
|
-
const valueNode = attributeNode.value as HTMLAttributeValueNode | null
|
|
267
|
-
|
|
268
|
-
return valueNode?.children || []
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Checks if an attribute value contains any static content (for validation purposes)
|
|
273
|
-
*/
|
|
274
|
-
export function hasStaticAttributeValueContent(attributeNode: HTMLAttributeNode): boolean {
|
|
275
|
-
const valueNodes = getAttributeValueNodes(attributeNode)
|
|
276
|
-
|
|
277
|
-
return hasStaticContent(valueNodes)
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Gets the static content of an attribute value (all literal parts combined)
|
|
282
|
-
* Returns the concatenated literal content, or null if no literal nodes exist
|
|
283
|
-
*/
|
|
284
|
-
export function getStaticAttributeValueContent(attributeNode: HTMLAttributeNode): string | null {
|
|
285
|
-
const valueNodes = getAttributeValueNodes(attributeNode)
|
|
286
|
-
|
|
287
|
-
return getStaticContentFromNodes(valueNodes)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Gets the attribute value content from an HTMLAttributeValueNode
|
|
292
|
-
*/
|
|
293
|
-
export function getAttributeValue(attributeNode: HTMLAttributeNode): string | null {
|
|
294
|
-
const valueNode: HTMLAttributeValueNode | null = attributeNode.value as HTMLAttributeValueNode
|
|
295
|
-
|
|
296
|
-
if (valueNode === null) return null
|
|
297
|
-
|
|
298
|
-
if (valueNode.type !== "AST_HTML_ATTRIBUTE_VALUE_NODE" || !valueNode.children?.length) {
|
|
299
|
-
return null
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
let result = ""
|
|
303
|
-
|
|
304
|
-
for (const child of valueNode.children) {
|
|
305
|
-
switch (child.type) {
|
|
306
|
-
case "AST_ERB_CONTENT_NODE": {
|
|
307
|
-
const erbNode = child as ERBContentNode
|
|
308
|
-
|
|
309
|
-
if (erbNode.content) {
|
|
310
|
-
result += `${erbNode.tag_opening?.value}${erbNode.content.value}${erbNode.tag_closing?.value}`
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
break
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
case "AST_LITERAL_NODE": {
|
|
317
|
-
result += (child as LiteralNode).content
|
|
318
|
-
break
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
return result
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Checks if an attribute has a value
|
|
328
|
-
*/
|
|
329
|
-
export function hasAttributeValue(attributeNode: HTMLAttributeNode): boolean {
|
|
330
|
-
return attributeNode.value?.type === "AST_HTML_ATTRIBUTE_VALUE_NODE"
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Gets the quote type used for an attribute value
|
|
335
|
-
*/
|
|
336
|
-
export function getAttributeValueQuoteType(nodeOrAttribute: HTMLAttributeNode | HTMLAttributeValueNode): "single" | "double" | "none" | null {
|
|
337
|
-
let valueNode: HTMLAttributeValueNode | undefined
|
|
338
|
-
|
|
339
|
-
if (nodeOrAttribute.type === "AST_HTML_ATTRIBUTE_NODE") {
|
|
340
|
-
const attributeNode = nodeOrAttribute as HTMLAttributeNode
|
|
341
|
-
|
|
342
|
-
if (attributeNode.value?.type === "AST_HTML_ATTRIBUTE_VALUE_NODE") {
|
|
343
|
-
valueNode = attributeNode.value as HTMLAttributeValueNode
|
|
344
|
-
}
|
|
345
|
-
} else if (nodeOrAttribute.type === "AST_HTML_ATTRIBUTE_VALUE_NODE") {
|
|
346
|
-
valueNode = nodeOrAttribute as HTMLAttributeValueNode
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (valueNode) {
|
|
350
|
-
if (valueNode.quoted && valueNode.open_quote) {
|
|
351
|
-
return valueNode.open_quote.value === '"' ? "double" : "single"
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return "none"
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
return null
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Finds an attribute by name in a list of attributes
|
|
362
|
-
*/
|
|
363
|
-
export function findAttributeByName(attributes: Node[], attributeName: string): HTMLAttributeNode | null {
|
|
364
|
-
for (const child of attributes) {
|
|
365
|
-
if (child.type === "AST_HTML_ATTRIBUTE_NODE") {
|
|
366
|
-
const attributeNode = child as HTMLAttributeNode
|
|
367
|
-
const name = getAttributeName(attributeNode)
|
|
368
|
-
|
|
369
|
-
if (name === attributeName.toLowerCase()) {
|
|
370
|
-
return attributeNode
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
return null
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Checks if a tag has a specific attribute
|
|
380
|
-
*/
|
|
381
|
-
export function hasAttribute(node: HTMLOpenTagNode | null | undefined, attributeName: string): boolean {
|
|
382
|
-
if (!node) return false
|
|
383
|
-
|
|
384
|
-
return getAttribute(node, attributeName) !== null
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Checks if a tag has a specific attribute
|
|
389
|
-
*/
|
|
390
|
-
export function getAttribute(node: HTMLOpenTagNode, attributeName: string): HTMLAttributeNode | null {
|
|
391
|
-
const attributes = getAttributes(node)
|
|
392
|
-
|
|
393
|
-
return findAttributeByName(attributes, attributeName)
|
|
394
|
-
}
|
|
395
166
|
|
|
396
167
|
/**
|
|
397
168
|
* Common HTML element categorization
|
|
@@ -485,6 +256,26 @@ export const VALID_ARIA_ROLES = new Set([
|
|
|
485
256
|
"log", "marquee"
|
|
486
257
|
]);
|
|
487
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Abstract ARIA roles used to support the WAI-ARIA Roles Model.
|
|
261
|
+
* Authors MUST NOT use abstract roles in content.
|
|
262
|
+
* @see https://www.w3.org/TR/wai-aria-1.0/roles#abstract_roles
|
|
263
|
+
*/
|
|
264
|
+
export const ABSTRACT_ARIA_ROLES = new Set([
|
|
265
|
+
"command",
|
|
266
|
+
"composite",
|
|
267
|
+
"input",
|
|
268
|
+
"landmark",
|
|
269
|
+
"range",
|
|
270
|
+
"roletype",
|
|
271
|
+
"section",
|
|
272
|
+
"sectionhead",
|
|
273
|
+
"select",
|
|
274
|
+
"structure",
|
|
275
|
+
"widget",
|
|
276
|
+
"window"
|
|
277
|
+
]);
|
|
278
|
+
|
|
488
279
|
/**
|
|
489
280
|
* Parameter types for AttributeVisitorMixin methods
|
|
490
281
|
*/
|
|
@@ -638,7 +429,7 @@ export abstract class AttributeVisitorMixin<TAutofixContext extends BaseAutofixC
|
|
|
638
429
|
private checkAttributesOnNode(node: HTMLOpenTagNode): void {
|
|
639
430
|
forEachAttribute(node, (attributeNode) => {
|
|
640
431
|
const staticAttributeName = getAttributeName(attributeNode)
|
|
641
|
-
const originalAttributeName = getAttributeName(attributeNode, false) ||
|
|
432
|
+
const originalAttributeName = getAttributeName(attributeNode, false) || ""
|
|
642
433
|
const isDynamicName = hasDynamicAttributeName(attributeNode)
|
|
643
434
|
const staticAttributeValue = getStaticAttributeValue(attributeNode)
|
|
644
435
|
const valueNodes = getAttributeValueNodes(attributeNode)
|
|
@@ -707,35 +498,6 @@ export abstract class AttributeVisitorMixin<TAutofixContext extends BaseAutofixC
|
|
|
707
498
|
}
|
|
708
499
|
}
|
|
709
500
|
|
|
710
|
-
/**
|
|
711
|
-
* Checks if an attribute value is quoted
|
|
712
|
-
*/
|
|
713
|
-
export function isAttributeValueQuoted(attributeNode: HTMLAttributeNode): boolean {
|
|
714
|
-
if (attributeNode.value?.type === "AST_HTML_ATTRIBUTE_VALUE_NODE") {
|
|
715
|
-
const valueNode = attributeNode.value as HTMLAttributeValueNode
|
|
716
|
-
|
|
717
|
-
return !!valueNode.quoted
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
return false
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
/**
|
|
724
|
-
* Iterates over all attributes of a tag node, calling the callback for each attribute
|
|
725
|
-
*/
|
|
726
|
-
export function forEachAttribute(
|
|
727
|
-
node: HTMLOpenTagNode,
|
|
728
|
-
callback: (attributeNode: HTMLAttributeNode) => void
|
|
729
|
-
): void {
|
|
730
|
-
const attributes = getAttributes(node)
|
|
731
|
-
|
|
732
|
-
for (const child of attributes) {
|
|
733
|
-
if (child.type === "AST_HTML_ATTRIBUTE_NODE") {
|
|
734
|
-
callback(child as HTMLAttributeNode)
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
|
|
739
501
|
/**
|
|
740
502
|
* Base lexer visitor class that provides common functionality for lexer-based rule visitors
|
|
741
503
|
*/
|
|
@@ -753,7 +515,7 @@ export abstract class BaseLexerRuleVisitor<TAutofixContext extends BaseAutofixCo
|
|
|
753
515
|
* Helper method to create an unbound lint offense (without severity).
|
|
754
516
|
* The Linter will bind severity based on the rule's config.
|
|
755
517
|
*/
|
|
756
|
-
protected createOffense(message: string, location: Location, autofixContext?: TAutofixContext): UnboundLintOffense<TAutofixContext> {
|
|
518
|
+
protected createOffense(message: string, location: Location, autofixContext?: TAutofixContext, severity?: LintSeverity): UnboundLintOffense<TAutofixContext> {
|
|
757
519
|
return {
|
|
758
520
|
rule: this.ruleName,
|
|
759
521
|
code: this.ruleName,
|
|
@@ -761,14 +523,15 @@ export abstract class BaseLexerRuleVisitor<TAutofixContext extends BaseAutofixCo
|
|
|
761
523
|
message,
|
|
762
524
|
location,
|
|
763
525
|
autofixContext,
|
|
526
|
+
severity,
|
|
764
527
|
}
|
|
765
528
|
}
|
|
766
529
|
|
|
767
530
|
/**
|
|
768
531
|
* Helper method to add an offense to the offenses array
|
|
769
532
|
*/
|
|
770
|
-
protected addOffense(message: string, location: Location, autofixContext?: TAutofixContext): void {
|
|
771
|
-
this.offenses.push(this.createOffense(message, location, autofixContext))
|
|
533
|
+
protected addOffense(message: string, location: Location, autofixContext?: TAutofixContext, severity?: LintSeverity): void {
|
|
534
|
+
this.offenses.push(this.createOffense(message, location, autofixContext, severity))
|
|
772
535
|
}
|
|
773
536
|
|
|
774
537
|
/**
|
|
@@ -815,7 +578,7 @@ export abstract class BaseSourceRuleVisitor<TAutofixContext extends BaseAutofixC
|
|
|
815
578
|
* Helper method to create an unbound lint offense (without severity).
|
|
816
579
|
* The Linter will bind severity based on the rule's config.
|
|
817
580
|
*/
|
|
818
|
-
protected createOffense(message: string, location: Location, autofixContext?: TAutofixContext): UnboundLintOffense<TAutofixContext> {
|
|
581
|
+
protected createOffense(message: string, location: Location, autofixContext?: TAutofixContext, severity?: LintSeverity): UnboundLintOffense<TAutofixContext> {
|
|
819
582
|
return {
|
|
820
583
|
rule: this.ruleName,
|
|
821
584
|
code: this.ruleName,
|
|
@@ -823,14 +586,15 @@ export abstract class BaseSourceRuleVisitor<TAutofixContext extends BaseAutofixC
|
|
|
823
586
|
message,
|
|
824
587
|
location,
|
|
825
588
|
autofixContext,
|
|
589
|
+
severity,
|
|
826
590
|
}
|
|
827
591
|
}
|
|
828
592
|
|
|
829
593
|
/**
|
|
830
594
|
* Helper method to add an offense to the offenses array
|
|
831
595
|
*/
|
|
832
|
-
protected addOffense(message: string, location: Location, autofixContext?: TAutofixContext): void {
|
|
833
|
-
this.offenses.push(this.createOffense(message, location, autofixContext))
|
|
596
|
+
protected addOffense(message: string, location: Location, autofixContext?: TAutofixContext, severity?: LintSeverity): void {
|
|
597
|
+
this.offenses.push(this.createOffense(message, location, autofixContext, severity))
|
|
834
598
|
}
|
|
835
599
|
|
|
836
600
|
/**
|
|
@@ -1065,3 +829,122 @@ export function isHeadTag(tagName: string): boolean {
|
|
|
1065
829
|
(isHeadOnlyTag(tag) || isHeadAndBodyTag(tag))
|
|
1066
830
|
)
|
|
1067
831
|
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Converts a character offset in a source string to a Position (line, column).
|
|
835
|
+
* Lines are 1-based, columns are 0-based.
|
|
836
|
+
*/
|
|
837
|
+
export function positionFromOffset(source: string, offset: number): Position {
|
|
838
|
+
let line = 1
|
|
839
|
+
let column = 0
|
|
840
|
+
let currentOffset = 0
|
|
841
|
+
|
|
842
|
+
for (let i = 0; i < source.length && currentOffset < offset; i++) {
|
|
843
|
+
const char = source[i]
|
|
844
|
+
currentOffset++
|
|
845
|
+
if (char === "\n") {
|
|
846
|
+
line++
|
|
847
|
+
column = 0
|
|
848
|
+
} else {
|
|
849
|
+
column++
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
return new Position(line, column)
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Creates a Location from a source string, a start offset, and a length.
|
|
858
|
+
*/
|
|
859
|
+
export function locationFromOffset(source: string, startOffset: number, length: number): Location {
|
|
860
|
+
const start = positionFromOffset(source, startOffset)
|
|
861
|
+
const end = positionFromOffset(source, startOffset + length)
|
|
862
|
+
return Location.from(start.line, start.column, end.line, end.column)
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Checks if a position (line, column) is within a node's location range.
|
|
867
|
+
* @param node - The node to check
|
|
868
|
+
* @param line - Line number (1-based)
|
|
869
|
+
* @param column - Column number (0-based)
|
|
870
|
+
* @returns true if the position is within the node's location
|
|
871
|
+
*/
|
|
872
|
+
function isPositionInNode(node: Node, line: number, column: number): boolean {
|
|
873
|
+
if (!node.location) return false
|
|
874
|
+
|
|
875
|
+
const { start, end } = node.location
|
|
876
|
+
|
|
877
|
+
if (line < start.line) return false
|
|
878
|
+
if (line === start.line && column < start.column) return false
|
|
879
|
+
|
|
880
|
+
if (line > end.line) return false
|
|
881
|
+
if (line === end.line && column >= end.column) return false
|
|
882
|
+
|
|
883
|
+
return true
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Finds a node in the AST that contains a specific position.
|
|
888
|
+
* Returns the deepest (most specific) node that matches the position and optional predicate.
|
|
889
|
+
*
|
|
890
|
+
* @param root - The root node to search from
|
|
891
|
+
* @param line - Line number (1-based)
|
|
892
|
+
* @param column - Column number (0-based)
|
|
893
|
+
* @param predicate - Optional predicate function to filter nodes
|
|
894
|
+
* @returns The matching node or null if not found
|
|
895
|
+
*/
|
|
896
|
+
export function findNodeAtPosition(root: Node, line: number, column: number, predicate?: (node: Node) => boolean): Node | null {
|
|
897
|
+
let bestMatch: Node | null = null
|
|
898
|
+
const visited = new Set<Node>()
|
|
899
|
+
|
|
900
|
+
function search(node: Node): void {
|
|
901
|
+
if (!node || visited.has(node)) return
|
|
902
|
+
visited.add(node)
|
|
903
|
+
|
|
904
|
+
if (isPositionInNode(node, line, column)) {
|
|
905
|
+
if (!predicate || predicate(node)) {
|
|
906
|
+
if (!bestMatch || isMoreSpecific(node, bestMatch)) {
|
|
907
|
+
bestMatch = node
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
const nodeAny = node as any
|
|
913
|
+
|
|
914
|
+
if (typeof nodeAny.compactChildNodes === 'function') {
|
|
915
|
+
for (const child of nodeAny.compactChildNodes()) {
|
|
916
|
+
search(child)
|
|
917
|
+
}
|
|
918
|
+
} else {
|
|
919
|
+
if (nodeAny.children && Array.isArray(nodeAny.children)) {
|
|
920
|
+
for (const child of nodeAny.children) {
|
|
921
|
+
if (child) search(child)
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
if (nodeAny.body && Array.isArray(nodeAny.body)) {
|
|
926
|
+
for (const child of nodeAny.body) {
|
|
927
|
+
if (child) search(child)
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
function isMoreSpecific(nodeA: Node, nodeB: Node): boolean {
|
|
934
|
+
if (!nodeA.location || !nodeB.location) return false
|
|
935
|
+
|
|
936
|
+
const aStart = nodeA.location.start
|
|
937
|
+
const aEnd = nodeA.location.end
|
|
938
|
+
const bStart = nodeB.location.start
|
|
939
|
+
const bEnd = nodeB.location.end
|
|
940
|
+
|
|
941
|
+
const startsAtOrAfter = aStart.line > bStart.line || (aStart.line === bStart.line && aStart.column >= bStart.column)
|
|
942
|
+
const endsAtOrBefore = aEnd.line < bEnd.line || (aEnd.line === bEnd.line && aEnd.column <= bEnd.column)
|
|
943
|
+
|
|
944
|
+
return startsAtOrAfter && endsAtOrBefore
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
search(root)
|
|
948
|
+
|
|
949
|
+
return bestMatch
|
|
950
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ParserRule } from "../types.js"
|
|
2
2
|
import { BaseRuleVisitor, SVG_CAMEL_CASE_ELEMENTS, SVG_LOWERCASE_TO_CAMELCASE } from "./rule-utils.js"
|
|
3
|
+
import { isHTMLOpenTagNode, isHTMLCloseTagNode } from "@herb-tools/core"
|
|
3
4
|
|
|
4
5
|
import type { UnboundLintOffense, LintOffense, LintContext, BaseAutofixContext, Mutable, FullRuleConfig } from "../types.js"
|
|
5
6
|
import type { HTMLElementNode, HTMLOpenTagNode, HTMLCloseTagNode, ParseResult } from "@herb-tools/core"
|
|
@@ -25,12 +26,12 @@ class SVGTagNameCapitalizationVisitor extends BaseRuleVisitor<SVGTagNameCapitali
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
if (this.insideSVG) {
|
|
28
|
-
if (node.open_tag) {
|
|
29
|
-
this.checkTagName(node.open_tag
|
|
29
|
+
if (isHTMLOpenTagNode(node.open_tag)) {
|
|
30
|
+
this.checkTagName(node.open_tag)
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
if (node.close_tag) {
|
|
33
|
-
this.checkTagName(node.close_tag
|
|
33
|
+
if (node.close_tag && isHTMLCloseTagNode(node.close_tag)) {
|
|
34
|
+
this.checkTagName(node.close_tag)
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
|
|
@@ -40,7 +41,6 @@ class SVGTagNameCapitalizationVisitor extends BaseRuleVisitor<SVGTagNameCapitali
|
|
|
40
41
|
|
|
41
42
|
private checkTagName(node: HTMLOpenTagNode | HTMLCloseTagNode): void {
|
|
42
43
|
const tagName = node.tag_name?.value
|
|
43
|
-
|
|
44
44
|
if (!tagName) return
|
|
45
45
|
|
|
46
46
|
if (SVG_CAMEL_CASE_ELEMENTS.has(tagName)) return
|
|
@@ -51,8 +51,8 @@ class SVGTagNameCapitalizationVisitor extends BaseRuleVisitor<SVGTagNameCapitali
|
|
|
51
51
|
if (correctCamelCase && tagName !== correctCamelCase) {
|
|
52
52
|
let type: string = node.type
|
|
53
53
|
|
|
54
|
-
if (node
|
|
55
|
-
if (node
|
|
54
|
+
if (isHTMLOpenTagNode(node)) type = "Opening"
|
|
55
|
+
if (isHTMLCloseTagNode(node)) type = "Closing"
|
|
56
56
|
|
|
57
57
|
this.addOffense(
|
|
58
58
|
`${type} SVG tag name \`${tagName}\` should use proper capitalization. Use \`${correctCamelCase}\` instead.`,
|
|
@@ -69,7 +69,7 @@ class SVGTagNameCapitalizationVisitor extends BaseRuleVisitor<SVGTagNameCapitali
|
|
|
69
69
|
|
|
70
70
|
export class SVGTagNameCapitalizationRule extends ParserRule<SVGTagNameCapitalizationAutofixContext> {
|
|
71
71
|
static autocorrectable = true
|
|
72
|
-
|
|
72
|
+
static ruleName = "svg-tag-name-capitalization"
|
|
73
73
|
|
|
74
74
|
get defaultConfig(): FullRuleConfig {
|
|
75
75
|
return {
|
|
@@ -79,7 +79,7 @@ export class SVGTagNameCapitalizationRule extends ParserRule<SVGTagNameCapitaliz
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense<SVGTagNameCapitalizationAutofixContext>[] {
|
|
82
|
-
const visitor = new SVGTagNameCapitalizationVisitor(this.
|
|
82
|
+
const visitor = new SVGTagNameCapitalizationVisitor(this.ruleName, context)
|
|
83
83
|
|
|
84
84
|
visitor.visit(result.value)
|
|
85
85
|
|
|
@@ -89,7 +89,7 @@ export class SVGTagNameCapitalizationRule extends ParserRule<SVGTagNameCapitaliz
|
|
|
89
89
|
autofix(offense: LintOffense<SVGTagNameCapitalizationAutofixContext>, result: ParseResult, _context?: Partial<LintContext>): ParseResult | null {
|
|
90
90
|
if (!offense.autofixContext) return null
|
|
91
91
|
|
|
92
|
-
const { node: {
|
|
92
|
+
const { node: { tag_name }, correctCamelCase } = offense.autofixContext
|
|
93
93
|
|
|
94
94
|
if (!tag_name) return null
|
|
95
95
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { BaseRuleVisitor } from "./rule-utils.js"
|
|
2
|
+
import { getAttribute } from "@herb-tools/core"
|
|
3
|
+
|
|
4
|
+
import { ParserRule } from "../types.js"
|
|
5
|
+
import type { UnboundLintOffense, LintContext, FullRuleConfig } from "../types.js"
|
|
6
|
+
import type { HTMLOpenTagNode, ParseResult } from "@herb-tools/core"
|
|
7
|
+
|
|
8
|
+
class TurboPermanentRequireIdVisitor extends BaseRuleVisitor {
|
|
9
|
+
visitHTMLOpenTagNode(node: HTMLOpenTagNode): void {
|
|
10
|
+
this.checkTurboPermanent(node)
|
|
11
|
+
super.visitHTMLOpenTagNode(node)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
private checkTurboPermanent(node: HTMLOpenTagNode): void {
|
|
15
|
+
const turboPermanentAttribute = getAttribute(node, "data-turbo-permanent")
|
|
16
|
+
|
|
17
|
+
if (!turboPermanentAttribute) {
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const idAttribute = getAttribute(node, "id")
|
|
22
|
+
|
|
23
|
+
if (!idAttribute) {
|
|
24
|
+
this.addOffense(
|
|
25
|
+
"Elements with `data-turbo-permanent` must have an `id` attribute. Without an `id`, Turbo can't track the element across page changes and the permanent behavior won't work as expected.",
|
|
26
|
+
turboPermanentAttribute.location,
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class TurboPermanentRequireIdRule extends ParserRule {
|
|
33
|
+
static ruleName = "turbo-permanent-require-id"
|
|
34
|
+
|
|
35
|
+
get defaultConfig(): FullRuleConfig {
|
|
36
|
+
return {
|
|
37
|
+
enabled: true,
|
|
38
|
+
severity: "error"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
check(result: ParseResult, context?: Partial<LintContext>): UnboundLintOffense[] {
|
|
43
|
+
const visitor = new TurboPermanentRequireIdVisitor(this.ruleName, context)
|
|
44
|
+
|
|
45
|
+
visitor.visit(result.value)
|
|
46
|
+
|
|
47
|
+
return visitor.offenses
|
|
48
|
+
}
|
|
49
|
+
}
|