@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
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Linter Rule: Disallow conditional open tags
|
|
2
|
+
|
|
3
|
+
**Rule:** `erb-no-conditional-open-tag`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Disallow the pattern of using ERB conditionals to vary only the open tag of an element (typically to conditionally change attributes) while sharing the same tag name, body content, and close tag.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
This pattern can be difficult to read and maintain. The conditional logic is split from the element's body and closing tag, making it harder to understand the full structure at a glance.
|
|
12
|
+
|
|
13
|
+
Prefer using:
|
|
14
|
+
|
|
15
|
+
- `content_tag` with conditional attributes
|
|
16
|
+
- A ternary or conditional for the class/attribute value directly
|
|
17
|
+
- Separate complete elements in each branch if the differences are significant
|
|
18
|
+
|
|
19
|
+
## Examples
|
|
20
|
+
|
|
21
|
+
### ✅ Good
|
|
22
|
+
|
|
23
|
+
Using conditional attributes directly:
|
|
24
|
+
|
|
25
|
+
```erb
|
|
26
|
+
<div class="<%= @condition ? 'a' : 'b' %>">
|
|
27
|
+
Content
|
|
28
|
+
</div>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Using `content_tag` with conditional options:
|
|
32
|
+
|
|
33
|
+
```erb
|
|
34
|
+
<%= content_tag :div, class: (@condition ? 'a' : 'b') do %>
|
|
35
|
+
Content
|
|
36
|
+
<% end %>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Using `class_names` helper:
|
|
40
|
+
|
|
41
|
+
```erb
|
|
42
|
+
<div class="<%= class_names('base', 'a': @condition, 'b': !@condition) %>">
|
|
43
|
+
Content
|
|
44
|
+
</div>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Complete separate elements when differences are significant:
|
|
48
|
+
|
|
49
|
+
```erb
|
|
50
|
+
<% if @condition %>
|
|
51
|
+
<div class="a" data-foo="bar">Content</div>
|
|
52
|
+
<% else %>
|
|
53
|
+
<div class="b" data-baz="qux">Content</div>
|
|
54
|
+
<% end %>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Self-closing/void elements in conditionals (each branch is complete):
|
|
58
|
+
|
|
59
|
+
```erb
|
|
60
|
+
<% if @large %>
|
|
61
|
+
<img src="photo.jpg" width="800" height="600">
|
|
62
|
+
<% else %>
|
|
63
|
+
<img src="photo.jpg" width="400" height="300">
|
|
64
|
+
<% end %>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 🚫 Bad
|
|
68
|
+
|
|
69
|
+
Conditional open tags with shared body and close tag:
|
|
70
|
+
|
|
71
|
+
```erb
|
|
72
|
+
<% if @condition %>
|
|
73
|
+
<div class="a">
|
|
74
|
+
<% else %>
|
|
75
|
+
<div class="b">
|
|
76
|
+
<% end %>
|
|
77
|
+
Content
|
|
78
|
+
</div>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```erb
|
|
82
|
+
<% if @with_icon %>
|
|
83
|
+
<button class="btn btn-icon" aria-label="Action">
|
|
84
|
+
<% else %>
|
|
85
|
+
<button class="btn">
|
|
86
|
+
<% end %>
|
|
87
|
+
Click me
|
|
88
|
+
</button>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Open tag in conditional without else branch:
|
|
92
|
+
|
|
93
|
+
```erb
|
|
94
|
+
<% if @wrap %>
|
|
95
|
+
<div class="wrapper">
|
|
96
|
+
<% end %>
|
|
97
|
+
Content
|
|
98
|
+
</div>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Missing open tag in else branch:
|
|
102
|
+
|
|
103
|
+
```erb
|
|
104
|
+
<% if @style == "a" %>
|
|
105
|
+
<div class="a">
|
|
106
|
+
<% elsif @style == "b" %>
|
|
107
|
+
<div class="b">
|
|
108
|
+
<% else %>
|
|
109
|
+
<% end %>
|
|
110
|
+
Content
|
|
111
|
+
</div>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Missing open tag in elsif branch:
|
|
115
|
+
|
|
116
|
+
```erb
|
|
117
|
+
<% if @style == "a" %>
|
|
118
|
+
<div class="a">
|
|
119
|
+
<% elsif @style == "b" %>
|
|
120
|
+
<%# no-open-tag %>
|
|
121
|
+
<% else %>
|
|
122
|
+
<div class="c">
|
|
123
|
+
<% end %>
|
|
124
|
+
Content
|
|
125
|
+
</div>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## References
|
|
129
|
+
|
|
130
|
+
\-
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Linter Rule: Disallow duplicate elements across conditional branches
|
|
2
|
+
|
|
3
|
+
**Rule:** `erb-no-duplicate-branch-elements`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Disallow the same HTML elements wrapping content in every branch of an ERB conditional (`if/elsif/else`, `unless/else`, `case/when/else`). When all branches share identical wrapper elements, those elements should be hoisted outside the conditional. Only flags when all branches are covered (i.e., an `else` clause is present).
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
Duplicated wrapper elements across all branches of a conditional are unnecessary repetition. Moving them outside the conditional reduces template size, makes the structure clearer, and avoids the risk of branches getting out of sync when one is updated but others are forgotten.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
### ✅ Good
|
|
16
|
+
|
|
17
|
+
Elements hoisted outside the conditional:
|
|
18
|
+
|
|
19
|
+
```erb
|
|
20
|
+
<div class="wrapper">
|
|
21
|
+
<% if condition %>
|
|
22
|
+
Hello World
|
|
23
|
+
<% else %>
|
|
24
|
+
Goodbye World
|
|
25
|
+
<% end %>
|
|
26
|
+
</div>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Branches with different elements:
|
|
30
|
+
|
|
31
|
+
```erb
|
|
32
|
+
<% if condition %>
|
|
33
|
+
<div>Hello World</div>
|
|
34
|
+
<% else %>
|
|
35
|
+
<span>World</span>
|
|
36
|
+
<% end %>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Same tag name but different attributes:
|
|
40
|
+
|
|
41
|
+
```erb
|
|
42
|
+
<% if condition %>
|
|
43
|
+
<div class="a">Hello World</div>
|
|
44
|
+
<% else %>
|
|
45
|
+
<div class="b">World</div>
|
|
46
|
+
<% end %>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Incomplete branch coverage:
|
|
50
|
+
|
|
51
|
+
```erb
|
|
52
|
+
<% if condition %>
|
|
53
|
+
<div>Hello World</div>
|
|
54
|
+
<% end %>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 🚫 Bad
|
|
58
|
+
|
|
59
|
+
```erb
|
|
60
|
+
<% if condition %>
|
|
61
|
+
<div>Hello World</div>
|
|
62
|
+
<% else %>
|
|
63
|
+
<div>Goodbye World</div>
|
|
64
|
+
<% end %>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```erb
|
|
68
|
+
<% if condition %>
|
|
69
|
+
<div>Hello World</div>
|
|
70
|
+
<% elsif other %>
|
|
71
|
+
<div>Goodbye World</div>
|
|
72
|
+
<% else %>
|
|
73
|
+
<div>Default</div>
|
|
74
|
+
<% end %>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```erb
|
|
78
|
+
<% case value %>
|
|
79
|
+
<% when "a" %>
|
|
80
|
+
<div>Hello World</div>
|
|
81
|
+
<% when "b" %>
|
|
82
|
+
<div>Goodbye World</div>
|
|
83
|
+
<% else %>
|
|
84
|
+
<div>Default</div>
|
|
85
|
+
<% end %>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```erb
|
|
89
|
+
<% if condition %>
|
|
90
|
+
<div><p>Hello World</p></div>
|
|
91
|
+
<% else %>
|
|
92
|
+
<div><p>Goodbye World</p></div>
|
|
93
|
+
<% end %>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## References
|
|
97
|
+
|
|
98
|
+
\-
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Linter Rule: Disallow inline case conditions
|
|
2
|
+
|
|
3
|
+
**Rule:** `erb-no-inline-case-conditions`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Disallow placing `case` and its first `when`/`in` condition in the same ERB tag. When a `case` statement and its condition appear in a single ERB tag (e.g., `<% case x when y %>`), the parser cannot reliably process, compile, or format the template. This rule flags such patterns and guides users toward separate ERB tags.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
ERB templates that combine `case` with a `when` or `in` condition in a single tag create parsing ambiguity. The parser creates synthetic condition nodes to handle this pattern in non-strict mode, but the resulting AST cannot be reliably formatted or compiled.
|
|
12
|
+
|
|
13
|
+
Using separate ERB tags for `case` and its conditions:
|
|
14
|
+
|
|
15
|
+
- Makes the template structure unambiguous for the parser
|
|
16
|
+
- Enables proper formatting and compilation
|
|
17
|
+
- Improves readability by clearly separating the case expression from its branches
|
|
18
|
+
- Follows the conventional ERB style used across the Ruby on Rails ecosystem
|
|
19
|
+
|
|
20
|
+
## Examples
|
|
21
|
+
|
|
22
|
+
### ✅ Good
|
|
23
|
+
|
|
24
|
+
`case`/`when` in separate ERB tags:
|
|
25
|
+
|
|
26
|
+
```erb
|
|
27
|
+
<% case variable %>
|
|
28
|
+
<% when "a" %>
|
|
29
|
+
A
|
|
30
|
+
<% when "b" %>
|
|
31
|
+
B
|
|
32
|
+
<% else %>
|
|
33
|
+
Other
|
|
34
|
+
<% end %>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`case`/`in` (pattern matching) in separate ERB tags:
|
|
38
|
+
|
|
39
|
+
```erb
|
|
40
|
+
<% case value %>
|
|
41
|
+
<% in 1 %>
|
|
42
|
+
One
|
|
43
|
+
<% in 2 %>
|
|
44
|
+
Two
|
|
45
|
+
<% else %>
|
|
46
|
+
Other
|
|
47
|
+
<% end %>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### 🚫 Bad
|
|
51
|
+
|
|
52
|
+
Inline `case`/`when` in a single ERB tag:
|
|
53
|
+
|
|
54
|
+
```erb
|
|
55
|
+
<% case variable when "a" %>
|
|
56
|
+
A
|
|
57
|
+
<% when "b" %>
|
|
58
|
+
B
|
|
59
|
+
<% end %>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Inline `case`/`in` in a single ERB tag:
|
|
63
|
+
|
|
64
|
+
```erb
|
|
65
|
+
<% case value in 1 %>
|
|
66
|
+
One
|
|
67
|
+
<% in 2 %>
|
|
68
|
+
Two
|
|
69
|
+
<% end %>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`case`/`when` on separate lines but still in the same ERB tag:
|
|
73
|
+
|
|
74
|
+
```erb
|
|
75
|
+
<% case variable
|
|
76
|
+
when "a" %>
|
|
77
|
+
A
|
|
78
|
+
<% when "b" %>
|
|
79
|
+
B
|
|
80
|
+
<% end %>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## References
|
|
84
|
+
|
|
85
|
+
\-
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Linter Rule: Disallow instance variables in partials
|
|
2
|
+
|
|
3
|
+
**Rule:** `erb-no-instance-variables-in-partials`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Prevent usage of instance variables inside ERB partials. Using instance variables inside partials can cause issues as their dependency is defined outside of the partial itself. This makes partials more fragile and less reusable. Local variables should be passed directly to partial renders.
|
|
8
|
+
|
|
9
|
+
A partial is any template whose filename begins with an underscore (e.g. `_card.html.erb`).
|
|
10
|
+
|
|
11
|
+
Instance variables in partials create implicit dependencies on the controller or parent view, making partials harder to reuse, test, and reason about. Passing data as local variables makes the partial's interface explicit and self-documenting.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
### ✅ Good
|
|
16
|
+
|
|
17
|
+
```erb [app/views/posts/index.html.erb]
|
|
18
|
+
<%= render partial: "posts/card", locals: { post: @post } %>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```erb [app/views/posts/_card.html.erb]
|
|
22
|
+
<div>
|
|
23
|
+
<%= post.title %>
|
|
24
|
+
</div>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 🚫 Bad
|
|
28
|
+
|
|
29
|
+
```erb [app/views/posts/index.html.erb]
|
|
30
|
+
<%= render partial: "posts/card" %>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```erb [app/views/posts/_card.html.erb]
|
|
34
|
+
<div>
|
|
35
|
+
<%= @post.title %>
|
|
36
|
+
</div>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## References
|
|
40
|
+
|
|
41
|
+
- [Rails Guide: Using Partials](https://guides.rubyonrails.org/layouts_and_rendering.html#using-partials)
|
|
42
|
+
- [Rails Guide: Passing Local Variables](https://guides.rubyonrails.org/layouts_and_rendering.html#passing-local-variables)
|
|
43
|
+
- [Inspiration: ERB Lint `PartialInstanceVariable` rule](https://github.com/Shopify/erb_lint?tab=readme-ov-file#partialinstancevariable)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Linter Rule: Disallow ERB interpolation inside CSS class names
|
|
2
|
+
|
|
3
|
+
**Rule:** `erb-no-interpolated-class-names`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Disallow ERB expressions that are embedded within CSS class names (e.g., `bg-<%= color %>-400`). Standalone ERB expressions that output complete class names are allowed.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
Since tools like Tailwind CSS and [Herb's Tailwind class sorter](https://herb-tools.dev/projects/rewriter#tailwind-class-sorter) scan your source files as plain text, they have no way of understanding string interpolation. A class like `bg-<%= color %>-400` doesn't exist as a complete string anywhere in the source, so Tailwind won't generate it and the class sorter can't recognize or reorder it.
|
|
12
|
+
|
|
13
|
+
Beyond tooling, interpolated class names are also impossible to search for in editors. Searching for `bg-red-400` will never match `bg-<%= color %>-400`, making it difficult to find all usages of a class name across the codebase. Static analysis tools face the same problem since they can't determine which class names a template produces without evaluating the Ruby expression at runtime.
|
|
14
|
+
|
|
15
|
+
Instead, always use complete class names in your templates. This keeps each class name searchable, sortable, and statically analyzable.
|
|
16
|
+
|
|
17
|
+
## Examples
|
|
18
|
+
|
|
19
|
+
### ✅ Good
|
|
20
|
+
|
|
21
|
+
```erb
|
|
22
|
+
<div class="bg-blue-400 <%= dynamic_classes %> text-green-400"></div>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```erb
|
|
26
|
+
<div class="<%= classes %>"></div>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```erb
|
|
30
|
+
<div class="<%= a %> bg-blue-500 <%= b %>"></div>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```erb
|
|
34
|
+
<div class="<%= class_names('bg-blue-400': blue?, 'bg-red-400': red?) %>"></div>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```erb
|
|
38
|
+
<div class="<%= error? ? 'text-red-600' : 'text-green-600' %>"></div>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 🚫 Bad
|
|
42
|
+
|
|
43
|
+
```erb
|
|
44
|
+
<div class="bg-<%= color %>-400"></div>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```erb
|
|
48
|
+
<div class="bg-<%= suffix %>"></div>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```erb
|
|
52
|
+
<div class="<%= prefix %>-blue-500"></div>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## References
|
|
56
|
+
|
|
57
|
+
- [Tailwind CSS: Dynamic class names](https://tailwindcss.com/docs/detecting-classes-in-source-files#dynamic-class-names)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Linter Rule: Disallow `javascript_tag` helper
|
|
2
|
+
|
|
3
|
+
**Rule:** `erb-no-javascript-tag-helper`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
The `javascript_tag do` helper syntax is deprecated. Use inline `<script>` tags instead, which allows the linter to properly analyze ERB output within JavaScript.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
The `javascript_tag` helper renders its block as raw text, which means unsafe ERB interpolation inside it cannot be detected by other safety rules like `erb-no-unsafe-script-interpolation` or `erb-no-statement-in-script`. By using inline `<script>` tags instead, the linter can properly parse and validate that Ruby data is safely serialized with `.to_json` before being interpolated into JavaScript.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
### ✅ Good
|
|
16
|
+
|
|
17
|
+
```erb
|
|
18
|
+
<script>
|
|
19
|
+
if (a < 1) { alert("hello") }
|
|
20
|
+
</script>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 🚫 Bad
|
|
24
|
+
|
|
25
|
+
```erb
|
|
26
|
+
<%= javascript_tag do %>
|
|
27
|
+
if (a < 1) { <%= unsafe %> }
|
|
28
|
+
<% end %>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## References
|
|
32
|
+
|
|
33
|
+
- [Shopify/better-html — `NoJavascriptTagHelper`](https://github.com/Shopify/better-html/blob/main/lib/better_html/test_helper/safe_erb/no_javascript_tag_helper.rb)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Linter Rule: Disallow ERB output in attribute names
|
|
2
|
+
|
|
3
|
+
**Rule:** `erb-no-output-in-attribute-name`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
ERB output tags (`<%= %>`) are not allowed in HTML attribute names. Use static attribute names with dynamic values instead.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
ERB output in attribute names (e.g., `<div data-<%= key %>="value">`) allows dynamic control over which attributes are rendered. When such a value is user-controlled, an attacker can inject arbitrary attributes including JavaScript event handlers, achieving cross-site scripting (XSS).
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
### Good
|
|
16
|
+
|
|
17
|
+
```erb
|
|
18
|
+
<div class="<%= css_class %>"></div>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```erb
|
|
22
|
+
<input type="text" data-target="value">
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Bad
|
|
26
|
+
|
|
27
|
+
```erb
|
|
28
|
+
<div data-<%= key %>="value"></div>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```erb
|
|
32
|
+
<div data-<%= key1 %>="value1" data-<%= key2 %>="value2"></div>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## References
|
|
36
|
+
|
|
37
|
+
- [Shopify/erb_lint: `ErbSafety`](https://github.com/Shopify/erb_lint/tree/main?tab=readme-ov-file#erbsafety)
|
|
38
|
+
- [Shopify/better_html](https://github.com/Shopify/better_html)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Linter Rule: Disallow ERB output in attribute position
|
|
2
|
+
|
|
3
|
+
**Rule:** `erb-no-output-in-attribute-position`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
ERB output tags (`<%= %>` or `<%== %>`) are not allowed in attribute position. Use ERB control flow (`<% %>`) with static attribute names instead.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
ERB output tags in attribute positions (e.g., `<div <%= attributes %>>`) allow arbitrary attribute injection at runtime. An attacker could inject event handler attributes like `onmouseover` or `onfocus` to execute JavaScript.
|
|
12
|
+
|
|
13
|
+
For example, a common pattern like:
|
|
14
|
+
|
|
15
|
+
```erb
|
|
16
|
+
<div <%= "hidden" if index != 0 %>>...</div>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
should be rewritten to use control flow with static attributes:
|
|
20
|
+
|
|
21
|
+
```erb
|
|
22
|
+
<div <% if index != 0 %> hidden <% end %>>...</div>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This ensures attribute names are always statically defined and prevents arbitrary attribute injection.
|
|
26
|
+
|
|
27
|
+
## Examples
|
|
28
|
+
|
|
29
|
+
### Good
|
|
30
|
+
|
|
31
|
+
```erb
|
|
32
|
+
<div class="<%= css_class %>"></div>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```erb
|
|
36
|
+
<input value="<%= user.name %>">
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```erb
|
|
40
|
+
<div <% if active? %> class="active" <% end %>></div>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Bad
|
|
44
|
+
|
|
45
|
+
```erb
|
|
46
|
+
<div <%= data_attributes %>></div>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```erb
|
|
50
|
+
<div <%== raw_attributes %>></div>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```erb
|
|
54
|
+
<div <%= first_attrs %> <%= second_attrs %>></div>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## References
|
|
58
|
+
|
|
59
|
+
- [Shopify/erb_lint: `ErbSafety`](https://github.com/Shopify/erb_lint/tree/main?tab=readme-ov-file#erbsafety)
|
|
60
|
+
- [Shopify/better_html](https://github.com/Shopify/better_html)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Linter Rule: Disallow `<%==` in attribute values
|
|
2
|
+
|
|
3
|
+
**Rule:** `erb-no-raw-output-in-attribute-value`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
ERB interpolation with `<%==` inside HTML attribute values is never safe. The `<%==` syntax bypasses HTML escaping entirely, allowing arbitrary attribute injection and XSS attacks. Use `<%=` instead to ensure proper escaping.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
The `<%==` syntax outputs content without any HTML escaping. In an attribute value context, this means an attacker can inject a quote character to terminate the attribute, then inject arbitrary attributes including JavaScript event handlers. Even when combined with `.to_json`, using `<%==` in attributes is unsafe because it bypasses the template engine's built-in escaping that prevents attribute breakout.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
### ✅ Good
|
|
16
|
+
|
|
17
|
+
```erb
|
|
18
|
+
<div class="<%= user_input %>"></div>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 🚫 Bad
|
|
22
|
+
|
|
23
|
+
```erb
|
|
24
|
+
<div class="<%== user_input %>"></div>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```erb
|
|
28
|
+
<a href="<%== unsafe %>">Link</a>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```erb
|
|
32
|
+
<a onclick="method(<%== unsafe.to_json %>)"></a>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## References
|
|
36
|
+
|
|
37
|
+
- [Shopify/better-html — `TagInterpolation`](https://github.com/Shopify/better-html/blob/main/lib/better_html/test_helper/safe_erb/tag_interpolation.rb)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Linter Rule: Disallow ERB statements inside `<script>` tags
|
|
2
|
+
|
|
3
|
+
**Rule:** `erb-no-statement-in-script`
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Only insert expressions (`<%=` or `<%==`) inside `<script>` tags, never statements (`<% %>`). Statement tags inside `<script>` are likely a mistake, the author probably meant to use `<%= %>` to output a value.
|
|
8
|
+
|
|
9
|
+
## Rationale
|
|
10
|
+
|
|
11
|
+
ERB statement tags inside `<script>` tags execute Ruby code but produce no output into the JavaScript context, which is rarely intentional. If you need to interpolate a value into JavaScript, use expression tags (`<%= %>`) with `.to_json` for safe serialization. If you need conditional logic, restructure the template to keep control flow outside the `<script>` tag.
|
|
12
|
+
|
|
13
|
+
Exceptions: `<% end %>` is allowed (for closing blocks), ERB comments (`<%# %>`) are allowed, and `<script type="text/html">` allows statement tags since it contains HTML templates, not JavaScript.
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
|
|
17
|
+
### ✅ Good
|
|
18
|
+
|
|
19
|
+
```erb
|
|
20
|
+
<script>
|
|
21
|
+
var myValue = <%== value.to_json %>;
|
|
22
|
+
if (myValue) doSomething();
|
|
23
|
+
</script>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```erb
|
|
27
|
+
<script type="text/template">
|
|
28
|
+
<%= ui_form do %>
|
|
29
|
+
<div></div>
|
|
30
|
+
<% end %>
|
|
31
|
+
</script>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```erb
|
|
35
|
+
<script type="text/javascript">
|
|
36
|
+
<%# comment %>
|
|
37
|
+
</script>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```erb
|
|
41
|
+
<script type="text/html">
|
|
42
|
+
<% if condition %>
|
|
43
|
+
<p>Content</p>
|
|
44
|
+
<% end %>
|
|
45
|
+
</script>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 🚫 Bad
|
|
49
|
+
|
|
50
|
+
```erb
|
|
51
|
+
<script>
|
|
52
|
+
<% if value %>
|
|
53
|
+
doSomething();
|
|
54
|
+
<% end %>
|
|
55
|
+
</script>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```erb
|
|
59
|
+
<script type="text/javascript">
|
|
60
|
+
<% if foo? %>
|
|
61
|
+
bla
|
|
62
|
+
<% end %>
|
|
63
|
+
</script>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## References
|
|
67
|
+
|
|
68
|
+
- [Shopify/better-html - `NoStatements`](https://github.com/Shopify/better-html/blob/main/lib/better_html/test_helper/safe_erb/no_statements.rb)
|