Skip to content
2 changes: 2 additions & 0 deletions docs/rules/template-no-attrs-in-components.md
Original file line number Diff line number Diff line change
@@ -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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not hbs only

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, fixed


<!-- end auto-generated rule header -->

This rule prevents the usage of `this.attrs` property to access values passed to the component. Use `@arg` syntax instead.
Expand Down
28 changes: 23 additions & 5 deletions lib/rules/template-no-attrs-in-components.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,42 @@
const COMPONENT_TEMPLATE_REGEX = new RegExp(
'templates/components|components/.*/template|ui/components|-components/'
);

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'suggestion',
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.<something>` (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' });
}
},
};
Expand Down
95 changes: 71 additions & 24 deletions tests/lib/rules/template-no-attrs-in-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,87 @@ const ruleTester = new RuleTester({

ruleTester.run('template-no-attrs-in-components', rule, {
valid: [
'<template>{{@value}}</template>',
'<template>{{this.value}}</template>',
// Class component with normal this access
`import Component from '@glimmer/component';
class MyComponent extends Component {
<template>{{this.args.name}}</template>
}`,
// Bare attrs is not accessible without this, so it's allowed
'<template>{{attrs.value}}</template>',
'<template>{{attrs}}</template>',
`import Component from '@glimmer/component';
class MyComponent extends Component {
<template>{{attrs.name}}</template>
}`,
// Not a component template path: nothing is flagged, regardless of content.
{
filename: 'app/templates/application.hbs',
code: '<template>{{@value}}</template>',
},
{
filename: 'app/templates/application.hbs',
code: '<template>{{this.value}}</template>',
},
// `this.attrs.*` is not a real Ember API, but it is NOT what this rule
// targets — only bare `attrs.*` is flagged. So outside of a component
// template, `this.attrs.*` should not be flagged.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be flagged, unless there is another lint rule that would cover it.

This is from @ember/component components.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, should be fixed now. The PR was using original.startsWith('attrs.') which misses it. In the Glimmer AST, this.attrs.foo has parts[0] === 'attrs' (this is the receiver, not a part), so switching to parts[0] === 'attrs' catches both forms.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't see any tests with this.attrs -- did I miss them?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
filename: 'app/templates/application.hbs',
code: '<template>{{this.attrs.foo}}</template>',
},
// Even `attrs.*` itself is only flagged inside component templates.
{
filename: 'app/templates/application.hbs',
code: '<template>{{attrs.value}}</template>',
},
// Inside a component template, non-attrs paths are fine.
{
filename: 'app/templates/components/foo.hbs',
code: '<template>{{@value}}</template>',
},
{
filename: 'app/templates/components/foo.hbs',
code: '<template>{{this.value}}</template>',
},
// This rule does NOT flag `this.attrs.*`; only bare `attrs.*`.
{
filename: 'app/templates/components/foo.hbs',
code: '<template>{{this.attrs.foo}}</template>',
},
// Pod-style components path matches the gate, but no `attrs` usage.
{
filename: 'app/components/foo/template.hbs',
code: '<template>{{@value}}</template>',
},
// `-components/` path gate, no `attrs` usage.
{
filename: 'app/ui-components/foo.hbs',
code: '<template>{{@value}}</template>',
},
],
invalid: [
// Bare `attrs.*` inside `templates/components/` — flagged.
{
filename: 'app/templates/components/foo.hbs',
code: '<template>{{attrs.foo}}</template>',
output: null,
errors: [{ messageId: 'noAttrs' }],
},
// Bare `attrs` (no dotted tail) inside `templates/components/` — flagged.
{
filename: 'app/templates/components/foo.hbs',
code: '<template>{{attrs}}</template>',
output: null,
errors: [{ messageId: 'noAttrs' }],
},
// Pod-style path `components/*/template` — flagged.
{
code: '<template>{{this.attrs.value}}</template>',
filename: 'app/components/foo/template.hbs',
code: '<template>{{attrs.name}}</template>',
output: null,
errors: [{ messageId: 'noThisAttrs' }],
errors: [{ messageId: 'noAttrs' }],
},
// `ui/components` path — flagged.
{
code: '<template>{{this.attrs}}</template>',
filename: 'app/ui/components/foo.hbs',
code: '<template>{{attrs.name}}</template>',
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 {
<template>{{this.attrs.name}}</template>
}`,
filename: 'app/ui-components/foo.hbs',
code: '<template>{{attrs.name}}</template>',
output: null,
errors: [{ messageId: 'noThisAttrs' }],
errors: [{ messageId: 'noAttrs' }],
},
],
});
Loading