Skip to content
Closed
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ rules in templates can be disabled with eslint directives with mustache or html
| [template-no-action-modifiers](docs/rules/template-no-action-modifiers.md) | disallow usage of {{action}} modifiers | | | |
| [template-no-arguments-for-html-elements](docs/rules/template-no-arguments-for-html-elements.md) | disallow @arguments on HTML elements | | | |
| [template-no-array-prototype-extensions](docs/rules/template-no-array-prototype-extensions.md) | disallow usage of Ember Array prototype extensions | | | |
| [template-no-block-params](docs/rules/template-no-block-params.md) | disallow the use of block params (`as \|...\|`) | | | |
| [template-no-block-params-for-html-elements](docs/rules/template-no-block-params-for-html-elements.md) | disallow block params on HTML elements | | | |
| [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 redundant `this.this` in templates | | 🔧 | |
Expand Down
85 changes: 85 additions & 0 deletions docs/rules/template-no-block-params.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# ember/template-no-block-params

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

Disallow the use of block params (`as |...|`).

## Rule Details

This rule disallows all usage of block params syntax (`as |...|`) in templates. This includes:

- Angle-bracket component invocations: `<MyComponent as |item|>`
- Curly component invocations: `{{#my-component as |val|}}`
- Built-in helpers: `{{#each items as |item|}}`, `{{#let val as |v|}}`
- HTML elements: `<div as |content|>`

This is a strict rule for codebases that want to completely avoid block params in favor of alternative patterns (e.g., contextual helpers, direct property access, or explicit argument passing).

## Examples

Examples of **incorrect** code for this rule:

```gjs
<template>
<MyComponent as |item|>
{{item.name}}
</MyComponent>
</template>
```

```gjs
<template>
{{#each this.items as |item index|}}
<li>{{index}}: {{item}}</li>
{{/each}}
</template>
```

```gjs
<template>
{{#let this.computedValue as |val|}}
{{val}}
{{/let}}
</template>
```

```gjs
<template>
{{#my-component as |api|}}
{{api.doSomething}}
{{/my-component}}
</template>
```

Examples of **correct** code for this rule:

```gjs
<template>
<MyComponent />
</template>
```

```gjs
<template>
<MyComponent>
Content without block params
</MyComponent>
</template>
```

```gjs
<template>
{{my-helper this.value}}
</template>
```

```gjs
<template>
{{yield}}
</template>
```

## Related Rules

- [template-no-block-params-for-html-elements](template-no-block-params-for-html-elements.md) — only disallows block params on HTML elements
- [template-no-unused-block-params](template-no-unused-block-params.md) — disallows block params that are declared but never used
58 changes: 58 additions & 0 deletions lib/rules/template-no-block-params.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow the use of block params (`as |...|`)',
category: 'Best Practices',
recommended: false,
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-block-params.md',
templateMode: 'both',
},
fixable: null,
schema: [],
messages: {
noBlockParams:
'Block params (`as |...|`) are not allowed. Avoid using `as |{{params}}|` on `{{name}}`.',
},
},

create(context) {
return {
// Catches angle-bracket invocations with block params:
// <MyComponent as |foo|>
// <div as |bar|>
GlimmerElementNode(node) {
if (node.blockParams && node.blockParams.length > 0) {
context.report({
node,
messageId: 'noBlockParams',
data: {
params: node.blockParams.join(', '),
name: node.tag,
},
});
}
},

// Catches curly block invocations with block params:
// {{#each items as |item|}}
// {{#let foo as |bar|}}
// {{#my-component as |val|}}
GlimmerBlockStatement(node) {
const blockParams = node.program?.blockParams || [];
if (blockParams.length > 0) {
const pathName = node.path?.original || node.path?.head?.original || 'block';
context.report({
node,
messageId: 'noBlockParams',
data: {
params: blockParams.join(', '),
name: pathName,
},
});
}
},
};
},
};
212 changes: 212 additions & 0 deletions tests/lib/rules/template-no-block-params.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
const rule = require('../../../lib/rules/template-no-block-params');
const RuleTester = require('eslint').RuleTester;

const ruleTester = new RuleTester({
parser: require.resolve('ember-eslint-parser'),
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
});

ruleTester.run('template-no-block-params', rule, {
valid: [
// Plain HTML elements without block params
'<template><div>Content</div></template>',
'<template><span>Text</span></template>',
'<template><button>Click</button></template>',

// Components without block params
'<template><MyComponent>Content</MyComponent></template>',
'<template><MyComponent @name="foo" /></template>',

// Mustache statements without block params
'<template>{{my-helper arg}}</template>',
'<template>{{this.value}}</template>',

// Simple {{yield}} (no block params)
'<template>{{yield}}</template>',

// Angle bracket self-closing
'<template><MyComponent @name="bar" /></template>',
],

invalid: [
// Component with block params (angle bracket)
{
code: '<template><MyComponent as |item|>{{item.name}}</MyComponent></template>',
output: null,
errors: [
{
messageId: 'noBlockParams',
type: 'GlimmerElementNode',
},
],
},

// Component with multiple block params
{
code: '<template><MyComponent as |item index|>{{item}} {{index}}</MyComponent></template>',
output: null,
errors: [
{
messageId: 'noBlockParams',
type: 'GlimmerElementNode',
},
],
},

// HTML element with block params
{
code: '<template><div as |content|>{{content}}</div></template>',
output: null,
errors: [
{
messageId: 'noBlockParams',
type: 'GlimmerElementNode',
},
],
},

// {{#each}} with block params
{
code: '<template>{{#each this.items as |item|}}<li>{{item}}</li>{{/each}}</template>',
output: null,
errors: [
{
messageId: 'noBlockParams',
type: 'GlimmerBlockStatement',
},
],
},

// {{#each}} with multiple block params
{
code: '<template>{{#each this.items as |item index|}}<li>{{index}}: {{item}}</li>{{/each}}</template>',
output: null,
errors: [
{
messageId: 'noBlockParams',
type: 'GlimmerBlockStatement',
},
],
},

// {{#let}} with block params
{
code: '<template>{{#let this.name as |name|}}{{name}}{{/let}}</template>',
output: null,
errors: [
{
messageId: 'noBlockParams',
type: 'GlimmerBlockStatement',
},
],
},

// Nested block params — two errors
{
code: '<template><MyComponent as |items|>{{#each items as |item|}}<li>{{item}}</li>{{/each}}</MyComponent></template>',
output: null,
errors: [
{
messageId: 'noBlockParams',
type: 'GlimmerElementNode',
},
{
messageId: 'noBlockParams',
type: 'GlimmerBlockStatement',
},
],
},

// Named blocks with block params
{
code: '<template><Something><:Item as |foo|>{{foo}}</:Item></Something></template>',
output: null,
errors: [
{
messageId: 'noBlockParams',
type: 'GlimmerElementNode',
},
],
},

// Curly component with block params
{
code: '<template>{{#my-component as |val|}}{{val}}{{/my-component}}</template>',
output: null,
errors: [
{
messageId: 'noBlockParams',
type: 'GlimmerBlockStatement',
},
],
},
],
});

const hbsRuleTester = new RuleTester({
parser: require.resolve('ember-eslint-parser/hbs'),
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
});

hbsRuleTester.run('template-no-block-params (hbs)', rule, {
valid: [
'<div>Content</div>',
'<MyComponent>Content</MyComponent>',
'<MyComponent @name="foo" />',
'{{my-helper arg}}',
'{{yield}}',
],

invalid: [
{
code: '<MyComponent as |item|>{{item.name}}</MyComponent>',
output: null,
errors: [
{
messageId: 'noBlockParams',
type: 'GlimmerElementNode',
},
],
},
{
code: '<div as |content|>{{content}}</div>',
output: null,
errors: [
{
messageId: 'noBlockParams',
type: 'GlimmerElementNode',
},
],
},
{
code: '{{#each this.items as |item|}}<li>{{item}}</li>{{/each}}',
output: null,
errors: [
{
messageId: 'noBlockParams',
type: 'GlimmerBlockStatement',
},
],
},
{
code: '{{#let this.name as |name|}}{{name}}{{/let}}',
output: null,
errors: [
{
messageId: 'noBlockParams',
type: 'GlimmerBlockStatement',
},
],
},
{
code: '{{#my-component as |val|}}{{val}}{{/my-component}}',
output: null,
errors: [
{
messageId: 'noBlockParams',
type: 'GlimmerBlockStatement',
},
],
},
],
});
Loading