Skip to content

Commit e29b213

Browse files
committed
Sync with template-lint
1 parent 6bc2977 commit e29b213

3 files changed

Lines changed: 87 additions & 185 deletions

File tree

docs/rules/template-no-yield-only.md

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,41 @@
22

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

5-
Disallows components that only yield without any wrapper or additional functionality.
5+
Templates that only contain a single `{{yield}}` instruction are not required
6+
and increase the total template payload size.
67

7-
## Rule Details
8-
9-
Components should provide some structure or functionality beyond just yielding. If a component only yields, it adds unnecessary indirection.
8+
This rule warns about templates that only contain a single `{{yield}}`
9+
instruction.
1010

1111
## Examples
1212

13-
Examples of **incorrect** code for this rule:
13+
This rule **forbids** the following:
1414

1515
```gjs
1616
<template>
1717
{{yield}}
1818
</template>
1919
```
2020

21-
Examples of **correct** code for this rule:
21+
```gjs
22+
<template>
23+
24+
{{yield}}
25+
26+
</template>
27+
```
28+
29+
This rule **allows** the following:
2230

2331
```gjs
2432
<template>
25-
<div class="wrapper">
26-
{{yield}}
27-
</div>
33+
{{yield something}}
2834
</template>
2935
```
3036

3137
```gjs
3238
<template>
33-
{{this.setup}}
34-
{{yield}}
39+
<div>{{yield}}</div>
3540
</template>
3641
```
3742

@@ -41,4 +46,4 @@ Examples of **correct** code for this rule:
4146

4247
## References
4348

44-
- [eslint-plugin-ember template-no-yield-only](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-yield-only.md)
49+
- <https://github.com/ember-template-lint/ember-template-lint/issues/29>

lib/rules/template-no-yield-only.js

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,14 @@
1-
const IGNORABLE_TYPES = new Set(['GlimmerTextNode', 'GlimmerMustacheCommentStatement']);
2-
3-
function isBareYield(node) {
1+
function isYieldOnly(node) {
42
return (
53
node.type === 'GlimmerMustacheStatement' &&
64
node.path &&
75
node.path.type === 'GlimmerPathExpression' &&
86
node.path.original === 'yield' &&
9-
(!node.params || node.params.length === 0) &&
10-
(!node.hash || !node.hash.pairs || node.hash.pairs.length === 0)
7+
node.params &&
8+
node.params.length === 0
119
);
1210
}
1311

14-
function isMeaningfulContent(node) {
15-
if (node.type === 'GlimmerTextNode') {
16-
return node.chars && node.chars.trim().length > 0;
17-
}
18-
return !IGNORABLE_TYPES.has(node.type);
19-
}
20-
2112
/** @type {import('eslint').Rule.RuleModule} */
2213
module.exports = {
2314
meta: {
@@ -31,8 +22,7 @@ module.exports = {
3122
fixable: null,
3223
schema: [],
3324
messages: {
34-
noYieldOnly:
35-
'Component should not only yield. Add wrapper element or additional functionality.',
25+
noYieldOnly: '{{yieldExpression}}-only templates are not allowed',
3626
},
3727
originallyFrom: {
3828
name: 'ember-template-lint',
@@ -43,35 +33,29 @@ module.exports = {
4333
},
4434

4535
create(context) {
46-
function checkChildren(children) {
47-
let yieldNode = null;
48-
49-
for (const child of children) {
50-
if (isBareYield(child)) {
51-
yieldNode = child;
52-
} else if (isMeaningfulContent(child)) {
53-
return;
54-
}
55-
}
56-
57-
if (yieldNode) {
58-
context.report({ node: yieldNode, messageId: 'noYieldOnly' });
59-
}
60-
}
36+
let isOnlyYield = false;
6137

6238
return {
6339
GlimmerTemplate(node) {
64-
if (!node.body || node.body.length === 0) {
65-
return;
40+
const templateNodes =
41+
node.body[0] &&
42+
node.body[0].type === 'GlimmerElementNode' &&
43+
node.body[0].tag === 'template'
44+
? node.body[0].children
45+
: node.body;
46+
47+
if (templateNodes.length === 1 && isYieldOnly(templateNodes[0])) {
48+
isOnlyYield = true;
6649
}
50+
},
6751

68-
const firstChild = node.body[0];
69-
if (firstChild && firstChild.type === 'GlimmerElementNode') {
70-
// gjs/gts: body[0] is the <template> element, check its children
71-
checkChildren(firstChild.children || []);
72-
} else {
73-
// hbs: body directly contains the template nodes
74-
checkChildren(node.body);
52+
GlimmerMustacheStatement(node) {
53+
if (isOnlyYield) {
54+
context.report({
55+
node,
56+
messageId: 'noYieldOnly',
57+
data: { yieldExpression: '{{yield}}' },
58+
});
7559
}
7660
},
7761
};
Lines changed: 48 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,57 @@
1-
//------------------------------------------------------------------------------
2-
// Requirements
3-
//------------------------------------------------------------------------------
4-
51
const rule = require('../../../lib/rules/template-no-yield-only');
62
const RuleTester = require('eslint').RuleTester;
73

8-
//------------------------------------------------------------------------------
9-
// Tests
10-
//------------------------------------------------------------------------------
4+
const validHbs = [
5+
'{{yield (hash someProp=someValue)}}',
6+
'{{field}}',
7+
'{{#yield}}{{/yield}}',
8+
'<Yield/>',
9+
'<yield/>',
10+
];
1111

12-
const ruleTester = new RuleTester({
13-
parser: require.resolve('ember-eslint-parser'),
14-
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
15-
});
12+
const invalidHbs = [
13+
{
14+
code: '{{yield}}',
15+
output: null,
16+
errors: [{ messageId: 'noYieldOnly' }],
17+
},
18+
{
19+
code: ' {{yield}}',
20+
output: null,
21+
errors: [{ messageId: 'noYieldOnly' }],
22+
},
23+
{
24+
code: '\n {{yield}}\n ',
25+
output: null,
26+
errors: [{ messageId: 'noYieldOnly' }],
27+
},
28+
{
29+
code: '\n{{! some comment }} {{yield}}\n ',
30+
output: null,
31+
errors: [{ messageId: 'noYieldOnly' }],
32+
},
33+
];
1634

17-
ruleTester.run('template-no-yield-only', rule, {
18-
valid: [
19-
`<template>
20-
<div>
21-
{{yield}}
22-
</div>
23-
</template>`,
24-
`<template>
25-
{{this.something}}
26-
{{yield}}
27-
</template>`,
28-
`<template>
29-
<div></div>
30-
</template>`,
35+
function wrapTemplate(entry) {
36+
if (typeof entry === 'string') {
37+
return `<template>${entry}</template>`;
38+
}
3139

32-
'<template>{{yield (hash someProp=someValue)}}</template>',
33-
'<template>{{field}}</template>',
34-
'<template>{{#yield}}{{/yield}}</template>',
35-
'<template><Yield/></template>',
36-
'<template><yield/></template>',
37-
],
40+
return {
41+
...entry,
42+
code: `<template>${entry.code}</template>`,
43+
output: entry.output ? `<template>${entry.output}</template>` : entry.output,
44+
};
45+
}
3846

39-
invalid: [
40-
{
41-
code: `<template>
42-
{{yield}}
43-
</template>`,
44-
output: null,
45-
errors: [
46-
{
47-
message:
48-
'Component should not only yield. Add wrapper element or additional functionality.',
49-
type: 'GlimmerMustacheStatement',
50-
},
51-
],
52-
},
53-
{
54-
code: `<template>
55-
56-
{{yield}}
57-
58-
</template>`,
59-
output: null,
60-
errors: [
61-
{
62-
message:
63-
'Component should not only yield. Add wrapper element or additional functionality.',
64-
type: 'GlimmerMustacheStatement',
65-
},
66-
],
67-
},
68-
{
69-
code: '<template>{{yield}}</template>',
70-
output: null,
71-
errors: [
72-
{
73-
message:
74-
'Component should not only yield. Add wrapper element or additional functionality.',
75-
type: 'GlimmerMustacheStatement',
76-
},
77-
],
78-
},
47+
const gjsRuleTester = new RuleTester({
48+
parser: require.resolve('ember-eslint-parser'),
49+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
50+
});
7951

80-
{
81-
code: '<template> {{yield}}</template>',
82-
output: null,
83-
errors: [
84-
{
85-
message:
86-
'Component should not only yield. Add wrapper element or additional functionality.',
87-
},
88-
],
89-
},
90-
{
91-
code: `<template>
92-
{{yield}}
93-
</template>`,
94-
output: null,
95-
errors: [
96-
{
97-
message:
98-
'Component should not only yield. Add wrapper element or additional functionality.',
99-
},
100-
],
101-
},
102-
{
103-
code: `<template>
104-
{{! some comment }} {{yield}}
105-
</template>`,
106-
output: null,
107-
errors: [
108-
{
109-
message:
110-
'Component should not only yield. Add wrapper element or additional functionality.',
111-
},
112-
],
113-
},
114-
],
52+
gjsRuleTester.run('template-no-yield-only', rule, {
53+
valid: validHbs.filter((entry) => !entry.includes('template-lint-disable')).map(wrapTemplate),
54+
invalid: invalidHbs.map(wrapTemplate),
11555
});
11656

11757
const hbsRuleTester = new RuleTester({
@@ -123,33 +63,6 @@ const hbsRuleTester = new RuleTester({
12363
});
12464

12565
hbsRuleTester.run('template-no-yield-only', rule, {
126-
valid: [
127-
'{{yield (hash someProp=someValue)}}',
128-
'{{field}}',
129-
'{{#yield}}{{/yield}}',
130-
'<Yield/>',
131-
'<yield/>',
132-
],
133-
invalid: [
134-
{
135-
code: '{{yield}}',
136-
output: null,
137-
errors: [{ messageId: 'noYieldOnly' }],
138-
},
139-
{
140-
code: ' {{yield}}',
141-
output: null,
142-
errors: [{ messageId: 'noYieldOnly' }],
143-
},
144-
{
145-
code: '\n {{yield}}\n ',
146-
output: null,
147-
errors: [{ messageId: 'noYieldOnly' }],
148-
},
149-
{
150-
code: '\n{{! some comment }} {{yield}}\n ',
151-
output: null,
152-
errors: [{ messageId: 'noYieldOnly' }],
153-
},
154-
],
66+
valid: validHbs,
67+
invalid: invalidHbs,
15568
});

0 commit comments

Comments
 (0)