From 7efa92fa47fd7870ec7b33a7a4636158e6227dcc Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Wed, 18 Mar 2026 18:17:15 -0400 Subject: [PATCH 1/2] Extract rule: template-require-strict-mode --- README.md | 1 + docs/rules/template-require-strict-mode.md | 42 +++++++++++++ lib/rules/template-require-strict-mode.js | 45 +++++++++++++ .../lib/rules/template-require-strict-mode.js | 63 +++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 docs/rules/template-require-strict-mode.md create mode 100644 lib/rules/template-require-strict-mode.js create mode 100644 tests/lib/rules/template-require-strict-mode.js diff --git a/README.md b/README.md index 515d97f416..61cc01f8ae 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,7 @@ rules in templates can be disabled with eslint directives with mustache or html | [template-no-obsolete-elements](docs/rules/template-no-obsolete-elements.md) | disallow obsolete HTML elements | | | | | [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 | | | | +| [template-require-strict-mode](docs/rules/template-require-strict-mode.md) | require templates to be in strict mode | | | | | [template-require-valid-named-block-naming-format](docs/rules/template-require-valid-named-block-naming-format.md) | require valid named block naming format | | 🔧 | | | [template-self-closing-void-elements](docs/rules/template-self-closing-void-elements.md) | require self-closing on void elements | | 🔧 | | | [template-simple-modifiers](docs/rules/template-simple-modifiers.md) | require simple modifier syntax | | | | diff --git a/docs/rules/template-require-strict-mode.md b/docs/rules/template-require-strict-mode.md new file mode 100644 index 0000000000..b54f2e5db0 --- /dev/null +++ b/docs/rules/template-require-strict-mode.md @@ -0,0 +1,42 @@ +# ember/template-require-strict-mode + +> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur. + + + +Require templates to be in strict mode. + +Templates should use the strict mode syntax (template tag format) rather than loose template files. Strict mode templates (`.gjs` / `.gts` files) provide better integration with JavaScript and type checking. + +## Examples + +This rule **forbids** the following: + +```hbs +
+ Hello World +
+``` + +(in a `.hbs` file) + +This rule **allows** the following: + +```hbs +Hello World +``` + +(in a `.gjs` or `.gts` file) + +## Why? + +Strict mode templates provide: + +- Better integration with JavaScript tooling +- Type safety in TypeScript projects +- Clearer component boundaries +- Easier refactoring and navigation + +## References + +- [Ember.js Guides - Template Syntax](https://guides.emberjs.com/release/templates/syntax/) diff --git a/lib/rules/template-require-strict-mode.js b/lib/rules/template-require-strict-mode.js new file mode 100644 index 0000000000..64a0fb161a --- /dev/null +++ b/lib/rules/template-require-strict-mode.js @@ -0,0 +1,45 @@ +const ERROR_MESSAGE = + 'Templates are required to be in strict mode. Consider refactoring to template tag format.'; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: 'require templates to be in strict mode', + category: 'Best Practices', + recommended: false, + url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-require-strict-mode.md', + templateMode: 'loose', + }, + fixable: null, + schema: [], + messages: {}, + originallyFrom: { + name: 'ember-template-lint', + rule: 'lib/rules/require-strict-mode.js', + docs: 'docs/rule/require-strict-mode.md', + tests: 'test/unit/rules/require-strict-mode-test.js', + }, + }, + + create(context) { + return { + 'GlimmerTemplate:exit'(node) { + // Check if the template is in strict mode + // Strict mode templates are in .gjs/.gts files + // Non-strict templates are in .hbs files + + const filePath = context.getFilename ? context.getFilename() : context.filename; + + // Only flag .hbs files as non-strict + if (filePath && filePath.endsWith('.hbs')) { + context.report({ + node, + message: ERROR_MESSAGE, + }); + } + }, + }; + }, +}; diff --git a/tests/lib/rules/template-require-strict-mode.js b/tests/lib/rules/template-require-strict-mode.js new file mode 100644 index 0000000000..7c26f93ce8 --- /dev/null +++ b/tests/lib/rules/template-require-strict-mode.js @@ -0,0 +1,63 @@ +const rule = require('../../../lib/rules/template-require-strict-mode'); +const RuleTester = require('eslint').RuleTester; + +const ruleTester = new RuleTester({ + parser: require.resolve('ember-eslint-parser'), + parserOptions: { ecmaVersion: 2022, sourceType: 'module' }, +}); + +ruleTester.run('template-require-strict-mode', rule, { + valid: [ + { + filename: 'hello.gjs', + code: '', + }, + ], + invalid: [ + { + filename: 'hello.hbs', + code: '', + output: null, + errors: [ + { + message: + 'Templates are required to be in strict mode. Consider refactoring to template tag format.', + }, + ], + }, + + { + filename: 'hello.hbs', + code: ``, + output: null, + errors: [ + { + message: + 'Templates are required to be in strict mode. Consider refactoring to template tag format.', + }, + ], + }, + ], +}); + +const hbsRuleTester = new RuleTester({ + parser: require.resolve('ember-eslint-parser/hbs'), + parserOptions: { + ecmaVersion: 2022, + sourceType: 'module', + }, +}); + +hbsRuleTester.run('template-require-strict-mode', rule, { + valid: [ + '', + `import Component from '@glimmer/component'; + + export default class HelloComponent extends Component { + + }`, + ], + invalid: [], +}); From 621c5f6c6c7eebc355890ab26667d2fc8a3e7199 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Thu, 19 Mar 2026 19:33:42 -0400 Subject: [PATCH 2/2] Sync with template-lint --- docs/rules/template-require-strict-mode.md | 49 ++++++++++++------- lib/rules/template-require-strict-mode.js | 15 +++--- .../lib/rules/template-require-strict-mode.js | 25 ++++++---- 3 files changed, 55 insertions(+), 34 deletions(-) diff --git a/docs/rules/template-require-strict-mode.md b/docs/rules/template-require-strict-mode.md index b54f2e5db0..cf76e9def5 100644 --- a/docs/rules/template-require-strict-mode.md +++ b/docs/rules/template-require-strict-mode.md @@ -6,37 +6,52 @@ Require templates to be in strict mode. -Templates should use the strict mode syntax (template tag format) rather than loose template files. Strict mode templates (`.gjs` / `.gts` files) provide better integration with JavaScript and type checking. +Ember's Polaris edition component authoring format is template tag, which makes +templates follow "strict mode" semantics. + +This rule requires all templates to use strict mode (template tag). Effectively this +means you may only have template content in `.gjs`/`.gts` files, not in `.hbs` or +`.js`/`.ts`. ## Examples This rule **forbids** the following: ```hbs -
- Hello World -
+// button.hbs + ``` -(in a `.hbs` file) - -This rule **allows** the following: +```js +// button-test.js +import { hbs } from 'ember-cli-htmlbars'; -```hbs -Hello World +test('it renders', async (assert) => { + await render(hbs``); + // ... +}); ``` -(in a `.gjs` or `.gts` file) +This rule **allows** the following: -## Why? +```gjs +// button.gjs + +``` -Strict mode templates provide: +```gjs +// button-test.gjs +import { Button } from 'ember-awesome-button'; -- Better integration with JavaScript tooling -- Type safety in TypeScript projects -- Clearer component boundaries -- Easier refactoring and navigation +test('it renders', async (assert) => { + await render(); + // .. +}); +``` ## References -- [Ember.js Guides - Template Syntax](https://guides.emberjs.com/release/templates/syntax/) +- [Template Tag Guide](https://guides.emberjs.com/release/components/template-tag-format/) +- [Strict Mode RFC](https://rfcs.emberjs.com/id/0496-handlebars-strict-mode/) diff --git a/lib/rules/template-require-strict-mode.js b/lib/rules/template-require-strict-mode.js index 64a0fb161a..9e6bfccb06 100644 --- a/lib/rules/template-require-strict-mode.js +++ b/lib/rules/template-require-strict-mode.js @@ -1,6 +1,10 @@ const ERROR_MESSAGE = 'Templates are required to be in strict mode. Consider refactoring to template tag format.'; +function isStrictModeFile(filePath) { + return filePath?.endsWith('.gjs') || filePath?.endsWith('.gts'); +} + /** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { @@ -24,16 +28,11 @@ module.exports = { }, create(context) { + const filePath = context.getFilename ? context.getFilename() : context.filename; + return { 'GlimmerTemplate:exit'(node) { - // Check if the template is in strict mode - // Strict mode templates are in .gjs/.gts files - // Non-strict templates are in .hbs files - - const filePath = context.getFilename ? context.getFilename() : context.filename; - - // Only flag .hbs files as non-strict - if (filePath && filePath.endsWith('.hbs')) { + if (!isStrictModeFile(filePath)) { context.report({ node, message: ERROR_MESSAGE, diff --git a/tests/lib/rules/template-require-strict-mode.js b/tests/lib/rules/template-require-strict-mode.js index 7c26f93ce8..994b573060 100644 --- a/tests/lib/rules/template-require-strict-mode.js +++ b/tests/lib/rules/template-require-strict-mode.js @@ -12,6 +12,10 @@ ruleTester.run('template-require-strict-mode', rule, { filename: 'hello.gjs', code: '', }, + { + filename: 'hello.gts', + code: '', + }, ], invalid: [ { @@ -25,7 +29,6 @@ ruleTester.run('template-require-strict-mode', rule, { }, ], }, - { filename: 'hello.hbs', code: `