From 3a7077de4da997739f2617eac607a96e64c9a6c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Mon, 12 Feb 2018 20:31:12 +0100 Subject: [PATCH 01/11] ignore require() calls inside already browserified bundles If a function expression declares a `require` parameter, its body is not analyzed for `require()` calls. This is mostly helpful for bundles that were already browserified, `detective` will ignore the calls to browser-pack's require runtime. --- index.js | 13 +++++++++++++ test/files/scope.js | 8 ++++++++ test/scope.js | 9 +++++++++ 3 files changed, 30 insertions(+) create mode 100644 test/files/scope.js create mode 100644 test/scope.js diff --git a/index.js b/index.js index 382d701a9..7313a348e 100644 --- a/index.js +++ b/index.js @@ -49,6 +49,7 @@ exports.find = function (src, opts) { function visit(node, st, c) { var hasRequire = wordRe.test(src.slice(node.start, node.end)); if (!hasRequire) return; + if (redefinesRequire(node)) return; walk.base[node.type](node, st, c); if (node.type !== 'CallExpression') return; if (isRequire(node)) { @@ -75,6 +76,18 @@ exports.find = function (src, opts) { Statement: visit, Expression: visit }); + + // Detect `require` redefinitions in function parameter lists, like + // in `[function(require,module,exports){` generated by browser-pack. + // This is a simple way to address the 99% case without doing full scope analysis + function redefinesRequire(node) { + if (node.type === 'FunctionExpression') { + return node.params.some(function (param) { + return param.type === 'Identifier' && param.name === word; + }); + } + return false; + } return modules; }; diff --git a/test/files/scope.js b/test/files/scope.js new file mode 100644 index 000000000..66ca0aed5 --- /dev/null +++ b/test/files/scope.js @@ -0,0 +1,8 @@ +(function(modules){ + modules[1](function(i){return modules[i]()}) +})({1:[function (require,module,exports) { + require('./y') +},{'./y':2}],2:function(require,module,exports){ + console.log("abc") +}}) +require('./z') diff --git a/test/scope.js b/test/scope.js new file mode 100644 index 000000000..e6b29bbb9 --- /dev/null +++ b/test/scope.js @@ -0,0 +1,9 @@ +var test = require('tap').test; +var detective = require('../'); +var fs = require('fs'); +var src = fs.readFileSync(__dirname + '/files/scope.js'); + +test('scope', function (t) { + t.plan(1); + t.deepEqual(detective(src), [ './z' ]); +}); From c9bfb246119b76119e39ce7a5e43ece033eca079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Fri, 13 Apr 2018 11:01:22 +0200 Subject: [PATCH 02/11] Be really strict about the kind of redefinition we expect --- index.js | 20 +++++++++++--------- test/files/scope.js | 6 +++++- test/scope.js | 2 +- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index 7313a348e..70cfc704a 100644 --- a/index.js +++ b/index.js @@ -49,7 +49,7 @@ exports.find = function (src, opts) { function visit(node, st, c) { var hasRequire = wordRe.test(src.slice(node.start, node.end)); if (!hasRequire) return; - if (redefinesRequire(node)) return; + if (isBundledDefinition(node)) return; walk.base[node.type](node, st, c); if (node.type !== 'CallExpression') return; if (isRequire(node)) { @@ -78,15 +78,17 @@ exports.find = function (src, opts) { }); // Detect `require` redefinitions in function parameter lists, like - // in `[function(require,module,exports){` generated by browser-pack. + // in `{0:[function(require,module,exports){` generated by browser-pack. // This is a simple way to address the 99% case without doing full scope analysis - function redefinesRequire(node) { - if (node.type === 'FunctionExpression') { - return node.params.some(function (param) { - return param.type === 'Identifier' && param.name === word; - }); - } - return false; + function isBundledDefinition(node) { + if (node.type !== 'ObjectExpression') return false; + if (node.properties.length < 1) return false; + var arr = node.properties[0].value; + if (arr.type !== 'ArrayExpression') return false; + if (arr.elements.length < 2) return false; + if (arr.elements[0].type !== 'FunctionExpression') return false; + var fn = arr.elements[0]; + return fn.params.length > 0 && fn.params[0].type === 'Identifier' && fn.params[0].name === word; } return modules; diff --git a/test/files/scope.js b/test/files/scope.js index 66ca0aed5..b94d787ac 100644 --- a/test/files/scope.js +++ b/test/files/scope.js @@ -1,8 +1,12 @@ (function(modules){ modules[1](function(i){return modules[i]()}) })({1:[function (require,module,exports) { - require('./y') + require('./y') // inside a bundle; should not be detected },{'./y':2}],2:function(require,module,exports){ console.log("abc") }}) + +(function (require) { + require('./x'); // not inside a bundle; should be detected +}(require)); // (because someone might do this) require('./z') diff --git a/test/scope.js b/test/scope.js index e6b29bbb9..3ffebca02 100644 --- a/test/scope.js +++ b/test/scope.js @@ -5,5 +5,5 @@ var src = fs.readFileSync(__dirname + '/files/scope.js'); test('scope', function (t) { t.plan(1); - t.deepEqual(detective(src), [ './z' ]); + t.deepEqual(detective(src), [ './x', './z' ]); }); From dcaedf9c8a098cdc7cbddad78aba8370f61680f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Sun, 26 Nov 2017 16:03:28 +0100 Subject: [PATCH 03/11] Add fast version using Acorn tokenizer --- find-fast.js | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++ index.js | 16 ++++++---- 2 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 find-fast.js diff --git a/find-fast.js b/find-fast.js new file mode 100644 index 000000000..da2a79e76 --- /dev/null +++ b/find-fast.js @@ -0,0 +1,84 @@ +var acorn = require('acorn'); +var defined = require('defined'); + +var ST_NONE = 0; +var ST_SAW_NAME = 1; +var ST_INSIDE_CALL = 2; +var ST_MEMBER_EXPRESSION = 3; + +module.exports = function detective (src, opts) { + opts = opts || {}; + src = String(src) + + if (!opts.word) opts.word = 'require' + + var tokenizer = acorn.tokenizer(src, opts.parse); + var token; + var any = {}; + var search = [ + acorn.tokTypes.name, + acorn.tokTypes.parenL, + any, + acorn.tokTypes.parenR + ]; + var state = ST_NONE; + var opener; + var args = []; + + var modules = { strings: [], expressions: [] }; + if (opts.nodes) modules.nodes = []; + + while ((token = tokenizer.getToken()) && token.type !== acorn.tokTypes.eof) { + if (state !== ST_INSIDE_CALL && token.type === acorn.tokTypes.dot) { + state = ST_MEMBER_EXPRESSION; + } else if (state === ST_NONE && token.type === acorn.tokTypes.name && mayBeRequire(token)) { + state = ST_SAW_NAME; + opener = token; + } else if (state === ST_SAW_NAME && token.type === acorn.tokTypes.parenL) { + state = ST_INSIDE_CALL; + args = [] + } else if (state === ST_INSIDE_CALL) { + if (token.type === acorn.tokTypes.parenR) { // End of fn() call + var node; + // When a custom `isRequire` is passed, we need to parse the entire CallExpression and pass it to the function. + if (opts.nodes || opts.isRequire) { + // Cut `src` at the end of this call, so that parseExpressionAt doesn't consider the `.abc` in + // `require('xyz').abc` + var chunk = src.slice(0, token.end); + node = acorn.parseExpressionAt(chunk, opener.start, opts.parse); + } + + if (opts.isRequire && !opts.isRequire(node)) { + state = ST_NONE; + continue; + } + + if (args.length === 1 && args[0].type === acorn.tokTypes.string) { + modules.strings.push(args[0].value); + } else if (args.length > 0) { + modules.expressions.push(src.slice(args[0].start, args[args.length - 1].end)); + } + + if (opts.nodes) { + modules.nodes.push(node); + } + + state = ST_NONE; + } else { + args.push(token) + } + } else { + state = ST_NONE; + } + } + return modules; + + function mayBeRequire (token) { + if (opts.isRequire) { + // We'll parse all callexpressions in this case. + return token.type === acorn.tokTypes.name; + } + return token.type === acorn.tokTypes.name && + token.value === opts.word; + } +} diff --git a/index.js b/index.js index 70cfc704a..c4a6d82f4 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,13 @@ var acorn = require('acorn-node'); var walk = require('acorn-node/walk'); var defined = require('defined'); +var fastFind = require('./find-fast'); var requireRe = /\brequire\b/; -function parse (src, opts) { - if (!opts) opts = {}; - return acorn.parse(src, { +function getParseOpts (opts) { + opts = opts || {}; + return { ecmaVersion: defined(opts.ecmaVersion, 9), sourceType: defined(opts.sourceType, 'script'), ranges: defined(opts.ranges, opts.range), @@ -19,7 +20,7 @@ function parse (src, opts) { opts.allowImportExportEverywhere, true ), allowHashBang: defined(opts.allowHashBang, true) - }); + }; } var exports = module.exports = function (src, opts) { @@ -28,6 +29,11 @@ var exports = module.exports = function (src, opts) { exports.find = function (src, opts) { if (!opts) opts = {}; + opts = Object.assign({}, opts, { parse: getParseOpts(opts.parse) }); + + if (!opts.isRequire && !opts.fullParse) { + return fastFind(src, opts); + } var word = opts.word === undefined ? 'require' : opts.word; if (typeof src !== 'string') src = String(src); @@ -44,7 +50,7 @@ exports.find = function (src, opts) { var wordRe = word === 'require' ? requireRe : RegExp('\\b' + word + '\\b'); if (!wordRe.test(src)) return modules; - var ast = parse(src, opts.parse); + var ast = acorn.parse(src, opts.parse); function visit(node, st, c) { var hasRequire = wordRe.test(src.slice(node.start, node.end)); From 3094474179717513f812c6f4d63ce15ad5e81543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Sun, 26 Nov 2017 16:05:39 +0100 Subject: [PATCH 04/11] formatting --- find-fast.js | 121 ++++++++++++++++++++++++--------------------------- 1 file changed, 57 insertions(+), 64 deletions(-) diff --git a/find-fast.js b/find-fast.js index da2a79e76..88d04c073 100644 --- a/find-fast.js +++ b/find-fast.js @@ -6,79 +6,72 @@ var ST_SAW_NAME = 1; var ST_INSIDE_CALL = 2; var ST_MEMBER_EXPRESSION = 3; -module.exports = function detective (src, opts) { - opts = opts || {}; - src = String(src) +module.exports = function findFast(src, opts) { + if (!opts) opts = {}; + if (typeof src !== 'string') src = String(src); + if (opts.word === undefined) opts.word = 'require'; - if (!opts.word) opts.word = 'require' + var tokenizer = acorn.tokenizer(src, opts.parse); + var token; + var state = ST_NONE; - var tokenizer = acorn.tokenizer(src, opts.parse); - var token; - var any = {}; - var search = [ - acorn.tokTypes.name, - acorn.tokTypes.parenL, - any, - acorn.tokTypes.parenR - ]; - var state = ST_NONE; - var opener; - var args = []; + var opener; + var args = []; - var modules = { strings: [], expressions: [] }; - if (opts.nodes) modules.nodes = []; + var modules = { strings: [], expressions: [] }; + if (opts.nodes) modules.nodes = []; - while ((token = tokenizer.getToken()) && token.type !== acorn.tokTypes.eof) { - if (state !== ST_INSIDE_CALL && token.type === acorn.tokTypes.dot) { - state = ST_MEMBER_EXPRESSION; - } else if (state === ST_NONE && token.type === acorn.tokTypes.name && mayBeRequire(token)) { - state = ST_SAW_NAME; - opener = token; - } else if (state === ST_SAW_NAME && token.type === acorn.tokTypes.parenL) { - state = ST_INSIDE_CALL; - args = [] - } else if (state === ST_INSIDE_CALL) { - if (token.type === acorn.tokTypes.parenR) { // End of fn() call - var node; - // When a custom `isRequire` is passed, we need to parse the entire CallExpression and pass it to the function. - if (opts.nodes || opts.isRequire) { - // Cut `src` at the end of this call, so that parseExpressionAt doesn't consider the `.abc` in - // `require('xyz').abc` - var chunk = src.slice(0, token.end); - node = acorn.parseExpressionAt(chunk, opener.start, opts.parse); - } + while ((token = tokenizer.getToken()) && token.type !== acorn.tokTypes.eof) { + if (state !== ST_INSIDE_CALL && token.type === acorn.tokTypes.dot) { + state = ST_MEMBER_EXPRESSION; + } else if (state === ST_NONE && token.type === acorn.tokTypes.name && mayBeRequire(token)) { + state = ST_SAW_NAME; + opener = token; + } else if (state === ST_SAW_NAME && token.type === acorn.tokTypes.parenL) { + state = ST_INSIDE_CALL; + args = []; + } else if (state === ST_INSIDE_CALL) { + if (token.type === acorn.tokTypes.parenR) { // End of fn() call + var node; + // When a custom `isRequire` is passed, we need to parse the entire CallExpression and pass it to the function. + if (opts.nodes || opts.isRequire) { + // Cut `src` at the end of this call, so that parseExpressionAt doesn't consider the `.abc` in + // `require('xyz').abc` + var chunk = src.slice(0, token.end); + node = acorn.parseExpressionAt(chunk, opener.start, opts.parse); + } - if (opts.isRequire && !opts.isRequire(node)) { - state = ST_NONE; - continue; - } + if (opts.isRequire && !opts.isRequire(node)) { + state = ST_NONE; + continue; + } - if (args.length === 1 && args[0].type === acorn.tokTypes.string) { - modules.strings.push(args[0].value); - } else if (args.length > 0) { - modules.expressions.push(src.slice(args[0].start, args[args.length - 1].end)); - } + if (args.length === 1 && args[0].type === acorn.tokTypes.string) { + modules.strings.push(args[0].value); + } else if (args.length > 0) { + modules.expressions.push(src.slice(args[0].start, args[args.length - 1].end)); + } - if (opts.nodes) { - modules.nodes.push(node); - } + if (opts.nodes) { + modules.nodes.push(node); + } - state = ST_NONE; - } else { - args.push(token) - } - } else { - state = ST_NONE; + state = ST_NONE; + } else { + args.push(token); + } + } else { + state = ST_NONE; + } } - } - return modules; + return modules; - function mayBeRequire (token) { - if (opts.isRequire) { - // We'll parse all callexpressions in this case. - return token.type === acorn.tokTypes.name; + function mayBeRequire(token) { + if (opts.isRequire) { + // We'll parse all callexpressions in this case. + return token.type === acorn.tokTypes.name; + } + return token.type === acorn.tokTypes.name && + token.value === opts.word; } - return token.type === acorn.tokTypes.name && - token.value === opts.word; - } } From 84763f07ba0998931950fd7f9eb09350cc49afbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Sun, 26 Nov 2017 16:06:17 +0100 Subject: [PATCH 05/11] Remove `isRequire` stuff from find-fast --- find-fast.js | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/find-fast.js b/find-fast.js index 88d04c073..bd937ee38 100644 --- a/find-fast.js +++ b/find-fast.js @@ -32,20 +32,6 @@ module.exports = function findFast(src, opts) { args = []; } else if (state === ST_INSIDE_CALL) { if (token.type === acorn.tokTypes.parenR) { // End of fn() call - var node; - // When a custom `isRequire` is passed, we need to parse the entire CallExpression and pass it to the function. - if (opts.nodes || opts.isRequire) { - // Cut `src` at the end of this call, so that parseExpressionAt doesn't consider the `.abc` in - // `require('xyz').abc` - var chunk = src.slice(0, token.end); - node = acorn.parseExpressionAt(chunk, opener.start, opts.parse); - } - - if (opts.isRequire && !opts.isRequire(node)) { - state = ST_NONE; - continue; - } - if (args.length === 1 && args[0].type === acorn.tokTypes.string) { modules.strings.push(args[0].value); } else if (args.length > 0) { @@ -53,6 +39,10 @@ module.exports = function findFast(src, opts) { } if (opts.nodes) { + // Cut `src` at the end of this call, so that parseExpressionAt doesn't consider the `.abc` in + // `require('xyz').abc` + var chunk = src.slice(0, token.end); + var node = acorn.parseExpressionAt(chunk, opener.start, opts.parse); modules.nodes.push(node); } @@ -67,10 +57,6 @@ module.exports = function findFast(src, opts) { return modules; function mayBeRequire(token) { - if (opts.isRequire) { - // We'll parse all callexpressions in this case. - return token.type === acorn.tokTypes.name; - } return token.type === acorn.tokTypes.name && token.value === opts.word; } From dd9a8e824c37140bd3e55d24df1ce2ffb43e932c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Sun, 26 Nov 2017 16:19:11 +0100 Subject: [PATCH 06/11] add comment test --- test/comment.js | 11 +++++++++++ test/files/comment.js | 5 +++++ 2 files changed, 16 insertions(+) create mode 100644 test/comment.js create mode 100644 test/files/comment.js diff --git a/test/comment.js b/test/comment.js new file mode 100644 index 000000000..985ec5d61 --- /dev/null +++ b/test/comment.js @@ -0,0 +1,11 @@ +var test = require('tap').test; +var detective = require('../'); +var fs = require('fs'); +var src = fs.readFileSync(__dirname + '/files/comment.js'); + +test('comment', function (t) { + var modules = detective.find(src); + t.deepEqual(modules.strings, [ 'beep' ]); + t.notOk(modules.nodes, 'has no nodes'); + t.end(); +}); diff --git a/test/files/comment.js b/test/files/comment.js new file mode 100644 index 000000000..7664ebeb0 --- /dev/null +++ b/test/files/comment.js @@ -0,0 +1,5 @@ +var x = require /* idk */ +// whatever +( +'beep' // boop +) From 7939f150b123b476ebc12951b973011cc83fa511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Sun, 26 Nov 2017 16:29:17 +0100 Subject: [PATCH 07/11] shallow-copy --- index.js | 4 +++- package.json | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index c4a6d82f4..b72fcf88f 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,6 @@ var acorn = require('acorn-node'); var walk = require('acorn-node/walk'); +var copy = require('shallow-copy'); var defined = require('defined'); var fastFind = require('./find-fast'); @@ -29,7 +30,8 @@ var exports = module.exports = function (src, opts) { exports.find = function (src, opts) { if (!opts) opts = {}; - opts = Object.assign({}, opts, { parse: getParseOpts(opts.parse) }); + else opts = copy(opts); + opts.parse = getParseOpts(opts.parse); if (!opts.isRequire && !opts.fullParse) { return fastFind(src, opts); diff --git a/package.json b/package.json index 380171ab1..ff576e7fd 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "dependencies": { "acorn-node": "^1.3.0", "defined": "^1.0.0", - "minimist": "^1.1.1" + "minimist": "^1.1.1", + "shallow-copy": "0.0.1" }, "devDependencies": { "tap": "^10.7.3" From e46549535ae8bc9f7d85033e781d44abdad58f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Mon, 25 Dec 2017 10:39:09 +0100 Subject: [PATCH 08/11] support template strings in the token based finder --- find-fast.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/find-fast.js b/find-fast.js index bd937ee38..b0465d51e 100644 --- a/find-fast.js +++ b/find-fast.js @@ -34,6 +34,11 @@ module.exports = function findFast(src, opts) { if (token.type === acorn.tokTypes.parenR) { // End of fn() call if (args.length === 1 && args[0].type === acorn.tokTypes.string) { modules.strings.push(args[0].value); + } else if (args.length === 3 // A template string without any expressions + && args[0].type === acorn.tokTypes.backQuote + && args[1].type === acorn.tokTypes.template + && args[2].type === acorn.tokTypes.backQuote) { + modules.strings.push(args[1].value); } else if (args.length > 0) { modules.expressions.push(src.slice(args[0].start, args[args.length - 1].end)); } From ec5d00303d2025579528c25f26e7d532df11b61e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Fri, 11 May 2018 10:43:14 +0200 Subject: [PATCH 09/11] Implement require scope logic in token based finder. --- find-fast.js | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/find-fast.js b/find-fast.js index b0465d51e..cb99e32d8 100644 --- a/find-fast.js +++ b/find-fast.js @@ -1,10 +1,22 @@ -var acorn = require('acorn'); +var acorn = require('acorn-node'); var defined = require('defined'); var ST_NONE = 0; var ST_SAW_NAME = 1; var ST_INSIDE_CALL = 2; var ST_MEMBER_EXPRESSION = 3; +var ST_REDEF_PATTERN = 4; +var ST_REDEFINED = 5; + +var REQUIRE_REDEF_PATTERN = [ + function (token) { return token.type === acorn.tokTypes.braceL; }, // { + function (token) { return token.type === acorn.tokTypes.num || token.type === acorn.tokTypes.string; }, // 0 + function (token) { return token.type === acorn.tokTypes.colon; }, // : + function (token) { return token.type === acorn.tokTypes.bracketL; }, // [ + function (token) { return token.type === acorn.tokTypes._function; }, // function + function (token) { return token.type === acorn.tokTypes.parenL; }, // ( + function (token, opts) { return token.type === acorn.tokTypes.name && token.value === opts.word; }, // require +]; module.exports = function findFast(src, opts) { if (!opts) opts = {}; @@ -14,6 +26,11 @@ module.exports = function findFast(src, opts) { var tokenizer = acorn.tokenizer(src, opts.parse); var token; var state = ST_NONE; + // Current index in the require redefinition pattern. + var redefIndex = 0; + // Block scope depth when require was redefined. This is used to match the + // correct } with the opening { after the redefining function parameter list. + var redefDepth = 0; var opener; var args = []; @@ -22,6 +39,31 @@ module.exports = function findFast(src, opts) { if (opts.nodes) modules.nodes = []; while ((token = tokenizer.getToken()) && token.type !== acorn.tokTypes.eof) { + if (state === ST_REDEFINED) { + if (token.type === acorn.tokTypes.braceL) redefDepth++; + if (token.type === acorn.tokTypes.braceR) redefDepth--; + if (redefDepth === 0) { + state = ST_NONE; + } + continue; + } + if (state === ST_REDEF_PATTERN) { + if (redefIndex >= REQUIRE_REDEF_PATTERN.length) { + // the { after the function() parameter list + if (token.type === acorn.tokTypes.braceL) { + state = ST_REDEFINED; + redefDepth = 1; + } + continue; + } else if (REQUIRE_REDEF_PATTERN[redefIndex](token, opts)) { + redefIndex++; + continue; + } else { + redefIndex = 0; + state = ST_NONE; + } + } + if (state !== ST_INSIDE_CALL && token.type === acorn.tokTypes.dot) { state = ST_MEMBER_EXPRESSION; } else if (state === ST_NONE && token.type === acorn.tokTypes.name && mayBeRequire(token)) { @@ -55,6 +97,9 @@ module.exports = function findFast(src, opts) { } else { args.push(token); } + } else if (REQUIRE_REDEF_PATTERN[0](token)) { + state = ST_REDEF_PATTERN; + redefIndex = 1; } else { state = ST_NONE; } From 3ed996830b152a632a670b91b5fbaa9bb20e9819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Fri, 11 May 2018 10:43:31 +0200 Subject: [PATCH 10/11] Update tests to use both the token and the AST based finder. --- test/both.js | 22 ++++++++++++++++++++++ test/chained.js | 1 + test/comment.js | 7 +++++++ test/complicated.js | 3 ++- test/es6-module.js | 3 ++- test/generators.js | 3 ++- test/nested.js | 1 + test/noargs.js | 17 ++++++++++++++++- test/return.js | 3 ++- test/scope.js | 3 ++- test/set-in-object-pattern.js | 6 +++++- test/shebang.js | 3 ++- test/sparse-array.js | 3 +++ test/strings.js | 1 + test/word.js | 4 ++++ test/yield.js | 3 ++- 16 files changed, 74 insertions(+), 9 deletions(-) diff --git a/test/both.js b/test/both.js index f09f1f854..29cd32894 100644 --- a/test/both.js +++ b/test/both.js @@ -11,6 +11,14 @@ test('both', function (t) { t.end(); }); +test('both fullParse', function (t) { + var modules = detective.find(src, { fullParse: true }); + t.deepEqual(modules.strings, [ 'a', 'b' ]); + t.deepEqual(modules.expressions, [ "'c' + x", "'d' + y" ]); + t.notOk(modules.nodes, 'has no nodes'); + t.end(); +}); + test('both with nodes specified in opts', function (t) { var modules = detective.find(src, { nodes: true }); t.deepEqual(modules.strings, [ 'a', 'b' ]); @@ -24,3 +32,17 @@ test('both with nodes specified in opts', function (t) { 'has a node for each require'); t.end(); }); + +test('both with nodes and fullParse', function (t) { + var modules = detective.find(src, { nodes: true, fullParse: true }); + t.deepEqual(modules.strings, [ 'a', 'b' ]); + t.deepEqual(modules.expressions, [ "'c' + x", "'d' + y" ]); + t.deepEqual( + modules.nodes.map(function (n) { + var arg = n.arguments[0]; + return arg.value || arg.left.value; + }), + [ 'a', 'b', 'c', 'd' ], + 'has a node for each require'); + t.end(); +}); diff --git a/test/chained.js b/test/chained.js index 307c20150..bb370d692 100644 --- a/test/chained.js +++ b/test/chained.js @@ -5,5 +5,6 @@ var src = fs.readFileSync(__dirname + '/files/chained.js'); test('chained', function (t) { t.deepEqual(detective(src), [ 'c', 'b', 'a' ]); + t.deepEqual(detective(src, { fullParse: true }), [ 'c', 'b', 'a' ]); t.end(); }); diff --git a/test/comment.js b/test/comment.js index 985ec5d61..d20f62a4b 100644 --- a/test/comment.js +++ b/test/comment.js @@ -9,3 +9,10 @@ test('comment', function (t) { t.notOk(modules.nodes, 'has no nodes'); t.end(); }); + +test('comment fullParse', function (t) { + var modules = detective.find(src, { fullParse: true }); + t.deepEqual(modules.strings, [ 'beep' ]); + t.notOk(modules.nodes, 'has no nodes'); + t.end(); +}); diff --git a/test/complicated.js b/test/complicated.js index af402e18c..b08eab96e 100644 --- a/test/complicated.js +++ b/test/complicated.js @@ -51,8 +51,9 @@ var sources = [ ]; test('complicated', function (t) { - t.plan(sources.length); + t.plan(sources.length * 2); sources.forEach(function(src) { t.deepEqual(detective(src), [ 'a' ]); + t.deepEqual(detective(src, { fullParse: true }), [ 'a' ]); }); }); diff --git a/test/es6-module.js b/test/es6-module.js index 379c89cc3..57d20bcf3 100644 --- a/test/es6-module.js +++ b/test/es6-module.js @@ -4,6 +4,7 @@ var fs = require('fs'); var src = fs.readFileSync(__dirname + '/files/es6-module.js'); test('es6-module', function (t) { - t.plan(1); + t.plan(2); t.deepEqual(detective(src, {parse: {sourceType: 'module'}}), [ 'a', 'b' ]); + t.deepEqual(detective(src, {parse: {sourceType: 'module'}, fullParse: true}), [ 'a', 'b' ]); }); diff --git a/test/generators.js b/test/generators.js index c16d53466..b3c099385 100644 --- a/test/generators.js +++ b/test/generators.js @@ -4,6 +4,7 @@ var fs = require('fs'); var src = fs.readFileSync(__dirname + '/files/generators.js'); test('generators', function (t) { - t.plan(1); + t.plan(2); t.deepEqual(detective(src), [ 'a', 'b' ]); + t.deepEqual(detective(src, { fullParse: true }), [ 'a', 'b' ]); }); diff --git a/test/nested.js b/test/nested.js index d688c0f80..6b694e4f9 100644 --- a/test/nested.js +++ b/test/nested.js @@ -5,5 +5,6 @@ var src = fs.readFileSync(__dirname + '/files/nested.js'); test('nested', function (t) { t.deepEqual(detective(src), [ 'a', 'b', 'c' ]); + t.deepEqual(detective(src, { fullParse: true }), [ 'a', 'b', 'c' ]); t.end(); }); diff --git a/test/noargs.js b/test/noargs.js index 4871b60be..8dc7ac6cd 100644 --- a/test/noargs.js +++ b/test/noargs.js @@ -7,8 +7,9 @@ var fs = require('fs'); var src = [ 'fn();', 'otherfn();', 'fn();' ].join('\n') test('noargs', function (t) { - t.plan(1); + t.plan(2); t.deepEqual(detective(src, { word: 'fn' }).length, 0, 'finds no arg id'); + t.deepEqual(detective(src, { word: 'fn', fullParse: true }).length, 0, 'finds no arg id'); }); test('find noargs with nodes', function (t) { @@ -24,3 +25,17 @@ test('find noargs with nodes', function (t) { 'all matches are correct' ); }); + +test('find noargs with nodes and fullParse', function (t) { + t.plan(4); + var modules = detective.find(src, { word: 'fn', nodes: true, fullParse: true }); + t.equal(modules.strings.length, 0, 'finds no arg id'); + t.equal(modules.expressions.length, 0, 'finds no expressions'); + t.equal(modules.nodes.length, 2, 'finds a node for each matching function call'); + t.equal( + modules.nodes.filter(function (x) { + return x.callee.name === 'fn' + }).length, 2, + 'all matches are correct' + ); +}); diff --git a/test/return.js b/test/return.js index c2da016bb..a23f92958 100644 --- a/test/return.js +++ b/test/return.js @@ -4,6 +4,7 @@ var fs = require('fs'); var src = [ 'require("a")\nreturn' ]; test('return', function (t) { - t.plan(1); + t.plan(2); t.deepEqual(detective(src), [ 'a' ]); + t.deepEqual(detective(src, { fullParse: true }), [ 'a' ]); }); diff --git a/test/scope.js b/test/scope.js index 3ffebca02..1c3e735f8 100644 --- a/test/scope.js +++ b/test/scope.js @@ -4,6 +4,7 @@ var fs = require('fs'); var src = fs.readFileSync(__dirname + '/files/scope.js'); test('scope', function (t) { - t.plan(1); + t.plan(2); t.deepEqual(detective(src), [ './x', './z' ]); + t.deepEqual(detective(src, { fullParse: true }), [ './x', './z' ]); }); diff --git a/test/set-in-object-pattern.js b/test/set-in-object-pattern.js index 4787b1edb..3d418b4c9 100644 --- a/test/set-in-object-pattern.js +++ b/test/set-in-object-pattern.js @@ -8,5 +8,9 @@ test('set in object pattern', function (t) { detective(src, { word : 'load' }), [ 'a', 'b', 'c', 'tt' ] ); + t.deepEqual( + detective(src, { word : 'load', fullParse: true }), + [ 'a', 'b', 'c', 'tt' ] + ); t.end(); -}); \ No newline at end of file +}); diff --git a/test/shebang.js b/test/shebang.js index b662ea241..92c1395da 100644 --- a/test/shebang.js +++ b/test/shebang.js @@ -4,6 +4,7 @@ var fs = require('fs'); var src = fs.readFileSync(__dirname + '/files/shebang.js'); test('shebang', function (t) { - t.plan(1); + t.plan(2); t.deepEqual(detective(src), [ 'a', 'b', 'c' ]); + t.deepEqual(detective(src, { fullParse: true }), [ 'a', 'b', 'c' ]); }); diff --git a/test/sparse-array.js b/test/sparse-array.js index f64f3593c..7cdbc4a37 100644 --- a/test/sparse-array.js +++ b/test/sparse-array.js @@ -8,6 +8,9 @@ test('sparse-array', function (t) { t.doesNotThrow(function () { detective(src) }) + t.doesNotThrow(function () { + detective(src, { fullParse: true }) + }) t.end(); }); diff --git a/test/strings.js b/test/strings.js index 3b5e7d821..3a87aca9d 100644 --- a/test/strings.js +++ b/test/strings.js @@ -5,5 +5,6 @@ var src = fs.readFileSync(__dirname + '/files/strings.js'); test('single', function (t) { t.deepEqual(detective(src), [ 'a', 'b', 'c', 'events', 'doom', 'y', 'events2' ]); + t.deepEqual(detective(src, { fullParse: true }), [ 'a', 'b', 'c', 'events', 'doom', 'y', 'events2' ]); t.end(); }); diff --git a/test/word.js b/test/word.js index cf5397d85..d9951640a 100644 --- a/test/word.js +++ b/test/word.js @@ -8,5 +8,9 @@ test('word', function (t) { detective(src, { word : 'load' }), [ 'a', 'b', 'c', 'events', 'doom', 'y', 'events2' ] ); + t.deepEqual( + detective(src, { word : 'load', fullParse: true }), + [ 'a', 'b', 'c', 'events', 'doom', 'y', 'events2' ] + ); t.end(); }); diff --git a/test/yield.js b/test/yield.js index 85560ab7a..d6b06dfd0 100644 --- a/test/yield.js +++ b/test/yield.js @@ -4,6 +4,7 @@ var fs = require('fs'); var src = fs.readFileSync(__dirname + '/files/yield.js'); test('yield', function (t) { - t.plan(1); + t.plan(2); t.deepEqual(detective(src), [ 'a', 'c' ]); + t.deepEqual(detective(src, { fullParse: true }), [ 'a', 'c' ]); }); From 9466172b903e1f453e6037da522a613ae933e625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Fri, 11 May 2018 13:06:12 +0200 Subject: [PATCH 11/11] comments about states --- find-fast.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/find-fast.js b/find-fast.js index cb99e32d8..bc206dcdc 100644 --- a/find-fast.js +++ b/find-fast.js @@ -1,12 +1,12 @@ var acorn = require('acorn-node'); var defined = require('defined'); -var ST_NONE = 0; -var ST_SAW_NAME = 1; -var ST_INSIDE_CALL = 2; -var ST_MEMBER_EXPRESSION = 3; -var ST_REDEF_PATTERN = 4; -var ST_REDEFINED = 5; +var ST_NONE = 0; // Default. +var ST_SAW_NAME = 1; // Saw a `require` identifier. +var ST_INSIDE_CALL = 2; // Found a `require(` sequence; if followed by a string, that is a dependency. +var ST_MEMBER_EXPRESSION = 3; // Saw a `.`; if followed by a `require` identifier that should be ignored. +var ST_REDEF_PATTERN = 4; // Currently in progress detecting a redefinition pattern: `{0:[function(require` +var ST_REDEFINED = 5; // Currently inside a scope with a redefined `require` identifier. var REQUIRE_REDEF_PATTERN = [ function (token) { return token.type === acorn.tokTypes.braceL; }, // {