From 1dca043b82a1b0d4ba57698b7bad34c5f0f41069 Mon Sep 17 00:00:00 2001 From: Chunpeng Huo Date: Mon, 12 Apr 2021 20:06:09 +1000 Subject: [PATCH] fix(cli-bundler): fix error on latest JS syntax, replaced outdated esprima with meriyah --- lib/build/amodro-trace/lib/parse.js | 266 ++---------------------- lib/build/amodro-trace/lib/transform.js | 10 +- lib/build/amodro-trace/write/replace.js | 4 +- lib/build/ast-matcher.js | 24 +-- lib/build/find-deps.js | 4 +- package.json | 2 +- spec/lib/build/ast-matcher.spec.js | 15 +- 7 files changed, 41 insertions(+), 284 deletions(-) diff --git a/lib/build/amodro-trace/lib/parse.js b/lib/build/amodro-trace/lib/parse.js index 078380da7..10861808c 100644 --- a/lib/build/amodro-trace/lib/parse.js +++ b/lib/build/amodro-trace/lib/parse.js @@ -7,7 +7,7 @@ var define = function(ary, fn) { /*jslint plusplus: true */ /*global define: false */ -define(['esprima', './lang'], function (esprima, lang) { +define(['meriyah', './lang'], function (meriyah, lang) { 'use strict'; function arrayToString(ary) { @@ -30,7 +30,7 @@ define(['esprima', './lang'], function (esprima, lang) { mixin = lang.mixin, hasProp = lang.hasProp; - //From an esprima example for traversing its ast. + //From an meriyah example for traversing its ast. function traverse(object, visitor) { var child; @@ -131,7 +131,7 @@ define(['esprima', './lang'], function (esprima, lang) { result = '', moduleList = [], needsDefine = true, - astRoot = esprima.parse(fileContents); + astRoot = meriyah.parseScript(fileContents, {next: true, webcompat: true}); parse.recurse(astRoot, function (callName, config, name, deps, node, factoryIdentifier, fnExpScope) { if (!deps) { @@ -296,85 +296,6 @@ define(['esprima', './lang'], function (esprima, lang) { } }; - /** - * Determines if the file defines the require/define module API. - * Specifically, it looks for the `define.amd = ` expression. - * @param {String} fileName - * @param {String} fileContents - * @returns {Boolean} - */ - parse.definesRequire = function (fileName, fileContents) { - var foundDefine = false, - foundDefineAmd = false; - - traverse(esprima.parse(fileContents), function (node) { - // Look for a top level declaration of a define, like - // var requirejs, require, define, off Program body. - if (node.type === 'Program' && node.body && node.body.length) { - foundDefine = node.body.some(function(bodyNode) { - // var define - if (bodyNode.type === 'VariableDeclaration') { - var decls = bodyNode.declarations; - if (decls) { - var hasVarDefine = decls.some(function(declNode) { - return (declNode.type === 'VariableDeclarator' && - declNode.id && - declNode.id.type === 'Identifier' && - declNode.id.name === 'define'); - }); - if (hasVarDefine) { - return true; - } - } - } - - // function define() {} - if (bodyNode.type === 'FunctionDeclaration' && - bodyNode.id && - bodyNode.id.type === 'Identifier' && - bodyNode.id.name === 'define') { - return true; - } - - - - - - - }); - } - - // Need define variable found first, before detecting define.amd. - if (foundDefine && parse.hasDefineAmd(node)) { - foundDefineAmd = true; - - //Stop traversal - return false; - } - }); - - return foundDefine && foundDefineAmd; - }; - - /** - * Finds require("") calls inside a CommonJS anonymous module wrapped in a - * define(function(require, exports, module){}) wrapper. These dependencies - * will be added to a modified define() call that lists the dependencies - * on the outside of the function. - * @param {String} fileName - * @param {String|Object} fileContents: a string of contents, or an already - * parsed AST tree. - * @returns {Array} an array of module names that are dependencies. Always - * returns an array, but could be of length zero. - */ - parse.getAnonDeps = function (fileName, fileContents) { - var astRoot = typeof fileContents === 'string' ? - esprima.parse(fileContents) : fileContents, - defFunc = this.findAnonDefineFactory(astRoot); - - return parse.getAnonDepsFromNode(defFunc); - }; - /** * Finds require("") calls inside a CommonJS anonymous module wrapped * in a define function, given an AST node for the definition function. @@ -457,8 +378,10 @@ define(['esprima', './lang'], function (esprima, lang) { /*jslint evil: true */ var jsConfig, foundConfig, stringData, foundRange, quote, quoteMatch, quoteRegExp = /(:\s|\[\s*)(['"])/, - astRoot = esprima.parse(fileContents, { - loc: true + astRoot = meriyah.parseScript(fileContents, { + loc: true, + next: true, + webcompat: true }); traverse(astRoot, function (node) { @@ -514,54 +437,6 @@ define(['esprima', './lang'], function (esprima, lang) { } }; - /** - * Renames require/requirejs/define calls to be ns + '.' + require/requirejs/define - * Does *not* do .config calls though. See pragma.namespace for the complete - * set of namespace transforms. This function is used because require calls - * inside a define() call should not be renamed, so a simple regexp is not - * good enough. - * @param {String} fileContents the contents to transform. - * @param {String} ns the namespace, *not* including trailing dot. - * @return {String} the fileContents with the namespace applied - */ - parse.renameNamespace = function (fileContents, ns) { - var lines, - locs = [], - astRoot = esprima.parse(fileContents, { - loc: true - }); - - parse.recurse(astRoot, function (callName, config, name, deps, node) { - locs.push(node.loc); - //Do not recurse into define functions, they should be using - //local defines. - return callName !== 'define'; - }, {}); - - if (locs.length) { - lines = fileContents.split('\n'); - - //Go backwards through the found locs, adding in the namespace name - //in front. - locs.reverse(); - locs.forEach(function (loc) { - var startIndex = loc.start.column, - //start.line is 1-based, not 0 based. - lineIndex = loc.start.line - 1, - line = lines[lineIndex]; - - lines[lineIndex] = line.substring(0, startIndex) + - ns + '.' + - line.substring(startIndex, - line.length); - }); - - fileContents = lines.join('\n'); - } - - return fileContents; - }; - /** * Finds all dependencies specified in dependency arrays and inside * simplified commonjs wrappers. @@ -577,7 +452,7 @@ define(['esprima', './lang'], function (esprima, lang) { if (fileContents && fileContents.type) { astRoot = fileContents } else { - astRoot = esprima.parse(fileContents); + astRoot = meriyah.parseScript(fileContents, {next: true, webcompat: true}); } parse.recurse(astRoot, function (callName, config, name, deps) { @@ -596,7 +471,7 @@ define(['esprima', './lang'], function (esprima, lang) { parse.findCjsDependencies = function (fileName, fileContents) { var dependencies = []; - traverse(esprima.parse(fileContents), function (node) { + traverse(meriyah.parseScript(fileContents, {next: true, webcompat: true}), function (node) { var arg; if (node && node.type === 'CallExpression' && node.callee && @@ -668,53 +543,6 @@ define(['esprima', './lang'], function (esprima, lang) { node.callee.name === 'define'; }; - /** - * If there is a named define in the file, returns the name. Does not - * scan for mulitple names, just the first one. - */ - parse.getNamedDefine = function (fileContents) { - var name; - traverse(esprima.parse(fileContents), function (node) { - if (node && node.type === 'CallExpression' && node.callee && - node.callee.type === 'Identifier' && - node.callee.name === 'define' && - node[argPropName] && node[argPropName][0] && - node[argPropName][0].type === 'Literal') { - name = node[argPropName][0].value; - return false; - } - }); - - return name; - }; - - /** - * Finds all the named define module IDs in a file. - */ - parse.getAllNamedDefines = function (fileContents, excludeMap) { - var names = []; - parse.recurse(esprima.parse(fileContents), - function (callName, config, name, deps, node, factoryIdentifier, fnExpScope) { - if (callName === 'define' && name) { - if (!excludeMap.hasOwnProperty(name)) { - names.push(name); - } - } - - //If a UMD definition that points to a factory that is an Identifier, - //indicate processing should not traverse inside the UMD definition. - if (callName === 'define' && factoryIdentifier && hasProp(fnExpScope, factoryIdentifier)) { - return factoryIdentifier; - } - - //If define was found, no need to dive deeper, unless - //the config explicitly wants to dig deeper. - return true; - }, {}); - - return names; - }; - /** * Determines if define(), require({}|[]) or requirejs was called in the * file. Also finds out if define() is declared and if define.amd is called. @@ -722,7 +550,7 @@ define(['esprima', './lang'], function (esprima, lang) { parse.usesAmdOrRequireJs = function (fileName, fileContents) { var uses; - traverse(esprima.parse(fileContents), function (node) { + traverse(meriyah.parseScript(fileContents, {next: true, webcompat: true}), function (node) { var type, callName, arg; if (parse.hasDefDefine(node)) { @@ -764,7 +592,7 @@ define(['esprima', './lang'], function (esprima, lang) { assignsExports = false; - traverse(esprima.parse(fileContents), function (node) { + traverse(meriyah.parseScript(fileContents, {next: true, webcompat: true}), function (node) { var type, // modified to fix a bug on (true || exports.name = {}) // https://github.com/requirejs/r.js/issues/980 @@ -980,7 +808,7 @@ define(['esprima', './lang'], function (esprima, lang) { /** * Converts an AST node into a JS source string by extracting * the node's location from the given contents string. Assumes - * esprima.parse() with loc was done. + * meriyah.parseScript() with loc was done. * @param {String} contents * @param {Object} node * @returns {String} a JS source string. @@ -1015,75 +843,5 @@ define(['esprima', './lang'], function (esprima, lang) { }; }; - /** - * Extracts license comments from JS text. - * @param {String} fileName - * @param {String} contents - * @returns {String} a string of license comments. - */ - parse.getLicenseComments = function (fileName, contents) { - var commentNode, refNode, subNode, value, i, j, - //xpconnect's Reflect does not support comment or range, but - //prefer continued operation vs strict parity of operation, - //as license comments can be expressed in other ways, like - //via wrap args, or linked via sourcemaps. - ast = esprima.parse(contents, { - comment: true, - range: true - }), - result = '', - existsMap = {}, - lineEnd = contents.indexOf('\r') === -1 ? '\n' : '\r\n'; - - if (ast.comments) { - for (i = 0; i < ast.comments.length; i++) { - commentNode = ast.comments[i]; - - if (commentNode.type === 'Line') { - value = '//' + commentNode.value + lineEnd; - refNode = commentNode; - - if (i + 1 >= ast.comments.length) { - value += lineEnd; - } else { - //Look for immediately adjacent single line comments - //since it could from a multiple line comment made out - //of single line comments. Like this comment. - for (j = i + 1; j < ast.comments.length; j++) { - subNode = ast.comments[j]; - if (subNode.type === 'Line' && - subNode.range[0] === refNode.range[1] + 1) { - //Adjacent single line comment. Collect it. - value += '//' + subNode.value + lineEnd; - refNode = subNode; - } else { - //No more single line comment blocks. Break out - //and continue outer looping. - break; - } - } - value += lineEnd; - i = j - 1; - } - } else { - value = '/*' + commentNode.value + '*/' + lineEnd + lineEnd; - } - - if (!existsMap[value] && (value.indexOf('license') !== -1 || - (commentNode.type === 'Block' && - value.indexOf('/*!') === 0) || - value.indexOf('opyright') !== -1 || - value.indexOf('(c)') !== -1)) { - - result += value; - existsMap[value] = true; - } - - } - } - - return result; - }; - return parse; }); diff --git a/lib/build/amodro-trace/lib/transform.js b/lib/build/amodro-trace/lib/transform.js index 6a6b0d264..d81204413 100644 --- a/lib/build/amodro-trace/lib/transform.js +++ b/lib/build/amodro-trace/lib/transform.js @@ -13,8 +13,8 @@ var define = function(ary, fn) { /*global define */ -define([ 'esprima', './parse', './lang'], -function (esprima, parse, lang) { +define([ 'meriyah', './parse', './lang'], +function (meriyah, parse, lang) { 'use strict'; var transform, jsExtRegExp = /\.js$/g, @@ -51,8 +51,10 @@ function (esprima, parse, lang) { }; try { - astRoot = esprima.parse(contents, { - loc: true + astRoot = meriyah.parseScript(contents, { + loc: true, + next: true, + webcompat: true }); } catch (e) { var logger = options.logger; diff --git a/lib/build/amodro-trace/write/replace.js b/lib/build/amodro-trace/write/replace.js index 180eda093..73afd3dde 100644 --- a/lib/build/amodro-trace/write/replace.js +++ b/lib/build/amodro-trace/write/replace.js @@ -4,7 +4,7 @@ // and also dep string cleanup // remove tailing '/', '.js' -const esprima = require('esprima'); +const meriyah = require('meriyah'); const astMatcher = require('../../ast-matcher').astMatcher; // it is definitely a named AMD module at this stage var amdDep = astMatcher('define(__str, [__anl_deps], __any)'); @@ -46,7 +46,7 @@ module.exports = function stubs(options) { }; // need node location - const parsed = esprima.parse(contents, {range: true}); + const parsed = meriyah.parseScript(contents, {ranges: true, next: true, webcompat: true}); if (isUMD(parsed) || isUMD2(parsed)) { // Skip lib in umd format, because browersify umd build could diff --git a/lib/build/ast-matcher.js b/lib/build/ast-matcher.js index 30611475a..a12765646 100644 --- a/lib/build/ast-matcher.js +++ b/lib/build/ast-matcher.js @@ -1,4 +1,4 @@ -const esprima = require('esprima'); +const meriyah = require('meriyah'); const STOP = false; const SKIP_BRANCH = 1; @@ -6,7 +6,7 @@ const SKIP_BRANCH = 1; // ignore position info, and raw const IGNORED_KEYS = ['start', 'end', 'loc', 'location', 'locations', 'line', 'column', 'range', 'ranges', 'raw']; -// From an esprima example for traversing its ast. +// From an meriyah example for traversing its ast. // modified to support branch skip. function traverse(object, visitor) { let child; @@ -60,7 +60,7 @@ function matchTerm(pattern) { } /** - * Extract info from a partial esprima syntax tree, see astMatcher for pattern format + * Extract info from a partial meriyah syntax tree, see astMatcher for pattern format * @param pattern The pattern used on matching * @param part The target partial syntax tree * @return Returns named matches, or false. @@ -172,19 +172,19 @@ exports.extract = function(pattern, part) { }; /** - * Compile a pattern into esprima syntax tree - * @param pattern The pattern used on matching, can be a string or esprima node - * @return Returns an esprima node to be used as pattern in extract(pattern, part) + * Compile a pattern into meriyah syntax tree + * @param pattern The pattern used on matching, can be a string or meriyah node + * @return Returns an meriyah node to be used as pattern in extract(pattern, part) */ exports.compilePattern = function(pattern) { - // pass esprima syntax tree obj + // pass meriyah syntax tree obj if (pattern && pattern.type) return pattern; if (typeof pattern !== 'string') { - throw new Error('input pattern is neither a string nor an esprima node.'); + throw new Error('input pattern is neither a string nor an meriyah node.'); } - let exp = esprima.parse(pattern); + let exp = meriyah.parseScript(pattern, {next: true, webcompat: true}); if (exp.type !== 'Program' || !exp.body) { throw new Error(`Not a valid expression: "${pattern}".`); @@ -207,13 +207,13 @@ exports.compilePattern = function(pattern) { function ensureParsed(codeOrNode) { // bypass parsed node if (codeOrNode && codeOrNode.type) return codeOrNode; - return esprima.parse(codeOrNode); + return meriyah.parseScript(codeOrNode, {next: true, webcompat: true}); } /** * Pattern matching using AST on JavaScript source code * @param pattern The pattern to be matched - * @return Returns a function that takes source code string (or esprima syntax tree) as input, produces matched result or undefined. + * @return Returns a function that takes source code string (or meriyah syntax tree) as input, produces matched result or undefined. * * __any matches any single node, but no extract * __anl matches array of nodes, but no extract @@ -268,7 +268,7 @@ exports.astMatcher = function(pattern) { /** * Dependency search for dummies, this is a high level api to simplify the usage of astMatcher * @param arguments Multiple patterns to match, instead of using __str_/__arr_, use __dep and __deps to match string or partial string array. - * @return Returns a function that takes source code string (or esprima syntax tree) as input, produces an array of string matched, or empty array. + * @return Returns a function that takes source code string (or meriyah syntax tree) as input, produces an array of string matched, or empty array. * * Usage: * let f = jsDepFinder('a(__dep)', '__any.globalResources([__deps])'); diff --git a/lib/build/find-deps.js b/lib/build/find-deps.js index 27df45343..2d2ef198b 100644 --- a/lib/build/find-deps.js +++ b/lib/build/find-deps.js @@ -1,4 +1,4 @@ -const esprima = require('esprima'); +const meriyah = require('meriyah'); const parse = require('./amodro-trace/lib/parse'); const am = require('./ast-matcher'); const jsDepFinder = am.jsDepFinder; @@ -231,7 +231,7 @@ exports.findJsDeps = function(filename, contents, loaderType = 'require') { // for all following static analysis, // only parse once for efficiency - let parsed = esprima.parse(contents); + let parsed = meriyah.parseScript(contents, {next: true, webcompat: true}); add(parse.findDependencies(filename, parsed)); // clear commonjs wrapper deps diff --git a/package.json b/package.json index de018f1f3..af65846ce 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "del": "^6.0.0", "domain-browser": "^4.19.0", "enquirer": "^2.3.6", - "esprima": "^4.0.1", "events": "^3.3.0", "fs-browser-stub": "^1.0.1", "gulp": "^4.0.2", @@ -67,6 +66,7 @@ "https-browserify": "^1.0.0", "lodash": "^4.17.21", "map-stream": "0.0.7", + "meriyah": "^4.1.5", "minimatch": "^3.0.4", "npm-which": "^3.0.1", "os-browserify": "^0.3.0", diff --git a/spec/lib/build/ast-matcher.spec.js b/spec/lib/build/ast-matcher.spec.js index 5534d92fe..cc9671000 100644 --- a/spec/lib/build/ast-matcher.spec.js +++ b/spec/lib/build/ast-matcher.spec.js @@ -1,4 +1,4 @@ -const esprima = require('esprima'); +const meriyah = require('meriyah'); const astm = require('../../../lib/build/ast-matcher'); const extract = astm.extract; @@ -11,8 +11,8 @@ const extractTest = function(pattern, part) { }; describe('compilePattern', () => { - it('bypass esprima node', () => { - let node = esprima.parse('a = 1'); + it('bypass meriyah node', () => { + let node = meriyah.parseScript('a = 1'); expect(compilePattern(node)).toBe(node); }); @@ -60,16 +60,13 @@ describe('extract', () => { expect(Object.keys(r)).toEqual(['a']); expect(r.a.type).toBe('Literal'); expect(r.a.value).toBe('foo'); - expect(r.a.raw).toBe('"foo"'); r = extractTest('a(__any_a,__any_b)', 'a("foo", "bar")'); expect(Object.keys(r).sort()).toEqual(['a', 'b']); expect(r.a.type).toBe('Literal'); expect(r.a.value).toBe('foo'); - expect(r.a.raw).toBe('"foo"'); expect(r.b.type).toBe('Literal'); expect(r.b.value).toBe('bar'); - expect(r.b.raw).toBe('"bar"'); }); it('__anl matches nodes array, but no extract', () => { @@ -208,7 +205,7 @@ describe('matcher built by astMatcher', () => { it('accepts both string input or node input', () => { let m = astMatcher('a(__str_foo)'); expect(m('a("foo")').length).toBe(1); - expect(m(esprima.parse('a("foo")')).length).toBe(1); + expect(m(meriyah.parseScript('a("foo")')).length).toBe(1); }); it('rejects unknown input', () => { @@ -281,9 +278,9 @@ describe('jsDepFinder', () => { expect(f('a("a"); b("b"); b.a("c")')).toEqual(['a']); }); - it('finds matching dep, accepts esprima node as input', () => { + it('finds matching dep, accepts meriyah node as input', () => { let f = jsDepFinder('a(__dep)'); - expect(f(esprima.parse('a("a"); b("b"); b.a("c")'))).toEqual(['a']); + expect(f(meriyah.parseScript('a("a"); b("b"); b.a("c")'))).toEqual(['a']); }); it('finds matching dep by matching length', () => {