From 4e6d3db7528b1d3eae54fcb62e57979162e68444 Mon Sep 17 00:00:00 2001 From: MURAKAMI Masahiko Date: Sat, 5 Sep 2020 18:34:01 +0900 Subject: [PATCH 1/3] feat: Add autoGlobalExports option to generate entry point functions from exports.* close #163 --- .eslintrc.js | 1 + README.md | 51 ++++++- index.js | 131 +++++++++++++++--- test/fixtures/exports-expected.js | 14 ++ ...s-generated-global-assignments-expected.js | 4 + test/fixtures/exports-source.js | 14 ++ test/index.test.js | 26 ++-- 7 files changed, 208 insertions(+), 33 deletions(-) create mode 100644 test/fixtures/exports-expected.js create mode 100644 test/fixtures/exports-generated-global-assignments-expected.js create mode 100644 test/fixtures/exports-source.js diff --git a/.eslintrc.js b/.eslintrc.js index d355660..ee9e664 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,6 @@ module.exports = { "env": { + "es6": true, "node": true, "commonjs": true }, diff --git a/README.md b/README.md index a366a42..7e48894 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# gas-entry-generator [![NPM version][npm-image]][npm-url]  [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url]  [![Coverage percentage][coveralls-image]][coveralls-url] [![Greenkeeper badge](https://badges.greenkeeper.io/fossamagna/gas-entry-generator.svg)](https://greenkeeper.io/) +# gas-entry-generator [![NPM version][npm-image]][npm-url]  [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url]  [![Coverage percentage][coveralls-image]][coveralls-url] Top level function generator for Google Apps Script. @@ -26,15 +26,15 @@ global.foo = function () { generate.js: ```js -var fs = require('fs'); -var gasEntryGenerator = require('gas-entry-generator'); +const fs = require('fs'); +const { generate } = require('gas-entry-generator'); -var fooSource = fs.readFileSync('foo.js', {encoding: 'utf8'}); -var options = { +const fooSource = fs.readFileSync('foo.js', {encoding: 'utf8'}); +const options = { comment: true }; -var entryFunction = gasEntryGenerator(fooSource, options); -console.log(entryFunction); +const output = generate(fooSource, options); +console.log(output.entryPointFunctions); ``` Console output: @@ -51,6 +51,43 @@ Execute to generate function as entry point. $ node generate.js ``` +## geranate global assignment expressions from exports.* + +foo.ts: +```ts +/** + * comment for foo function. + */ +exports.foo = () => 'bar'; +``` + +generate.js: +```js +const fs = require('fs'); +const { generate } = require('gas-entry-generator'); + +const fooSource = fs.readFileSync('foo.js', {encoding: 'utf8'}); +const options = { + comment: true, + autoGlobalExports: true // Enable to detect exports.* to generate entry point functions. +}; +const output = generate(fooSource, options); +console.log(output.entryPointFunctions); +console.log('-----'); +console.log(output.globalAssignments); +``` + +Console output: +``` +/** + * comment for foo function. + */ +function foo() { +} +----- +global.foo = exports.foo; +``` + [npm-image]: https://badge.fury.io/js/gas-entry-generator.svg [npm-url]: https://npmjs.org/package/gas-entry-generator [travis-image]: https://travis-ci.org/fossamagna/gas-entry-generator.svg?branch=master diff --git a/index.js b/index.js index f2e2183..0099f23 100644 --- a/index.js +++ b/index.js @@ -1,18 +1,18 @@ 'use strict'; -var esprima = require('esprima'); -var estraverse = require('estraverse'); -var escodegen = require('escodegen'); +const esprima = require('esprima'); +const estraverse = require('estraverse'); +const escodegen = require('escodegen'); function createBaseAST() { - var ast = {}; + const ast = {}; ast.type = 'Program'; ast.body = []; return ast; } function createStubFunctionASTNode(functionName, leadingComments, params) { - var node = { + const node = { type: 'FunctionDeclaration', id: { type: 'Identifier', @@ -36,26 +36,41 @@ function createStubFunctionASTNode(functionName, leadingComments, params) { return node; } -function _generateStubs(data, options) { - var ast = esprima.parseScript(data, { attachComment: options.comment }); - var stubs = []; - var functionName; +function _generateStubs(ast, options) { + const autoGlobalExports = options.autoGlobalExports; + const stubs = []; estraverse.traverse(ast, { leave: function (node) { if (node.type === 'ExpressionStatement' && isGlobalAssignmentExpression(node.expression)) { - functionName = node.expression.left.property.name; + const functionName = node.expression.left.property.name; stubs.push(createStubFunctionASTNode(functionName, node.leadingComments, node.expression.right.params)); } else if (node.type === 'ExpressionStatement' && node.expression.type === 'SequenceExpression') { node.expression.expressions.forEach(function (expression) { if (isGlobalAssignmentExpression(expression)) { - functionName = expression.left.property.name; + const functionName = expression.left.property.name; stubs.push(createStubFunctionASTNode(functionName, expression.leadingComments ? expression.leadingComments : node.leadingComments, expression.right.params)); } }); } + if (autoGlobalExports) { + if (node.type === 'ExpressionStatement' + && isExportsAssignmentExpression(node.expression)) { + const functionName = node.expression.left.property.name; + stubs.push(createStubFunctionASTNode(functionName, node.leadingComments, node.expression.right.params)); + } else if (node.type === 'ExpressionStatement' + && node.expression.type === 'SequenceExpression') { + node.expression.expressions.forEach(function (expression) { + if (isExportsAssignmentExpression(expression)) { + const functionName = expression.left.property.name; + stubs.push(createStubFunctionASTNode(functionName, expression.leadingComments ? + expression.leadingComments : node.leadingComments, expression.right.params)); + } + }); + } + } } }); @@ -70,15 +85,95 @@ function isGlobalAssignmentExpression(node) { && node.left.object.name === 'global' } -function generateStubs(source, options) { - options = options || {comment: false}; - var comment = !!options.comment; - var baseAST = createBaseAST(); - var stubs = _generateStubs(source, options); +function isExportsAssignmentExpression(node) { + return node.type === 'AssignmentExpression' + && node.operator === '=' + && node.left.type === 'MemberExpression' + && node.left.object.type === 'Identifier' + && node.left.object.name === 'exports' +} + +function generateStubs(ast, options) { + const baseAST = createBaseAST(); + const stubs = _generateStubs(ast, options); stubs.forEach(function (stub) { baseAST.body.push(stub); }); - return escodegen.generate(baseAST, { comment: comment }); + return escodegen.generate(baseAST, { comment: !!options.comment }); +} + +function generateGlobalAssignments(ast) { + const stubs = []; + estraverse.traverse(ast, { + leave: (node) => { + if (node.type === 'ExpressionStatement' + && isExportsAssignmentExpression(node.expression)) { + const functionName = node.expression.left.property.name; + stubs.push(createGlobalAssignmentASTNode(functionName)); + } else if (node.type === 'ExpressionStatement' + && node.expression.type === 'SequenceExpression') { + node.expression.expressions.forEach(function (expression) { + if (isExportsAssignmentExpression(expression)) { + const functionName = expression.left.property.name; + stubs.push(createGlobalAssignmentASTNode(functionName)); + } + }); + } + } + }); + const baseAST = createBaseAST(); + stubs.forEach(function (stub) { + baseAST.body.push(stub); + }); + return escodegen.generate(baseAST); +} + +function createGlobalAssignmentASTNode(functionName) { + const node = { + type: "ExpressionStatement", + expression: { + type: "AssignmentExpression", + operator: "=", + left: { + type: "MemberExpression", + computed: false, + object: { + type: "Identifier", + name: "global" + }, + property: { + type: "Identifier", + name: functionName + } + }, + right: { + type: "MemberExpression", + computed: false, + object: { + type: "Identifier", + name: "exports" + }, + property: { + type: "Identifier", + name: functionName + } + } + } + }; + return node; +} + +// exports.generateEntryPointFunctions = generateStubs; + +// exports.generateGlobalAssignments = generateGlobalAssignments; + +exports.generate = function(source, options = { comment: false, autoGlobalExports: false }){ + const ast = esprima.parseScript(source, { attachComment: options.comment }); + const functions = generateStubs(ast, options); + const globalAssignments = options.autoGlobalExports ? generateGlobalAssignments(ast, options) : undefined; + return { + entryPointFunctions : functions, + globalAssignments + }; } -module.exports = generateStubs; diff --git a/test/fixtures/exports-expected.js b/test/fixtures/exports-expected.js new file mode 100644 index 0000000..e5e6525 --- /dev/null +++ b/test/fixtures/exports-expected.js @@ -0,0 +1,14 @@ +/** + * This is foo. + */ +function foo() { +} +/** + * This is boo. + */ +function boo() { +} +function test() { +} +function X() { +} \ No newline at end of file diff --git a/test/fixtures/exports-generated-global-assignments-expected.js b/test/fixtures/exports-generated-global-assignments-expected.js new file mode 100644 index 0000000..38de16e --- /dev/null +++ b/test/fixtures/exports-generated-global-assignments-expected.js @@ -0,0 +1,4 @@ +global.foo = exports.foo; +global.boo = exports.boo; +global.test = exports.test; +global.X = exports.X; \ No newline at end of file diff --git a/test/fixtures/exports-source.js b/test/fixtures/exports-source.js new file mode 100644 index 0000000..74445d3 --- /dev/null +++ b/test/fixtures/exports-source.js @@ -0,0 +1,14 @@ +/** + * This is foo. + */ +exports.foo = function() {} + +/** + * This is boo. + */ +exports.boo = () => {}; + +function test() { +}; +var X = 'x'; +exports.test = test, exports.X = X; diff --git a/test/index.test.js b/test/index.test.js index dbe7cea..8d1cbb2 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1,12 +1,22 @@ -var test = require('tap').test; -var fs = require('fs'); +const test = require('tap').test; +const fs = require('fs'); +const { generate } = require('../'); -var gasEntryGenerator = require('../'); +test('generate function generate entry functions and global assignments from exports.*', function(t) { + const source = fs.readFileSync(__dirname + '/fixtures/exports-source.js', {encoding: 'utf8'}); + const expected = fs.readFileSync(__dirname + '/fixtures/exports-expected.js', {encoding: 'utf8'}); + const expectedGlobalAssignments = fs.readFileSync(__dirname + '/fixtures/exports-generated-global-assignments-expected.js', {encoding: 'utf8'}); + const output = generate(source, { comment: true, autoGlobalExports: true }); + t.equal(output.entryPointFunctions, expected, 'actual output will match expected'); + t.equal(output.globalAssignments, expectedGlobalAssignments, 'actual output will match expected'); + t.end(); +}); -test('gas-entry-generator', function(t) { - var source = fs.readFileSync(__dirname + '/fixtures/source.js', {encoding: 'utf8'}); - var expected = fs.readFileSync(__dirname + '/fixtures/expected.js', {encoding: 'utf8'}); - var entryFunctions = gasEntryGenerator(source, {comment: true}); - t.equal(entryFunctions.toString(), expected.toString(), 'actual output will match expected'); +test('generate function generate entry functions from global assignments', function(t) { + const source = fs.readFileSync(__dirname + '/fixtures/source.js', {encoding: 'utf8'}); + const expected = fs.readFileSync(__dirname + '/fixtures/expected.js', {encoding: 'utf8'}); + const output = generate(source, { comment: true }); + t.equal(output.entryPointFunctions, expected, 'actual output will match expected'); + t.equal(output.globalAssignments, undefined, 'actual output will match expected'); t.end(); }); From 8879ccad514f2b54028a25a900a70cfa6fab0c93 Mon Sep 17 00:00:00 2001 From: MURAKAMI Masahiko Date: Sat, 5 Sep 2020 18:39:14 +0900 Subject: [PATCH 2/3] chore: Remove unnecessary comment --- index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/index.js b/index.js index 0099f23..f0e6e5b 100644 --- a/index.js +++ b/index.js @@ -163,10 +163,6 @@ function createGlobalAssignmentASTNode(functionName) { return node; } -// exports.generateEntryPointFunctions = generateStubs; - -// exports.generateGlobalAssignments = generateGlobalAssignments; - exports.generate = function(source, options = { comment: false, autoGlobalExports: false }){ const ast = esprima.parseScript(source, { attachComment: options.comment }); const functions = generateStubs(ast, options); From c0527f731d198e55ca03f42abf17370c36963d77 Mon Sep 17 00:00:00 2001 From: MURAKAMI Masahiko Date: Sat, 5 Sep 2020 18:42:15 +0900 Subject: [PATCH 3/3] 2.0.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 01989bb..49ba79d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gas-entry-generator", - "version": "1.1.2", + "version": "2.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9bc3090..7c960a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gas-entry-generator", - "version": "1.1.2", + "version": "2.0.0", "description": "Generator of Google Apps Script entry point function", "main": "index.js", "scripts": {