Skip to content

Commit 2ecc18c

Browse files
committed
Make helpers unambiguious with parens aka subexpressions
1 parent 55945e3 commit 2ecc18c

4 files changed

Lines changed: 89 additions & 38 deletions

File tree

transforms/angle-brackets/known-helpers.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,12 @@ const KNOWN_HELPERS = [
1818
'loc',
1919
'log',
2020
'mut',
21-
'outlet',
2221
'partial',
2322
'query-params',
2423
'readonly',
2524
'unbound',
2625
'unless',
2726
'with',
28-
'yield',
2927

3028
// Glimmer VM
3129
'identity', // glimmer blocks

transforms/angle-brackets/telemetry/mock-invokables.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,7 @@ module.exports = {
101101
'app/helpers/nested/helper': {
102102
type: 'Helper',
103103
},
104+
'app/helpers/fooknownhelper': {
105+
type: 'Helper',
106+
},
104107
};

transforms/angle-brackets/transform.js

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ function getNonDataAttributesFromParams(params) {
326326
return params.filter((it) => !isDataAttrPathExpression(it));
327327
}
328328

329-
function shouldIgnoreMustacheStatement(fullName, config, invokableData) {
329+
function isKnownHelper(fullName, config, invokableData) {
330330
let { helpers, components } = invokableData;
331331
let isTelemetryData = !!(helpers || components);
332332

@@ -337,11 +337,18 @@ function shouldIgnoreMustacheStatement(fullName, config, invokableData) {
337337
}
338338

339339
if (isTelemetryData) {
340+
let isComponent =
341+
!config.helpers.includes(name) &&
342+
[...(components || []), ...BUILT_IN_COMPONENTS].includes(name);
343+
344+
if (isComponent) {
345+
return false;
346+
}
347+
340348
let mergedHelpers = [...KNOWN_HELPERS, ...(helpers || [])];
341349
let isHelper = mergedHelpers.includes(name) || config.helpers.includes(name);
342-
let isComponent = [...(components || []), ...BUILT_IN_COMPONENTS].includes(name);
343350
let strName = `${name}`; // coerce boolean and number to string
344-
return (isHelper || !isComponent) && !strName.includes('.');
351+
return isHelper && !strName.includes('.');
345352
} else {
346353
return KNOWN_HELPERS.includes(name) || config.helpers.includes(name);
347354
}
@@ -363,7 +370,7 @@ function nodeHasPositionalParameters(node) {
363370
return false;
364371
}
365372

366-
function transformNode(node, fileInfo, config) {
373+
function transformComponentNode(node, fileInfo, config) {
367374
if (
368375
hasValuelessDataParams(node.params) &&
369376
shouldSkipDataTestParams(node.params, config.includeValuelessDataTestAttributes)
@@ -425,6 +432,10 @@ function transformNode(node, fileInfo, config) {
425432
);
426433
}
427434

435+
function transformHelperNode(node) {
436+
return b.mustache(b.sexpr(node.path, node.params, node.hash));
437+
}
438+
428439
function subExpressionToMustacheStatement(subExpression) {
429440
return b.mustache(subExpression.path, subExpression.params, subExpression.hash);
430441
}
@@ -457,34 +468,38 @@ function transformToAngleBracket(fileInfo, config, invokableData) {
457468
* Transform the attributes names & values properly
458469
*/
459470
return {
460-
MustacheStatement(node) {
471+
MustacheStatement(node, walkerPath) {
461472
const tagName = `${node.path && node.path.original}`;
462473

463474
if (config.components && !config.components.includes(tagName)) return;
464475

476+
const isTagKnownHelper = isKnownHelper(tagName, config, invokableData);
477+
465478
// Don't change attribute statements
466-
const isValidMustache =
467-
node.loc.source !== '(synthetic)' &&
468-
!shouldIgnoreMustacheStatement(tagName, config, invokableData);
479+
const isValidMustacheComponent = node.loc.source !== '(synthetic)' && !isTagKnownHelper;
469480
const isNestedComponent = isNestedComponentTagName(tagName);
470481

471482
if (
472-
isValidMustache &&
483+
isValidMustacheComponent &&
473484
(node.hash.pairs.length > 0 || node.params.length > 0 || isNestedComponent)
474485
) {
475-
return transformNode(node, fileInfo, config);
486+
return transformComponentNode(node, fileInfo, config);
487+
} else if (
488+
isTagKnownHelper &&
489+
node.path.type !== 'SubExpression' &&
490+
walkerPath.parent.node.type !== 'AttrNode' &&
491+
walkerPath.parent.node.type !== 'ConcatStatement'
492+
) {
493+
return transformHelperNode(node, walkerPath);
476494
}
477495
},
478496
BlockStatement(node) {
479497
let tagName = `${node.path.original}`;
480498

481499
if (config.components && !config.components.includes(tagName)) return;
482500

483-
if (
484-
!shouldIgnoreMustacheStatement(node.path.original, config, invokableData) ||
485-
isWallStreet(tagName)
486-
) {
487-
return transformNode(node, fileInfo, config);
501+
if (!isKnownHelper(node.path.original, config, invokableData) || isWallStreet(tagName)) {
502+
return transformComponentNode(node, fileInfo, config);
488503
}
489504
},
490505
AttrNode: {

transforms/angle-brackets/transform.test.js

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,6 @@ test('deeply-nested-sub', () => {
288288
)
289289
}}
290290
`;
291-
292291
/**
293292
* NOTE: An issue has been opened in `ember-template-recast` (https://github.com/ember-template-lint/ember-template-recast/issues/82)
294293
* regarding to create an API to allow a transform to customize the whitespace for newly created nodes.
@@ -477,7 +476,7 @@ test('let', () => {
477476
{{#let (capitalize this.person.firstName) (capitalize this.person.lastName)
478477
as |firstName lastName|
479478
}}
480-
Welcome back {{concat firstName ' ' lastName}}
479+
Welcome back {{(concat firstName ' ' lastName)}}
481480
482481
Account Details:
483482
First Name: {{firstName}}
@@ -555,8 +554,8 @@ test('link-to-inline', () => {
555554
<LinkTo @route=\\"apps.segments\\" class=\\"tabs__discrete-tab\\" @activeClass=\\"o__selected\\" @current-when=\\"apps.segments\\" data-test-segment-link=\\"segments\\">Segments</LinkTo>
556555
<LinkTo @route={{this.dynamicPath}} class=\\"tabs__discrete-tab\\" @activeClass=\\"o__selected\\" @current-when=\\"apps.segments\\" data-test-segment-link=\\"segments\\">Segments</LinkTo>
557556
<LinkTo @route=\\"apps.app.companies.segments.segment\\" @model={{segment}} class=\\"t__em-link\\">{{segment.name}}</LinkTo>
558-
<LinkTo @route=\\"flight\\" @model={{event.flight.id}} class=\\"btn btn-default btn-sm pull-right\\">{{t \\"show\\"}}</LinkTo>
559-
<LinkTo @route=\\"user\\" @model={{if linkActor event.actor.id event.user.id}}>{{t \\"show\\"}}</LinkTo>
557+
<LinkTo @route=\\"flight\\" @model={{event.flight.id}} class=\\"btn btn-default btn-sm pull-right\\">{{(t \\"show\\")}}</LinkTo>
558+
<LinkTo @route=\\"user\\" @model={{if linkActor event.actor.id event.user.id}}>{{(t \\"show\\")}}</LinkTo>
560559
<LinkTo @route=\\"user\\" @models={{array this.first this.second}}>Show</LinkTo>
561560
<LinkTo @route=\\"user\\" @models={{array this.first this.second}} @query={{hash foo=\\"baz\\"}}>Show</LinkTo>
562561
<LinkTo @route=\\"user\\" @model={{this.first}}>Show</LinkTo>
@@ -840,7 +839,7 @@ test('t-helper', () => {
840839

841840
expect(runTest('t-helper.hbs', input)).toMatchInlineSnapshot(`
842841
"
843-
{{t \\"some.string\\" param=\\"string\\" another=1}}
842+
{{(t \\"some.string\\" param=\\"string\\" another=1)}}
844843
"
845844
`);
846845
});
@@ -978,7 +977,7 @@ test('skip-default-helpers', () => {
978977
expect(runTest('skip-default-helpers.hbs', input, options)).toMatchInlineSnapshot(`
979978
"
980979
<div id=\\"main-container\\">
981-
{{liquid-outlet}}
980+
{{(liquid-outlet)}}
982981
</div>
983982
<button {{action \\"toggle\\" \\"showA\\"}}>
984983
Toggle A/B</button>
@@ -998,11 +997,11 @@ test('skip-default-helpers', () => {
998997
Two
999998
</div>
1000999
{{/liquid-if}}
1001-
{{moment '12-25-1995' 'MM-DD-YYYY'}}
1002-
{{moment-from '1995-12-25' '2995-12-25' hideAffix=true}}
1000+
{{(moment '12-25-1995' 'MM-DD-YYYY')}}
1001+
{{(moment-from '1995-12-25' '2995-12-25' hideAffix=true)}}
10031002
<SomeComponent @foo={{true}} />
1004-
{{some-helper1 foo=true}}
1005-
{{some-helper2 foo=true}}
1003+
{{(some-helper1 foo=true)}}
1004+
{{(some-helper2 foo=true)}}
10061005
"
10071006
`);
10081007
});
@@ -1040,7 +1039,7 @@ test('skip-default-helpers (no-config)', () => {
10401039
expect(runTest('skip-default-helpers.hbs', input)).toMatchInlineSnapshot(`
10411040
"
10421041
<div id=\\"main-container\\">
1043-
{{liquid-outlet}}
1042+
{{(liquid-outlet)}}
10441043
</div>
10451044
<button {{action \\"toggle\\" \\"showA\\"}}>
10461045
Toggle A/B</button>
@@ -1060,8 +1059,8 @@ test('skip-default-helpers (no-config)', () => {
10601059
Two
10611060
</div>
10621061
{{/liquid-if}}
1063-
{{moment '12-25-1995' 'MM-DD-YYYY'}}
1064-
{{moment-from '1995-12-25' '2995-12-25' hideAffix=true}}
1062+
{{(moment '12-25-1995' 'MM-DD-YYYY')}}
1063+
{{(moment-from '1995-12-25' '2995-12-25' hideAffix=true)}}
10651064
<SomeComponent @foo={{true}} />
10661065
<SomeHelper1 @foo={{true}} />
10671066
<SomeHelper2 @foo={{true}} />
@@ -1087,8 +1086,8 @@ test('custom-options', () => {
10871086
expect(runTest('custom-options.hbs', input, options)).toMatchInlineSnapshot(`
10881087
"
10891088
<SomeComponent @foo={{true}} />
1090-
{{some-helper1 foo=true}}
1091-
{{some-helper2 foo=true}}
1089+
{{(some-helper1 foo=true)}}
1090+
{{(some-helper2 foo=true)}}
10921091
{{link-to \\"Title\\" \\"some.route\\"}}
10931092
{{textarea value=this.model.body}}
10941093
{{input type=\\"checkbox\\" name=\\"email-opt-in\\" checked=this.model.emailPreference}}
@@ -1181,7 +1180,7 @@ test('preserve arguments', () => {
11811180
"
11821181
<FooBar @class=\\"baz\\" />
11831182
{{foo-bar data-baz class=\\"baz\\"}}
1184-
<LinkTo @route=\\"flight\\" @model={{event.flight.id}} class=\\"btn btn-default btn-sm pull-right\\">{{t \\"show\\"}}</LinkTo>
1183+
<LinkTo @route=\\"flight\\" @model={{event.flight.id}} class=\\"btn btn-default btn-sm pull-right\\">{{(t \\"show\\")}}</LinkTo>
11851184
"
11861185
`);
11871186
});
@@ -1278,18 +1277,34 @@ test('wallstreet-telemetry', () => {
12781277
let input = `
12791278
{{nested$helper}}
12801279
{{nested::helper}}
1281-
{{nested$helper param="cool!"}}
12821280
{{nested::helper param="yeah!"}}
12831281
{{helper-1}}
12841282
`;
12851283

12861284
expect(runTest('wallstreet-telemetry.hbs', input)).toMatchInlineSnapshot(`
12871285
"
12881286
{{nested$helper}}
1289-
{{nested::helper}}
1290-
{{nested$helper param=\\"cool!\\"}}
1291-
{{nested::helper param=\\"yeah!\\"}}
1292-
{{helper-1}}
1287+
{{(nested::helper)}}
1288+
{{(nested::helper param=\\"yeah!\\")}}
1289+
{{(helper-1)}}
1290+
"
1291+
`);
1292+
});
1293+
1294+
test('wrapping-helpers-with-parens', () => {
1295+
let input = `
1296+
{{fooknownhelper}}
1297+
{{(fooknownhelper)}}
1298+
{{fooknownhelper data-test-foo foo="bar"}}
1299+
{{foounknownhelper}}
1300+
`;
1301+
1302+
expect(runTest('wrapping-helpers-with-parens.hbs', input)).toMatchInlineSnapshot(`
1303+
"
1304+
{{(fooknownhelper)}}
1305+
{{(fooknownhelper)}}
1306+
{{(fooknownhelper data-test-foo foo=\\"bar\\")}}
1307+
{{foounknownhelper}}
12931308
"
12941309
`);
12951310
});
@@ -1335,3 +1350,23 @@ test('No telemetry', () => {
13351350
"
13361351
`);
13371352
});
1353+
1354+
test('pipe', () => {
1355+
let input = `<Foo @title={{concat ">"}} as |bar|></Foo>`;
1356+
1357+
expect(runTestWithData('pipe.hbs', input, {}, {})).toMatchInlineSnapshot(
1358+
`"<Foo @title={{concat \\">\\"}} as |bar|></Foo>"`
1359+
);
1360+
});
1361+
1362+
test('outlet', () => {
1363+
let input = `{{outlet}}`;
1364+
1365+
expect(runTestWithData('pipe.hbs', input, {}, {})).toMatchInlineSnapshot(`"{{outlet}}"`);
1366+
});
1367+
1368+
test('yield', () => {
1369+
let input = `{{yield}}`;
1370+
1371+
expect(runTestWithData('pipe.hbs', input, {}, {})).toMatchInlineSnapshot(`"{{yield}}"`);
1372+
});

0 commit comments

Comments
 (0)