diff --git a/docs/rules/template-no-attrs-in-components.md b/docs/rules/template-no-attrs-in-components.md index 6c23603159..2092996c68 100644 --- a/docs/rules/template-no-attrs-in-components.md +++ b/docs/rules/template-no-attrs-in-components.md @@ -1,5 +1,7 @@ # ember/template-no-attrs-in-components +> **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. + This rule prevents the usage of `this.attrs` property to access values passed to the component. Use `@arg` syntax instead. diff --git a/lib/rules/template-no-attrs-in-components.js b/lib/rules/template-no-attrs-in-components.js index 3b3898baaf..18e0b63d42 100644 --- a/lib/rules/template-no-attrs-in-components.js +++ b/lib/rules/template-no-attrs-in-components.js @@ -1,3 +1,7 @@ +const COMPONENT_TEMPLATE_REGEX = new RegExp( + 'templates/components|components/.*/template|ui/components|-components/' +); + /** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { @@ -5,20 +9,34 @@ module.exports = { docs: { description: 'disallow attrs in component templates', category: 'Deprecations', - url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-attrs-in-components.md', + templateMode: 'loose', }, schema: [], messages: { - noThisAttrs: - 'Component templates should not contain `this.attrs`. Use `@arg` syntax instead.', + noAttrs: 'Component templates should not contain `attrs`.', + }, + originallyFrom: { + name: 'ember-template-lint', + rule: 'lib/rules/no-attrs-in-components.js', + docs: 'docs/rule/no-attrs-in-components.md', + tests: 'test/unit/rules/no-attrs-in-components-test.js', }, }, create(context) { + if (!COMPONENT_TEMPLATE_REGEX.test(context.filename)) { + return {}; + } return { GlimmerPathExpression(node) { - if (node.original?.startsWith('this.attrs.') || node.original === 'this.attrs') { - context.report({ node, messageId: 'noThisAttrs' }); + const original = node.original; + if (typeof original !== 'string') { + return; + } + // Flag bare `attrs` or `attrs.` (pre-Octane args-leakage). + // Do NOT flag `this.attrs.*` — that is a different (non-existent) API. + if (original === 'attrs' || original.startsWith('attrs.')) { + context.report({ node, messageId: 'noAttrs' }); } }, }; diff --git a/tests/lib/rules/template-no-attrs-in-components.js b/tests/lib/rules/template-no-attrs-in-components.js index 68de644421..5211af3f19 100644 --- a/tests/lib/rules/template-no-attrs-in-components.js +++ b/tests/lib/rules/template-no-attrs-in-components.js @@ -8,40 +8,87 @@ const ruleTester = new RuleTester({ ruleTester.run('template-no-attrs-in-components', rule, { valid: [ - '', - '', - // Class component with normal this access - `import Component from '@glimmer/component'; - class MyComponent extends Component { - - }`, - // Bare attrs is not accessible without this, so it's allowed - '', - '', - `import Component from '@glimmer/component'; - class MyComponent extends Component { - - }`, + // Not a component template path: nothing is flagged, regardless of content. + { + filename: 'app/templates/application.hbs', + code: '', + }, + { + filename: 'app/templates/application.hbs', + code: '', + }, + // `this.attrs.*` is not a real Ember API, but it is NOT what this rule + // targets — upstream only flags bare `attrs.*`. So outside of a component + // template, `this.attrs.*` should not be flagged. + { + filename: 'app/templates/application.hbs', + code: '', + }, + // Even `attrs.*` itself is only flagged inside component templates. + { + filename: 'app/templates/application.hbs', + code: '', + }, + // Inside a component template, non-attrs paths are fine. + { + filename: 'app/templates/components/foo.hbs', + code: '', + }, + { + filename: 'app/templates/components/foo.hbs', + code: '', + }, + // Upstream does NOT flag `this.attrs.*`; only bare `attrs.*`. + { + filename: 'app/templates/components/foo.hbs', + code: '', + }, + // Pod-style components path matches the gate, but no `attrs` usage. + { + filename: 'app/components/foo/template.hbs', + code: '', + }, + // `-components/` path gate, no `attrs` usage. + { + filename: 'app/ui-components/foo.hbs', + code: '', + }, ], invalid: [ + // Bare `attrs.*` inside `templates/components/` — flagged. + { + filename: 'app/templates/components/foo.hbs', + code: '', + output: null, + errors: [{ messageId: 'noAttrs' }], + }, + // Bare `attrs` (no dotted tail) inside `templates/components/` — flagged. + { + filename: 'app/templates/components/foo.hbs', + code: '', + output: null, + errors: [{ messageId: 'noAttrs' }], + }, + // Pod-style path `components/*/template` — flagged. { - code: '', + filename: 'app/components/foo/template.hbs', + code: '', output: null, - errors: [{ messageId: 'noThisAttrs' }], + errors: [{ messageId: 'noAttrs' }], }, + // `ui/components` path — flagged. { - code: '', + filename: 'app/ui/components/foo.hbs', + code: '', output: null, - errors: [{ messageId: 'noThisAttrs' }], + errors: [{ messageId: 'noAttrs' }], }, - // Class component using this.attrs + // `-components/` path — flagged. { - code: `import Component from '@glimmer/component'; - class MyComponent extends Component { - - }`, + filename: 'app/ui-components/foo.hbs', + code: '', output: null, - errors: [{ messageId: 'noThisAttrs' }], + errors: [{ messageId: 'noAttrs' }], }, ], });