@herb-tools/linter 0.8.10 → 0.9.1
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 +61834 -17340
- package/dist/herb-lint.js.map +1 -1
- package/dist/index.cjs +3109 -956
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2969 -897
- package/dist/index.js.map +1 -1
- package/dist/lint-worker.js +72889 -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 +32163 -7842
- package/dist/loader.cjs.map +1 -1
- package/dist/loader.js +32121 -7828
- 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/rules/actionview-no-silent-render.js +31 -0
- package/dist/rules/actionview-no-silent-render.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 +5 -3
- 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 +329 -0
- package/dist/rules/erb-no-duplicate-branch-elements.js.map +1 -0
- package/dist/rules/erb-no-empty-control-flow.js +190 -0
- package/dist/rules/erb-no-empty-control-flow.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/rules/erb-no-silent-statement.js +44 -0
- package/dist/rules/erb-no-silent-statement.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 +88 -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 +4 -4
- 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 +27 -4
- 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 +144 -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} +62 -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 +2 -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/actionview-no-silent-render.d.ts +9 -0
- 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 +18 -0
- package/dist/types/{src/rules/html-anchor-require-href.d.ts → rules/erb-no-empty-control-flow.d.ts} +2 -2
- 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/{src/rules/html-no-title-attribute.d.ts → rules/erb-no-silent-statement.d.ts} +4 -3
- 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 +9 -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-no-empty-attributes.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/rules/html-details-has-summary.d.ts +9 -0
- 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 +27 -4
- package/dist/types/rules/parser-no-errors.d.ts +1 -1
- package/dist/types/rules/rule-utils.d.ts +36 -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 +26 -7
- package/dist/types/urls.d.ts +1 -0
- package/dist/{src/types.js → types.js} +56 -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 +26 -2
- package/docs/rules/actionview-no-silent-helper.md +57 -0
- package/docs/rules/actionview-no-silent-render.md +47 -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-empty-control-flow.md +83 -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-silent-statement.md +53 -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 +140 -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 +11 -10
- 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 +22 -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/actionview-no-silent-render.ts +44 -0
- package/src/rules/erb-comment-syntax.ts +2 -2
- package/src/rules/erb-no-case-node-children.ts +5 -3
- 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 +436 -0
- package/src/rules/erb-no-empty-control-flow.ts +255 -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-statement.ts +58 -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 +122 -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 +4 -4
- 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 +28 -4
- package/src/rules/parser-no-errors.ts +3 -3
- package/src/rules/rule-utils.ts +166 -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 +66 -10
- package/src/types.ts +80 -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-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
|
+
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,8 @@ 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 {
|
|
30
|
+
import type { DiagnosticTag } from "@herb-tools/core"
|
|
31
|
+
import type { UnboundLintOffense, LintContext, LintSeverity, BaseAutofixContext } from "../types.js"
|
|
31
32
|
|
|
32
33
|
export enum ControlFlowType {
|
|
33
34
|
CONDITIONAL,
|
|
@@ -53,7 +54,7 @@ export abstract class BaseRuleVisitor<TAutofixContext extends BaseAutofixContext
|
|
|
53
54
|
* Helper method to create an unbound lint offense (without severity).
|
|
54
55
|
* The Linter will bind severity based on the rule's config.
|
|
55
56
|
*/
|
|
56
|
-
protected createOffense(message: string, location: Location, autofixContext?: TAutofixContext): UnboundLintOffense<TAutofixContext> {
|
|
57
|
+
protected createOffense(message: string, location: Location, autofixContext?: TAutofixContext, severity?: LintSeverity, tags?: DiagnosticTag[]): UnboundLintOffense<TAutofixContext> {
|
|
57
58
|
return {
|
|
58
59
|
rule: this.ruleName,
|
|
59
60
|
code: this.ruleName,
|
|
@@ -61,14 +62,16 @@ export abstract class BaseRuleVisitor<TAutofixContext extends BaseAutofixContext
|
|
|
61
62
|
message,
|
|
62
63
|
location,
|
|
63
64
|
autofixContext,
|
|
65
|
+
severity,
|
|
66
|
+
tags,
|
|
64
67
|
}
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
/**
|
|
68
71
|
* Helper method to add an offense to the offenses array
|
|
69
72
|
*/
|
|
70
|
-
protected addOffense(message: string, location: Location, autofixContext?: TAutofixContext): void {
|
|
71
|
-
this.offenses.push(this.createOffense(message, location, autofixContext))
|
|
73
|
+
protected addOffense(message: string, location: Location, autofixContext?: TAutofixContext, severity?: LintSeverity, tags?: DiagnosticTag[]): void {
|
|
74
|
+
this.offenses.push(this.createOffense(message, location, autofixContext, severity, tags))
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
|
|
@@ -162,236 +165,6 @@ export abstract class ControlFlowTrackingVisitor<TAutofixContext extends BaseAut
|
|
|
162
165
|
protected abstract onExitBranch(stateToRestore: TBranchState): void
|
|
163
166
|
}
|
|
164
167
|
|
|
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
168
|
|
|
396
169
|
/**
|
|
397
170
|
* Common HTML element categorization
|
|
@@ -485,6 +258,26 @@ export const VALID_ARIA_ROLES = new Set([
|
|
|
485
258
|
"log", "marquee"
|
|
486
259
|
]);
|
|
487
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Abstract ARIA roles used to support the WAI-ARIA Roles Model.
|
|
263
|
+
* Authors MUST NOT use abstract roles in content.
|
|
264
|
+
* @see https://www.w3.org/TR/wai-aria-1.0/roles#abstract_roles
|
|
265
|
+
*/
|
|
266
|
+
export const ABSTRACT_ARIA_ROLES = new Set([
|
|
267
|
+
"command",
|
|
268
|
+
"composite",
|
|
269
|
+
"input",
|
|
270
|
+
"landmark",
|
|
271
|
+
"range",
|
|
272
|
+
"roletype",
|
|
273
|
+
"section",
|
|
274
|
+
"sectionhead",
|
|
275
|
+
"select",
|
|
276
|
+
"structure",
|
|
277
|
+
"widget",
|
|
278
|
+
"window"
|
|
279
|
+
]);
|
|
280
|
+
|
|
488
281
|
/**
|
|
489
282
|
* Parameter types for AttributeVisitorMixin methods
|
|
490
283
|
*/
|
|
@@ -638,7 +431,7 @@ export abstract class AttributeVisitorMixin<TAutofixContext extends BaseAutofixC
|
|
|
638
431
|
private checkAttributesOnNode(node: HTMLOpenTagNode): void {
|
|
639
432
|
forEachAttribute(node, (attributeNode) => {
|
|
640
433
|
const staticAttributeName = getAttributeName(attributeNode)
|
|
641
|
-
const originalAttributeName = getAttributeName(attributeNode, false) ||
|
|
434
|
+
const originalAttributeName = getAttributeName(attributeNode, false) || ""
|
|
642
435
|
const isDynamicName = hasDynamicAttributeName(attributeNode)
|
|
643
436
|
const staticAttributeValue = getStaticAttributeValue(attributeNode)
|
|
644
437
|
const valueNodes = getAttributeValueNodes(attributeNode)
|
|
@@ -707,35 +500,6 @@ export abstract class AttributeVisitorMixin<TAutofixContext extends BaseAutofixC
|
|
|
707
500
|
}
|
|
708
501
|
}
|
|
709
502
|
|
|
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
503
|
/**
|
|
740
504
|
* Base lexer visitor class that provides common functionality for lexer-based rule visitors
|
|
741
505
|
*/
|
|
@@ -753,7 +517,7 @@ export abstract class BaseLexerRuleVisitor<TAutofixContext extends BaseAutofixCo
|
|
|
753
517
|
* Helper method to create an unbound lint offense (without severity).
|
|
754
518
|
* The Linter will bind severity based on the rule's config.
|
|
755
519
|
*/
|
|
756
|
-
protected createOffense(message: string, location: Location, autofixContext?: TAutofixContext): UnboundLintOffense<TAutofixContext> {
|
|
520
|
+
protected createOffense(message: string, location: Location, autofixContext?: TAutofixContext, severity?: LintSeverity, tags?: DiagnosticTag[]): UnboundLintOffense<TAutofixContext> {
|
|
757
521
|
return {
|
|
758
522
|
rule: this.ruleName,
|
|
759
523
|
code: this.ruleName,
|
|
@@ -761,14 +525,16 @@ export abstract class BaseLexerRuleVisitor<TAutofixContext extends BaseAutofixCo
|
|
|
761
525
|
message,
|
|
762
526
|
location,
|
|
763
527
|
autofixContext,
|
|
528
|
+
severity,
|
|
529
|
+
tags,
|
|
764
530
|
}
|
|
765
531
|
}
|
|
766
532
|
|
|
767
533
|
/**
|
|
768
534
|
* Helper method to add an offense to the offenses array
|
|
769
535
|
*/
|
|
770
|
-
protected addOffense(message: string, location: Location, autofixContext?: TAutofixContext): void {
|
|
771
|
-
this.offenses.push(this.createOffense(message, location, autofixContext))
|
|
536
|
+
protected addOffense(message: string, location: Location, autofixContext?: TAutofixContext, severity?: LintSeverity, tags?: DiagnosticTag[]): void {
|
|
537
|
+
this.offenses.push(this.createOffense(message, location, autofixContext, severity, tags))
|
|
772
538
|
}
|
|
773
539
|
|
|
774
540
|
/**
|
|
@@ -815,7 +581,7 @@ export abstract class BaseSourceRuleVisitor<TAutofixContext extends BaseAutofixC
|
|
|
815
581
|
* Helper method to create an unbound lint offense (without severity).
|
|
816
582
|
* The Linter will bind severity based on the rule's config.
|
|
817
583
|
*/
|
|
818
|
-
protected createOffense(message: string, location: Location, autofixContext?: TAutofixContext): UnboundLintOffense<TAutofixContext> {
|
|
584
|
+
protected createOffense(message: string, location: Location, autofixContext?: TAutofixContext, severity?: LintSeverity, tags?: DiagnosticTag[]): UnboundLintOffense<TAutofixContext> {
|
|
819
585
|
return {
|
|
820
586
|
rule: this.ruleName,
|
|
821
587
|
code: this.ruleName,
|
|
@@ -823,14 +589,16 @@ export abstract class BaseSourceRuleVisitor<TAutofixContext extends BaseAutofixC
|
|
|
823
589
|
message,
|
|
824
590
|
location,
|
|
825
591
|
autofixContext,
|
|
592
|
+
severity,
|
|
593
|
+
tags,
|
|
826
594
|
}
|
|
827
595
|
}
|
|
828
596
|
|
|
829
597
|
/**
|
|
830
598
|
* Helper method to add an offense to the offenses array
|
|
831
599
|
*/
|
|
832
|
-
protected addOffense(message: string, location: Location, autofixContext?: TAutofixContext): void {
|
|
833
|
-
this.offenses.push(this.createOffense(message, location, autofixContext))
|
|
600
|
+
protected addOffense(message: string, location: Location, autofixContext?: TAutofixContext, severity?: LintSeverity, tags?: DiagnosticTag[]): void {
|
|
601
|
+
this.offenses.push(this.createOffense(message, location, autofixContext, severity, tags))
|
|
834
602
|
}
|
|
835
603
|
|
|
836
604
|
/**
|
|
@@ -1065,3 +833,122 @@ export function isHeadTag(tagName: string): boolean {
|
|
|
1065
833
|
(isHeadOnlyTag(tag) || isHeadAndBodyTag(tag))
|
|
1066
834
|
)
|
|
1067
835
|
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Converts a character offset in a source string to a Position (line, column).
|
|
839
|
+
* Lines are 1-based, columns are 0-based.
|
|
840
|
+
*/
|
|
841
|
+
export function positionFromOffset(source: string, offset: number): Position {
|
|
842
|
+
let line = 1
|
|
843
|
+
let column = 0
|
|
844
|
+
let currentOffset = 0
|
|
845
|
+
|
|
846
|
+
for (let i = 0; i < source.length && currentOffset < offset; i++) {
|
|
847
|
+
const char = source[i]
|
|
848
|
+
currentOffset++
|
|
849
|
+
if (char === "\n") {
|
|
850
|
+
line++
|
|
851
|
+
column = 0
|
|
852
|
+
} else {
|
|
853
|
+
column++
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
return new Position(line, column)
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Creates a Location from a source string, a start offset, and a length.
|
|
862
|
+
*/
|
|
863
|
+
export function locationFromOffset(source: string, startOffset: number, length: number): Location {
|
|
864
|
+
const start = positionFromOffset(source, startOffset)
|
|
865
|
+
const end = positionFromOffset(source, startOffset + length)
|
|
866
|
+
return Location.from(start.line, start.column, end.line, end.column)
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Checks if a position (line, column) is within a node's location range.
|
|
871
|
+
* @param node - The node to check
|
|
872
|
+
* @param line - Line number (1-based)
|
|
873
|
+
* @param column - Column number (0-based)
|
|
874
|
+
* @returns true if the position is within the node's location
|
|
875
|
+
*/
|
|
876
|
+
function isPositionInNode(node: Node, line: number, column: number): boolean {
|
|
877
|
+
if (!node.location) return false
|
|
878
|
+
|
|
879
|
+
const { start, end } = node.location
|
|
880
|
+
|
|
881
|
+
if (line < start.line) return false
|
|
882
|
+
if (line === start.line && column < start.column) return false
|
|
883
|
+
|
|
884
|
+
if (line > end.line) return false
|
|
885
|
+
if (line === end.line && column >= end.column) return false
|
|
886
|
+
|
|
887
|
+
return true
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* Finds a node in the AST that contains a specific position.
|
|
892
|
+
* Returns the deepest (most specific) node that matches the position and optional predicate.
|
|
893
|
+
*
|
|
894
|
+
* @param root - The root node to search from
|
|
895
|
+
* @param line - Line number (1-based)
|
|
896
|
+
* @param column - Column number (0-based)
|
|
897
|
+
* @param predicate - Optional predicate function to filter nodes
|
|
898
|
+
* @returns The matching node or null if not found
|
|
899
|
+
*/
|
|
900
|
+
export function findNodeAtPosition(root: Node, line: number, column: number, predicate?: (node: Node) => boolean): Node | null {
|
|
901
|
+
let bestMatch: Node | null = null
|
|
902
|
+
const visited = new Set<Node>()
|
|
903
|
+
|
|
904
|
+
function search(node: Node): void {
|
|
905
|
+
if (!node || visited.has(node)) return
|
|
906
|
+
visited.add(node)
|
|
907
|
+
|
|
908
|
+
if (isPositionInNode(node, line, column)) {
|
|
909
|
+
if (!predicate || predicate(node)) {
|
|
910
|
+
if (!bestMatch || isMoreSpecific(node, bestMatch)) {
|
|
911
|
+
bestMatch = node
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const nodeAny = node as any
|
|
917
|
+
|
|
918
|
+
if (typeof nodeAny.compactChildNodes === 'function') {
|
|
919
|
+
for (const child of nodeAny.compactChildNodes()) {
|
|
920
|
+
search(child)
|
|
921
|
+
}
|
|
922
|
+
} else {
|
|
923
|
+
if (nodeAny.children && Array.isArray(nodeAny.children)) {
|
|
924
|
+
for (const child of nodeAny.children) {
|
|
925
|
+
if (child) search(child)
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
if (nodeAny.body && Array.isArray(nodeAny.body)) {
|
|
930
|
+
for (const child of nodeAny.body) {
|
|
931
|
+
if (child) search(child)
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function isMoreSpecific(nodeA: Node, nodeB: Node): boolean {
|
|
938
|
+
if (!nodeA.location || !nodeB.location) return false
|
|
939
|
+
|
|
940
|
+
const aStart = nodeA.location.start
|
|
941
|
+
const aEnd = nodeA.location.end
|
|
942
|
+
const bStart = nodeB.location.start
|
|
943
|
+
const bEnd = nodeB.location.end
|
|
944
|
+
|
|
945
|
+
const startsAtOrAfter = aStart.line > bStart.line || (aStart.line === bStart.line && aStart.column >= bStart.column)
|
|
946
|
+
const endsAtOrBefore = aEnd.line < bEnd.line || (aEnd.line === bEnd.line && aEnd.column <= bEnd.column)
|
|
947
|
+
|
|
948
|
+
return startsAtOrAfter && endsAtOrBefore
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
search(root)
|
|
952
|
+
|
|
953
|
+
return bestMatch
|
|
954
|
+
}
|
|
@@ -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
|
+
}
|