Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert fs operations from sync to async #236

Merged
merged 6 commits into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"acorn-private-class-elements": "^1.0.0",
"acorn-static-class-features": "^1.0.0",
"bindings": "^1.4.0",
"estree-walker": "^0.6.1",
"estree-walker": "2.0.2",
"glob": "^7.1.3",
"graceful-fs": "^4.1.15",
"micromatch": "^4.0.2",
Expand Down
89 changes: 51 additions & 38 deletions src/analyze.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import path from 'path';
import { existsSync, statSync } from 'fs';
import { walk, WalkerContext, Node } from 'estree-walker';
import { WalkerContext, Node } from 'estree-walker';
import { attachScopes } from 'rollup-pluginutils';
import { evaluate, UNKNOWN, FUNCTION, WILDCARD, wildcardRegEx } from './utils/static-eval';
import { Parser } from 'acorn';
Expand All @@ -19,6 +18,11 @@ import mapboxPregyp from '@mapbox/node-pre-gyp';
import { Job } from './node-file-trace';
import { fileURLToPath, pathToFileURL, URL } from 'url';


// TypeScript fails to resolve estree-walker to the top due to the conflicting
// estree-walker version in rollup-pluginutils so we use require here instead
const asyncWalk: typeof import('../node_modules/estree-walker').asyncWalk = require('estree-walker').asyncWalk
styfle marked this conversation as resolved.
Show resolved Hide resolved

// Note: these should be deprecated over time as they ship in Acorn core
const acorn = Parser.extend(
require("acorn-class-fields"),
Expand Down Expand Up @@ -353,7 +357,7 @@ export default async function analyze(id: string, code: string, job: Job): Promi
}
}

function computePureStaticValue (expr: Node, computeBranches = true) {
async function computePureStaticValue (expr: Node, computeBranches = true) {
const vars = Object.create(null);
Object.keys(globalBindings).forEach(name => {
vars[name] = { value: globalBindings[name] };
Expand All @@ -363,7 +367,7 @@ export default async function analyze(id: string, code: string, job: Job): Promi
});
vars['import.meta'] = { url: importMetaUrl };
// evaluate returns undefined for non-statically-analyzable
const result = evaluate(expr, vars, computeBranches);
const result = await evaluate(expr, vars, computeBranches);
return result;
}

Expand Down Expand Up @@ -410,7 +414,7 @@ export default async function analyze(id: string, code: string, job: Job): Promi
});
}

function processRequireArg (expression: Node, isImport = false) {
async function processRequireArg (expression: Node, isImport = false) {
if (expression.type === 'ConditionalExpression') {
processRequireArg(expression.consequent, isImport);
processRequireArg(expression.alternate, isImport);
Expand All @@ -422,7 +426,7 @@ export default async function analyze(id: string, code: string, job: Job): Promi
return;
}

let computed = computePureStaticValue(expression, true);
let computed = await computePureStaticValue(expression, true);
if (!computed) return;

if ('value' in computed && typeof computed.value === 'string') {
Expand All @@ -442,14 +446,14 @@ export default async function analyze(id: string, code: string, job: Job): Promi
let scope = attachScopes(ast, 'scope');
if (isAst(ast)) {
handleWrappers(ast);
handleSpecialCases({ id, ast, emitAsset: path => assets.add(path), emitAssetDirectory, job });
await handleSpecialCases({ id, ast, emitAsset: path => assets.add(path), emitAssetDirectory, job });
}
function backtrack (parent: Node, context?: WalkerContext) {
async function backtrack (parent: Node, context?: WalkerContext) {
// 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 = computePureStaticValue(parent, true);
const curStaticValue = await computePureStaticValue(parent, true);
if (curStaticValue) {
if ('value' in curStaticValue && typeof curStaticValue.value !== 'symbol' ||
'then' in curStaticValue && typeof curStaticValue.then !== 'symbol' && typeof curStaticValue.else !== 'symbol') {
Expand All @@ -460,11 +464,14 @@ export default async function analyze(id: string, code: string, job: Job): Promi
}
}
// no static value -> see if we should emit the asset if it exists
emitStaticChildAsset();
await emitStaticChildAsset();
}

walk(ast, {
enter (node, parent) {
await asyncWalk(ast, {
async enter (_node, _parent) {
const node: Node = _node as any
const parent: Node = _parent as any

if (node.scope) {
scope = node.scope;
for (const id in node.scope.declarations) {
Expand All @@ -488,15 +495,15 @@ export default async function analyze(id: string, code: string, job: Job): Promi
binding && (typeof binding === 'function' || typeof binding === 'object') && binding[TRIGGER]) {
staticChildValue = { value: typeof binding === 'string' ? binding : undefined };
staticChildNode = node;
backtrack(parent, this);
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;
backtrack(parent, this);
await backtrack(parent, this);
}
else if (node.type === 'ImportExpression') {
processRequireArg(node.source, true);
Expand Down Expand Up @@ -528,15 +535,15 @@ export default async function analyze(id: string, code: string, job: Job): Promi
return;
}

const calleeValue = job.analysis.evaluatePureExpressions && computePureStaticValue(node.callee, false);
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 as any)[TRIGGER] && job.analysis.computeFileReferences) {
staticChildValue = computePureStaticValue(node, true);
staticChildValue = await computePureStaticValue(node, true);
// if it computes, then we start backtracking
if (staticChildValue && parent) {
staticChildNode = node;
backtrack(parent, this);
await backtrack(parent, this);
}
}
// handle well-known function symbol cases
Expand All @@ -554,7 +561,7 @@ export default async function analyze(id: string, code: string, job: Job): Promi
// require('bindings')(...)
case BINDINGS:
if (node.arguments.length) {
const arg = computePureStaticValue(node.arguments[0], false);
const arg = await computePureStaticValue(node.arguments[0], false);
if (arg && 'value' in arg && arg.value) {
let opts: any;
if (typeof arg.value === 'object')
Expand All @@ -573,7 +580,7 @@ export default async function analyze(id: string, code: string, job: Job): Promi
if (resolved) {
staticChildValue = { value: resolved };
staticChildNode = node;
emitStaticChildAsset();
await emitStaticChildAsset();
}
}
}
Expand All @@ -589,14 +596,14 @@ export default async function analyze(id: string, code: string, job: Job): Promi
if (resolved) {
staticChildValue = { value: resolved };
staticChildNode = node;
emitStaticChildAsset();
await emitStaticChildAsset();
}
}
break;
// nbind.init(...) -> require('./resolved.node')
case NBIND_INIT:
if (node.arguments.length) {
const arg = computePureStaticValue(node.arguments[0], false);
const arg = await computePureStaticValue(node.arguments[0], false);
if (arg && 'value' in arg && (typeof arg.value === 'string' || typeof arg.value === 'undefined')) {
const bindingInfo = nbind(arg.value);
if (bindingInfo && bindingInfo.path) {
Expand All @@ -623,19 +630,19 @@ export default async function analyze(id: string, code: string, job: Job): Promi
break;
case FS_FN:
if (node.arguments[0] && job.analysis.computeFileReferences) {
staticChildValue = computePureStaticValue(node.arguments[0], true);
staticChildValue = await computePureStaticValue(node.arguments[0], true);
// if it computes, then we start backtracking
if (staticChildValue) {
staticChildNode = node.arguments[0];
backtrack(parent, this);
await backtrack(parent, this);
return this.skip();
}
}
break;
// strong globalize (emits intl folder)
case SET_ROOT_DIR:
if (node.arguments[0]) {
const rootDir = computePureStaticValue(node.arguments[0], false);
const rootDir = await computePureStaticValue(node.arguments[0], false);
if (rootDir && 'value' in rootDir && rootDir.value)
emitAssetDirectory(rootDir.value + '/intl');
return this.skip();
Expand All @@ -645,7 +652,7 @@ export default async function analyze(id: string, code: string, job: Job): Promi
case PKG_INFO:
let pjsonPath = path.resolve(id, '../package.json');
const rootPjson = path.resolve('/package.json');
while (pjsonPath !== rootPjson && !existsSync(pjsonPath))
while (pjsonPath !== rootPjson && (await job.stat(pjsonPath) === null))
pjsonPath = path.resolve(pjsonPath, '../../package.json');
if (pjsonPath !== rootPjson)
assets.add(pjsonPath);
Expand All @@ -656,7 +663,7 @@ export default async function analyze(id: string, code: string, job: Job): Promi
else if (node.type === 'VariableDeclaration' && parent && !isVarLoop(parent) && job.analysis.evaluatePureExpressions) {
for (const decl of node.declarations) {
if (!decl.init) continue;
const computed = computePureStaticValue(decl.init, true);
const computed = await computePureStaticValue(decl.init, true);
if (computed) {
// var known = ...;
if (decl.id.type === 'Identifier') {
Expand All @@ -678,14 +685,14 @@ export default async function analyze(id: string, code: string, job: Job): Promi
if (!('value' in computed) && isAbsolutePathOrUrl(computed.then) && isAbsolutePathOrUrl(computed.else)) {
staticChildValue = computed;
staticChildNode = decl.init;
emitStaticChildAsset();
await emitStaticChildAsset();
}
}
}
}
else if (node.type === 'AssignmentExpression' && parent && !isLoop(parent) && job.analysis.evaluatePureExpressions) {
if (!hasKnownBindingValue(node.left.name)) {
const computed = computePureStaticValue(node.right, false);
const computed = await computePureStaticValue(node.right, false);
if (computed && 'value' in computed) {
// var known = ...
if (node.left.type === 'Identifier') {
Expand All @@ -707,7 +714,7 @@ export default async function analyze(id: string, code: string, job: Job): Promi
if (isAbsolutePathOrUrl(computed.value)) {
staticChildValue = computed;
staticChildNode = node.right;
emitStaticChildAsset();
await emitStaticChildAsset();
}
}
}
Expand Down Expand Up @@ -761,7 +768,10 @@ export default async function analyze(id: string, code: string, job: Job): Promi
}
}
},
leave (node, parent) {
async leave (_node, _parent) {
const node: Node = _node as any
const parent: Node = _parent as any

if (node.scope) {
if (scope.parent) {
scope = scope.parent;
Expand All @@ -776,20 +786,23 @@ export default async function analyze(id: string, code: string, job: Job): Promi
}
}

if (staticChildNode && parent) backtrack(parent, this);
if (staticChildNode && parent) await backtrack(parent, this);
}
});

await assetEmissionPromises;
return { assets, deps, imports, isESM };

function emitAssetPath (assetPath: string) {
async function emitAssetPath (assetPath: string) {
// verify the asset file / directory exists
const wildcardIndex = assetPath.indexOf(WILDCARD);
const dirIndex = wildcardIndex === -1 ? assetPath.length : assetPath.lastIndexOf(path.sep, wildcardIndex);
const basePath = assetPath.substr(0, dirIndex);
try {
var stats = statSync(basePath);
var stats = await job.stat(basePath);
if (stats === null) {
throw new Error('file not found')
}
}
catch (e) {
return;
Expand Down Expand Up @@ -840,15 +853,15 @@ export default async function analyze(id: string, code: string, job: Job): Promi
return value instanceof URL ? fileURLToPath(value) : value.startsWith('file:') ? fileURLToPath(new URL(value)) : path.resolve(value);
}

function emitStaticChildAsset () {
async function emitStaticChildAsset () {
if (!staticChildValue) {
return;
}

if ('value' in staticChildValue && isAbsolutePathOrUrl(staticChildValue.value)) {
try {
const resolved = resolveAbsolutePathOrUrl(staticChildValue.value);
emitAssetPath(resolved);
await emitAssetPath(resolved);
}
catch (e) {}
}
Expand All @@ -859,14 +872,14 @@ export default async function analyze(id: string, code: string, job: Job): Promi
let resolvedElse;
try { resolvedElse = resolveAbsolutePathOrUrl(staticChildValue.else); }
catch (e) {}
if (resolvedThen) emitAssetPath(resolvedThen);
if (resolvedElse) emitAssetPath(resolvedElse);
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);
emitAssetPath(resolved);
await emitAssetPath(resolved);
}
catch (e) {}
}
Expand Down
Loading