Skip to content

Commit 6a244f4

Browse files
Merge pull request #2582 from NullVoxPopuli/nvp/template-lint-extract-rule-template-no-unbound
Extract rule: template-no-unbound
2 parents 707cf7b + 1afad8d commit 6a244f4

4 files changed

Lines changed: 121 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ rules in templates can be disabled with eslint directives with mustache or html
340340
| [template-no-attrs-in-components](docs/rules/template-no-attrs-in-components.md) | disallow attrs in component templates | | | |
341341
| [template-no-link-to-positional-params](docs/rules/template-no-link-to-positional-params.md) | disallow positional params in LinkTo component | | | |
342342
| [template-no-link-to-tagname](docs/rules/template-no-link-to-tagname.md) | disallow tagName attribute on LinkTo component | | | |
343+
| [template-no-unbound](docs/rules/template-no-unbound.md) | disallow {{unbound}} helper | | | |
343344
| [template-no-with](docs/rules/template-no-with.md) | disallow {{with}} helper | | | |
344345

345346
### Ember Data

docs/rules/template-no-unbound.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# ember/template-no-unbound
2+
3+
> **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.
4+
5+
<!-- end auto-generated rule header -->
6+
7+
`{{unbound}}` is a legacy hold over from the days in which Ember's template engine was less performant. Its use today
8+
is vestigial, and it no longer offers performance benefits.
9+
10+
It is also a poor practice to use it for rendering only the initial value of a property that may later change.
11+
12+
## Examples
13+
14+
This rule **forbids** the following:
15+
16+
```gjs
17+
<template>
18+
{{unbound aVar}}
19+
</template>
20+
```
21+
22+
```gjs
23+
<template>
24+
{{some-component foo=(unbound aVar)}}
25+
</template>
26+
```
27+
28+
## References
29+
30+
- [deprecations/unbound block syntax](https://deprecations.emberjs.com/v1.x/#toc_block-and-multi-argument-unbound-helper)
31+
- [Ember api/unbound helper](https://api.emberjs.com/ember/release/classes/Ember.Templates.helpers/methods/each?anchor=unbound)

lib/rules/template-no-unbound.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/** @type {import('eslint').Rule.RuleModule} */
2+
module.exports = {
3+
meta: {
4+
type: 'suggestion',
5+
docs: {
6+
description: 'disallow {{unbound}} helper',
7+
category: 'Deprecations',
8+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unbound.md',
9+
templateMode: 'loose',
10+
},
11+
schema: [],
12+
messages: { unexpected: 'Unexpected {{unboundHelper}} usage.' },
13+
originallyFrom: {
14+
name: 'ember-template-lint',
15+
rule: 'lib/rules/no-unbound.js',
16+
docs: 'docs/rule/no-unbound.md',
17+
tests: 'test/unit/rules/no-unbound-test.js',
18+
},
19+
},
20+
create(context) {
21+
function check(node) {
22+
if (node.path?.type === 'GlimmerPathExpression' && node.path.original === 'unbound') {
23+
context.report({
24+
node,
25+
messageId: 'unexpected',
26+
data: { unboundHelper: '{{unbound}}' },
27+
});
28+
}
29+
}
30+
return {
31+
GlimmerMustacheStatement: check,
32+
GlimmerBlockStatement: check,
33+
GlimmerSubExpression: check,
34+
};
35+
},
36+
};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
const rule = require('../../../lib/rules/template-no-unbound');
2+
const RuleTester = require('eslint').RuleTester;
3+
4+
const validHbs = ['{{foo}}', '{{button}}'];
5+
6+
const invalidHbs = [
7+
{
8+
code: '{{unbound foo}}',
9+
output: null,
10+
errors: [{ message: 'Unexpected {{unbound}} usage.' }],
11+
},
12+
{
13+
code: '{{my-thing foo=(unbound foo)}}',
14+
output: null,
15+
errors: [{ message: 'Unexpected {{unbound}} usage.' }],
16+
},
17+
];
18+
19+
function wrapTemplate(entry) {
20+
if (typeof entry === 'string') {
21+
return `<template>${entry}</template>`;
22+
}
23+
24+
return {
25+
...entry,
26+
code: `<template>${entry.code}</template>`,
27+
output: entry.output ? `<template>${entry.output}</template>` : entry.output,
28+
errors: entry.errors.map(() => ({ messageId: 'unexpected' })),
29+
};
30+
}
31+
32+
const gjsRuleTester = new RuleTester({
33+
parser: require.resolve('ember-eslint-parser'),
34+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
35+
});
36+
37+
gjsRuleTester.run('template-no-unbound', rule, {
38+
valid: validHbs.map(wrapTemplate),
39+
invalid: invalidHbs.map(wrapTemplate),
40+
});
41+
42+
const hbsRuleTester = new RuleTester({
43+
parser: require.resolve('ember-eslint-parser/hbs'),
44+
parserOptions: {
45+
ecmaVersion: 2022,
46+
sourceType: 'module',
47+
},
48+
});
49+
50+
hbsRuleTester.run('template-no-unbound', rule, {
51+
valid: validHbs,
52+
invalid: invalidHbs,
53+
});

0 commit comments

Comments
 (0)