From 20fe896766a271225d27ecdee1af3fc41f7996d3 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Mon, 22 Mar 2021 08:20:04 -0700 Subject: [PATCH] Add ability to parse scope function Adds the ability to parse scope functions as well as object expressions, and deprecates parsing object expressions. --- __tests__/tests.js | 56 ++++++++++++++++++++++++++++++++++++++++++++++ index.js | 55 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 99 insertions(+), 12 deletions(-) diff --git a/__tests__/tests.js b/__tests__/tests.js index 619e5481..e6ab8309 100644 --- a/__tests__/tests.js +++ b/__tests__/tests.js @@ -844,6 +844,62 @@ describe('htmlbars-inline-precompile', function () { }); }); + it('correctly handles scope function', function () { + let source = 'hello'; + transform( + `import { precompileTemplate } from '@ember/template-compilation';\nvar compiled = precompileTemplate('${source}', { scope: () => ({ foo, bar }) });` + ); + + expect(optionsReceived).toEqual({ + contents: source, + locals: ['foo', 'bar'], + }); + }); + + it('correctly handles scope function (non-block arrow function)', function () { + let source = 'hello'; + transform( + `import { precompileTemplate } from '@ember/template-compilation';\nvar compiled = precompileTemplate('${source}', { scope: () => ({ foo, bar }) });` + ); + expect(optionsReceived).toEqual({ + contents: source, + locals: ['foo', 'bar'], + }); + }); + + it('correctly handles scope function (block arrow function)', function () { + let source = 'hello'; + transform( + `import { precompileTemplate } from '@ember/template-compilation';\nvar compiled = precompileTemplate('${source}', { scope: () => { return { foo, bar }; }});` + ); + expect(optionsReceived).toEqual({ + contents: source, + locals: ['foo', 'bar'], + }); + }); + + it('correctly handles scope function (normal function)', function () { + let source = 'hello'; + transform( + `import { precompileTemplate } from '@ember/template-compilation';\nvar compiled = precompileTemplate('${source}', { scope: function() { return { foo, bar }; }});` + ); + expect(optionsReceived).toEqual({ + contents: source, + locals: ['foo', 'bar'], + }); + }); + + it('correctly handles scope function (object method)', function () { + let source = 'hello'; + transform( + `import { precompileTemplate } from '@ember/template-compilation';\nvar compiled = precompileTemplate('${source}', { scope() { return { foo, bar }; }});` + ); + expect(optionsReceived).toEqual({ + contents: source, + locals: ['foo', 'bar'], + }); + }); + it('errors if scope contains mismatched keys/values', function () { expect(() => { transform( diff --git a/index.js b/index.js index 7eaadcec..19ddddc6 100644 --- a/index.js +++ b/index.js @@ -11,12 +11,12 @@ module.exports = function (babel) { `(function() {\n throw new Error('ERROR_MESSAGE');\n})();` ); - function parseExpression(buildError, name, node) { + function parseExpression(state, buildError, name, node) { switch (node.type) { case 'ObjectExpression': - return parseObjectExpression(buildError, name, node); + return parseObjectExpression(state, buildError, name, node); case 'ArrayExpression': { - return parseArrayExpression(buildError, name, node); + return parseArrayExpression(state, buildError, name, node); } case 'StringLiteral': case 'BooleanLiteral': @@ -29,20 +29,50 @@ module.exports = function (babel) { } } - function parseArrayExpression(buildError, name, node) { - let result = node.elements.map((element) => parseExpression(buildError, name, element)); + function parseArrayExpression(state, buildError, name, node) { + let result = node.elements.map((element) => parseExpression(state, buildError, name, element)); return result; } - function parseScopeObject(buildError, name, node) { - if (node.type !== 'ObjectExpression') { + function parseScope(state, buildError, name, node) { + let body; + + if (node.type === 'ObjectMethod') { + body = node.body; + } else if (node.value.type === 'ObjectExpression') { + console.warn( + `Passing an object as the \`scope\` property to inline templates has been deprecated. Please pass a function that returns an object expression instead. Usage in: ${state.file.opts.filename}` + ); + + body = node.value; + } else { + body = node.value.body; + } + + let objExpression; + + if (body && body.type === 'ObjectExpression') { + objExpression = body; + } else if (body && body.type === 'BlockStatement') { + let returnStatement = body.body[0]; + + if (body.body.length !== 1 || returnStatement.type !== 'ReturnStatement') { + throw new Error( + 'Scope functions can only consist of a single return statement which returns an object expression containing references to in-scope values' + ); + } + + objExpression = returnStatement.argument; + } + + if (!objExpression || objExpression.type !== 'ObjectExpression') { throw buildError( - `Scope objects for \`${name}\` must be an object expression containing only references to in-scope values` + `Scope objects for \`${name}\` must be an object expression containing only references to in-scope values, or a function that returns an object expression containing only references to in-scope values` ); } - return node.properties.map((prop) => { + return objExpression.properties.map((prop) => { let { key, value } = prop; if (value.type !== 'Identifier' || value.name !== key.name) { @@ -55,7 +85,7 @@ module.exports = function (babel) { }); } - function parseObjectExpression(buildError, name, node, shouldParseScope = false) { + function parseObjectExpression(state, buildError, name, node, shouldParseScope = false) { let result = {}; node.properties.forEach((property) => { @@ -67,9 +97,9 @@ module.exports = function (babel) { property.key.type === 'Identifier' ? property.key.name : property.key.value; if (shouldParseScope && propertyName === 'scope') { - result.locals = parseScopeObject(buildError, name, property.value); + result.locals = parseScope(state, buildError, name, property); } else { - result[propertyName] = parseExpression(buildError, name, property.value); + result[propertyName] = parseExpression(state, buildError, name, property.value); } }); @@ -455,6 +485,7 @@ module.exports = function (babel) { } compilerOptions = parseObjectExpression( + state, path.buildCodeFrameError.bind(path), options.originalName, args[1],