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
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,15 @@ rules in templates can be disabled with eslint directives with mustache or html

### Best Practices

| Name | Description | 💼 | 🔧 | 💡 |
| :----------------------------------------------------------------------------------------------- | :-------------------------------------------------------- | :- | :- | :- |
| [template-builtin-component-arguments](docs/rules/template-builtin-component-arguments.md) | disallow setting certain attributes on builtin components | | | |
| [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-debugger](docs/rules/template-no-debugger.md) | disallow {{debugger}} in templates | | | |
| [template-no-log](docs/rules/template-no-log.md) | disallow {{log}} in templates | | | |
| Name | Description | 💼 | 🔧 | 💡 |
| :----------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------- | :- | :- | :- |
| [template-builtin-component-arguments](docs/rules/template-builtin-component-arguments.md) | disallow setting certain attributes on builtin components | | | |
| [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-debugger](docs/rules/template-no-debugger.md) | disallow {{debugger}} in templates | | | |
| [template-no-dynamic-subexpression-invocations](docs/rules/template-no-dynamic-subexpression-invocations.md) | disallow dynamic subexpression invocations | | | |
| [template-no-log](docs/rules/template-no-log.md) | disallow {{log}} in templates | | | |

### Components

Expand Down
62 changes: 62 additions & 0 deletions docs/rules/template-no-dynamic-subexpression-invocations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# ember/template-no-dynamic-subexpression-invocations

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

Disallow dynamic helper invocations.

Dynamic helper invocations (where the helper name comes from a property or argument) make code harder to understand and can have performance implications. Use explicit helper names instead.

## Rule Details

This rule disallows invoking helpers dynamically using `this` or `@` properties.

## Examples

### Incorrect ❌

```gjs
<template>
{{(this.helper "arg")}}
</template>
```

```gjs
<template>
{{(@helperName "value")}}
</template>
```

```gjs
<template>
{{this.formatter this.data}}
</template>
```

### Correct ✅

```gjs
<template>
{{format-date this.date}}
</template>
```

```gjs
<template>
{{(upper-case this.name)}}
</template>
```

```gjs
<template>
{{this.formattedData}}
</template>
```

## Related Rules

- [template-no-implicit-this](./template-no-implicit-this.md)

## References

- [Ember Guides - Template Helpers](https://guides.emberjs.com/release/components/helper-functions/)
- [eslint-plugin-ember template-no-dynamic-subexpression-invocations](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-dynamic-subexpression-invocations.md)
59 changes: 59 additions & 0 deletions lib/rules/template-no-dynamic-subexpression-invocations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'disallow dynamic subexpression invocations',
category: 'Best Practices',
strictGjs: true,
strictGts: true,
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-dynamic-subexpression-invocations.md',
},
fixable: null,
schema: [],
messages: {
noDynamicSubexpressionInvocations:
'Do not use dynamic helper invocations. Use explicit helper names instead.',
},
},

create(context) {
return {
GlimmerSubExpression(node) {
// Check if the path is dynamic (contains @ or this)
if (
node.path &&
node.path.type === 'GlimmerPathExpression' &&
(node.path.head?.type === 'AtHead' ||
node.path.head?.type === 'ThisHead' ||
node.path.parts?.length > 0)
) {
// If it's not a simple identifier, it's dynamic
if (node.path.head?.type === 'AtHead' || node.path.head?.type === 'ThisHead') {
context.report({
node,
messageId: 'noDynamicSubexpressionInvocations',
});
}
}
},
GlimmerMustacheStatement(node) {
// Check for dynamic invocations in mustache statements
if (
node.path &&
node.path.type === 'GlimmerPathExpression' &&
node.params &&
node.params.length > 0
) {
// If the helper name starts with @ or this, it's dynamic
if (node.path.head?.type === 'AtHead' || node.path.head?.type === 'ThisHead') {
context.report({
node,
messageId: 'noDynamicSubexpressionInvocations',
});
}
}
},
};
},
};
48 changes: 48 additions & 0 deletions tests/lib/rules/template-no-dynamic-subexpression-invocations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const rule = require('../../../lib/rules/template-no-dynamic-subexpression-invocations');
const RuleTester = require('eslint').RuleTester;

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

ruleTester.run('template-no-dynamic-subexpression-invocations', rule, {
valid: [
'<template>{{format-date this.date}}</template>',
'<template>{{(upper-case this.name)}}</template>',
'<template>{{helper "static"}}</template>',
],

invalid: [
{
code: '<template>{{(this.helper "arg")}}</template>',
output: null,
errors: [
{
message: 'Do not use dynamic helper invocations. Use explicit helper names instead.',
type: 'GlimmerSubExpression',
},
],
},
{
code: '<template>{{(@helperName "value")}}</template>',
output: null,
errors: [
{
message: 'Do not use dynamic helper invocations. Use explicit helper names instead.',
type: 'GlimmerSubExpression',
},
],
},
{
code: '<template>{{this.formatter this.data}}</template>',
output: null,
errors: [
{
message: 'Do not use dynamic helper invocations. Use explicit helper names instead.',
type: 'GlimmerMustacheStatement',
},
],
},
],
});
Loading