Refactor how source type is handled by the parser

This commit is contained in:
Florent Cailhol 2018-01-21 22:35:30 +01:00
parent a2eada1d33
commit 86e6edf1ab
4 changed files with 66 additions and 70 deletions

View File

@ -11,13 +11,15 @@ class JavascriptModulesPlugin {
compiler.hooks.compilation.tap("JavascriptModulesPlugin", (compilation, {
normalModuleFactory
}) => {
const createParser = () => {
return new Parser();
};
normalModuleFactory.hooks.createParser.for("javascript/auto").tap("JavascriptModulesPlugin", createParser);
normalModuleFactory.hooks.createParser.for("javascript/dynamic").tap("JavascriptModulesPlugin", createParser);
normalModuleFactory.hooks.createParser.for("javascript/esm").tap("JavascriptModulesPlugin", createParser);
normalModuleFactory.hooks.createParser.for("javascript/auto").tap("JavascriptModulesPlugin", options => {
return new Parser(options, "auto");
});
normalModuleFactory.hooks.createParser.for("javascript/dynamic").tap("JavascriptModulesPlugin", options => {
return new Parser(options, "script");
});
normalModuleFactory.hooks.createParser.for("javascript/esm").tap("JavascriptModulesPlugin", options => {
return new Parser(options, "module");
});
});
}
}

View File

@ -88,7 +88,7 @@ class NormalModuleFactory extends Tapable {
this.ruleSet = new RuleSet(options.defaultRules.concat(options.rules));
this.cachePredicate = typeof options.unsafeCache === "function" ? options.unsafeCache : Boolean.bind(null, options.unsafeCache);
this.context = context || "";
this.parserCache = {};
this.parserCache = Object.create(null);
this.hooks.factory.tap("NormalModuleFactory", () => (result, callback) => {
let resolver = this.hooks.resolver.call(null);
@ -332,14 +332,13 @@ class NormalModuleFactory extends Tapable {
else
ident = JSON.stringify([type, parserOptions]);
}
const parser = this.parserCache[ident];
if(parser)
return parser;
if(ident in this.parserCache) {
return this.parserCache[ident];
}
return this.parserCache[ident] = this.createParser(type, parserOptions);
}
createParser(type, parserOptions) {
parserOptions = parserOptions || {};
createParser(type, parserOptions = {}) {
const parser = this.hooks.createParser.for(type).call(parserOptions);
if(!parser) {
throw new Error(`No parser registered for ${type}`);

View File

@ -21,28 +21,19 @@ const joinRanges = (startRange, endRange) => {
return [startRange[0], endRange[1]];
};
const ECMA_VERSION = 2017;
const POSSIBLE_AST_OPTIONS = [{
const defaultParserOptions = {
ranges: true,
locations: true,
ecmaVersion: ECMA_VERSION,
ecmaVersion: 2017,
sourceType: "module",
onComment: null,
plugins: {
dynamicImport: true
}
}, {
ranges: true,
locations: true,
ecmaVersion: ECMA_VERSION,
sourceType: "script",
plugins: {
dynamicImport: true
}
}];
};
class Parser extends Tapable {
constructor(options) {
constructor(options, sourceType = "auto") {
super();
this.hooks = {
evaluateTypeof: new HookMap(() => new SyncBailHook(["expression"])),
@ -116,6 +107,7 @@ class Parser extends Tapable {
}
});
this.options = options;
this.sourceType = sourceType;
this.scope = undefined;
this.state = undefined;
this.comments = undefined;
@ -1761,37 +1753,18 @@ class Parser extends Tapable {
parse(source, initialState) {
let ast;
let comments = [];
let comments;
if(typeof source === "object" && source !== null) {
ast = source;
comments = source.comments;
}
for(let i = 0, len = POSSIBLE_AST_OPTIONS.length; i < len; i++) {
if(!ast) {
try {
comments.length = 0;
POSSIBLE_AST_OPTIONS[i].onComment = comments;
ast = acorn.parse(source, POSSIBLE_AST_OPTIONS[i]);
} catch(e) {
// ignore the error
}
}
}
if(!ast) {
// for the error
ast = acorn.parse(source, {
ranges: true,
locations: true,
ecmaVersion: ECMA_VERSION,
sourceType: "module",
plugins: {
dynamicImport: true
},
} else {
comments = [];
ast = Parser.parse(source, {
sourceType: this.sourceType,
onComment: comments
});
}
if(!ast || typeof ast !== "object")
throw new Error("Source couldn't be parsed");
const oldScope = this.scope;
const oldState = this.state;
const oldComments = this.comments;
@ -1817,17 +1790,10 @@ class Parser extends Tapable {
}
evaluate(source) {
const ast = acorn.parse("(" + source + ")", {
ranges: true,
locations: true,
ecmaVersion: ECMA_VERSION,
sourceType: "module",
plugins: {
dynamicImport: true
}
const ast = Parser.parse("(" + source + ")", {
sourceType: this.sourceType,
locations: false,
});
if(!ast || typeof ast !== "object" || ast.type !== "Program")
throw new Error("evaluate: Source couldn't be parsed");
if(ast.body.length !== 1 || ast.body[0].type !== "ExpressionStatement")
throw new Error("evaluate: Source is not a expression");
return this.evaluateExpression(ast.body[0].expression);
@ -1885,8 +1851,41 @@ class Parser extends Tapable {
};
}
static parse(code, options) {
const type = options.sourceType;
const parserOptions = Object.assign(Object.create(null), defaultParserOptions, options);
if(type === "auto") {
parserOptions.sourceType = "module";
}
let ast;
let error;
let threw = false;
try {
ast = acorn.parse(code, parserOptions);
} catch(e) {
error = e;
threw = true;
}
if(threw && type === "auto") {
parserOptions.sourceType = "script";
if(Array.isArray(parserOptions.onComment)) {
parserOptions.onComment.length = 0;
}
try {
ast = acorn.parse(code, parserOptions);
threw = false;
} catch(e) {}
}
if(threw) {
throw error;
}
return ast;
}
}
Parser.ECMA_VERSION = ECMA_VERSION;
module.exports = Parser;

View File

@ -7,7 +7,6 @@
const Module = require("../Module");
const Template = require("../Template");
const Parser = require("../Parser");
const acorn = require("acorn");
const eslintScope = require("eslint-scope");
const ReplaceSource = require("webpack-sources").ReplaceSource;
const ConcatSource = require("webpack-sources").ConcatSource;
@ -483,11 +482,8 @@ class ConcatenatedModule extends Module {
const code = source.source();
let ast;
try {
ast = acorn.parse(code, {
ranges: true,
locations: true,
ecmaVersion: Parser.ECMA_VERSION,
sourceType: "module"
ast = Parser.parse(code, {
sourceType: "module",
});
} catch(err) {
if(err.loc && typeof err.loc === "object" && typeof err.loc.line === "number") {