Make AMD parser modules extensible

This commit is contained in:
chuckd 2018-03-05 07:51:12 -05:00
parent 6970103b79
commit 1b37115e4b
2 changed files with 430 additions and 408 deletions

View File

@ -40,272 +40,275 @@ class AMDDefineDependencyParserPlugin {
this.options = options; this.options = options;
} }
newDefineDependency( apply(parser) {
range, this.parser = parser;
arrayRange, parser.hooks.call.for("define").tap(
functionRange, "AMDDefineDependencyParserPlugin",
objectRange, this.processCallDefine.bind(
namedModule Object.create(this, {
) { parser: { value: parser }
return new AMDDefineDependency( })
range, )
arrayRange,
functionRange,
objectRange,
namedModule
); );
} }
apply(parser) { processArray(expr, param, identifiers, namedModule) {
const options = this.options; const parser = this.parser;
if (param.isArray()) {
const processArray = (expr, param, identifiers, namedModule) => { param.items.forEach((param, idx) => {
if (param.isArray()) { if (
param.items.forEach((param, idx) => { param.isString() &&
if ( ["require", "module", "exports"].includes(param.string)
param.isString() && )
["require", "module", "exports"].includes(param.string) identifiers[idx] = param.string;
) const result = this.processItem(expr, param, namedModule);
identifiers[idx] = param.string; if (result === undefined) {
const result = processItem(expr, param, namedModule); this.processContext(expr, param);
if (result === undefined) { }
processContext(expr, param); });
} return true;
}); } else if (param.isConstArray()) {
return true; const deps = [];
} else if (param.isConstArray()) { param.array.forEach((request, idx) => {
const deps = []; let dep;
param.array.forEach((request, idx) => { let localModule;
let dep; if (request === "require") {
let localModule; identifiers[idx] = request;
if (request === "require") { dep = "__webpack_require__";
identifiers[idx] = request; } else if (["exports", "module"].includes(request)) {
dep = "__webpack_require__"; identifiers[idx] = request;
} else if (["exports", "module"].includes(request)) { dep = request;
identifiers[idx] = request;
dep = request;
} else if (
(localModule = LocalModulesHelpers.getLocalModule(
parser.state,
request
))
) {
// eslint-disable-line no-cond-assign
dep = new LocalModuleDependency(localModule);
dep.loc = expr.loc;
parser.state.current.addDependency(dep);
} else {
dep = new AMDRequireItemDependency(request);
dep.loc = expr.loc;
dep.optional = !!parser.scope.inTry;
parser.state.current.addDependency(dep);
}
deps.push(dep);
});
const dep = new AMDRequireArrayDependency(deps, param.range);
dep.loc = expr.loc;
dep.optional = !!parser.scope.inTry;
parser.state.current.addDependency(dep);
return true;
}
};
const processItem = (expr, param, namedModule) => {
if (param.isConditional()) {
param.options.forEach(param => {
const result = processItem(expr, param);
if (result === undefined) {
processContext(expr, param);
}
});
return true;
} else if (param.isString()) {
let dep, localModule;
if (param.string === "require") {
dep = new ConstDependency("__webpack_require__", param.range);
} else if (["require", "exports", "module"].includes(param.string)) {
dep = new ConstDependency(param.string, param.range);
} else if ( } else if (
(localModule = LocalModulesHelpers.getLocalModule( (localModule = LocalModulesHelpers.getLocalModule(
parser.state, parser.state,
param.string, request
namedModule
)) ))
) { ) {
// eslint-disable-line no-cond-assign // eslint-disable-line no-cond-assign
dep = new LocalModuleDependency(localModule, param.range); dep = new LocalModuleDependency(localModule);
dep.loc = expr.loc;
parser.state.current.addDependency(dep);
} else { } else {
dep = new AMDRequireItemDependency(param.string, param.range); dep = this.newRequireItemDependency(request);
dep.loc = expr.loc;
dep.optional = !!parser.scope.inTry;
parser.state.current.addDependency(dep);
} }
dep.loc = expr.loc; deps.push(dep);
dep.optional = !!parser.scope.inTry; });
parser.state.current.addDependency(dep); const dep = this.newRequireArrayDependency(deps, param.range);
return true;
}
};
const processContext = (expr, param) => {
const dep = ContextDependencyHelpers.create(
AMDRequireContextDependency,
param.range,
param,
expr,
options
);
if (!dep) return;
dep.loc = expr.loc; dep.loc = expr.loc;
dep.optional = !!parser.scope.inTry; dep.optional = !!parser.scope.inTry;
parser.state.current.addDependency(dep); parser.state.current.addDependency(dep);
return true; return true;
}; }
}
parser.hooks.call processItem(expr, param, namedModule) {
.for("define") const parser = this.parser;
.tap("AMDDefineDependencyParserPlugin", expr => { if (param.isConditional()) {
let array, fn, obj, namedModule; param.options.forEach(param => {
switch (expr.arguments.length) { const result = this.processItem(expr, param);
case 1: if (result === undefined) {
if (isCallable(expr.arguments[0])) { this.processContext(expr, param);
// define(f() {...})
fn = expr.arguments[0];
} else if (expr.arguments[0].type === "ObjectExpression") {
// define({...})
obj = expr.arguments[0];
} else {
// define(expr)
// unclear if function or object
obj = fn = expr.arguments[0];
}
break;
case 2:
if (expr.arguments[0].type === "Literal") {
namedModule = expr.arguments[0].value;
// define("...", ...)
if (isCallable(expr.arguments[1])) {
// define("...", f() {...})
fn = expr.arguments[1];
} else if (expr.arguments[1].type === "ObjectExpression") {
// define("...", {...})
obj = expr.arguments[1];
} else {
// define("...", expr)
// unclear if function or object
obj = fn = expr.arguments[1];
}
} else {
array = expr.arguments[0];
if (isCallable(expr.arguments[1])) {
// define([...], f() {})
fn = expr.arguments[1];
} else if (expr.arguments[1].type === "ObjectExpression") {
// define([...], {...})
obj = expr.arguments[1];
} else {
// define([...], expr)
// unclear if function or object
obj = fn = expr.arguments[1];
}
}
break;
case 3:
// define("...", [...], f() {...})
namedModule = expr.arguments[0].value;
array = expr.arguments[1];
if (isCallable(expr.arguments[2])) {
// define("...", [...], f() {})
fn = expr.arguments[2];
} else if (expr.arguments[2].type === "ObjectExpression") {
// define("...", [...], {...})
obj = expr.arguments[2];
} else {
// define("...", [...], expr)
// unclear if function or object
obj = fn = expr.arguments[2];
}
break;
default:
return;
} }
let fnParams = null; });
let fnParamsOffset = 0; return true;
if (fn) { } else if (param.isString()) {
if (isUnboundFunctionExpression(fn)) fnParams = fn.params; let dep, localModule;
else if (isBoundFunctionExpression(fn)) { if (param.string === "require") {
fnParams = fn.callee.object.params; dep = new ConstDependency("__webpack_require__", param.range);
fnParamsOffset = fn.arguments.length - 1; } else if (["require", "exports", "module"].includes(param.string)) {
if (fnParamsOffset < 0) fnParamsOffset = 0; dep = new ConstDependency(param.string, param.range);
} else if (
(localModule = LocalModulesHelpers.getLocalModule(
parser.state,
param.string,
namedModule
))
) {
// eslint-disable-line no-cond-assign
dep = new LocalModuleDependency(localModule, param.range);
} else {
dep = this.newRequireItemDependency(param.string, param.range);
}
dep.loc = expr.loc;
dep.optional = !!parser.scope.inTry;
parser.state.current.addDependency(dep);
return true;
}
}
processContext(expr, param) {
const dep = ContextDependencyHelpers.create(
AMDRequireContextDependency,
param.range,
param,
expr,
this.options
);
if (!dep) return;
dep.loc = expr.loc;
dep.optional = !!this.parser.scope.inTry;
this.parser.state.current.addDependency(dep);
return true;
}
processCallDefine(expr) {
const parser = this.parser;
let array, fn, obj, namedModule;
switch (expr.arguments.length) {
case 1:
if (isCallable(expr.arguments[0])) {
// define(f() {...})
fn = expr.arguments[0];
} else if (expr.arguments[0].type === "ObjectExpression") {
// define({...})
obj = expr.arguments[0];
} else {
// define(expr)
// unclear if function or object
obj = fn = expr.arguments[0];
}
break;
case 2:
if (expr.arguments[0].type === "Literal") {
namedModule = expr.arguments[0].value;
// define("...", ...)
if (isCallable(expr.arguments[1])) {
// define("...", f() {...})
fn = expr.arguments[1];
} else if (expr.arguments[1].type === "ObjectExpression") {
// define("...", {...})
obj = expr.arguments[1];
} else {
// define("...", expr)
// unclear if function or object
obj = fn = expr.arguments[1];
}
} else {
array = expr.arguments[0];
if (isCallable(expr.arguments[1])) {
// define([...], f() {})
fn = expr.arguments[1];
} else if (expr.arguments[1].type === "ObjectExpression") {
// define([...], {...})
obj = expr.arguments[1];
} else {
// define([...], expr)
// unclear if function or object
obj = fn = expr.arguments[1];
} }
} }
let fnRenames = parser.scope.renames.createChild(); break;
let identifiers; case 3:
if (array) { // define("...", [...], f() {...})
identifiers = {}; namedModule = expr.arguments[0].value;
const param = parser.evaluateExpression(array); array = expr.arguments[1];
const result = processArray(expr, param, identifiers, namedModule); if (isCallable(expr.arguments[2])) {
if (!result) return; // define("...", [...], f() {})
if (fnParams) fn = expr.arguments[2];
fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => { } else if (expr.arguments[2].type === "ObjectExpression") {
if (identifiers[idx]) { // define("...", [...], {...})
fnRenames.set(param.name, identifiers[idx]); obj = expr.arguments[2];
return false;
}
return true;
});
} else { } else {
identifiers = ["require", "exports", "module"]; // define("...", [...], expr)
if (fnParams) // unclear if function or object
fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => { obj = fn = expr.arguments[2];
if (identifiers[idx]) {
fnRenames.set(param.name, identifiers[idx]);
return false;
}
return true;
});
} }
let inTry; break;
if (fn && isUnboundFunctionExpression(fn)) { default:
inTry = parser.scope.inTry; return;
parser.inScope(fnParams, () => { }
parser.scope.renames = fnRenames; let fnParams = null;
parser.scope.inTry = inTry; let fnParamsOffset = 0;
if (fn.body.type === "BlockStatement") if (fn) {
parser.walkStatement(fn.body); if (isUnboundFunctionExpression(fn)) fnParams = fn.params;
else parser.walkExpression(fn.body); else if (isBoundFunctionExpression(fn)) {
}); fnParams = fn.callee.object.params;
} else if (fn && isBoundFunctionExpression(fn)) { fnParamsOffset = fn.arguments.length - 1;
inTry = parser.scope.inTry; if (fnParamsOffset < 0) fnParamsOffset = 0;
parser.inScope( }
fn.callee.object.params.filter( }
i => !["require", "module", "exports"].includes(i.name) let fnRenames = parser.scope.renames.createChild();
), let identifiers;
() => { if (array) {
parser.scope.renames = fnRenames; identifiers = {};
parser.scope.inTry = inTry; const param = parser.evaluateExpression(array);
if (fn.callee.object.body.type === "BlockStatement") const result = this.processArray(expr, param, identifiers, namedModule);
parser.walkStatement(fn.callee.object.body); if (!result) return;
else parser.walkExpression(fn.callee.object.body); if (fnParams)
} fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => {
); if (identifiers[idx]) {
if (fn.arguments) parser.walkExpressions(fn.arguments); fnRenames.set(param.name, identifiers[idx]);
} else if (fn || obj) { return false;
parser.walkExpression(fn || obj); }
} return true;
});
const dep = this.newDefineDependency( } else {
expr.range, identifiers = ["require", "exports", "module"];
array ? array.range : null, if (fnParams)
fn ? fn.range : null, fnParams = fnParams.slice(fnParamsOffset).filter((param, idx) => {
obj ? obj.range : null, if (identifiers[idx]) {
namedModule ? namedModule : null fnRenames.set(param.name, identifiers[idx]);
); return false;
dep.loc = expr.loc; }
if (namedModule) { return true;
dep.localModule = LocalModulesHelpers.addLocalModule( });
parser.state, }
namedModule let inTry;
); if (fn && isUnboundFunctionExpression(fn)) {
} inTry = parser.scope.inTry;
parser.state.current.addDependency(dep); parser.inScope(fnParams, () => {
return true; parser.scope.renames = fnRenames;
parser.scope.inTry = inTry;
if (fn.body.type === "BlockStatement")
parser.walkStatement(fn.body);
else parser.walkExpression(fn.body);
}); });
} else if (fn && isBoundFunctionExpression(fn)) {
inTry = parser.scope.inTry;
parser.inScope(
fn.callee.object.params.filter(
i => !["require", "module", "exports"].includes(i.name)
),
() => {
parser.scope.renames = fnRenames;
parser.scope.inTry = inTry;
if (fn.callee.object.body.type === "BlockStatement")
parser.walkStatement(fn.callee.object.body);
else parser.walkExpression(fn.callee.object.body);
}
);
if (fn.arguments) parser.walkExpressions(fn.arguments);
} else if (fn || obj) {
parser.walkExpression(fn || obj);
}
const dep = this.newDefineDependency(
expr.range,
array ? array.range : null,
fn ? fn.range : null,
obj ? obj.range : null,
namedModule ? namedModule : null
);
dep.loc = expr.loc;
if (namedModule) {
dep.localModule = LocalModulesHelpers.addLocalModule(
parser.state,
namedModule
);
}
parser.state.current.addDependency(dep);
return true;
}
newDefineDependency(...args) {
return new AMDDefineDependency(...args);
}
newRequireArrayDependency(...args) {
return new AMDRequireArrayDependency(...args);
}
newRequireItemDependency(...args) {
return new AMDRequireItemDependency(...args);
} }
} }
module.exports = AMDDefineDependencyParserPlugin; module.exports = AMDDefineDependencyParserPlugin;

View File

@ -46,8 +46,117 @@ class AMDRequireDependenciesBlockParserPlugin {
} }
apply(parser) { apply(parser) {
const options = this.options; this.parser = parser;
parser.hooks.call.for("require").tap(
"AMDRequireDependenciesBlockParserPlugin",
this.processCallRequire.bind(
Object.create(this, {
parser: { value: parser }
})
)
);
}
processArray(expr, param) {
const parser = this.parser;
if (param.isArray()) {
for (const p of param.items) {
const result = this.processItem(expr, p);
if (result === undefined) {
this.processContext(expr, p);
}
}
return true;
} else if (param.isConstArray()) {
const deps = [];
for (const request of param.array) {
let dep, localModule;
if (request === "require") {
dep = "__webpack_require__";
} else if (["exports", "module"].includes(request)) {
dep = request;
} else if (
(localModule = LocalModulesHelpers.getLocalModule(
parser.state,
request
))
) {
// eslint-disable-line no-cond-assign
dep = new LocalModuleDependency(localModule);
dep.loc = expr.loc;
parser.state.current.addDependency(dep);
} else {
dep = this.newRequireItemDependency(request);
dep.loc = expr.loc;
dep.optional = !!parser.scope.inTry;
parser.state.current.addDependency(dep);
}
deps.push(dep);
}
const dep = this.newRequireArrayDependency(deps, param.range);
dep.loc = expr.loc;
dep.optional = !!parser.scope.inTry;
parser.state.current.addDependency(dep);
return true;
}
}
processItem(expr, param) {
const parser = this.parser;
if (param.isConditional()) {
for (const p of param.options) {
const result = this.processItem(expr, p);
if (result === undefined) {
this.processContext(expr, p);
}
}
return true;
} else if (param.isString()) {
let dep, localModule;
if (param.string === "require") {
dep = new ConstDependency("__webpack_require__", param.string);
} else if (param.string === "module") {
dep = new ConstDependency(
parser.state.module.buildInfo.moduleArgument,
param.range
);
} else if (param.string === "exports") {
dep = new ConstDependency(
parser.state.module.buildInfo.exportsArgument,
param.range
);
} else if (
(localModule = LocalModulesHelpers.getLocalModule(
parser.state,
param.string
))
) {
// eslint-disable-line no-cond-assign
dep = new LocalModuleDependency(localModule, param.range);
} else {
dep = this.newRequireItemDependency(param.string, param.range);
}
dep.loc = expr.loc;
dep.optional = !!parser.scope.inTry;
parser.state.current.addDependency(dep);
return true;
}
}
processContext(expr, param) {
const dep = ContextDependencyHelpers.create(
AMDRequireContextDependency,
param.range,
param,
expr,
this.options
);
if (!dep) return;
dep.loc = expr.loc;
dep.optional = !!this.parser.scope.inTry;
this.parser.state.current.addDependency(dep);
return true;
}
processCallRequire(expr) {
const processArrayForRequestString = param => { const processArrayForRequestString = param => {
if (param.isArray()) { if (param.isArray()) {
const result = param.items.map(item => const result = param.items.map(item =>
@ -70,172 +179,82 @@ class AMDRequireDependenciesBlockParserPlugin {
} }
}; };
const processArray = (expr, param) => { let param;
if (param.isArray()) { let dep;
for (const p of param.items) { let result;
const result = processItem(expr, p);
if (result === undefined) { const parser = this.parser;
processContext(expr, p); const old = parser.state.current;
}
} if (expr.arguments.length >= 1) {
return true; param = parser.evaluateExpression(expr.arguments[0]);
} else if (param.isConstArray()) { dep = this.newRequireDependenciesBlock(
const deps = [];
for (const request of param.array) {
let dep, localModule;
if (request === "require") {
dep = "__webpack_require__";
} else if (["exports", "module"].includes(request)) {
dep = request;
} else if (
(localModule = LocalModulesHelpers.getLocalModule(
parser.state,
request
))
) {
// eslint-disable-line no-cond-assign
dep = new LocalModuleDependency(localModule);
dep.loc = expr.loc;
parser.state.current.addDependency(dep);
} else {
dep = new AMDRequireItemDependency(request);
dep.loc = expr.loc;
dep.optional = !!parser.scope.inTry;
parser.state.current.addDependency(dep);
}
deps.push(dep);
}
const dep = new AMDRequireArrayDependency(deps, param.range);
dep.loc = expr.loc;
dep.optional = !!parser.scope.inTry;
parser.state.current.addDependency(dep);
return true;
}
};
const processItem = (expr, param) => {
if (param.isConditional()) {
for (const p of param.options) {
const result = processItem(expr, p);
if (result === undefined) {
processContext(expr, p);
}
}
return true;
} else if (param.isString()) {
let dep, localModule;
if (param.string === "require") {
dep = new ConstDependency("__webpack_require__", param.string);
} else if (param.string === "module") {
dep = new ConstDependency(
parser.state.module.buildInfo.moduleArgument,
param.range
);
} else if (param.string === "exports") {
dep = new ConstDependency(
parser.state.module.buildInfo.exportsArgument,
param.range
);
} else if (
(localModule = LocalModulesHelpers.getLocalModule(
parser.state,
param.string
))
) {
// eslint-disable-line no-cond-assign
dep = new LocalModuleDependency(localModule, param.range);
} else {
dep = new AMDRequireItemDependency(param.string, param.range);
}
dep.loc = expr.loc;
dep.optional = !!parser.scope.inTry;
parser.state.current.addDependency(dep);
return true;
}
};
const processContext = (expr, param) => {
const dep = ContextDependencyHelpers.create(
AMDRequireContextDependency,
param.range,
param,
expr, expr,
options param.range,
expr.arguments.length > 1 ? expr.arguments[1].range : null,
expr.arguments.length > 2 ? expr.arguments[2].range : null,
parser.state.module,
expr.loc,
processArrayForRequestString(param)
); );
if (!dep) return; parser.state.current = dep;
dep.loc = expr.loc; }
dep.optional = !!parser.scope.inTry;
parser.state.current.addDependency(dep);
return true;
};
parser.hooks.call if (expr.arguments.length === 1) {
.for("require") parser.inScope([], () => {
.tap("AMDRequireDependenciesBlockParserPlugin", expr => { result = this.processArray(expr, param);
let param;
let dep;
let result;
const old = parser.state.current;
if (expr.arguments.length >= 1) {
param = parser.evaluateExpression(expr.arguments[0]);
dep = new AMDRequireDependenciesBlock(
expr,
param.range,
expr.arguments.length > 1 ? expr.arguments[1].range : null,
expr.arguments.length > 2 ? expr.arguments[2].range : null,
parser.state.module,
expr.loc,
processArrayForRequestString(param)
);
parser.state.current = dep;
}
if (expr.arguments.length === 1) {
parser.inScope([], () => {
result = processArray(expr, param);
});
parser.state.current = old;
if (!result) return;
parser.state.current.addBlock(dep);
return true;
}
if (expr.arguments.length === 2 || expr.arguments.length === 3) {
try {
parser.inScope([], () => {
result = processArray(expr, param);
});
if (!result) {
dep = new UnsupportedDependency("unsupported", expr.range);
old.addDependency(dep);
if (parser.state.module)
parser.state.module.errors.push(
new UnsupportedFeatureWarning(
parser.state.module,
"Cannot statically analyse 'require(..., ...)' in line " +
expr.loc.start.line
)
);
dep = null;
return true;
}
dep.functionBindThis = this.processFunctionArgument(
parser,
expr.arguments[1]
);
if (expr.arguments.length === 3) {
dep.errorCallbackBindThis = this.processFunctionArgument(
parser,
expr.arguments[2]
);
}
} finally {
parser.state.current = old;
if (dep) parser.state.current.addBlock(dep);
}
return true;
}
}); });
parser.state.current = old;
if (!result) return;
parser.state.current.addBlock(dep);
return true;
}
if (expr.arguments.length === 2 || expr.arguments.length === 3) {
try {
parser.inScope([], () => {
result = this.processArray(expr, param);
});
if (!result) {
dep = new UnsupportedDependency("unsupported", expr.range);
old.addDependency(dep);
if (parser.state.module)
parser.state.module.errors.push(
new UnsupportedFeatureWarning(
parser.state.module,
"Cannot statically analyse 'require(..., ...)' in line " +
expr.loc.start.line
)
);
dep = null;
return true;
}
dep.functionBindThis = this.processFunctionArgument(
parser,
expr.arguments[1]
);
if (expr.arguments.length === 3) {
dep.errorCallbackBindThis = this.processFunctionArgument(
parser,
expr.arguments[2]
);
}
} finally {
parser.state.current = old;
if (dep) parser.state.current.addBlock(dep);
}
return true;
}
}
newRequireDependenciesBlock(...args) {
return new AMDRequireDependenciesBlock(...args);
}
newRequireItemDependency(...args) {
return new AMDRequireItemDependency(...args);
}
newRequireArrayDependency(...args) {
return new AMDRequireArrayDependency(...args);
} }
} }
module.exports = AMDRequireDependenciesBlockParserPlugin; module.exports = AMDRequireDependenciesBlockParserPlugin;