/* eslint-disable operator-linebreak */ /* eslint-disable prefer-const */ const common = require('../prelude/common.js'); const generate = require('escodegen').generate; const babelParser = require('@babel/parser'); const parse = babelParser.parse; const ALIAS_AS_RELATIVE = common.ALIAS_AS_RELATIVE; const ALIAS_AS_RESOLVABLE = common.ALIAS_AS_RESOLVABLE; function forge (pattern, was) { return pattern.replace('{c1}', ', ') .replace('{v1}', '"' + was.v1 + '"') .replace('{c2}', was.v2 ? ', ' : '') .replace('{v2}', was.v2 ? ('"' + was.v2 + '"') : '') .replace('{c3}', was.v3 ? ' from ' : '') .replace('{v3}', was.v3 ? was.v3 : ''); } function valid2 (v2) { return (v2 === undefined) || (v2 === null) || (v2 === 'must-exclude') || (v2 === 'may-exclude'); } function visitor_REQUIRE_RESOLVE (n) { // eslint-disable-line camelcase const c = n.callee; if (!c) return null; const ci = (c.object && c.object.type === 'Identifier' && c.object.name === 'require' && c.property && c.property.type === 'Identifier' && c.property.name === 'resolve'); if (!ci) return null; const f = (n.type === 'CallExpression' && n.arguments && n.arguments[0] && n.arguments[0].type === 'Literal'); if (!f) return null; const m = (n.arguments[1] && n.arguments[1].type === 'Literal'); return { v1: n.arguments[0].value, v2: m ? n.arguments[1].value : null }; } function visitor_REQUIRE (n) { // eslint-disable-line camelcase const c = n.callee; if (!c) return null; const ci = (c.type === 'Identifier' && c.name === 'require'); if (!ci) return null; const f = (n.type === 'CallExpression' && n.arguments && n.arguments[0] && n.arguments[0].type === 'Literal'); if (!f) return null; const m = (n.arguments[1] && n.arguments[1].type === 'Literal'); return { v1: n.arguments[0].value, v2: m ? n.arguments[1].value : null }; } function visitor_IMPORT (n) { // eslint-disable-line camelcase const ni = (n.type === 'ImportDeclaration'); if (!ni) return null; const s = n.specifiers; return { v1: n.source.value, v3: reconstructSpecifiers(s) }; } function visitor_PATH_JOIN (n) { // eslint-disable-line camelcase const c = n.callee; if (!c) return null; const ci = (c.object && c.object.type === 'Identifier' && c.object.name === 'path' && c.property && c.property.type === 'Identifier' && c.property.name === 'join'); if (!ci) return null; const dn = (n.arguments[0] && n.arguments[0].type === 'Identifier' && n.arguments[0].name === '__dirname'); if (!dn) return null; const f = (n.type === 'CallExpression' && n.arguments && n.arguments[1] && n.arguments[1].type === 'Literal' && n.arguments.length === 2); // TODO concate them if (!f) return null; return { v1: n.arguments[1].value }; } module.exports.visitor_SUCCESSFUL = function (node, test) { // eslint-disable-line camelcase let mustExclude, mayExclude, was; was = visitor_REQUIRE_RESOLVE(node); if (was) { if (test) return forge('require.resolve({v1}{c2}{v2})', was); if (!valid2(was.v2)) return null; mustExclude = (was.v2 === 'must-exclude'); mayExclude = (was.v2 === 'may-exclude'); return { alias: was.v1, aliasType: ALIAS_AS_RESOLVABLE, mustExclude: mustExclude, mayExclude: mayExclude }; } was = visitor_REQUIRE(node); if (was) { if (test) return forge('require({v1}{c2}{v2})', was); if (!valid2(was.v2)) return null; mustExclude = (was.v2 === 'must-exclude'); mayExclude = (was.v2 === 'may-exclude'); return { alias: was.v1, aliasType: ALIAS_AS_RESOLVABLE, mustExclude: mustExclude, mayExclude: mayExclude }; } was = visitor_IMPORT(node); if (was) { if (test) return forge('import {v3}{c3}{v1}', was); return { alias: was.v1, aliasType: ALIAS_AS_RESOLVABLE }; } was = visitor_PATH_JOIN(node); if (was) { if (test) return forge('path.join(__dirname{c1}{v1})', was); return { alias: was.v1, aliasType: ALIAS_AS_RELATIVE, mayExclude: false }; } return null; }; function visitor_NONLITERAL (n) { // eslint-disable-line camelcase return (function () { const c = n.callee; if (!c) return null; const ci = (c.object && c.object.type === 'Identifier' && c.object.name === 'require' && c.property && c.property.type === 'Identifier' && c.property.name === 'resolve'); if (!ci) return null; const f = (n.type === 'CallExpression' && n.arguments && n.arguments[0] && n.arguments[0].type !== 'Literal'); if (!f) return null; const m = n.arguments[1]; if (!m) return { v1: reconstruct(n.arguments[0]) }; const q = (n.arguments[1] && n.arguments[1].type === 'Literal'); if (!q) return null; return { v1: reconstruct(n.arguments[0]), v2: n.arguments[1].value }; }()) || (function () { const c = n.callee; if (!c) return null; const ci = (c.type === 'Identifier' && c.name === 'require'); if (!ci) return null; const f = (n.type === 'CallExpression' && n.arguments && n.arguments[0] && n.arguments[0].type !== 'Literal'); if (!f) return null; const m = n.arguments[1]; if (!m) return { v1: reconstruct(n.arguments[0]) }; const q = (n.arguments[1] && n.arguments[1].type === 'Literal'); if (!q) return null; return { v1: reconstruct(n.arguments[0]), v2: n.arguments[1].value }; }()); } module.exports.visitor_NONLITERAL = function (node) { // eslint-disable-line camelcase let mustExclude, mayExclude, was; was = visitor_NONLITERAL(node); if (was) { if (!valid2(was.v2)) return null; mustExclude = (was.v2 === 'must-exclude'); mayExclude = (was.v2 === 'may-exclude'); return { alias: was.v1, mustExclude: mustExclude, mayExclude: mayExclude }; } return null; }; function visitor_MALFORMED (n) { // eslint-disable-line camelcase return (function () { const c = n.callee; if (!c) return null; const ci = (c.object && c.object.type === 'Identifier' && c.object.name === 'require' && c.property && c.property.type === 'Identifier' && c.property.name === 'resolve'); if (!ci) return null; const f = (n.type === 'CallExpression' && n.arguments && n.arguments[0]); if (!f) return null; return { v1: reconstruct(n.arguments[0]) }; }()) || (function () { const c = n.callee; if (!c) return null; const ci = (c.type === 'Identifier' && c.name === 'require'); if (!ci) return null; const f = (n.type === 'CallExpression' && n.arguments && n.arguments[0]); if (!f) return null; return { v1: reconstruct(n.arguments[0]) }; }()); } module.exports.visitor_MALFORMED = function (node) { // eslint-disable-line camelcase let was; was = visitor_MALFORMED(node); if (was) return { alias: was.v1 }; return null; }; function visitor_USESCWD (n) { // eslint-disable-line camelcase const c = n.callee; if (!c) return null; const ci = (c.object && c.object.type === 'Identifier' && c.object.name === 'path' && c.property && c.property.type === 'Identifier' && c.property.name === 'resolve'); if (!ci) return null; return { v1: n.arguments.map(reconstruct).join(', ') }; } module.exports.visitor_USESCWD = function (node) { // eslint-disable-line camelcase let was; was = visitor_USESCWD(node); if (was) return { alias: was.v1 }; return null; }; function reconstructSpecifiers (specs) { if (!specs || !specs.length) return ''; const defaults = []; for (const spec of specs) { if (spec.type === 'ImportDefaultSpecifier') { defaults.push(spec.local.name); } } const nonDefaults = []; for (const spec of specs) { if (spec.type === 'ImportSpecifier') { if (spec.local.name === spec.imported.name) { nonDefaults.push(spec.local.name); } else { nonDefaults.push( spec.imported.name + ' as ' + spec.local.name ); } } } if (nonDefaults.length) { defaults.push('{ ' + nonDefaults.join(', ') + ' }'); } return defaults.join(', '); } function reconstruct (node) { let v = generate(node).replace(/\n/g, ''); let v2; while (true) { v2 = v.replace(/\[ /g, '[') .replace(/ \]/g, ']') .replace(/ {2}/g, ' '); if (v2 === v) break; v = v2; } return v2; } function traverse (ast, visitor) { // modified esprima-walk to support // visitor return value and "trying" flag let stack = [ [ ast, false ] ]; let i, j, key; let len, item, node, trying, child; for (i = 0; i < stack.length; i += 1) { item = stack[i]; node = item[0]; if (node) { trying = item[1] || (node.type === 'TryStatement'); if (visitor(node, trying)) { for (key in node) { child = node[key]; if (child instanceof Array) { len = child.length; for (j = 0; j < len; j += 1) { stack.push([ child[j], trying ]); } } else if (child && typeof child.type === 'string') { stack.push([ child, trying ]); } } } } } } module.exports.parse = function (body) { return parse(body, { allowImportExportEverywhere: true, allowReturnOutsideFunction: true, ecmaVersion: 8, plugins: [ 'estree', 'bigInt', 'classPrivateProperties', 'classProperties' ] }); }; module.exports.detect = function (body, visitor) { const json = module.exports.parse(body); if (!json) return; traverse(json, visitor); };