Skip to content

Commit da96c63

Browse files
committed
Fix locals tracking
1 parent 438984d commit da96c63

2 files changed

Lines changed: 98 additions & 32 deletions

File tree

lib/rules/template-deprecated-inline-view-helper.js

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,3 @@
1-
function isViewPath(pathNode) {
2-
return (
3-
pathNode &&
4-
pathNode.type === 'GlimmerPathExpression' &&
5-
pathNode.original &&
6-
pathNode.original.startsWith('view.') &&
7-
pathNode.head?.type !== 'ThisHead' &&
8-
pathNode.head?.type !== 'AtHead'
9-
);
10-
}
11-
121
/** @type {import('eslint').Rule.RuleModule} */
132
module.exports = {
143
meta: {
@@ -19,7 +8,7 @@ module.exports = {
198
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-deprecated-inline-view-helper.md',
209
templateMode: 'loose',
2110
},
22-
fixable: null,
11+
fixable: 'code',
2312
schema: [],
2413
messages: {
2514
deprecated:
@@ -34,13 +23,52 @@ module.exports = {
3423
},
3524

3625
create(context) {
26+
const sourceCode = context.sourceCode;
27+
28+
// Track block param names to avoid false positives on locals like:
29+
// {{#each items as |view|}} {{view.name}} {{/each}}
30+
const localScopes = [];
31+
32+
function pushLocals(params) {
33+
localScopes.push(new Set(params || []));
34+
}
35+
36+
function popLocals() {
37+
localScopes.pop();
38+
}
39+
40+
function isLocal(name) {
41+
for (const scope of localScopes) {
42+
if (scope.has(name)) {
43+
return true;
44+
}
45+
}
46+
return false;
47+
}
48+
49+
function isViewPath(pathNode) {
50+
return (
51+
pathNode &&
52+
pathNode.type === 'GlimmerPathExpression' &&
53+
pathNode.original &&
54+
pathNode.original.startsWith('view.') &&
55+
pathNode.head?.type !== 'ThisHead' &&
56+
pathNode.head?.type !== 'AtHead' &&
57+
!isLocal('view')
58+
);
59+
}
60+
3761
function checkHashForViewPaths(node) {
3862
if (node.hash && node.hash.pairs) {
3963
for (const pair of node.hash.pairs) {
4064
if (isViewPath(pair.value)) {
65+
const strippedValue = pair.value.original.replace('view.', '');
4166
context.report({
4267
node,
4368
messageId: 'deprecated',
69+
fix(fixer) {
70+
return fixer.replaceText(pair.value, strippedValue);
71+
},
4472
});
4573
return true;
4674
}
@@ -52,11 +80,19 @@ module.exports = {
5280
function checkForView(node) {
5381
if (node.path && node.path.type === 'GlimmerPathExpression') {
5482
// Check for {{view ...}} with params or hash pairs
55-
if (node.path.original === 'view') {
56-
if (
57-
(node.hash && node.hash.pairs && node.hash.pairs.length > 0) ||
58-
(node.params && node.params.length > 0)
59-
) {
83+
if (node.path.original === 'view' && !isLocal('view')) {
84+
if (node.params && node.params.length > 0) {
85+
// {{view 'component-name'}} with a single string param is fixable
86+
const firstParam = node.params[0];
87+
const isFixable = node.params.length === 1 && firstParam.type === 'GlimmerStringLiteral';
88+
context.report({
89+
node,
90+
messageId: 'deprecated',
91+
fix: isFixable ? (fixer) => fixer.replaceText(node, `{{${firstParam.value}}}`) : null,
92+
});
93+
return;
94+
}
95+
if (node.hash && node.hash.pairs && node.hash.pairs.length > 0) {
6096
context.report({
6197
node,
6298
messageId: 'deprecated',
@@ -66,9 +102,13 @@ module.exports = {
66102
}
67103
// Check for {{view.something}} paths
68104
if (isViewPath(node.path)) {
105+
const strippedPath = node.path.original.replace('view.', '');
69106
context.report({
70107
node,
71108
messageId: 'deprecated',
109+
fix(fixer) {
110+
return fixer.replaceText(node.path, strippedPath);
111+
},
72112
});
73113
return;
74114
}
@@ -78,11 +118,33 @@ module.exports = {
78118
}
79119

80120
return {
81-
GlimmerMustacheStatement(node) {
121+
GlimmerBlockStatement(node) {
122+
// Push this block's params into scope so children can detect locals,
123+
// but check the block's own path/hash first (params aren't in scope yet
124+
// for the block's own arguments in real Handlebars semantics).
82125
checkForView(node);
126+
if (node.program && node.program.blockParams) {
127+
pushLocals(node.program.blockParams);
128+
}
129+
},
130+
'GlimmerBlockStatement:exit'(node) {
131+
if (node.program && node.program.blockParams) {
132+
popLocals();
133+
}
83134
},
84135

85-
GlimmerBlockStatement(node) {
136+
GlimmerElementNode(node) {
137+
if (node.blockParams && node.blockParams.length > 0) {
138+
pushLocals(node.blockParams);
139+
}
140+
},
141+
'GlimmerElementNode:exit'(node) {
142+
if (node.blockParams && node.blockParams.length > 0) {
143+
popLocals();
144+
}
145+
},
146+
147+
GlimmerMustacheStatement(node) {
86148
checkForView(node);
87149
},
88150
};

tests/lib/rules/template-deprecated-inline-view-helper.js

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,42 +24,44 @@ ruleTester.run('template-deprecated-inline-view-helper', rule, {
2424
'<template>{{this.view}}</template>',
2525
'<template>{{@view}}</template>',
2626
'<template>{{#let this.prop as |view|}} {{view}} {{/let}}</template>',
27+
// isLocal: view is a block param, view.name should not be flagged
28+
'<template>{{#each items as |view|}} {{view.name}} {{/each}}</template>',
2729
],
2830
invalid: [
2931
{
3032
code: '<template>{{view class="foo"}}</template>',
3133
output: null,
3234
errors: [{ messageId: 'deprecated' }],
3335
},
34-
3536
{
3637
code: "<template>{{view 'awful-fishsticks'}}</template>",
37-
output: null,
38+
output: '<template>{{awful-fishsticks}}</template>',
3839
errors: [{ messageId: 'deprecated' }],
3940
},
4041
{
4142
code: '<template>{{view.bad-fishsticks}}</template>',
42-
output: null,
43+
output: '<template>{{bad-fishsticks}}</template>',
4344
errors: [{ messageId: 'deprecated' }],
4445
},
4546
{
4647
code: '<template>{{view.terrible.fishsticks}}</template>',
47-
output: null,
48+
output: '<template>{{terrible.fishsticks}}</template>',
4849
errors: [{ messageId: 'deprecated' }],
4950
},
5051
{
5152
code: '<template>{{foo-bar bab=good baz=view.qux.qaz boo=okay}}</template>',
52-
output: null,
53+
output: '<template>{{foo-bar bab=good baz=qux.qaz boo=okay}}</template>',
5354
errors: [{ messageId: 'deprecated' }],
5455
},
5556
{
5657
code: '<template><div class="whatever-class" data-foo={{view.hallo}} sure=thing></div></template>',
57-
output: null,
58+
output:
59+
'<template><div class="whatever-class" data-foo={{hallo}} sure=thing></div></template>',
5860
errors: [{ messageId: 'deprecated' }],
5961
},
6062
{
6163
code: '<template>{{#foo-bar derp=view.whoops thing=whatever}}{{/foo-bar}}</template>',
62-
output: null,
64+
output: '<template>{{#foo-bar derp=whoops thing=whatever}}{{/foo-bar}}</template>',
6365
errors: [{ messageId: 'deprecated' }],
6466
},
6567
],
@@ -89,11 +91,13 @@ hbsRuleTester.run('template-deprecated-inline-view-helper', rule, {
8991
'{{this.view}}',
9092
'{{@view}}',
9193
'{{#let this.prop as |view|}} {{view}} {{/let}}',
94+
// isLocal: view is a block param, view.name should not be flagged
95+
'{{#each items as |view|}} {{view.name}} {{/each}}',
9296
],
9397
invalid: [
9498
{
9599
code: "{{view 'awful-fishsticks'}}",
96-
output: null,
100+
output: '{{awful-fishsticks}}',
97101
errors: [
98102
{
99103
message:
@@ -103,7 +107,7 @@ hbsRuleTester.run('template-deprecated-inline-view-helper', rule, {
103107
},
104108
{
105109
code: '{{view.bad-fishsticks}}',
106-
output: null,
110+
output: '{{bad-fishsticks}}',
107111
errors: [
108112
{
109113
message:
@@ -113,7 +117,7 @@ hbsRuleTester.run('template-deprecated-inline-view-helper', rule, {
113117
},
114118
{
115119
code: '{{view.terrible.fishsticks}}',
116-
output: null,
120+
output: '{{terrible.fishsticks}}',
117121
errors: [
118122
{
119123
message:
@@ -123,7 +127,7 @@ hbsRuleTester.run('template-deprecated-inline-view-helper', rule, {
123127
},
124128
{
125129
code: '{{foo-bar bab=good baz=view.qux.qaz boo=okay}}',
126-
output: null,
130+
output: '{{foo-bar bab=good baz=qux.qaz boo=okay}}',
127131
errors: [
128132
{
129133
message:
@@ -133,7 +137,7 @@ hbsRuleTester.run('template-deprecated-inline-view-helper', rule, {
133137
},
134138
{
135139
code: '<div class="whatever-class" data-foo={{view.hallo}} sure=thing></div>',
136-
output: null,
140+
output: '<div class="whatever-class" data-foo={{hallo}} sure=thing></div>',
137141
errors: [
138142
{
139143
message:
@@ -143,7 +147,7 @@ hbsRuleTester.run('template-deprecated-inline-view-helper', rule, {
143147
},
144148
{
145149
code: '{{#foo-bar derp=view.whoops thing=whatever}}{{/foo-bar}}',
146-
output: null,
150+
output: '{{#foo-bar derp=whoops thing=whatever}}{{/foo-bar}}',
147151
errors: [
148152
{
149153
message:

0 commit comments

Comments
 (0)