From 4fba465a5481d5cfa519fa2fa43fd8786878de92 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Thu, 15 Aug 2019 09:48:28 -0400 Subject: [PATCH] Add support for using $ as a separator in addition to `::`. Since Ember has decided to repurpose the `::` sigil to mean "nested folder invocation" (see emberjs/rfcs#457), this introduces `$` as a replacement sigil. Ultimately, usage of `::` will be deprecated by this addon (though that will be in a future commit). --- addon/helpers/-translate-dynamic.js | 2 +- index.js | 31 ++- lib/namespacing-transform.js | 43 ++- .../components/test-namespacing-test.js | 256 +++++++++++++----- vendor/service-inject.js | 3 +- 5 files changed, 252 insertions(+), 83 deletions(-) diff --git a/addon/helpers/-translate-dynamic.js b/addon/helpers/-translate-dynamic.js index 35a5297..f3e7fb0 100644 --- a/addon/helpers/-translate-dynamic.js +++ b/addon/helpers/-translate-dynamic.js @@ -2,7 +2,7 @@ import { helper } from '@ember/component/helper'; export default helper(function(args) { if (args && typeof args[0] === 'string') { - return args[0].replace('::', '@'); + return args[0].replace('::', '@').replace('$', '@'); } return args && args[0]; }); diff --git a/index.js b/index.js index a2d5489..6ba1ad4 100644 --- a/index.js +++ b/index.js @@ -14,20 +14,39 @@ module.exports = { }, setupPreprocessorRegistry(type, registry) { - let pluginObj = this._buildPlugin(); - pluginObj.parallelBabel = { + let dollarPluginObj = this._buildDollarPlugin(); + dollarPluginObj.parallelBabel = { requireFile: __filename, - buildUsing: '_buildPlugin', + buildUsing: '_buildDollarPlugin', params: {} } - registry.add("htmlbars-ast-plugin", pluginObj); + registry.add("htmlbars-ast-plugin", dollarPluginObj); + + let colonPluginObj = this._buildColonPlugin(); + colonPluginObj.parallelBabel = { + requireFile: __filename, + buildUsing: '_buildColonPlugin', + params: {} + } + + registry.add("htmlbars-ast-plugin", colonPluginObj); + }, + + _buildDollarPlugin() { + return { + name: 'holy-futuristic-template-namespacing-batman', + plugin: require("./lib/namespacing-transform").DollarNamespacingTransform, + baseDir: function() { + return __dirname; + } + }; }, - _buildPlugin() { + _buildColonPlugin() { return { name: 'holy-futuristic-template-namespacing-batman', - plugin: require("./lib/namespacing-transform"), + plugin: require("./lib/namespacing-transform").ColonNamespacingTransform, baseDir: function() { return __dirname; } diff --git a/lib/namespacing-transform.js b/lib/namespacing-transform.js index a34fc6d..5e02848 100644 --- a/lib/namespacing-transform.js +++ b/lib/namespacing-transform.js @@ -2,14 +2,14 @@ const TRANSLATE_HELPER = 'ember-holy-futuristic-template-namespacing-batman@-translate-dynamic'; -function rewriteOrWrapComponentParam(node, b) { +function rewriteOrWrapComponentParam(node, b, sigil) { if (!node.params.length) { return; } let firstParam = node.params[0]; if (firstParam.type === 'StringLiteral') { // handle string constant - node.params[0] = b.string(firstParam.original.replace('::', '@')); + node.params[0] = b.string(firstParam.original.replace(sigil, '@')); return; } if (firstParam.type === 'PathExpression' || firstParam.type === 'SubExpression') { @@ -18,18 +18,20 @@ function rewriteOrWrapComponentParam(node, b) { } } -module.exports = class NamespacingTransform { +class NamespacingTransform { constructor(options) { this.syntax = null; this.options = options; + this.sigil = undefined; } transform(ast) { const b = this.syntax.builders; + let sigil = this.sigil; this.syntax.traverse(ast, { PathExpression(node) { - if (node.parts.length > 1 || !node.original.includes('::')) { + if (node.parts.length > 1 || !node.original.includes(sigil)) { return; } @@ -38,11 +40,11 @@ module.exports = class NamespacingTransform { if (loc === null) { loc = undefined; } - return b.path(node.original.replace('::', '@'), loc); + return b.path(node.original.replace(sigil, '@'), loc); }, ElementNode(node) { - if (node.tag.indexOf('::') > -1) { - node.tag = node.tag.replace('::', '@'); + if (node.tag.indexOf(sigil) > -1) { + node.tag = node.tag.replace(sigil, '@'); } }, MustacheStatement(node) { @@ -50,24 +52,45 @@ module.exports = class NamespacingTransform { // we don't care about non-component expressions return; } - rewriteOrWrapComponentParam(node, b); + rewriteOrWrapComponentParam(node, b, sigil); }, SubExpression(node) { if (node.path.original !== 'component') { // we don't care about non-component expressions return; } - rewriteOrWrapComponentParam(node, b); + rewriteOrWrapComponentParam(node, b, sigil); }, BlockStatement(node) { if (node.path.original !== 'component') { // we don't care about blocks not using component return; } - rewriteOrWrapComponentParam(node, b); + rewriteOrWrapComponentParam(node, b, sigil); } }); return ast; } } + +class DollarNamespacingTransform extends NamespacingTransform { + constructor(options) { + super(options); + + this.sigil = '$'; + } +} + +class ColonNamespacingTransform extends NamespacingTransform { + constructor(options) { + super(options); + + this.sigil = '::'; + } +} + +module.exports = { + DollarNamespacingTransform, + ColonNamespacingTransform, +}; diff --git a/tests/integration/components/test-namespacing-test.js b/tests/integration/components/test-namespacing-test.js index 52f1024..8ad4f01 100644 --- a/tests/integration/components/test-namespacing-test.js +++ b/tests/integration/components/test-namespacing-test.js @@ -14,11 +14,21 @@ define('other-namespace/services/some-service', ['exports'], function(exports) { }); }); -define('other-namespace/templates/components/some-service-thing', ['exports'], function(exports) { +define('other-namespace/templates/components/some-dollar-service-thing', ['exports'], function(exports) { exports.default = hbs`{{someService.text}}`; }); -define('other-namespace/components/some-service-thing', ['exports'], function(exports) { +define('other-namespace/components/some-dollar-service-thing', ['exports'], function(exports) { + exports.default = Component.extend({ + someService: inject('other-namespace$some-service'), + }); +}); + +define('other-namespace/templates/components/some-colon-service-thing', ['exports'], function(exports) { + exports.default = hbs`{{someService.text}}`; +}); + +define('other-namespace/components/some-colon-service-thing', ['exports'], function(exports) { exports.default = Component.extend({ someService: inject('other-namespace::some-service'), }); @@ -61,69 +71,184 @@ define('other-namespace/helpers/some-helper-thing', ['exports'], function(export module('test-namespacing', function(hooks) { setupRenderingTest(hooks); - test('it can render a helper', async function(assert) { - await render(hbs`{{other-namespace::some-helper-thing 'hi'}}`); + module('$ scoping', function() { + test('it can render a helper', async function(assert) { + await render(hbs`{{other-namespace$some-helper-thing 'hi'}}`); - assert.equal(find('*').textContent.trim(), 'hi'); - }); + assert.equal(find('*').textContent.trim(), 'hi'); + }); - test('it can render a template only component', async function(assert) { - await render(hbs`{{other-namespace::some-template-thing derp="here"}}`); + test('it can render a template only component', async function(assert) { + await render(hbs`{{other-namespace$some-template-thing derp="here"}}`); - assert.equal(find('*').textContent.trim(), 'some-template-thing'); - }); + assert.equal(find('*').textContent.trim(), 'some-template-thing'); + }); - test('it can render a JS only component', async function(assert) { - await render(hbs`{{#other-namespace::some-js-thing}}hi{{/other-namespace::some-js-thing}}`); + test('it can render a JS only component', async function(assert) { + await render(hbs`{{#other-namespace$some-js-thing}}hi{{/other-namespace$some-js-thing}}`); - assert.equal(find('*').textContent.trim(), 'hi'); - }); + assert.equal(find('*').textContent.trim(), 'hi'); + }); - test('it can render an angle bracket component', async function(assert) { - await render(hbs`hi`); + test('it can render an angle bracket component', async function(assert) { + await render(hbs`hi`); - assert.equal(find('*').textContent.trim(), 'hi'); - }); + assert.equal(find('*').textContent.trim(), 'hi'); + }); - test('it can render service injection', async function(assert) { - await render(hbs`{{other-namespace::some-service-thing}}`); + test('it can render service injection', async function(assert) { + await render(hbs`{{other-namespace$some-dollar-service-thing}}`); - assert.equal(find('*').textContent.trim(), 'some random text'); - }); + assert.equal(find('*').textContent.trim(), 'some random text'); + }); - test('it can render dynamic component', async function(assert) { - this.dynamicName = "other-namespace::some-service-thing"; - await render(hbs`{{component dynamicName}}`); + test('it can render dynamic component', async function(assert) { + this.dynamicName = "other-namespace$some-dollar-service-thing"; + await render(hbs`{{component dynamicName}}`); - assert.equal(find('*').textContent.trim(), 'some random text'); - }); + assert.equal(find('*').textContent.trim(), 'some random text'); + }); - test('it can render component helper with static name', async function(assert) { - await render(hbs`{{component "other-namespace::some-service-thing"}}`); + test('it can render component helper with static name', async function(assert) { + await render(hbs`{{component "other-namespace$some-dollar-service-thing"}}`); - assert.equal(find('*').textContent.trim(), 'some random text'); - }); + assert.equal(find('*').textContent.trim(), 'some random text'); + }); - test('it can render component helper with expression', async function(assert) { - await render(hbs` + test('it can render component helper with expression', async function(assert) { + await render(hbs` {{component - (other-namespace::some-helper-thing "other-namespace::some-service-thing") + (other-namespace$some-helper-thing "other-namespace$some-dollar-service-thing") }} `); - assert.equal(find('*').textContent.trim(), 'some random text'); - }); + assert.equal(find('*').textContent.trim(), 'some random text'); + }); - test('it can render component helper in block mode with static name', async function(assert) { - await render(hbs`{{#component "other-namespace::some-yield-static" as |cmpt|}} + test('it can render component helper in block mode with static name', async function(assert) { + await render(hbs`{{#component "other-namespace$some-yield-static" as |cmpt|}} {{component cmpt}} {{/component}}`); - assert.equal(find('*').textContent.trim(), 'some-template-thing'); + assert.equal(find('*').textContent.trim(), 'some-template-thing'); + }); + + test('it can render component helper in block mode with expression', async function(assert) { + await render(hbs` + {{#component + (other-namespace$some-helper-thing "other-namespace$some-yield-static") + as |cmpt|}} + {{component cmpt}} + {{/component}} + `); + + assert.equal(find('*').textContent.trim(), 'some-template-thing'); + }); + + test('it can render component helper in block mode with dynamic name', async function(assert) { + this.dynamicName = 'other-namespace$some-yield-static'; + await render(hbs`{{#component dynamicName as |cmpt|}} + {{component cmpt}} + {{/component}}`); + + assert.equal(find('*').textContent.trim(), 'some-template-thing'); + }); + + test('it can yield component with dynamic name', async function(assert) { + await render( + hbs`{{#other-namespace$some-yield-dynamic dynamicName="other-namespace$some-template-thing" as |cmpt|}} + {{component cmpt}} + {{/other-namespace$some-yield-dynamic}}` + ); + + assert.equal(find('*').textContent.trim(), 'some-template-thing'); + }); + + test('it can yield component with static name', async function(assert) { + await render( + hbs`{{#other-namespace$some-yield-static as |cmpt|}} + {{component cmpt}} + {{/other-namespace$some-yield-static}}` + ); + + assert.equal(find('*').textContent.trim(), 'some-template-thing'); + }); + + test('it can yield component with expression', async function(assert) { + await render( + hbs`{{#other-namespace$some-yield-helper as |cmpt|}} + {{component cmpt}} + {{/other-namespace$some-yield-helper}}` + ); + + assert.equal(find('*').textContent.trim(), 'some-template-thing'); + }); }); - test('it can render component helper in block mode with expression', async function(assert) { - await render(hbs` + module(':: scoping [deprecated]', function() { + test('it can render a helper', async function(assert) { + await render(hbs`{{other-namespace::some-helper-thing 'hi'}}`); + + assert.equal(find('*').textContent.trim(), 'hi'); + }); + + test('it can render a template only component', async function(assert) { + await render(hbs`{{other-namespace::some-template-thing derp="here"}}`); + + assert.equal(find('*').textContent.trim(), 'some-template-thing'); + }); + + test('it can render a JS only component', async function(assert) { + await render(hbs`{{#other-namespace::some-js-thing}}hi{{/other-namespace::some-js-thing}}`); + + assert.equal(find('*').textContent.trim(), 'hi'); + }); + + test('it can render an angle bracket component', async function(assert) { + await render(hbs`hi`); + + assert.equal(find('*').textContent.trim(), 'hi'); + }); + + test('it can render service injection', async function(assert) { + await render(hbs`{{other-namespace::some-colon-service-thing}}`); + + assert.equal(find('*').textContent.trim(), 'some random text'); + }); + + test('it can render dynamic component', async function(assert) { + this.dynamicName = "other-namespace::some-colon-service-thing"; + await render(hbs`{{component dynamicName}}`); + + assert.equal(find('*').textContent.trim(), 'some random text'); + }); + + test('it can render component helper with static name', async function(assert) { + await render(hbs`{{component "other-namespace::some-colon-service-thing"}}`); + + assert.equal(find('*').textContent.trim(), 'some random text'); + }); + + test('it can render component helper with expression', async function(assert) { + await render(hbs` + {{component + (other-namespace::some-helper-thing "other-namespace::some-colon-service-thing") + }} + `); + + assert.equal(find('*').textContent.trim(), 'some random text'); + }); + + test('it can render component helper in block mode with static name', async function(assert) { + await render(hbs`{{#component "other-namespace::some-yield-static" as |cmpt|}} + {{component cmpt}} + {{/component}}`); + + assert.equal(find('*').textContent.trim(), 'some-template-thing'); + }); + + test('it can render component helper in block mode with expression', async function(assert) { + await render(hbs` {{#component (other-namespace::some-helper-thing "other-namespace::some-yield-static") as |cmpt|}} @@ -131,45 +256,46 @@ module('test-namespacing', function(hooks) { {{/component}} `); - assert.equal(find('*').textContent.trim(), 'some-template-thing'); - }); + assert.equal(find('*').textContent.trim(), 'some-template-thing'); + }); - test('it can render component helper in block mode with dynamic name', async function(assert) { - this.dynamicName = 'other-namespace::some-yield-static'; - await render(hbs`{{#component dynamicName as |cmpt|}} + test('it can render component helper in block mode with dynamic name', async function(assert) { + this.dynamicName = 'other-namespace::some-yield-static'; + await render(hbs`{{#component dynamicName as |cmpt|}} {{component cmpt}} {{/component}}`); - assert.equal(find('*').textContent.trim(), 'some-template-thing'); - }); + assert.equal(find('*').textContent.trim(), 'some-template-thing'); + }); - test('it can yield component with dynamic name', async function(assert) { - await render( - hbs`{{#other-namespace::some-yield-dynamic dynamicName="other-namespace::some-template-thing" as |cmpt|}} + test('it can yield component with dynamic name', async function(assert) { + await render( + hbs`{{#other-namespace::some-yield-dynamic dynamicName="other-namespace::some-template-thing" as |cmpt|}} {{component cmpt}} {{/other-namespace::some-yield-dynamic}}` - ); + ); - assert.equal(find('*').textContent.trim(), 'some-template-thing'); - }); + assert.equal(find('*').textContent.trim(), 'some-template-thing'); + }); - test('it can yield component with static name', async function(assert) { - await render( - hbs`{{#other-namespace::some-yield-static as |cmpt|}} + test('it can yield component with static name', async function(assert) { + await render( + hbs`{{#other-namespace::some-yield-static as |cmpt|}} {{component cmpt}} {{/other-namespace::some-yield-static}}` - ); + ); - assert.equal(find('*').textContent.trim(), 'some-template-thing'); - }); + assert.equal(find('*').textContent.trim(), 'some-template-thing'); + }); - test('it can yield component with expression', async function(assert) { - await render( - hbs`{{#other-namespace::some-yield-helper as |cmpt|}} + test('it can yield component with expression', async function(assert) { + await render( + hbs`{{#other-namespace::some-yield-helper as |cmpt|}} {{component cmpt}} {{/other-namespace::some-yield-helper}}` - ); + ); - assert.equal(find('*').textContent.trim(), 'some-template-thing'); + assert.equal(find('*').textContent.trim(), 'some-template-thing'); + }); }); }); diff --git a/vendor/service-inject.js b/vendor/service-inject.js index 3eeeb74..455c043 100644 --- a/vendor/service-inject.js +++ b/vendor/service-inject.js @@ -4,7 +4,8 @@ var ORIGINAL_INJECT_SERVICE = Ember.inject.service; // eslint-disable-next-line ember/new-module-imports Ember.inject.service = function(_name) { - var name = _name === undefined ? undefined : _name.replace('::', '@'); + var name = _name === undefined ? undefined : _name.replace('::', '@').replace('$', '@'); + return ORIGINAL_INJECT_SERVICE.call(this, name); }; })();