1074 lines
46 KiB
JavaScript
1074 lines
46 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.default = analyze;
|
|
const path_1 = __importDefault(require("path"));
|
|
const estree_walker_1 = require("estree-walker");
|
|
const pluginutils_1 = require("@rollup/pluginutils");
|
|
const static_eval_1 = require("./utils/static-eval");
|
|
const acorn_1 = require("acorn");
|
|
const bindings_1 = __importDefault(require("bindings"));
|
|
const ast_helpers_1 = require("./utils/ast-helpers");
|
|
const glob_1 = require("glob");
|
|
const get_package_base_1 = require("./utils/get-package-base");
|
|
const binary_locators_1 = require("./utils/binary-locators");
|
|
const interop_require_1 = require("./utils/interop-require");
|
|
const special_cases_1 = __importDefault(require("./utils/special-cases"));
|
|
const resolve_dependency_js_1 = __importDefault(require("./resolve-dependency.js"));
|
|
//@ts-ignore
|
|
const node_gyp_build_1 = __importDefault(require("node-gyp-build"));
|
|
//@ts-ignore
|
|
const node_pre_gyp_1 = __importDefault(require("@mapbox/node-pre-gyp"));
|
|
const url_1 = require("url");
|
|
// Note: these should be deprecated over time as they ship in Acorn core
|
|
const acorn = acorn_1.Parser.extend(
|
|
//require("acorn-class-fields"),
|
|
//require("acorn-static-class-features"),
|
|
//require("acorn-private-class-elements")
|
|
require('acorn-import-attributes').importAttributesOrAssertions);
|
|
const os_1 = __importDefault(require("os"));
|
|
const url_2 = __importDefault(require("url"));
|
|
const wrappers_1 = require("./utils/wrappers");
|
|
const resolve_from_1 = __importDefault(require("resolve-from"));
|
|
const staticProcess = {
|
|
cwd: () => {
|
|
return cwd;
|
|
},
|
|
env: {
|
|
NODE_ENV: static_eval_1.UNKNOWN,
|
|
[static_eval_1.UNKNOWN]: true,
|
|
},
|
|
[static_eval_1.UNKNOWN]: true,
|
|
};
|
|
// unique symbol value to identify express instance in static analysis
|
|
const EXPRESS_SET = Symbol();
|
|
const EXPRESS_ENGINE = Symbol();
|
|
const NBIND_INIT = Symbol();
|
|
const SET_ROOT_DIR = Symbol();
|
|
const PKG_INFO = Symbol();
|
|
const FS_FN = Symbol();
|
|
const FS_DIR_FN = Symbol();
|
|
const BINDINGS = Symbol();
|
|
const NODE_GYP_BUILD = Symbol();
|
|
const fsSymbols = {
|
|
access: FS_FN,
|
|
accessSync: FS_FN,
|
|
createReadStream: FS_FN,
|
|
exists: FS_FN,
|
|
existsSync: FS_FN,
|
|
fstat: FS_FN,
|
|
fstatSync: FS_FN,
|
|
lstat: FS_FN,
|
|
lstatSync: FS_FN,
|
|
open: FS_FN,
|
|
readdir: FS_DIR_FN,
|
|
readdirSync: FS_DIR_FN,
|
|
readFile: FS_FN,
|
|
readFileSync: FS_FN,
|
|
stat: FS_FN,
|
|
statSync: FS_FN,
|
|
};
|
|
const fsExtraSymbols = {
|
|
...fsSymbols,
|
|
pathExists: FS_FN,
|
|
pathExistsSync: FS_FN,
|
|
readJson: FS_FN,
|
|
readJSON: FS_FN,
|
|
readJsonSync: FS_FN,
|
|
readJSONSync: FS_FN,
|
|
};
|
|
const MODULE_FN = Symbol();
|
|
const moduleSymbols = {
|
|
register: MODULE_FN,
|
|
};
|
|
const staticModules = Object.assign(Object.create(null), {
|
|
bindings: {
|
|
default: BINDINGS,
|
|
},
|
|
express: {
|
|
default: function () {
|
|
return {
|
|
[static_eval_1.UNKNOWN]: true,
|
|
set: EXPRESS_SET,
|
|
engine: EXPRESS_ENGINE,
|
|
};
|
|
},
|
|
},
|
|
fs: {
|
|
default: fsSymbols,
|
|
...fsSymbols,
|
|
},
|
|
module: {
|
|
default: moduleSymbols,
|
|
...moduleSymbols,
|
|
},
|
|
'fs-extra': {
|
|
default: fsExtraSymbols,
|
|
...fsExtraSymbols,
|
|
},
|
|
'graceful-fs': {
|
|
default: fsSymbols,
|
|
...fsSymbols,
|
|
},
|
|
process: {
|
|
default: staticProcess,
|
|
...staticProcess,
|
|
},
|
|
// populated below
|
|
path: {
|
|
default: {},
|
|
},
|
|
os: {
|
|
default: os_1.default,
|
|
...os_1.default,
|
|
},
|
|
url: {
|
|
default: url_2.default,
|
|
...url_2.default,
|
|
},
|
|
'@mapbox/node-pre-gyp': {
|
|
default: node_pre_gyp_1.default,
|
|
...node_pre_gyp_1.default,
|
|
},
|
|
'node-pre-gyp': binary_locators_1.pregyp,
|
|
'node-pre-gyp/lib/pre-binding': binary_locators_1.pregyp,
|
|
'node-pre-gyp/lib/pre-binding.js': binary_locators_1.pregyp,
|
|
'node-gyp-build': {
|
|
default: NODE_GYP_BUILD,
|
|
},
|
|
'@aminya/node-gyp-build': {
|
|
default: NODE_GYP_BUILD,
|
|
},
|
|
nbind: {
|
|
init: NBIND_INIT,
|
|
default: {
|
|
init: NBIND_INIT,
|
|
},
|
|
},
|
|
'resolve-from': {
|
|
default: resolve_from_1.default,
|
|
},
|
|
'strong-globalize': {
|
|
default: {
|
|
SetRootDir: SET_ROOT_DIR,
|
|
},
|
|
SetRootDir: SET_ROOT_DIR,
|
|
},
|
|
pkginfo: {
|
|
default: PKG_INFO,
|
|
},
|
|
});
|
|
const globalBindings = {
|
|
// Support for require calls generated from `import` statements by babel
|
|
_interopRequireDefault: interop_require_1.normalizeDefaultRequire,
|
|
_interopRequireWildcard: interop_require_1.normalizeWildcardRequire,
|
|
// Support for require calls generated from `import` statements by tsc
|
|
__importDefault: interop_require_1.normalizeDefaultRequire,
|
|
__importStar: interop_require_1.normalizeWildcardRequire,
|
|
MONGOOSE_DRIVER_PATH: undefined,
|
|
URL: url_1.URL,
|
|
Object: {
|
|
assign: Object.assign,
|
|
},
|
|
};
|
|
globalBindings.global =
|
|
globalBindings.GLOBAL =
|
|
globalBindings.globalThis =
|
|
globalBindings;
|
|
// call expression triggers
|
|
const TRIGGER = Symbol();
|
|
binary_locators_1.pregyp.find[TRIGGER] = true;
|
|
const staticPath = staticModules.path;
|
|
Object.keys(path_1.default).forEach((name) => {
|
|
const pathFn = path_1.default[name];
|
|
if (typeof pathFn === 'function') {
|
|
const fn = function mockPath() {
|
|
return pathFn.apply(mockPath, arguments);
|
|
};
|
|
fn[TRIGGER] = true;
|
|
staticPath[name] = staticPath.default[name] = fn;
|
|
}
|
|
else {
|
|
staticPath[name] = staticPath.default[name] = pathFn;
|
|
}
|
|
});
|
|
// overload path.resolve to support custom cwd
|
|
staticPath.resolve = staticPath.default.resolve = function (...args) {
|
|
return path_1.default.resolve.apply(this, [cwd, ...args]);
|
|
};
|
|
staticPath.resolve[TRIGGER] = true;
|
|
const excludeAssetExtensions = new Set(['.h', '.cmake', '.c', '.cpp']);
|
|
const excludeAssetFiles = new Set([
|
|
'CHANGELOG.md',
|
|
'README.md',
|
|
'readme.md',
|
|
'changelog.md',
|
|
]);
|
|
let cwd;
|
|
const absoluteRegEx = /^\/[^\/]+|^[a-z]:[\\/][^\\/]+/i;
|
|
function isAbsolutePathOrUrl(str) {
|
|
if (str instanceof url_1.URL)
|
|
return str.protocol === 'file:';
|
|
if (typeof str === 'string') {
|
|
if (str.startsWith('file:')) {
|
|
try {
|
|
new url_1.URL(str);
|
|
return true;
|
|
}
|
|
catch {
|
|
return false;
|
|
}
|
|
}
|
|
return absoluteRegEx.test(str);
|
|
}
|
|
return false;
|
|
}
|
|
const BOUND_REQUIRE = Symbol();
|
|
const repeatGlobRegEx = /([\/\\]\*\*[\/\\]\*)+/g;
|
|
async function analyze(id, code, job) {
|
|
const assets = new Set();
|
|
const deps = new Set();
|
|
const imports = new Set();
|
|
const dir = path_1.default.dirname(id);
|
|
// if (typeof options.production === 'boolean' && staticProcess.env.NODE_ENV === UNKNOWN)
|
|
// staticProcess.env.NODE_ENV = options.production ? 'production' : 'dev';
|
|
cwd = job.cwd;
|
|
const pkgBase = (0, get_package_base_1.getPackageBase)(id);
|
|
const emitAssetDirectory = (wildcardPath) => {
|
|
if (!job.analysis.emitGlobs)
|
|
return;
|
|
wildcardPath = wildcardPath.replaceAll(path_1.default.sep, path_1.default.posix.sep);
|
|
const wildcardIndex = wildcardPath.indexOf(static_eval_1.WILDCARD);
|
|
const dirIndex = wildcardIndex === -1
|
|
? wildcardPath.length
|
|
: wildcardPath.lastIndexOf(path_1.default.posix.sep, wildcardIndex);
|
|
const assetDirPath = wildcardPath.substring(0, dirIndex);
|
|
const patternPath = wildcardPath.slice(dirIndex);
|
|
const wildcardPattern = patternPath
|
|
.replace(static_eval_1.wildcardRegEx, (_match, index) => {
|
|
return patternPath[index - 1] === path_1.default.posix.sep ? '**/*' : '*';
|
|
})
|
|
.replace(repeatGlobRegEx, '/**/*') || '/**/*';
|
|
if (job.ignoreFn(path_1.default.relative(job.base, assetDirPath + wildcardPattern)))
|
|
return;
|
|
assetEmissionPromises = assetEmissionPromises.then(async () => {
|
|
if (job.log)
|
|
console.log('Globbing ' + assetDirPath + wildcardPattern);
|
|
const files = await (0, glob_1.glob)(assetDirPath + wildcardPattern, {
|
|
mark: true,
|
|
ignore: assetDirPath + '/**/node_modules/**/*',
|
|
dot: true,
|
|
nodir: true,
|
|
});
|
|
files
|
|
.filter((name) => !excludeAssetExtensions.has(path_1.default.extname(name)) &&
|
|
!excludeAssetFiles.has(path_1.default.basename(name)))
|
|
.forEach((file) => assets.add(file));
|
|
});
|
|
};
|
|
let assetEmissionPromises = Promise.resolve();
|
|
// remove shebang
|
|
code = code.replace(/^#![^\n\r]*[\r\n]/, '');
|
|
let ast;
|
|
let isESM = false;
|
|
try {
|
|
ast = acorn.parse(code, {
|
|
ecmaVersion: 'latest',
|
|
allowReturnOutsideFunction: true,
|
|
});
|
|
isESM = false;
|
|
}
|
|
catch (e) {
|
|
const isModule = e && e.message && e.message.includes('sourceType: module');
|
|
if (!isModule) {
|
|
job.warnings.add(new Error(`Failed to parse ${id} as script:\n${e && e.message}`));
|
|
}
|
|
}
|
|
//@ts-ignore
|
|
if (!ast) {
|
|
try {
|
|
ast = acorn.parse(code, {
|
|
ecmaVersion: 'latest',
|
|
sourceType: 'module',
|
|
allowAwaitOutsideFunction: true,
|
|
});
|
|
isESM = true;
|
|
}
|
|
catch (e) {
|
|
job.warnings.add(new Error(`Failed to parse ${id} as module:\n${e && e.message}`));
|
|
// Parser errors just skip analysis
|
|
return { assets, deps, imports, isESM: false };
|
|
}
|
|
}
|
|
const importMetaUrl = (0, url_1.pathToFileURL)(id).href;
|
|
const knownBindings = Object.assign(Object.create(null), {
|
|
__dirname: {
|
|
shadowDepth: 0,
|
|
value: { value: path_1.default.resolve(id, '..') },
|
|
},
|
|
__filename: {
|
|
shadowDepth: 0,
|
|
value: { value: id },
|
|
},
|
|
process: {
|
|
shadowDepth: 0,
|
|
value: { value: staticProcess },
|
|
},
|
|
});
|
|
if (!isESM || job.mixedModules) {
|
|
knownBindings.require = {
|
|
shadowDepth: 0,
|
|
value: {
|
|
value: {
|
|
[static_eval_1.FUNCTION](specifier) {
|
|
deps.add(specifier);
|
|
const m = staticModules[specifier.startsWith('node:') ? specifier.slice(5) : specifier];
|
|
return m.default;
|
|
},
|
|
resolve(specifier) {
|
|
return (0, resolve_dependency_js_1.default)(specifier, id, job);
|
|
},
|
|
},
|
|
},
|
|
};
|
|
knownBindings.require.value.value.resolve[TRIGGER] = true;
|
|
}
|
|
function setKnownBinding(name, value) {
|
|
// require is somewhat special in that we shadow it but don't
|
|
// statically analyze it ("known unknown" of sorts)
|
|
if (name === 'require')
|
|
return;
|
|
knownBindings[name] = {
|
|
shadowDepth: 0,
|
|
value: value,
|
|
};
|
|
}
|
|
function getKnownBinding(name) {
|
|
const binding = knownBindings[name];
|
|
if (binding) {
|
|
if (binding.shadowDepth === 0) {
|
|
return binding.value;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
function hasKnownBindingValue(name) {
|
|
const binding = knownBindings[name];
|
|
return binding && binding.shadowDepth === 0;
|
|
}
|
|
if ((isESM || job.mixedModules) && isAst(ast)) {
|
|
for (const decl of ast.body) {
|
|
if (decl.type === 'ImportDeclaration') {
|
|
const source = String(decl.source.value);
|
|
deps.add(source);
|
|
const staticModule = staticModules[source.startsWith('node:') ? source.slice(5) : source];
|
|
if (staticModule) {
|
|
for (const impt of decl.specifiers) {
|
|
if (impt.type === 'ImportNamespaceSpecifier')
|
|
setKnownBinding(impt.local.name, { value: staticModule });
|
|
else if (impt.type === 'ImportDefaultSpecifier' &&
|
|
'default' in staticModule)
|
|
setKnownBinding(impt.local.name, { value: staticModule.default });
|
|
else if (impt.type === 'ImportSpecifier' &&
|
|
impt.imported.name in staticModule)
|
|
setKnownBinding(impt.local.name, {
|
|
value: staticModule[impt.imported.name],
|
|
});
|
|
}
|
|
}
|
|
}
|
|
else if (decl.type === 'ExportNamedDeclaration' ||
|
|
decl.type === 'ExportAllDeclaration') {
|
|
if (decl.source)
|
|
deps.add(String(decl.source.value));
|
|
}
|
|
}
|
|
}
|
|
async function computePureStaticValue(expr, computeBranches = true) {
|
|
const vars = Object.create(null);
|
|
Object.keys(globalBindings).forEach((name) => {
|
|
vars[name] = { value: globalBindings[name] };
|
|
});
|
|
Object.keys(knownBindings).forEach((name) => {
|
|
vars[name] = getKnownBinding(name);
|
|
});
|
|
vars['import.meta'] = { url: importMetaUrl };
|
|
// evaluate returns undefined for non-statically-analyzable
|
|
const result = await (0, static_eval_1.evaluate)(expr, vars, computeBranches);
|
|
return result;
|
|
}
|
|
// statically determinable leaves are tracked, and inlined when the
|
|
// greatest parent statically known leaf computation corresponds to an asset path
|
|
let staticChildNode;
|
|
let staticChildValue;
|
|
// Express engine opt-out
|
|
let definedExpressEngines = false;
|
|
function emitWildcardRequire(wildcardRequire) {
|
|
if (!job.analysis.emitGlobs ||
|
|
(!wildcardRequire.startsWith('./') && !wildcardRequire.startsWith('../')))
|
|
return;
|
|
wildcardRequire = path_1.default
|
|
.resolve(dir, wildcardRequire)
|
|
.replaceAll(path_1.default.sep, path_1.default.posix.sep);
|
|
const wildcardIndex = wildcardRequire.indexOf(static_eval_1.WILDCARD);
|
|
const dirIndex = wildcardIndex === -1
|
|
? wildcardRequire.length
|
|
: wildcardRequire.lastIndexOf(path_1.default.posix.sep, wildcardIndex);
|
|
const wildcardDirPath = wildcardRequire.substring(0, dirIndex);
|
|
const patternPath = wildcardRequire.slice(dirIndex);
|
|
let wildcardPattern = patternPath.replace(static_eval_1.wildcardRegEx, (_match, index) => {
|
|
return patternPath[index - 1] === path_1.default.posix.sep ? '**/*' : '*';
|
|
}) || '/**/*';
|
|
if (!wildcardPattern.endsWith('*'))
|
|
wildcardPattern +=
|
|
'?(' + (job.ts ? '.ts|.tsx|' : '') + '.js|.json|.node)';
|
|
if (job.ignoreFn(path_1.default.relative(job.base, wildcardDirPath + wildcardPattern)))
|
|
return;
|
|
assetEmissionPromises = assetEmissionPromises.then(async () => {
|
|
if (job.log)
|
|
console.log('Globbing ' + wildcardDirPath + wildcardPattern);
|
|
const files = await (0, glob_1.glob)(wildcardDirPath + wildcardPattern, {
|
|
mark: true,
|
|
ignore: wildcardDirPath + '/**/node_modules/**/*',
|
|
nodir: true,
|
|
});
|
|
files
|
|
.filter((name) => !excludeAssetExtensions.has(path_1.default.extname(name)) &&
|
|
!excludeAssetFiles.has(path_1.default.basename(name)))
|
|
.forEach((file) => deps.add(file));
|
|
});
|
|
}
|
|
async function processRequireArg(expression, isImport = false) {
|
|
if (expression.type === 'ConditionalExpression') {
|
|
await processRequireArg(expression.consequent, isImport);
|
|
await processRequireArg(expression.alternate, isImport);
|
|
return;
|
|
}
|
|
if (expression.type === 'LogicalExpression') {
|
|
await processRequireArg(expression.left, isImport);
|
|
await processRequireArg(expression.right, isImport);
|
|
return;
|
|
}
|
|
let computed = await computePureStaticValue(expression, true);
|
|
if (!computed)
|
|
return;
|
|
function add(value) {
|
|
(isImport ? imports : deps).add(value);
|
|
}
|
|
if ('value' in computed && typeof computed.value === 'string') {
|
|
if (!computed.wildcards)
|
|
add(computed.value);
|
|
else if (computed.wildcards.length >= 1)
|
|
emitWildcardRequire(computed.value);
|
|
}
|
|
else {
|
|
if ('ifTrue' in computed && typeof computed.ifTrue === 'string')
|
|
add(computed.ifTrue);
|
|
if ('else' in computed && typeof computed.else === 'string')
|
|
add(computed.else);
|
|
}
|
|
}
|
|
let scope = (0, pluginutils_1.attachScopes)(ast, 'scope');
|
|
if (isAst(ast)) {
|
|
(0, wrappers_1.handleWrappers)(ast);
|
|
await (0, special_cases_1.default)({
|
|
id,
|
|
ast,
|
|
emitDependency: (path) => deps.add(path),
|
|
emitAsset: (path) => assets.add(path),
|
|
emitAssetDirectory,
|
|
job,
|
|
});
|
|
}
|
|
async function backtrack(parent, context) {
|
|
// computing a static expression outward
|
|
// -> compute and backtrack
|
|
// Note that `context` can be undefined in `leave()`
|
|
if (!staticChildNode)
|
|
throw new Error('Internal error: No staticChildNode for backtrack.');
|
|
const curStaticValue = await computePureStaticValue(parent, true);
|
|
if (curStaticValue) {
|
|
if (('value' in curStaticValue &&
|
|
typeof curStaticValue.value !== 'symbol') ||
|
|
('ifTrue' in curStaticValue &&
|
|
typeof curStaticValue.ifTrue !== 'symbol' &&
|
|
typeof curStaticValue.else !== 'symbol')) {
|
|
staticChildValue = curStaticValue;
|
|
staticChildNode = parent;
|
|
if (context)
|
|
context.skip();
|
|
return;
|
|
}
|
|
}
|
|
// no static value -> see if we should emit the asset if it exists
|
|
await emitStaticChildAsset();
|
|
}
|
|
await (0, estree_walker_1.asyncWalk)(ast, {
|
|
async enter(_node, _parent) {
|
|
const node = _node;
|
|
const parent = _parent;
|
|
if (node.scope) {
|
|
scope = node.scope;
|
|
for (const id in node.scope.declarations) {
|
|
if (id in knownBindings)
|
|
knownBindings[id].shadowDepth++;
|
|
}
|
|
}
|
|
// currently backtracking
|
|
if (staticChildNode)
|
|
return;
|
|
if (!parent)
|
|
return;
|
|
if (node.type === 'Identifier') {
|
|
if ((0, ast_helpers_1.isIdentifierRead)(node, parent) &&
|
|
job.analysis.computeFileReferences) {
|
|
let binding;
|
|
// detect asset leaf expression triggers (if not already)
|
|
// __dirname, __filename
|
|
if ((typeof (binding = getKnownBinding(node.name)?.value) === 'string' &&
|
|
binding.match(absoluteRegEx)) ||
|
|
(binding &&
|
|
(typeof binding === 'function' || typeof binding === 'object') &&
|
|
binding[TRIGGER])) {
|
|
staticChildValue = {
|
|
value: typeof binding === 'string' ? binding : undefined,
|
|
};
|
|
staticChildNode = node;
|
|
await backtrack(parent, this);
|
|
}
|
|
}
|
|
}
|
|
else if (job.analysis.computeFileReferences &&
|
|
node.type === 'MemberExpression' &&
|
|
node.object.type === 'MetaProperty' &&
|
|
node.object.meta.name === 'import' &&
|
|
node.object.property.name === 'meta' &&
|
|
(node.property.computed ? node.property.value : node.property.name) ===
|
|
'url') {
|
|
// import.meta.url leaf trigger
|
|
staticChildValue = { value: importMetaUrl };
|
|
staticChildNode = node;
|
|
await backtrack(parent, this);
|
|
}
|
|
else if (node.type === 'ImportExpression') {
|
|
await processRequireArg(node.source, true);
|
|
return;
|
|
}
|
|
// Call expression cases and asset triggers
|
|
// - fs triggers: fs.readFile(...)
|
|
// - require.resolve()
|
|
// - bindings()(...)
|
|
// - nodegyp()
|
|
// - etc.
|
|
else if (node.type === 'CallExpression') {
|
|
if ((!isESM || job.mixedModules) &&
|
|
node.callee.type === 'Identifier' &&
|
|
node.arguments.length) {
|
|
if (node.callee.name === 'require' &&
|
|
knownBindings.require.shadowDepth === 0) {
|
|
await processRequireArg(node.arguments[0]);
|
|
return;
|
|
}
|
|
}
|
|
else if ((!isESM || job.mixedModules) &&
|
|
node.callee.type === 'MemberExpression' &&
|
|
node.callee.object.type === 'Identifier' &&
|
|
node.callee.object.name === 'module' &&
|
|
'module' in knownBindings === false &&
|
|
node.callee.property.type === 'Identifier' &&
|
|
!node.callee.computed &&
|
|
node.callee.property.name === 'require' &&
|
|
node.arguments.length) {
|
|
await processRequireArg(node.arguments[0]);
|
|
return;
|
|
}
|
|
else if ((!isESM || job.mixedModules) &&
|
|
node.callee.type === 'MemberExpression' &&
|
|
node.callee.object.type === 'Identifier' &&
|
|
node.callee.object.name === 'require' &&
|
|
knownBindings.require.shadowDepth === 0 &&
|
|
node.callee.property.type === 'Identifier' &&
|
|
!node.callee.computed &&
|
|
node.callee.property.name === 'resolve' &&
|
|
node.arguments.length) {
|
|
await processRequireArg(node.arguments[0]);
|
|
return;
|
|
}
|
|
const calleeValue = job.analysis.evaluatePureExpressions &&
|
|
(await computePureStaticValue(node.callee, false));
|
|
// if we have a direct pure static function,
|
|
// and that function has a [TRIGGER] symbol -> trigger asset emission from it
|
|
if (calleeValue &&
|
|
'value' in calleeValue &&
|
|
typeof calleeValue.value === 'function' &&
|
|
calleeValue.value[TRIGGER] &&
|
|
job.analysis.computeFileReferences) {
|
|
staticChildValue = await computePureStaticValue(node, true);
|
|
// if it computes, then we start backtracking
|
|
if (staticChildValue && parent) {
|
|
staticChildNode = node;
|
|
await backtrack(parent, this);
|
|
}
|
|
}
|
|
// handle well-known function symbol cases
|
|
else if (calleeValue &&
|
|
'value' in calleeValue &&
|
|
typeof calleeValue.value === 'symbol') {
|
|
switch (calleeValue.value) {
|
|
// customRequireWrapper('...')
|
|
case BOUND_REQUIRE:
|
|
if (node.arguments.length === 1 &&
|
|
node.arguments[0].type === 'Literal' &&
|
|
node.callee.type === 'Identifier' &&
|
|
knownBindings.require.shadowDepth === 0) {
|
|
await processRequireArg(node.arguments[0]);
|
|
}
|
|
break;
|
|
// require('bindings')(...)
|
|
case BINDINGS:
|
|
if (node.arguments.length) {
|
|
const arg = await computePureStaticValue(node.arguments[0], false);
|
|
if (arg && 'value' in arg && arg.value) {
|
|
let opts;
|
|
if (typeof arg.value === 'object')
|
|
opts = arg.value;
|
|
else if (typeof arg.value === 'string')
|
|
opts = { bindings: arg.value };
|
|
if (!opts.path) {
|
|
opts.path = true;
|
|
}
|
|
opts.module_root = pkgBase;
|
|
let resolved;
|
|
try {
|
|
resolved = (0, bindings_1.default)(opts);
|
|
}
|
|
catch (e) { }
|
|
if (resolved) {
|
|
staticChildValue = { value: resolved };
|
|
staticChildNode = node;
|
|
await emitStaticChildAsset();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case NODE_GYP_BUILD:
|
|
// handle case: require('node-gyp-build')(__dirname)
|
|
// handle case: require('node-gyp-build')(join(__dirname, ''))
|
|
// also case: require('@aminya/node-gyp-build')(__dirname)
|
|
if (node.arguments.length) {
|
|
const arg = await computePureStaticValue(node.arguments[0], false);
|
|
if (arg && 'value' in arg && arg.value) {
|
|
const pathJoinedDir = arg.value;
|
|
let resolved;
|
|
try {
|
|
// the pkg could be 'node-gyp-build' or '@aminya/node-gyp-build'
|
|
const pkgName = node?.callee?.arguments?.[0]?.value || 'node-gyp-build';
|
|
// use installed version of node-gyp-build since resolving
|
|
// binaries can differ among versions
|
|
const nodeGypBuildPath = (0, resolve_from_1.default)(pathJoinedDir, pkgName);
|
|
resolved = require(nodeGypBuildPath).path(pathJoinedDir);
|
|
}
|
|
catch (e) {
|
|
try {
|
|
resolved = node_gyp_build_1.default.path(pathJoinedDir);
|
|
}
|
|
catch (e) { }
|
|
}
|
|
if (resolved) {
|
|
staticChildValue = { value: resolved };
|
|
staticChildNode = node;
|
|
await emitStaticChildAsset();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
// nbind.init(...) -> require('./resolved.node')
|
|
case NBIND_INIT:
|
|
if (node.arguments.length) {
|
|
const arg = await computePureStaticValue(node.arguments[0], false);
|
|
if (arg &&
|
|
'value' in arg &&
|
|
(typeof arg.value === 'string' ||
|
|
typeof arg.value === 'undefined')) {
|
|
const bindingInfo = (0, binary_locators_1.nbind)(arg.value);
|
|
if (bindingInfo && bindingInfo.path) {
|
|
deps.add(path_1.default.relative(dir, bindingInfo.path).replace(/\\/g, '/'));
|
|
return this.skip();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
// Express templates:
|
|
// app.set("view engine", [name]) -> 'name' is a require
|
|
case EXPRESS_SET:
|
|
if (node.arguments.length === 2 &&
|
|
node.arguments[0].type === 'Literal' &&
|
|
node.arguments[0].value === 'view engine' &&
|
|
!definedExpressEngines) {
|
|
await processRequireArg(node.arguments[1]);
|
|
return this.skip();
|
|
}
|
|
break;
|
|
// app.engine('name', ...) causes opt-out of express dynamic require
|
|
case EXPRESS_ENGINE:
|
|
definedExpressEngines = true;
|
|
break;
|
|
case FS_FN:
|
|
case FS_DIR_FN:
|
|
if (node.arguments[0] && job.analysis.computeFileReferences) {
|
|
staticChildValue = await computePureStaticValue(node.arguments[0], true);
|
|
// if it computes, then we start backtracking
|
|
if (staticChildValue) {
|
|
staticChildNode = node.arguments[0];
|
|
if (calleeValue.value === FS_DIR_FN &&
|
|
node.arguments[0].type === 'Identifier' &&
|
|
node.arguments[0].name === '__dirname') {
|
|
// Special case `fs.readdirSync(__dirname)` to emit right away
|
|
emitAssetDirectory(dir);
|
|
}
|
|
else {
|
|
await backtrack(parent, this);
|
|
}
|
|
return this.skip();
|
|
}
|
|
}
|
|
break;
|
|
// strong globalize (emits intl folder)
|
|
case SET_ROOT_DIR:
|
|
if (node.arguments[0]) {
|
|
const rootDir = await computePureStaticValue(node.arguments[0], false);
|
|
if (rootDir && 'value' in rootDir && rootDir.value)
|
|
emitAssetDirectory(rootDir.value + '/intl');
|
|
return this.skip();
|
|
}
|
|
break;
|
|
// pkginfo - require('pkginfo')(module) -> loads package.json
|
|
case PKG_INFO:
|
|
let pjsonPath = path_1.default.resolve(id, '../package.json');
|
|
const rootPjson = path_1.default.resolve('/package.json');
|
|
while (pjsonPath !== rootPjson &&
|
|
(await job.stat(pjsonPath)) === null)
|
|
pjsonPath = path_1.default.resolve(pjsonPath, '../../package.json');
|
|
if (pjsonPath !== rootPjson)
|
|
assets.add(pjsonPath);
|
|
break;
|
|
case MODULE_FN:
|
|
if (node.arguments.length &&
|
|
// TODO: We only cater for the case where the first argument is a string
|
|
node.arguments[0].type === 'Literal') {
|
|
const pathOrSpecifier = node.arguments[0].value;
|
|
// It's a relative URL
|
|
if (pathOrSpecifier.startsWith('.')) {
|
|
// Compute the parentURL if it's statically analyzable
|
|
const computedParentURL = node.arguments.length > 1
|
|
? await computePureStaticValue(node.arguments[1])
|
|
: undefined;
|
|
if (computedParentURL && 'value' in computedParentURL) {
|
|
const parentURL = computedParentURL.value instanceof url_1.URL
|
|
? computedParentURL.value.href
|
|
: typeof computedParentURL.value === 'string'
|
|
? computedParentURL.value
|
|
: computedParentURL.value.parentURL;
|
|
// Resolve the srcURL from the parentURL
|
|
const srcURL = new url_1.URL(pathOrSpecifier, parentURL).href;
|
|
const currentDirURL = importMetaUrl.slice(0, importMetaUrl.lastIndexOf('/'));
|
|
// Resolve the srcPath relative to the current file
|
|
const srcPath = path_1.default.relative(currentDirURL, srcURL);
|
|
// Make it a proper relative path
|
|
const relativeSrcPath = srcPath.startsWith('.')
|
|
? srcPath
|
|
: './' + srcPath;
|
|
imports.add(relativeSrcPath);
|
|
}
|
|
}
|
|
else {
|
|
// It's a bare specifier, so just add into the imports
|
|
imports.add(pathOrSpecifier);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (node.type === 'VariableDeclaration' &&
|
|
parent &&
|
|
!(0, ast_helpers_1.isVarLoop)(parent) &&
|
|
job.analysis.evaluatePureExpressions) {
|
|
for (const decl of node.declarations) {
|
|
if (!decl.init)
|
|
continue;
|
|
const computed = await computePureStaticValue(decl.init, true);
|
|
if (computed) {
|
|
// var known = ...;
|
|
if (decl.id.type === 'Identifier') {
|
|
setKnownBinding(decl.id.name, computed);
|
|
}
|
|
// var { known } = ...;
|
|
else if (decl.id.type === 'ObjectPattern' && 'value' in computed) {
|
|
for (const prop of decl.id.properties) {
|
|
if (prop.type !== 'Property' ||
|
|
prop.key.type !== 'Identifier' ||
|
|
prop.value.type !== 'Identifier' ||
|
|
typeof computed.value !== 'object' ||
|
|
computed.value === null ||
|
|
!(prop.key.name in computed.value))
|
|
continue;
|
|
setKnownBinding(prop.value.name, {
|
|
value: computed.value[prop.key.name],
|
|
});
|
|
}
|
|
}
|
|
if (!('value' in computed) &&
|
|
isAbsolutePathOrUrl(computed.ifTrue) &&
|
|
isAbsolutePathOrUrl(computed.else)) {
|
|
staticChildValue = computed;
|
|
staticChildNode = decl.init;
|
|
await emitStaticChildAsset();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (node.type === 'AssignmentExpression' &&
|
|
parent &&
|
|
!(0, ast_helpers_1.isLoop)(parent) &&
|
|
job.analysis.evaluatePureExpressions) {
|
|
if (!hasKnownBindingValue(node.left.name)) {
|
|
const computed = await computePureStaticValue(node.right, false);
|
|
if (computed && 'value' in computed) {
|
|
// var known = ...
|
|
if (node.left.type === 'Identifier') {
|
|
setKnownBinding(node.left.name, computed);
|
|
}
|
|
// var { known } = ...
|
|
else if (node.left.type === 'ObjectPattern') {
|
|
for (const prop of node.left.properties) {
|
|
if (prop.type !== 'Property' ||
|
|
prop.key.type !== 'Identifier' ||
|
|
prop.value.type !== 'Identifier' ||
|
|
typeof computed.value !== 'object' ||
|
|
computed.value === null ||
|
|
!(prop.key.name in computed.value))
|
|
continue;
|
|
setKnownBinding(prop.value.name, {
|
|
value: computed.value[prop.key.name],
|
|
});
|
|
}
|
|
}
|
|
if (isAbsolutePathOrUrl(computed.value)) {
|
|
staticChildValue = computed;
|
|
staticChildNode = node.right;
|
|
await emitStaticChildAsset();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Support require wrappers like function p (x) { ...; var y = require(x); ...; return y; }
|
|
else if ((!isESM || job.mixedModules) &&
|
|
(node.type === 'FunctionDeclaration' ||
|
|
node.type === 'FunctionExpression' ||
|
|
node.type === 'ArrowFunctionExpression') &&
|
|
(node.arguments || node.params)[0] &&
|
|
(node.arguments || node.params)[0].type === 'Identifier') {
|
|
let fnName;
|
|
let args;
|
|
if ((node.type === 'ArrowFunctionExpression' ||
|
|
node.type === 'FunctionExpression') &&
|
|
parent &&
|
|
parent.type === 'VariableDeclarator' &&
|
|
parent.id.type === 'Identifier') {
|
|
fnName = parent.id;
|
|
args = node.arguments || node.params;
|
|
}
|
|
else if (node.id) {
|
|
fnName = node.id;
|
|
args = node.arguments || node.params;
|
|
}
|
|
if (fnName && node.body.body) {
|
|
let requireDecl, returned = false;
|
|
for (let i = 0; i < node.body.body.length; i++) {
|
|
if (node.body.body[i].type === 'VariableDeclaration' &&
|
|
!requireDecl) {
|
|
requireDecl = node.body.body[i].declarations.find((decl) => decl &&
|
|
decl.id &&
|
|
decl.id.type === 'Identifier' &&
|
|
decl.init &&
|
|
decl.init.type === 'CallExpression' &&
|
|
decl.init.callee.type === 'Identifier' &&
|
|
decl.init.callee.name === 'require' &&
|
|
knownBindings.require.shadowDepth === 0 &&
|
|
decl.init.arguments[0] &&
|
|
decl.init.arguments[0].type === 'Identifier' &&
|
|
decl.init.arguments[0].name === args[0].name);
|
|
}
|
|
if (requireDecl &&
|
|
node.body.body[i].type === 'ReturnStatement' &&
|
|
node.body.body[i].argument &&
|
|
node.body.body[i].argument.type === 'Identifier' &&
|
|
node.body.body[i].argument.name === requireDecl.id.name) {
|
|
returned = true;
|
|
break;
|
|
}
|
|
}
|
|
if (returned)
|
|
setKnownBinding(fnName.name, { value: BOUND_REQUIRE });
|
|
}
|
|
}
|
|
// Support module.createRequire
|
|
if (node.type === 'CallExpression' &&
|
|
node.callee.type === 'MemberExpression' &&
|
|
node.callee.object.type === 'Identifier' &&
|
|
node.callee.object.name === 'module' &&
|
|
node.callee.property.type === 'Identifier' &&
|
|
node.callee.property.name === 'createRequire') {
|
|
if (parent.type === 'VariableDeclarator') {
|
|
const requireName = parent.id.name;
|
|
setKnownBinding(requireName, { value: BOUND_REQUIRE });
|
|
}
|
|
}
|
|
},
|
|
async leave(_node, _parent) {
|
|
const node = _node;
|
|
const parent = _parent;
|
|
if (node.scope) {
|
|
if (scope.parent) {
|
|
scope = scope.parent;
|
|
}
|
|
for (const id in node.scope.declarations) {
|
|
if (id in knownBindings) {
|
|
if (knownBindings[id].shadowDepth > 0)
|
|
knownBindings[id].shadowDepth--;
|
|
else
|
|
delete knownBindings[id];
|
|
}
|
|
}
|
|
}
|
|
if (staticChildNode && parent)
|
|
await backtrack(parent, this);
|
|
},
|
|
});
|
|
await assetEmissionPromises;
|
|
return { assets, deps, imports, isESM };
|
|
async function emitAssetPath(assetPath) {
|
|
// verify the asset file / directory exists
|
|
const wildcardIndex = assetPath.indexOf(static_eval_1.WILDCARD);
|
|
const dirIndex = wildcardIndex === -1
|
|
? assetPath.length
|
|
: assetPath.lastIndexOf(path_1.default.sep, wildcardIndex);
|
|
const basePath = assetPath.substring(0, dirIndex);
|
|
try {
|
|
var stats = await job.stat(basePath);
|
|
if (stats === null) {
|
|
throw new Error('file not found');
|
|
}
|
|
}
|
|
catch (e) {
|
|
return;
|
|
}
|
|
if (wildcardIndex !== -1 && stats.isFile())
|
|
return;
|
|
if (stats.isFile()) {
|
|
assets.add(assetPath);
|
|
}
|
|
else if (stats.isDirectory()) {
|
|
if (validWildcard(assetPath))
|
|
emitAssetDirectory(assetPath);
|
|
}
|
|
}
|
|
function validWildcard(assetPath) {
|
|
let wildcardSuffix = '';
|
|
if (assetPath.endsWith(path_1.default.sep))
|
|
wildcardSuffix = path_1.default.sep;
|
|
else if (assetPath.endsWith(path_1.default.sep + static_eval_1.WILDCARD))
|
|
wildcardSuffix = path_1.default.sep + static_eval_1.WILDCARD;
|
|
else if (assetPath.endsWith(static_eval_1.WILDCARD))
|
|
wildcardSuffix = static_eval_1.WILDCARD;
|
|
// do not emit __dirname
|
|
if (assetPath === dir + wildcardSuffix)
|
|
return false;
|
|
// do not emit cwd
|
|
if (assetPath === cwd + wildcardSuffix)
|
|
return false;
|
|
// do not emit node_modules
|
|
if (assetPath.endsWith(path_1.default.sep + 'node_modules' + wildcardSuffix))
|
|
return false;
|
|
// do not emit directories above __dirname
|
|
if (dir.startsWith(assetPath.slice(0, assetPath.length - wildcardSuffix.length) + path_1.default.sep))
|
|
return false;
|
|
// do not emit asset directories higher than the node_modules base if a package
|
|
if (pkgBase) {
|
|
const nodeModulesBase = id.substring(0, id.indexOf(path_1.default.sep + 'node_modules')) +
|
|
path_1.default.sep +
|
|
'node_modules' +
|
|
path_1.default.sep;
|
|
if (!assetPath.startsWith(nodeModulesBase)) {
|
|
if (job.log)
|
|
console.log('Skipping asset emission of ' +
|
|
assetPath.replace(static_eval_1.wildcardRegEx, '*') +
|
|
' for ' +
|
|
id +
|
|
' as it is outside the package base ' +
|
|
pkgBase);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function resolveAbsolutePathOrUrl(value) {
|
|
return value instanceof url_1.URL
|
|
? (0, url_1.fileURLToPath)(value)
|
|
: value.startsWith('file:')
|
|
? (0, url_1.fileURLToPath)(new url_1.URL(value))
|
|
: path_1.default.resolve(value);
|
|
}
|
|
async function emitStaticChildAsset() {
|
|
if (!staticChildValue) {
|
|
return;
|
|
}
|
|
if ('value' in staticChildValue &&
|
|
isAbsolutePathOrUrl(staticChildValue.value)) {
|
|
try {
|
|
const resolved = resolveAbsolutePathOrUrl(staticChildValue.value);
|
|
await emitAssetPath(resolved);
|
|
}
|
|
catch (e) { }
|
|
}
|
|
else if ('ifTrue' in staticChildValue &&
|
|
'else' in staticChildValue &&
|
|
isAbsolutePathOrUrl(staticChildValue.ifTrue) &&
|
|
isAbsolutePathOrUrl(staticChildValue.else)) {
|
|
let resolvedThen;
|
|
try {
|
|
resolvedThen = resolveAbsolutePathOrUrl(staticChildValue.ifTrue);
|
|
}
|
|
catch (e) { }
|
|
let resolvedElse;
|
|
try {
|
|
resolvedElse = resolveAbsolutePathOrUrl(staticChildValue.else);
|
|
}
|
|
catch (e) { }
|
|
if (resolvedThen)
|
|
await emitAssetPath(resolvedThen);
|
|
if (resolvedElse)
|
|
await emitAssetPath(resolvedElse);
|
|
}
|
|
else if (staticChildNode &&
|
|
staticChildNode.type === 'ArrayExpression' &&
|
|
'value' in staticChildValue &&
|
|
staticChildValue.value instanceof Array) {
|
|
for (const value of staticChildValue.value) {
|
|
try {
|
|
const resolved = resolveAbsolutePathOrUrl(value);
|
|
await emitAssetPath(resolved);
|
|
}
|
|
catch (e) { }
|
|
}
|
|
}
|
|
staticChildNode = staticChildValue = undefined;
|
|
}
|
|
}
|
|
function isAst(ast) {
|
|
return 'body' in ast;
|
|
}
|