mirror of
https://github.com/tiennm99/FBcount.git
synced 2026-05-14 14:58:04 +00:00
340 lines
9.2 KiB
JavaScript
340 lines
9.2 KiB
JavaScript
"use strict";
|
|
|
|
/* 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']
|
|
});
|
|
};
|
|
|
|
module.exports.detect = function (body, visitor) {
|
|
const json = module.exports.parse(body);
|
|
if (!json) return;
|
|
traverse(json, visitor);
|
|
}; |