Skip to content

Commit 882c956

Browse files
committed
Make unambiguous helpers opt-in, fixes #514, fixes #504
1 parent 5b29cde commit 882c956

9 files changed

Lines changed: 105 additions & 58 deletions

File tree

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,24 @@ If you would like to only convert certain component invocations to use the angle
191191
}
192192
```
193193

194+
### Making helper invocations unambiguous
195+
196+
In order to make helper invocations unambiguous, use this:
197+
198+
**config/anglebrackets-codemod-config.json**
199+
200+
```js
201+
{
202+
"unambiguousHelpers": true
203+
}
204+
```
205+
206+
This will result in invocations like `{{concat "foo" "bar"}}` to be converted into `{{(concat "foo" "bar")}}`, which may be useful in strict-mode Embroider.
207+
208+
Note that it does not work in non-Embroider Ember, as of January 2024.
209+
210+
Note that ambiguous invocations, that cannot be statically distinguished between a helper, a property and a component, will not be modified.
211+
194212
## Debugging Workflow
195213

196214
Oftentimes, you want to debug the codemod or the transform to identify issues with the code or to understand

test/fixtures/with-telemetry/input/app/templates/application.hbs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
<h3 class="sr-only">
44
Components
55
</h3>
6-
{{#bs-nav type="pills" stacked=true as |nav|}}
6+
<BsNav @type="pills" @stacked={{true}} as |nav|>
77
{{#each this.model as |comp|}}
8-
{{#nav.item}}
9-
{{#nav.link-to route=comp.demoRoute}}
8+
<nav.item>
9+
<nav.link-to @route={{comp.demoRoute}}>
1010
{{comp.title}}
11-
{{/nav.link-to}}
12-
{{/nav.item}}
11+
</nav.link-to>
12+
</nav.item>
1313
{{/each}}
14-
{{/bs-nav}}
14+
</BsNav>
1515
</div>
1616
<div class="col-sm-8 col-sm-pull-4 col-md-9 col-md-pull-3">
1717
{{utils/bee-bop}}
@@ -28,13 +28,13 @@
2828
{{/if}}
2929
{{outlet}}
3030

31-
{{#bs-button id="openModal" onClick=(action this.addModal)}}Open{{/bs-button}}
31+
{{#bs-button id="openModal" onClick=(action "addModal")}}Open{{/bs-button}}
3232

3333
{{#if hasModal}}
3434
{{#bs-modal-simple open=modal onHidden=(action "removeModal") title="Dynamic Dialog"}}
3535
Hi there
3636
{{/bs-modal-simple}}
3737
{{/if}}
38-
{{file-less foo=true}}
38+
<FileLess @foo={{true}} />
3939
</div>
4040
</div>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
<div>this template has no js </div>
2-
{{#bs-button type="primary"}}Primary{{/bs-button}}
2+
<BsButton @type="primary">Primary</BsButton>

test/fixtures/with-telemetry/output/app/templates/application.hbs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
</BsNav>
1515
</div>
1616
<div class="col-sm-8 col-sm-pull-4 col-md-9 col-md-pull-3">
17-
{{(utils/bee-bop)}}
18-
{{(-wat-wat)}}
19-
{{(utils/-wat-wat)}}
17+
<Utils::BeeBop />
18+
{{-wat-wat}}
19+
<Utils::-WatWat />
2020
{{#if this.isDetailPage}}
2121
<h1>
2222
{{currentComponent.title}}
@@ -28,7 +28,7 @@
2828
{{/if}}
2929
{{outlet}}
3030

31-
<BsButton @id="openModal" @onClick={{action this.addModal}}>Open</BsButton>
31+
<BsButton @id="openModal" @onClick={{action "addModal"}}>Open</BsButton>
3232

3333
{{#if hasModal}}
3434
<BsModalSimple @open={{modal}} @onHidden={{action "removeModal"}} @title="Dynamic Dialog">
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<button>
2-
{{(titleize "string helpers for ember")}}
2+
{{titleize "string helpers for ember"}}
33
</button>
4-
{{(biz-baz canConvert="no" why="helper" where="local")}}
4+
{{biz-baz canConvert="no" why="helper" where="local"}}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
{{site-header user=this.user class=(if this.user.isAdmin "admin")}}
1+
<SiteHeader @user={{this.user}} @class={{if this.user.isAdmin "admin"}} />
22

3-
{{#super-select selected=this.user.country as |s|}}
3+
<SuperSelect @selected={{this.user.country}} as |s|>
44
{{#each this.availableCountries as |country|}}
5-
{{#s.option value=country}}{{country.name}}{{/s.option}}
5+
<s.option @value={{country}}>{{country.name}}</s.option>
66
{{/each}}
7-
{{/super-select}}
7+
</SuperSelect>
88

9-
{{ui/button text="Click me"}}
9+
<Ui::Button @text="Click me" />

transforms/angle-brackets/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ function getOptions() {
3131

3232
options.includeValuelessDataTestAttributes = !!config.includeValuelessDataTestAttributes;
3333
options.skipBuiltInComponents = !!config.skipBuiltInComponents;
34+
options.unambiguousHelpers = !!config.unambiguousHelpers;
3435
}
3536

3637
if (cliOptions.telemetry) {

transforms/angle-brackets/transform.js

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,16 @@ function isBuiltInComponent(key) {
4545
return BUILT_IN_COMPONENTS.includes(key);
4646
}
4747

48-
function isNestedComponentTagName(tagName) {
49-
return (
50-
tagName &&
51-
tagName.includes &&
52-
(tagName.includes('/') || (tagName.includes('-') && tagName.includes('.')))
53-
);
48+
function isNestedComponentTagName(tagName, unambiguousHelpers = false) {
49+
if (unambiguousHelpers) {
50+
return (
51+
tagName &&
52+
tagName.includes &&
53+
(tagName.includes('/') || (tagName.includes('-') && tagName.includes('.')))
54+
);
55+
}
56+
57+
return tagName && tagName.includes && (tagName.includes('/') || tagName.includes('-'));
5458
}
5559

5660
function isWallStreet(tagName) {
@@ -341,18 +345,21 @@ function isKnownHelper(fullName, config, invokableData) {
341345
}
342346

343347
if (isTelemetryData) {
344-
let isComponent =
345-
!config.helpers.includes(name) &&
346-
[...(components || []), ...BUILT_IN_COMPONENTS].includes(name);
348+
if (config.unambiguousHelpers) {
349+
let isComponent =
350+
!config.helpers.includes(name) &&
351+
[...(components || []), ...BUILT_IN_COMPONENTS].includes(name);
347352

348-
if (isComponent) {
349-
return false;
353+
if (isComponent) {
354+
return false;
355+
}
350356
}
351357

352358
let mergedHelpers = [...KNOWN_HELPERS, ...(helpers || [])];
353359
let isHelper = mergedHelpers.includes(name) || config.helpers.includes(name);
360+
let isComponent = [...(components || []), ...BUILT_IN_COMPONENTS].includes(name);
354361
let strName = `${name}`; // coerce boolean and number to string
355-
return isHelper && !strName.includes('.');
362+
return (isHelper || (!config.unambiguousHelpers && !isComponent)) && !strName.includes('.');
356363
} else {
357364
return KNOWN_HELPERS.includes(name) || config.helpers.includes(name);
358365
}
@@ -480,15 +487,19 @@ function transformToAngleBracket(fileInfo, config, invokableData) {
480487
const isTagKnownHelper = isKnownHelper(tagName, config, invokableData);
481488

482489
// Don't change attribute statements
483-
const isValidMustacheComponent = node.loc.source !== '(synthetic)' && !isTagKnownHelper;
484-
const isNestedComponent = isNestedComponentTagName(tagName);
490+
const isValidMustacheComponent = config.unambiguousHelpers
491+
? node.loc.source !== '(synthetic)' && !isTagKnownHelper
492+
: node.loc.source !== '(synthetic)' && !isKnownHelper(tagName, config, invokableData);
493+
494+
const isNestedComponent = isNestedComponentTagName(tagName, config.unambiguousHelpers);
485495

486496
if (
487497
isValidMustacheComponent &&
488498
(node.hash.pairs.length > 0 || node.params.length > 0 || isNestedComponent)
489499
) {
490500
return transformComponentNode(node, fileInfo, config);
491501
} else if (
502+
config.unambiguousHelpers &&
492503
isTagKnownHelper &&
493504
node.path.type !== 'SubExpression' &&
494505
walkerPath.parent.node.type !== 'AttrNode' &&

transforms/angle-brackets/transform.test.js

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ test('let', () => {
476476
{{#let (capitalize this.person.firstName) (capitalize this.person.lastName)
477477
as |firstName lastName|
478478
}}
479-
Welcome back {{(concat firstName ' ' lastName)}}
479+
Welcome back {{concat firstName ' ' lastName}}
480480
481481
Account Details:
482482
First Name: {{firstName}}
@@ -554,8 +554,8 @@ test('link-to-inline', () => {
554554
<LinkTo @route=\\"apps.segments\\" class=\\"tabs__discrete-tab\\" @activeClass=\\"o__selected\\" @current-when=\\"apps.segments\\" data-test-segment-link=\\"segments\\">Segments</LinkTo>
555555
<LinkTo @route={{this.dynamicPath}} class=\\"tabs__discrete-tab\\" @activeClass=\\"o__selected\\" @current-when=\\"apps.segments\\" data-test-segment-link=\\"segments\\">Segments</LinkTo>
556556
<LinkTo @route=\\"apps.app.companies.segments.segment\\" @model={{segment}} class=\\"t__em-link\\">{{segment.name}}</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>
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>
559559
<LinkTo @route=\\"user\\" @models={{array this.first this.second}}>Show</LinkTo>
560560
<LinkTo @route=\\"user\\" @models={{array this.first this.second}} @query={{hash foo=\\"baz\\"}}>Show</LinkTo>
561561
<LinkTo @route=\\"user\\" @model={{this.first}}>Show</LinkTo>
@@ -839,7 +839,7 @@ test('t-helper', () => {
839839

840840
expect(runTest('t-helper.hbs', input)).toMatchInlineSnapshot(`
841841
"
842-
{{(t \\"some.string\\" param=\\"string\\" another=1)}}
842+
{{t \\"some.string\\" param=\\"string\\" another=1}}
843843
"
844844
`);
845845
});
@@ -878,7 +878,7 @@ test('tilde', () => {
878878
expect(runTest('tilde.hbs', input)).toMatchInlineSnapshot(`
879879
"
880880
{{#if foo~}}
881-
{{some-component}}
881+
<SomeComponent />
882882
{{/if}}
883883
"
884884
`);
@@ -977,7 +977,7 @@ test('skip-default-helpers', () => {
977977
expect(runTest('skip-default-helpers.hbs', input, options)).toMatchInlineSnapshot(`
978978
"
979979
<div id=\\"main-container\\">
980-
{{(liquid-outlet)}}
980+
{{liquid-outlet}}
981981
</div>
982982
<button {{action \\"toggle\\" \\"showA\\"}}>
983983
Toggle A/B</button>
@@ -997,11 +997,11 @@ test('skip-default-helpers', () => {
997997
Two
998998
</div>
999999
{{/liquid-if}}
1000-
{{(moment '12-25-1995' 'MM-DD-YYYY')}}
1001-
{{(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}}
10021002
<SomeComponent @foo={{true}} />
1003-
{{(some-helper1 foo=true)}}
1004-
{{(some-helper2 foo=true)}}
1003+
{{some-helper1 foo=true}}
1004+
{{some-helper2 foo=true}}
10051005
"
10061006
`);
10071007
});
@@ -1039,7 +1039,7 @@ test('skip-default-helpers (no-config)', () => {
10391039
expect(runTest('skip-default-helpers.hbs', input)).toMatchInlineSnapshot(`
10401040
"
10411041
<div id=\\"main-container\\">
1042-
{{(liquid-outlet)}}
1042+
{{liquid-outlet}}
10431043
</div>
10441044
<button {{action \\"toggle\\" \\"showA\\"}}>
10451045
Toggle A/B</button>
@@ -1059,8 +1059,8 @@ test('skip-default-helpers (no-config)', () => {
10591059
Two
10601060
</div>
10611061
{{/liquid-if}}
1062-
{{(moment '12-25-1995' 'MM-DD-YYYY')}}
1063-
{{(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}}
10641064
<SomeComponent @foo={{true}} />
10651065
<SomeHelper1 @foo={{true}} />
10661066
<SomeHelper2 @foo={{true}} />
@@ -1086,8 +1086,8 @@ test('custom-options', () => {
10861086
expect(runTest('custom-options.hbs', input, options)).toMatchInlineSnapshot(`
10871087
"
10881088
<SomeComponent @foo={{true}} />
1089-
{{(some-helper1 foo=true)}}
1090-
{{(some-helper2 foo=true)}}
1089+
{{some-helper1 foo=true}}
1090+
{{some-helper2 foo=true}}
10911091
{{link-to \\"Title\\" \\"some.route\\"}}
10921092
{{textarea value=this.model.body}}
10931093
{{input type=\\"checkbox\\" name=\\"email-opt-in\\" checked=this.model.emailPreference}}
@@ -1180,7 +1180,7 @@ test('preserve arguments', () => {
11801180
"
11811181
<FooBar @class=\\"baz\\" />
11821182
{{foo-bar data-baz class=\\"baz\\"}}
1183-
<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>
11841184
"
11851185
`);
11861186
});
@@ -1284,26 +1284,26 @@ test('wallstreet-telemetry', () => {
12841284
expect(runTest('wallstreet-telemetry.hbs', input)).toMatchInlineSnapshot(`
12851285
"
12861286
{{nested$helper}}
1287-
{{(nested::helper)}}
1288-
{{(nested::helper param=\\"yeah!\\")}}
1289-
{{(helper-1)}}
1287+
{{nested::helper}}
1288+
{{nested::helper param=\\"yeah!\\"}}
1289+
{{helper-1}}
12901290
"
12911291
`);
12921292
});
12931293

12941294
test('wrapping-helpers-with-parens', () => {
12951295
let input = `
12961296
{{fooknownhelper}}
1297-
{{(fooknownhelper)}}
1297+
{{fooknownhelper}}
12981298
{{fooknownhelper data-test-foo foo="bar"}}
12991299
{{foounknownhelper}}
13001300
`;
13011301

13021302
expect(runTest('wrapping-helpers-with-parens.hbs', input)).toMatchInlineSnapshot(`
13031303
"
1304-
{{(fooknownhelper)}}
1305-
{{(fooknownhelper)}}
1306-
{{(fooknownhelper data-test-foo foo=\\"bar\\")}}
1304+
{{fooknownhelper}}
1305+
{{fooknownhelper}}
1306+
{{fooknownhelper data-test-foo foo=\\"bar\\"}}
13071307
{{foounknownhelper}}
13081308
"
13091309
`);
@@ -1382,7 +1382,24 @@ test('unknown helper with args', () => {
13821382
"
13831383
<ApiReference @component={{this.currentComponent}} />
13841384
{{api-reference someArg}}
1385-
{{api-reference}}
1385+
<ApiReference />
1386+
"
1387+
`);
1388+
});
1389+
1390+
test('unambiguousHelpers: true', () => {
1391+
let input = `
1392+
{{concat}}
1393+
{{unknown}}
1394+
{{t "some.string" param="string" another=1}}
1395+
`;
1396+
1397+
expect(runTest('unambiguousHelpers: true', input, { unambiguousHelpers: true }))
1398+
.toMatchInlineSnapshot(`
1399+
"
1400+
{{(concat)}}
1401+
{{unknown}}
1402+
{{(t \\"some.string\\" param=\\"string\\" another=1)}}
13861403
"
13871404
`);
13881405
});

0 commit comments

Comments
 (0)