Skip to content

Commit

Permalink
Merge e04da34 into 95f0a04
Browse files Browse the repository at this point in the history
  • Loading branch information
ijjk authored Sep 22, 2021
2 parents 95f0a04 + e04da34 commit 1ef8c19
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 168 deletions.
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

// 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

0 comments on commit 1ef8c19

Please sign in to comment.