Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

<!-- 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 — upstream only flags bare `attrs.*`. So outside of a component
// template, `this.attrs.*` should not be flagged.
{
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>',
},
// Upstream 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