diff --git a/.gitignore b/.gitignore
index 3e88eb2cce..bc16c6be0c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@ npm-debug.log
# eslint-remote-tester
eslint-remote-tester-results
+package-lock.json
diff --git a/README.md b/README.md
index 7f28e984ba..9ffcfab0d9 100644
--- a/README.md
+++ b/README.md
@@ -110,6 +110,27 @@ module.exports = {
};
```
+### Strict Template Linting (Optional)
+
+For enhanced template linting in `gjs`/`gts` files, use the `strict-gjs` or `strict-gts` configs. These include additional template rules ported from `ember-template-lint`:
+
+```javascript
+// eslint.config.js
+const ember = require('eslint-plugin-ember');
+
+module.exports = [
+ ...ember.configs.recommended,
+ {
+ files: ['**/*.gts'],
+ extends: ['plugin:ember/strict-gts'],
+ },
+ {
+ files: ['**/*.gjs'],
+ extends: ['plugin:ember/strict-gjs'],
+ },
+];
+```
+
### rules applied to fcct templates
- semi rule, same as [prettier plugin](https://github.com/gitKrystan/prettier-plugin-ember-template-tag/issues/1)
@@ -160,6 +181,8 @@ rules in templates can be disabled with eslint directives with mustache or html
| ✅ | `recommended` |
|  | `recommended-gjs` |
|  | `recommended-gts` |
+| | `strict-gjs` |
+| | `strict-gts` |
@@ -174,6 +197,114 @@ rules in templates can be disabled with eslint directives with mustache or html
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
+### Accessibility
+
+| Name | Description | 💼 | 🔧 | 💡 |
+| :--------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------- | :------------------------------------------ | :- | :- |
+| [template-link-href-attributes](docs/rules/template-link-href-attributes.md) | require href attribute on link elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-abstract-roles](docs/rules/template-no-abstract-roles.md) | disallow abstract ARIA roles | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-accesskey-attribute](docs/rules/template-no-accesskey-attribute.md) | disallow accesskey attribute | ![badge-strict-gjs][] ![badge-strict-gts][] | 🔧 | |
+| [template-no-aria-hidden-body](docs/rules/template-no-aria-hidden-body.md) | disallow aria-hidden on body element | ![badge-strict-gjs][] ![badge-strict-gts][] | 🔧 | |
+| [template-no-aria-unsupported-elements](docs/rules/template-no-aria-unsupported-elements.md) | disallow ARIA roles, states, and properties on elements that do not support them | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-autofocus-attribute](docs/rules/template-no-autofocus-attribute.md) | disallow autofocus attribute | ![badge-strict-gjs][] ![badge-strict-gts][] | 🔧 | |
+| [template-no-down-event-binding](docs/rules/template-no-down-event-binding.md) | disallow mouse down event bindings | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-duplicate-landmark-elements](docs/rules/template-no-duplicate-landmark-elements.md) | disallow duplicate landmark elements without unique labels | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-empty-headings](docs/rules/template-no-empty-headings.md) | disallow empty heading elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-heading-inside-button](docs/rules/template-no-heading-inside-button.md) | disallow heading elements inside button elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-invalid-aria-attributes](docs/rules/template-no-invalid-aria-attributes.md) | disallow invalid aria-* attributes | | | |
+| [template-no-invalid-interactive](docs/rules/template-no-invalid-interactive.md) | disallow non-interactive elements with interactive handlers | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-invalid-link-text](docs/rules/template-no-invalid-link-text.md) | disallow invalid or uninformative link text content | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-invalid-link-title](docs/rules/template-no-invalid-link-title.md) | disallow invalid title attributes on link elements | | | |
+| [template-no-invalid-role](docs/rules/template-no-invalid-role.md) | disallow invalid ARIA roles | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-nested-interactive](docs/rules/template-no-nested-interactive.md) | disallow nested interactive elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-nested-landmark](docs/rules/template-no-nested-landmark.md) | disallow nested landmark elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-pointer-down-event-binding](docs/rules/template-no-pointer-down-event-binding.md) | disallow pointer down event bindings | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-positive-tabindex](docs/rules/template-no-positive-tabindex.md) | disallow positive tabindex values | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-redundant-landmark-role](docs/rules/template-no-redundant-landmark-role.md) | disallow redundant landmark roles that are implicit on HTML elements | ![badge-strict-gjs][] ![badge-strict-gts][] | 🔧 | |
+| [template-no-unsupported-role-attributes](docs/rules/template-no-unsupported-role-attributes.md) | disallow ARIA attributes that are not supported by the element role | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-require-aria-activedescendant-tabindex](docs/rules/template-require-aria-activedescendant-tabindex.md) | require elements with aria-activedescendant to be tabbable (have tabindex) | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-require-context-role](docs/rules/template-require-context-role.md) | require ARIA roles to be used in appropriate context | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-require-iframe-title](docs/rules/template-require-iframe-title.md) | require iframe elements to have a title attribute | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-require-input-label](docs/rules/template-require-input-label.md) | require label for form input elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-require-lang-attribute](docs/rules/template-require-lang-attribute.md) | require lang attribute on html element | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-require-mandatory-role-attributes](docs/rules/template-require-mandatory-role-attributes.md) | require mandatory ARIA attributes for ARIA roles | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-require-media-caption](docs/rules/template-require-media-caption.md) | require captions for audio and video elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-require-presentational-children](docs/rules/template-require-presentational-children.md) | require presentational elements to only contain presentational children | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-require-valid-alt-text](docs/rules/template-require-valid-alt-text.md) | require valid alt text for images | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-table-groups](docs/rules/template-table-groups.md) | require table elements to use table grouping elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+
+### Best Practices
+
+| Name | Description | 💼 | 🔧 | 💡 |
+| :--------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------- | :------------------------------------------ | :- | :- |
+| [template-no-action-modifiers](docs/rules/template-no-action-modifiers.md) | disallow usage of {{action}} modifiers | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-action-on-submit-button](docs/rules/template-no-action-on-submit-button.md) | disallow action attribute on submit buttons | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-ambiguous-glimmer-paths](docs/rules/template-no-ambiguous-glimmer-paths.md) | disallow ambiguous path in templates | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-args-paths](docs/rules/template-no-args-paths.md) | disallow @args in paths | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-arguments-for-html-elements](docs/rules/template-no-arguments-for-html-elements.md) | disallow @arguments on HTML elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-array-prototype-extensions](docs/rules/template-no-array-prototype-extensions.md) | disallow usage of Ember Array prototype extensions | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-at-ember-render-modifiers](docs/rules/template-no-at-ember-render-modifiers.md) | disallow usage of @ember/render-modifiers | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-attribute-splat-on-html-element](docs/rules/template-no-attribute-splat-on-html-element.md) | disallow ...attributes on HTML elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-attrs-splat](docs/rules/template-no-attrs-splat.md) | disallow attribute splat on components | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-bare-strings](docs/rules/template-no-bare-strings.md) | disallow bare strings in templates (require translation/localization) | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-bare-yield](docs/rules/template-no-bare-yield.md) | disallow {{yield}} without parameters outside of contextual components | | | |
+| [template-no-block-params](docs/rules/template-no-block-params.md) | disallow yielding/invoking a component block without parameters | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-block-params-for-html-elements](docs/rules/template-no-block-params-for-html-elements.md) | disallow block params on HTML elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-builtin-form-components](docs/rules/template-no-builtin-form-components.md) | disallow usage of built-in form components | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-capital-arguments](docs/rules/template-no-capital-arguments.md) | disallow capital arguments (use lowercase @arg instead of @Arg) | | | |
+| [template-no-chained-this](docs/rules/template-no-chained-this.md) | disallow chained property access on this | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-class-bindings](docs/rules/template-no-class-bindings.md) | disallow usage of class attribute bindings | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-curly-component-invocation](docs/rules/template-no-curly-component-invocation.md) | disallow curly component invocation | | | |
+| [template-no-debugger](docs/rules/template-no-debugger.md) | disallow {{debugger}} in templates | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-duplicate-attributes](docs/rules/template-no-duplicate-attributes.md) | disallow duplicate attribute names in templates | ![badge-strict-gjs][] ![badge-strict-gts][] | 🔧 | |
+| [template-no-duplicate-id](docs/rules/template-no-duplicate-id.md) | disallow duplicate id attributes | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-dynamic-subexpression-invocations](docs/rules/template-no-dynamic-subexpression-invocations.md) | disallow dynamic subexpression invocations | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-element-event-actions](docs/rules/template-no-element-event-actions.md) | disallow element event actions (use {{on}} modifier instead) | | | |
+| [template-no-extra-mut-helpers](docs/rules/template-no-extra-mut-helpers.md) | disallow unnecessary mut helpers | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-forbidden-elements](docs/rules/template-no-forbidden-elements.md) | disallow specific HTML elements | | | |
+| [template-no-html-comments](docs/rules/template-no-html-comments.md) | disallow HTML comments in templates | | | |
+| [template-no-implicit-this](docs/rules/template-no-implicit-this.md) | require explicit `this` in property access | | | |
+| [template-no-inline-event-handlers](docs/rules/template-no-inline-event-handlers.md) | disallow DOM event handler attributes | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-inline-linkto](docs/rules/template-no-inline-linkto.md) | disallow inline form of LinkTo component | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-inline-styles](docs/rules/template-no-inline-styles.md) | disallow inline styles | | | |
+| [template-no-input-block](docs/rules/template-no-input-block.md) | disallow block usage of {{input}} helper | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-input-placeholder](docs/rules/template-no-input-placeholder.md) | disallow placeholder attribute on input elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-input-tagname](docs/rules/template-no-input-tagname.md) | disallow tagName attribute on {{input}} helper | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-invalid-meta](docs/rules/template-no-invalid-meta.md) | disallow invalid meta tags | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-log](docs/rules/template-no-log.md) | disallow {{log}} in templates | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-model-argument-in-route-templates](docs/rules/template-no-model-argument-in-route-templates.md) | disallow @model argument in route templates | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-multiple-empty-lines](docs/rules/template-no-multiple-empty-lines.md) | disallow multiple consecutive empty lines in templates | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-mut-helper](docs/rules/template-no-mut-helper.md) | disallow usage of (mut) helper | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-negated-comparison](docs/rules/template-no-negated-comparison.md) | disallow negated comparisons in templates | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-negated-condition](docs/rules/template-no-negated-condition.md) | disallow negated conditions in if/unless | | | |
+| [template-no-nested-splattributes](docs/rules/template-no-nested-splattributes.md) | disallow nested ...attributes usage | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-obscure-array-access](docs/rules/template-no-obscure-array-access.md) | disallow obscure array access patterns like `objectPath.@each.property` | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-obsolete-elements](docs/rules/template-no-obsolete-elements.md) | disallow obsolete HTML elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-outlet-outside-routes](docs/rules/template-no-outlet-outside-routes.md) | disallow {{outlet}} outside of route templates | | | |
+| [template-no-page-title-component](docs/rules/template-no-page-title-component.md) | disallow usage of ember-page-title component | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-passed-in-event-handlers](docs/rules/template-no-passed-in-event-handlers.md) | disallow passing event handlers directly as component arguments | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-positional-data-test-selectors](docs/rules/template-no-positional-data-test-selectors.md) | disallow positional data-test selectors | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-potential-path-strings](docs/rules/template-no-potential-path-strings.md) | disallow potential path strings in templates | | | |
+| [template-no-redundant-fn](docs/rules/template-no-redundant-fn.md) | disallow unnecessary usage of (fn) helper | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-this-in-template-only-components](docs/rules/template-no-this-in-template-only-components.md) | disallow this in template-only components (gjs/gts) | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-trailing-spaces](docs/rules/template-no-trailing-spaces.md) | disallow trailing whitespace at the end of lines in templates | ![badge-strict-gjs][] ![badge-strict-gts][] | 🔧 | |
+| [template-no-unnecessary-component-helper](docs/rules/template-no-unnecessary-component-helper.md) | disallow unnecessary component helper | | | |
+| [template-no-unnecessary-concat](docs/rules/template-no-unnecessary-concat.md) | disallow unnecessary string concatenation | ![badge-strict-gjs][] ![badge-strict-gts][] | 🔧 | |
+| [template-no-unnecessary-curly-parens](docs/rules/template-no-unnecessary-curly-parens.md) | disallow unnecessary curlies around single values | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-unnecessary-service-injection-argument](docs/rules/template-no-unnecessary-service-injection-argument.md) | disallow unnecessary service injection argument | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-unused-block-params](docs/rules/template-no-unused-block-params.md) | disallow unused block parameters in templates | | | |
+| [template-no-valueless-arguments](docs/rules/template-no-valueless-arguments.md) | disallow valueless named arguments | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-whitespace-for-layout](docs/rules/template-no-whitespace-for-layout.md) | disallow using whitespace for layout purposes | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-yield-only](docs/rules/template-no-yield-only.md) | disallow components that only yield | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-yield-to-default](docs/rules/template-no-yield-to-default.md) | disallow yield to default block | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-require-button-type](docs/rules/template-require-button-type.md) | require button elements to have a valid type attribute | ![badge-strict-gjs][] ![badge-strict-gts][] | 🔧 | |
+| [template-require-each-key](docs/rules/template-require-each-key.md) | require key attribute in {{#each}} loops | | | |
+| [template-require-has-block-helper](docs/rules/template-require-has-block-helper.md) | require (has-block) helper usage instead of hasBlock property | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-self-closing-void-elements](docs/rules/template-self-closing-void-elements.md) | require self-closing on void elements | ![badge-strict-gjs][] ![badge-strict-gts][] | 🔧 | |
+| [template-simple-unless](docs/rules/template-simple-unless.md) | require simple conditions in unless blocks | | | |
+| [template-splat-attributes-only](docs/rules/template-splat-attributes-only.md) | disallow ...spread other than ...attributes | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-style-concatenation](docs/rules/template-style-concatenation.md) | disallow string concatenation in inline styles | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+
### Components
| Name | Description | 💼 | 🔧 | 💡 |
@@ -215,20 +346,31 @@ rules in templates can be disabled with eslint directives with mustache or html
### Deprecations
-| Name | Description | 💼 | 🔧 | 💡 |
-| :----------------------------------------------------------------------------------------------- | :-------------------------------------------------------- | :- | :- | :- |
-| [closure-actions](docs/rules/closure-actions.md) | enforce usage of closure actions | ✅ | | |
-| [new-module-imports](docs/rules/new-module-imports.md) | enforce using "New Module Imports" from Ember RFC #176 | ✅ | | |
-| [no-array-prototype-extensions](docs/rules/no-array-prototype-extensions.md) | disallow usage of Ember's `Array` prototype extensions | | 🔧 | |
-| [no-at-ember-render-modifiers](docs/rules/no-at-ember-render-modifiers.md) | disallow importing from @ember/render-modifiers | ✅ | | |
-| [no-deprecated-router-transition-methods](docs/rules/no-deprecated-router-transition-methods.md) | enforce usage of router service transition methods | ✅ | 🔧 | |
-| [no-function-prototype-extensions](docs/rules/no-function-prototype-extensions.md) | disallow usage of Ember's `function` prototype extensions | ✅ | | |
-| [no-implicit-injections](docs/rules/no-implicit-injections.md) | enforce usage of implicit service injections | ✅ | 🔧 | |
-| [no-mixins](docs/rules/no-mixins.md) | disallow the usage of mixins | ✅ | | |
-| [no-new-mixins](docs/rules/no-new-mixins.md) | disallow the creation of new mixins | ✅ | | |
-| [no-observers](docs/rules/no-observers.md) | disallow usage of observers | ✅ | | |
-| [no-old-shims](docs/rules/no-old-shims.md) | disallow usage of old shims for modules | ✅ | 🔧 | |
-| [no-string-prototype-extensions](docs/rules/no-string-prototype-extensions.md) | disallow usage of `String` prototype extensions | ✅ | | |
+| Name | Description | 💼 | 🔧 | 💡 |
+| :----------------------------------------------------------------------------------------------- | :-------------------------------------------------------- | :------------------------------------------ | :- | :- |
+| [closure-actions](docs/rules/closure-actions.md) | enforce usage of closure actions | ✅ | | |
+| [new-module-imports](docs/rules/new-module-imports.md) | enforce using "New Module Imports" from Ember RFC #176 | ✅ | | |
+| [no-array-prototype-extensions](docs/rules/no-array-prototype-extensions.md) | disallow usage of Ember's `Array` prototype extensions | | 🔧 | |
+| [no-at-ember-render-modifiers](docs/rules/no-at-ember-render-modifiers.md) | disallow importing from @ember/render-modifiers | ✅ | | |
+| [no-deprecated-router-transition-methods](docs/rules/no-deprecated-router-transition-methods.md) | enforce usage of router service transition methods | ✅ | 🔧 | |
+| [no-function-prototype-extensions](docs/rules/no-function-prototype-extensions.md) | disallow usage of Ember's `function` prototype extensions | ✅ | | |
+| [no-implicit-injections](docs/rules/no-implicit-injections.md) | enforce usage of implicit service injections | ✅ | 🔧 | |
+| [no-mixins](docs/rules/no-mixins.md) | disallow the usage of mixins | ✅ | | |
+| [no-new-mixins](docs/rules/no-new-mixins.md) | disallow the creation of new mixins | ✅ | | |
+| [no-observers](docs/rules/no-observers.md) | disallow usage of observers | ✅ | | |
+| [no-old-shims](docs/rules/no-old-shims.md) | disallow usage of old shims for modules | ✅ | 🔧 | |
+| [no-string-prototype-extensions](docs/rules/no-string-prototype-extensions.md) | disallow usage of `String` prototype extensions | ✅ | | |
+| [template-deprecated-inline-view-helper](docs/rules/template-deprecated-inline-view-helper.md) | disallow inline {{view}} helper | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-deprecated-render-helper](docs/rules/template-deprecated-render-helper.md) | disallow {{render}} helper | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-action](docs/rules/template-no-action.md) | disallow {{action}} helper | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-attrs-in-components](docs/rules/template-no-attrs-in-components.md) | disallow attrs in component templates | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-form-action](docs/rules/template-no-form-action.md) | disallow usage of action attribute on form elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-link-to-positional-params](docs/rules/template-no-link-to-positional-params.md) | disallow positional params in LinkTo component | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-link-to-tagname](docs/rules/template-no-link-to-tagname.md) | disallow tagName attribute on LinkTo component | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-partial](docs/rules/template-no-partial.md) | disallow {{partial}} helper | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-route-action](docs/rules/template-no-route-action.md) | disallow usage of route-action helper | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-unbound](docs/rules/template-no-unbound.md) | disallow {{unbound}} helper | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-with](docs/rules/template-no-with.md) | disallow {{with}} helper | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
### Ember Data
@@ -284,6 +426,15 @@ rules in templates can be disabled with eslint directives with mustache or html
| [no-runloop](docs/rules/no-runloop.md) | disallow usage of `@ember/runloop` functions | ✅ | | |
| [require-fetch-import](docs/rules/require-fetch-import.md) | enforce explicit import for `fetch()` | | | |
+### Possible Errors
+
+| Name | Description | 💼 | 🔧 | 💡 |
+| :------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------- | :------------------------------------------ | :- | :- |
+| [template-no-scope-outside-table-headings](docs/rules/template-no-scope-outside-table-headings.md) | disallow scope attribute outside th/td elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-shadowed-elements](docs/rules/template-no-shadowed-elements.md) | disallow shadowing of built-in HTML elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-unbalanced-curlies](docs/rules/template-no-unbalanced-curlies.md) | disallow unbalanced mustache curlies | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-no-unknown-arguments-for-builtin-components](docs/rules/template-no-unknown-arguments-for-builtin-components.md) | disallow unknown arguments for built-in components | | | |
+
### Routes
| Name | Description | 💼 | 🔧 | 💡 |
@@ -297,6 +448,13 @@ rules in templates can be disabled with eslint directives with mustache or html
| [route-path-style](docs/rules/route-path-style.md) | enforce usage of kebab-case (instead of snake_case or camelCase) in route paths | | | 💡 |
| [routes-segments-snake-case](docs/rules/routes-segments-snake-case.md) | enforce usage of snake_cased dynamic segments in routes | ✅ | | |
+### Security
+
+| Name | Description | 💼 | 🔧 | 💡 |
+| :--------------------------------------------------------------------- | :-------------------------------------------------------------- | :------------------------------------------ | :- | :- |
+| [template-link-rel-noopener](docs/rules/template-link-rel-noopener.md) | require rel="noopener noreferrer" on links with target="_blank" | ![badge-strict-gjs][] ![badge-strict-gts][] | 🔧 | |
+| [template-no-triple-curlies](docs/rules/template-no-triple-curlies.md) | disallow usage of triple curly brackets (unescaped variables) | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+
### Services
| Name | Description | 💼 | 🔧 | 💡 |
@@ -306,14 +464,29 @@ rules in templates can be disabled with eslint directives with mustache or html
| [no-unnecessary-service-injection-argument](docs/rules/no-unnecessary-service-injection-argument.md) | disallow unnecessary argument when injecting services | | 🔧 | |
| [no-unused-services](docs/rules/no-unused-services.md) | disallow unused service injections (see rule doc for limitations) | | | 💡 |
+### Style
+
+| Name | Description | 💼 | 🔧 | 💡 |
+| :----------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------- | :- | :- | :- |
+| [template-no-quoteless-attributes](docs/rules/template-no-quoteless-attributes.md) | require quotes on all attribute values | | 🔧 | |
+| [template-no-unnecessary-curly-in-string-attrs](docs/rules/template-no-unnecessary-curly-in-string-attrs.md) | disallow unnecessary curly braces in attributes | | | |
+| [template-no-unnecessary-curly-strings](docs/rules/template-no-unnecessary-curly-strings.md) | disallow unnecessary curly braces in string interpolations | | | |
+| [template-no-whitespace-within-word](docs/rules/template-no-whitespace-within-word.md) | disallow whitespace within mustache or block expressions | | | |
+
### Stylistic Issues
-| Name | Description | 💼 | 🔧 | 💡 |
-| :--------------------------------------------------------- | :------------------------------------------------ | :- | :- | :- |
-| [order-in-components](docs/rules/order-in-components.md) | enforce proper order of properties in components | | 🔧 | |
-| [order-in-controllers](docs/rules/order-in-controllers.md) | enforce proper order of properties in controllers | | 🔧 | |
-| [order-in-models](docs/rules/order-in-models.md) | enforce proper order of properties in models | | 🔧 | |
-| [order-in-routes](docs/rules/order-in-routes.md) | enforce proper order of properties in routes | | 🔧 | |
+| Name | Description | 💼 | 🔧 | 💡 |
+| :----------------------------------------------------------------------------- | :------------------------------------------------------------- | :------------------------------------------ | :- | :- |
+| [order-in-components](docs/rules/order-in-components.md) | enforce proper order of properties in components | | 🔧 | |
+| [order-in-controllers](docs/rules/order-in-controllers.md) | enforce proper order of properties in controllers | | 🔧 | |
+| [order-in-models](docs/rules/order-in-models.md) | enforce proper order of properties in models | | 🔧 | |
+| [order-in-routes](docs/rules/order-in-routes.md) | enforce proper order of properties in routes | | 🔧 | |
+| [template-attribute-indentation](docs/rules/template-attribute-indentation.md) | enforce consistent attribute indentation in templates | | 🔧 | |
+| [template-attribute-order](docs/rules/template-attribute-order.md) | enforce consistent ordering of attributes in template elements | ![badge-strict-gjs][] ![badge-strict-gts][] | | |
+| [template-block-indentation](docs/rules/template-block-indentation.md) | enforce consistent block indentation in templates | | 🔧 | |
+| [template-eol-last](docs/rules/template-eol-last.md) | require newline at end of template files | ![badge-strict-gjs][] ![badge-strict-gts][] | 🔧 | |
+| [template-linebreak-style](docs/rules/template-linebreak-style.md) | enforce consistent linebreak style in templates | | 🔧 | |
+| [template-quotes](docs/rules/template-quotes.md) | enforce consistent quote style in templates | | 🔧 | |
### Testing
@@ -360,3 +533,6 @@ Note that new rules should not immediately be added to the [recommended](./lib/r
## 🔓 License
See the [LICENSE](LICENSE.md) file for license rights and limitations (MIT).
+
+[badge-strict-gjs]: https://img.shields.io/badge/strict--gjs-blue
+[badge-strict-gts]: https://img.shields.io/badge/strict--gts-blue
diff --git a/docs/rules/template-attribute-indentation.md b/docs/rules/template-attribute-indentation.md
new file mode 100644
index 0000000000..2ba112658a
--- /dev/null
+++ b/docs/rules/template-attribute-indentation.md
@@ -0,0 +1,33 @@
+# ember/template-attribute-indentation
+
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+
+
+Enforces consistent attribute indentation in templates.
+
+This is a stylistic rule that is disabled by default.
+
+## Rule Details
+
+This rule enforces consistent indentation for attributes in template elements.
+
+## Examples
+
+Examples of **correct** code for this rule:
+
+```gjs
+
+
+
+```
+
+```gjs
+
+
+
+```
+
+## References
+
+- [ember-template-lint attribute-indentation](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/attribute-indentation.md)
diff --git a/docs/rules/template-attribute-order.md b/docs/rules/template-attribute-order.md
new file mode 100644
index 0000000000..d366dbf3a2
--- /dev/null
+++ b/docs/rules/template-attribute-order.md
@@ -0,0 +1,70 @@
+# ember/template-attribute-order
+
+💼 This rule is enabled in the following [configs](https://github.com/ember-cli/eslint-plugin-ember#-configurations): `strict-gjs`, `strict-gts`.
+
+
+
+Enforces a consistent ordering of attributes in template elements. This helps improve readability and maintainability of templates.
+
+## Rule Details
+
+This rule enforces a consistent order for attributes on template elements. By default, it follows this order:
+
+1. `class`
+2. `id`
+3. `role`
+4. `aria-*` attributes
+5. `data-test-*` attributes
+6. `type`
+7. `name`
+8. `value`
+9. `placeholder`
+10. `disabled`
+
+## Examples
+
+Examples of **incorrect** code for this rule:
+
+```gjs
+
+
+
+```
+
+```gjs
+
+
+
+```
+
+Examples of **correct** code for this rule:
+
+```gjs
+
+
+
+```
+
+```gjs
+
+
+
+```
+
+## Configuration
+
+You can customize the order by providing an `order` array:
+
+```js
+module.exports = {
+ rules: {
+ 'ember/template-attribute-order': ['error', {
+ order: ['class', 'id', 'role', 'aria-', 'type']
+ }]
+ }
+};
+```
+
+## References
+
+- [ember-template-lint attribute-order](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/attribute-order.md)
diff --git a/docs/rules/template-block-indentation.md b/docs/rules/template-block-indentation.md
new file mode 100644
index 0000000000..a522ecc5d2
--- /dev/null
+++ b/docs/rules/template-block-indentation.md
@@ -0,0 +1,35 @@
+# ember/template-block-indentation
+
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+
+
+Enforces consistent block indentation in templates.
+
+This is a stylistic rule that is disabled by default.
+
+## Rule Details
+
+This rule enforces consistent indentation for block statements in templates.
+
+## Examples
+
+Examples of **correct** code for this rule:
+
+```gjs
+
+ {{#if condition}}
+
Content
+ {{/if}}
+
+```
+
+```gjs
+
+
Content
+
+```
+
+## References
+
+- [ember-template-lint block-indentation](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/block-indentation.md)
diff --git a/docs/rules/template-deprecated-inline-view-helper.md b/docs/rules/template-deprecated-inline-view-helper.md
new file mode 100644
index 0000000000..c7c7a17935
--- /dev/null
+++ b/docs/rules/template-deprecated-inline-view-helper.md
@@ -0,0 +1,13 @@
+# ember/template-deprecated-inline-view-helper
+
+💼 This rule is enabled in the following [configs](https://github.com/ember-cli/eslint-plugin-ember#-configurations): `strict-gjs`, `strict-gts`.
+
+
+
+## Examples
+
+See ember-template-lint documentation.
+
+## References
+
+- [ember-template-lint](https://github.com/ember-template-lint/ember-template-lint)
diff --git a/docs/rules/template-deprecated-render-helper.md b/docs/rules/template-deprecated-render-helper.md
new file mode 100644
index 0000000000..59cd7a7eb2
--- /dev/null
+++ b/docs/rules/template-deprecated-render-helper.md
@@ -0,0 +1,25 @@
+# ember/template-deprecated-render-helper
+
+💼 This rule is enabled in the following [configs](https://github.com/ember-cli/eslint-plugin-ember#-configurations): `strict-gjs`, `strict-gts`.
+
+
+
+Disallows the {{render}} helper which is deprecated.
+
+## Examples
+
+Incorrect:
+
+```gjs
+{{render "user"}}
+```
+
+Correct:
+
+```gjs
+
+```
+
+## References
+
+- [ember-template-lint deprecated-render-helper](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/deprecated-render-helper.md)
diff --git a/docs/rules/template-eol-last.md b/docs/rules/template-eol-last.md
new file mode 100644
index 0000000000..dd4b3bdcba
--- /dev/null
+++ b/docs/rules/template-eol-last.md
@@ -0,0 +1,73 @@
+# ember/template-eol-last
+
+💼 This rule is enabled in the following [configs](https://github.com/ember-cli/eslint-plugin-ember#-configurations): `strict-gjs`, `strict-gts`.
+
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+
+
+Requires or disallows newline at the end of template files.
+
+Consistent handling of line endings at the end of files helps maintain clean diffs in version control.
+
+## Rule Details
+
+This rule enforces a newline (or lack thereof) at the end of template blocks.
+
+## Examples
+
+Examples of **incorrect** code for this rule with default `"always"` option:
+
+```gjs
+// Missing newline at end
+
+
Hello
+
+```
+
+Examples of **correct** code for this rule with default `"always"` option:
+
+```gjs
+
+
Hello
+
+
+```
+
+Examples of **incorrect** code for this rule with `"never"` option:
+
+```gjs
+// Unwanted newline at end
+
+
Hello
+
+
+```
+
+Examples of **correct** code for this rule with `"never"` option:
+
+```gjs
+
+
Hello
+
+```
+
+## Configuration
+
+This rule takes one option:
+
+- `"always"` (default): requires a newline at the end
+- `"never"`: disallows a newline at the end
+
+```js
+module.exports = {
+ rules: {
+ 'ember/template-eol-last': ['error', 'always']
+ }
+};
+```
+
+## References
+
+- [ember-template-lint eol-last](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/eol-last.md)
+- [ESLint eol-last](https://eslint.org/docs/rules/eol-last)
diff --git a/docs/rules/template-linebreak-style.md b/docs/rules/template-linebreak-style.md
new file mode 100644
index 0000000000..73952d3f00
--- /dev/null
+++ b/docs/rules/template-linebreak-style.md
@@ -0,0 +1,27 @@
+# ember/template-linebreak-style
+
+🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
+
+
+
+Enforces consistent linebreak style in templates.
+
+This is a stylistic rule that is disabled by default.
+
+## Rule Details
+
+This rule enforces consistent linebreak style (LF or CRLF) in templates.
+
+## Examples
+
+Examples of **correct** code for this rule:
+
+```gjs
+
+
Content
+
+```
+
+## References
+
+- [ember-template-lint linebreak-style](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/linebreak-style.md)
diff --git a/docs/rules/template-link-href-attributes.md b/docs/rules/template-link-href-attributes.md
new file mode 100644
index 0000000000..c848edccd0
--- /dev/null
+++ b/docs/rules/template-link-href-attributes.md
@@ -0,0 +1,61 @@
+# ember/template-link-href-attributes
+
+💼 This rule is enabled in the following [configs](https://github.com/ember-cli/eslint-plugin-ember#-configurations): `strict-gjs`, `strict-gts`.
+
+
+
+Requires `href` attribute on `` elements.
+
+Anchor elements should have an `href` attribute to be properly recognized as links by browsers and assistive technologies. If an element is meant to be interactive but not navigate, use a `