Skip to content

Commit

Permalink
Refactor nested AST transform.
Browse files Browse the repository at this point in the history
* Ensures only one `let` is ever created
* Reuses the same yielded block param for every invocation of the same
  underlying component
* Ensures that `Foo::Bar::Baz` works (arbitrary nesting levels)
* Uses custom suffix to ensure no block param collisions exist
  • Loading branch information
rwjblue committed Apr 4, 2019
1 parent bbd4dbb commit baca90b
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 39 deletions.
78 changes: 40 additions & 38 deletions lib/ast-nested-transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ class AngleBracketPolyfill {
transform(ast) {
let b = this.syntax.builders;

// in order to debug in https://https://astexplorer.net/#/gist/0590eb883edfcd163b183514df4cc717
// **** copy from here ****

function dasherize(string) {
return string.replace(/[A-Z]/g, function(char, index) {
if (index === 0 || !ALPHA.test(string[index - 1])) {
Expand All @@ -22,58 +25,57 @@ class AngleBracketPolyfill {
});
}

function replaceNestedComponents(string) {
return string.replace('::', '/');
}
let rootProgram;
let letBlock;
let yieldedComponents = new Map();

function getBlockParamName(node) {
let unnestedName = node.tag.replace('::', '');
let possibleName = unnestedName;

const nestedNames = node.children
.map(child => {
switch (child.type) {
case 'ElementNode':
return child.tag;
case 'MustacheStatement':
return child.path.original;
default:
break;
}
})
.filter(child => child);

let adder = 0;
while (nestedNames.indexOf(possibleName) !== -1) {
adder++;
possibleName = `${unnestedName}${adder}`;
function ensureLetWrapper() {
if (!letBlock) {
letBlock = b.block('let', [], b.hash([]), b.program(rootProgram.body), null, null);
rootProgram.body = [letBlock];
}

return possibleName;
}

function wrapAngeBrackedComponentWithLetHelper(node) {
let tag = node.tag;
let counter = 0;
function localNameForYieldedComponent(tag) {
let localName = yieldedComponents.get(tag);
if (!localName) {
localName = tag.replace(/::/g, '') + '_ANGLE_' + counter++;
let transformedPath = dasherize(tag.replace(/::/g, '/'));

let params = [
b.sexpr(b.path('component'), [b.string(dasherize(replaceNestedComponents(tag)))]),
];
let positionalArg = b.sexpr(b.path('component'), [b.string(transformedPath)]);
letBlock.params.push(positionalArg);
letBlock.program.blockParams.push(localName);

node.tag = getBlockParamName(node);
let program = b.program([node], [getBlockParamName(node)]);
program.__ignore = true;
yieldedComponents.set(tag, localName);
}

return b.block('let', params, b.hash([]), program, null, null);
return localName;
}

let visitor = {
// supports [email protected]
Template(node) {
rootProgram = node;
},

// supports glimmer-vm < 0.39
Program(node) {
// on older ember versions `Program` is used for both the "wrapping
// template" and for each block
if (!rootProgram) {
rootProgram = node;
}
},

ElementNode(node) {
let tag = node.tag;

if (tag.indexOf('::') !== -1 && tag.charAt(0) === tag.charAt(0).toUpperCase()) {
let newNode = wrapAngeBrackedComponentWithLetHelper(node);
if (tag.indexOf('::') !== -1) {
ensureLetWrapper();

return newNode;
let localName = localNameForYieldedComponent(tag);
node.tag = localName;
}
},
};
Expand Down
2 changes: 1 addition & 1 deletion lib/ast-transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class AngleBracketPolyfill {
}

function replaceNestedComponents(string) {
return string.replace('::', '/');
return string.replace(/::/g, '/');
}

function isSimple(mustache) {
Expand Down
23 changes: 23 additions & 0 deletions tests/integration/components/angle-bracket-invocation-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,19 @@ module('Integration | Component | angle-bracket-invocation', function(hooks) {
assert.dom('[data-foo="bar"]').exists();
});

test('nested paths do not conflict with non-nested paths with similar names', async function(assert) {
this.owner.register('template:components/foo/bar', hbs`hi rwjblue!`);
this.owner.register('template:components/foo-bar', hbs`hi rtablada!`);

await render(hbs`
<Foo::Bar data-foo="bar"/>
<FooBar data-foo="baz" />
`);

assert.dom('[data-foo="bar"]').hasText('hi rwjblue!');
assert.dom('[data-foo="baz"]').hasText('hi rtablada!');
});

test('invoke nested path', async function(assert) {
this.owner.register('template:components/foo/bar', hbs`hi rwjblue!`);

Expand All @@ -202,6 +215,16 @@ module('Integration | Component | angle-bracket-invocation', function(hooks) {
assert.dom('[data-foo="bar"]').exists();
});

test('invoke deeply nested path', async function(assert) {
this.owner.register('template:components/foo/bar/baz/qux', hbs`hi rwjblue!`);

await render(hbs`
<Foo::Bar::Baz::Qux data-foo="bar"/>
`);

assert.dom('[data-foo="bar"]').exists();
});

test('invoke dynamic - path', async function(assert) {
this.owner.register('service:elsewhere', Service.extend());
this.owner.register(
Expand Down

0 comments on commit baca90b

Please sign in to comment.