Convert each plugin to a function from a class to an overriding class (#459)

* Convert each plugin to a function from a class to an overriding class

* Handle undefined options

* Fix indentation

* Fix double space
This commit is contained in:
Andy 2017-04-21 04:53:51 -07:00 committed by Daniel Tschinder
parent 2ef436641e
commit ad284d5c36
5 changed files with 671 additions and 763 deletions

View File

@ -19,11 +19,11 @@ plugins.flow = flowPlugin;
plugins.jsx = jsxPlugin;
export function parse(input, options) {
return new Parser(options, input).parse();
return getParser(options, input).parse();
}
export function parseExpression(input, options) {
const parser = new Parser(options, input);
const parser = getParser(options, input);
if (parser.options.strictMode) {
parser.state.strict = true;
}
@ -32,3 +32,39 @@ export function parseExpression(input, options) {
export { tokTypes };
function getParser(options, input) {
const cls = options && options.plugins ? getParserClass(options.plugins) : Parser;
return new cls(options, input);
}
const parserClassCache = {};
/** Get a Parser class with plugins applied. */
function getParserClass(pluginsFromOptions) {
// Filter out just the plugins that have an actual mixin associated with them.
let pluginList = pluginsFromOptions.filter((p) => p === "estree" || p === "flow" || p === "jsx");
if (pluginList.indexOf("flow") >= 0) {
// ensure flow plugin loads last
pluginList = pluginList.filter((plugin) => plugin !== "flow");
pluginList.push("flow");
}
if (pluginList.indexOf("estree") >= 0) {
// ensure estree plugin loads first
pluginList = pluginList.filter((plugin) => plugin !== "estree");
pluginList.unshift("estree");
}
const key = pluginList.join("/");
let cls = parserClassCache[key];
if (!cls) {
cls = Parser;
for (const plugin of pluginList) {
cls = plugins[plugin](cls);
}
parserClassCache[key] = cls;
}
return cls;
}

View File

@ -12,7 +12,7 @@ export default class Parser extends Tokenizer {
this.options = options;
this.inModule = this.options.sourceType === "module";
this.input = input;
this.plugins = this.loadPlugins(this.options.plugins);
this.plugins = pluginsMap(this.options.plugins);
this.filename = options.sourceFilename;
// If enabled, skip leading hashbang line.
@ -33,37 +33,6 @@ export default class Parser extends Tokenizer {
return !!this.plugins[name];
}
extend(name: string, f: Function) {
this[name] = f(this[name]);
}
loadPlugins(pluginList: Array<string>): { [key: string]: boolean } {
const pluginMap = {};
if (pluginList.indexOf("flow") >= 0) {
// ensure flow plugin loads last
pluginList = pluginList.filter((plugin) => plugin !== "flow");
pluginList.push("flow");
}
if (pluginList.indexOf("estree") >= 0) {
// ensure estree plugin loads first
pluginList = pluginList.filter((plugin) => plugin !== "estree");
pluginList.unshift("estree");
}
for (const name of pluginList) {
if (!pluginMap[name]) {
pluginMap[name] = true;
const plugin = plugins[name];
if (plugin) plugin(this);
}
}
return pluginMap;
}
parse(): {
type: "File",
program: {
@ -77,3 +46,11 @@ export default class Parser extends Tokenizer {
return this.parseTopLevel(file, program);
}
}
function pluginsMap(pluginList: $ReadOnlyArray<string>): { [key: string]: boolean } {
const pluginMap = {};
for (const name of pluginList) {
pluginMap[name] = true;
}
return pluginMap;
}

View File

@ -43,206 +43,178 @@ function isSimpleProperty(node) {
node.method === false;
}
export default function (instance) {
instance.extend("checkDeclaration", function(inner) {
return function (node) {
if (isSimpleProperty(node)) {
this.checkDeclaration(node.value);
export default (superClass) => class extends superClass {
checkDeclaration(node) {
if (isSimpleProperty(node)) {
this.checkDeclaration(node.value);
} else {
super.checkDeclaration(node);
}
}
checkGetterSetterParamCount(prop) {
const paramCount = prop.kind === "get" ? 0 : 1;
if (prop.value.params.length !== paramCount) {
const start = prop.start;
if (prop.kind === "get") {
this.raise(start, "getter should have no params");
} else {
inner.call(this, node);
this.raise(start, "setter should have exactly one param");
}
};
});
}
}
instance.extend("checkGetterSetterParamCount", function() {
return function (prop) {
const paramCount = prop.kind === "get" ? 0 : 1;
if (prop.value.params.length !== paramCount) {
const start = prop.start;
if (prop.kind === "get") {
this.raise(start, "getter should have no params");
checkLVal(expr, isBinding, checkClashes, ...args) {
switch (expr.type) {
case "ObjectPattern":
expr.properties.forEach((prop) => {
this.checkLVal(
prop.type === "Property" ? prop.value : prop,
isBinding,
checkClashes,
"object destructuring pattern"
);
});
break;
default:
super.checkLVal(expr, isBinding, checkClashes, ...args);
}
}
checkPropClash(prop, propHash) {
if (prop.computed || !isSimpleProperty(prop)) return;
const key = prop.key;
// It is either an Identifier or a String/NumericLiteral
const name = key.type === "Identifier" ? key.name : String(key.value);
if (name === "__proto__") {
if (propHash.proto) this.raise(key.start, "Redefinition of __proto__ property");
propHash.proto = true;
}
}
isStrictBody(node, isExpression) {
if (!isExpression && node.body.body.length > 0) {
for (const directive of (node.body.body: Array<Object>)) {
if (directive.type === "ExpressionStatement" && directive.expression.type === "Literal") {
if (directive.expression.value === "use strict") return true;
} else {
this.raise(start, "setter should have exactly one param");
}
}
};
});
instance.extend("checkLVal", function(inner) {
return function (expr, isBinding, checkClashes, ...args) {
switch (expr.type) {
case "ObjectPattern":
expr.properties.forEach((prop) => {
this.checkLVal(
prop.type === "Property" ? prop.value : prop,
isBinding,
checkClashes,
"object destructuring pattern"
);
});
// Break for the first non literal expression
break;
default:
inner.call(this, expr, isBinding, checkClashes, ...args);
}
}
};
});
}
instance.extend("checkPropClash", function () {
return function (prop, propHash) {
if (prop.computed || !isSimpleProperty(prop)) return;
return false;
}
const key = prop.key;
// It is either an Identifier or a String/NumericLiteral
const name = key.type === "Identifier" ? key.name : String(key.value);
isValidDirective(stmt) {
return stmt.type === "ExpressionStatement" &&
stmt.expression.type === "Literal" &&
typeof stmt.expression.value === "string" &&
(!stmt.expression.extra || !stmt.expression.extra.parenthesized);
}
if (name === "__proto__") {
if (propHash.proto) this.raise(key.start, "Redefinition of __proto__ property");
propHash.proto = true;
}
};
});
parseBlockBody(node, ...args) {
super.parseBlockBody(node, ...args);
instance.extend("isStrictBody", function () {
return function (node, isExpression) {
if (!isExpression && node.body.body.length > 0) {
for (const directive of (node.body.body: Array<Object>)) {
if (directive.type === "ExpressionStatement" && directive.expression.type === "Literal") {
if (directive.expression.value === "use strict") return true;
} else {
// Break for the first non literal expression
break;
}
node.directives.reverse().forEach((directive) => {
node.body.unshift(this.directiveToStmt(directive));
});
delete node.directives;
}
parseClassMethod(classBody, ...args) {
super.parseClassMethod(classBody, ...args);
const body = classBody.body;
body[body.length - 1].type = "MethodDefinition";
}
parseExprAtom(...args) {
switch (this.state.type) {
case tt.regexp:
return this.estreeParseRegExpLiteral(this.state.value);
case tt.num:
case tt.string:
return this.estreeParseLiteral(this.state.value);
case tt._null:
return this.estreeParseLiteral(null);
case tt._true:
return this.estreeParseLiteral(true);
case tt._false:
return this.estreeParseLiteral(false);
default:
return super.parseExprAtom(...args);
}
}
parseLiteral(...args) {
const node = super.parseLiteral(...args);
node.raw = node.extra.raw;
delete node.extra;
return node;
}
parseMethod(node, ...args) {
let funcNode = this.startNode();
funcNode.kind = node.kind; // provide kind, so super method correctly sets state
funcNode = super.parseMethod(funcNode, ...args);
delete funcNode.kind;
node.value = this.finishNode(funcNode, "FunctionExpression");
return node;
}
parseObjectMethod(...args) {
const node = super.parseObjectMethod(...args);
if (node) {
if (node.kind === "method") node.kind = "init";
node.type = "Property";
}
return node;
}
parseObjectProperty(...args) {
const node = super.parseObjectProperty(...args);
if (node) {
node.kind = "init";
node.type = "Property";
}
return node;
}
toAssignable(node, isBinding, ...args) {
if (isSimpleProperty(node)) {
this.toAssignable(node.value, isBinding, ...args);
return node;
} else if (node.type === "ObjectExpression") {
node.type = "ObjectPattern";
for (const prop of (node.properties: Array<Object>)) {
if (prop.kind === "get" || prop.kind === "set") {
this.raise(prop.key.start, "Object pattern can't contain getter or setter");
} else if (prop.method) {
this.raise(prop.key.start, "Object pattern can't contain methods");
} else {
this.toAssignable(prop, isBinding, "object destructuring pattern");
}
}
return false;
};
});
instance.extend("isValidDirective", function () {
return function (stmt) {
return stmt.type === "ExpressionStatement" &&
stmt.expression.type === "Literal" &&
typeof stmt.expression.value === "string" &&
(!stmt.expression.extra || !stmt.expression.extra.parenthesized);
};
});
instance.extend("parseBlockBody", function (inner) {
return function (node, ...args) {
inner.call(this, node, ...args);
node.directives.reverse().forEach((directive) => {
node.body.unshift(this.directiveToStmt(directive));
});
delete node.directives;
};
});
instance.extend("parseClassMethod", function (inner) {
return function (classBody, ...args) {
inner.call(this, classBody, ...args);
const body = classBody.body;
body[body.length - 1].type = "MethodDefinition";
};
});
instance.extend("parseExprAtom", function(inner) {
return function (...args) {
switch (this.state.type) {
case tt.regexp:
return this.estreeParseRegExpLiteral(this.state.value);
case tt.num:
case tt.string:
return this.estreeParseLiteral(this.state.value);
case tt._null:
return this.estreeParseLiteral(null);
case tt._true:
return this.estreeParseLiteral(true);
case tt._false:
return this.estreeParseLiteral(false);
default:
return inner.call(this, ...args);
}
};
});
instance.extend("parseLiteral", function(inner) {
return function (...args) {
const node = inner.call(this, ...args);
node.raw = node.extra.raw;
delete node.extra;
return node;
};
});
}
instance.extend("parseMethod", function(inner) {
return function (node, ...args) {
let funcNode = this.startNode();
funcNode.kind = node.kind; // provide kind, so inner method correctly sets state
funcNode = inner.call(this, funcNode, ...args);
delete funcNode.kind;
node.value = this.finishNode(funcNode, "FunctionExpression");
return node;
};
});
instance.extend("parseObjectMethod", function(inner) {
return function (...args) {
const node = inner.call(this, ...args);
if (node) {
if (node.kind === "method") node.kind = "init";
node.type = "Property";
}
return node;
};
});
instance.extend("parseObjectProperty", function(inner) {
return function (...args) {
const node = inner.call(this, ...args);
if (node) {
node.kind = "init";
node.type = "Property";
}
return node;
};
});
instance.extend("toAssignable", function(inner) {
return function (node, isBinding, ...args) {
if (isSimpleProperty(node)) {
this.toAssignable(node.value, isBinding, ...args);
return node;
} else if (node.type === "ObjectExpression") {
node.type = "ObjectPattern";
for (const prop of (node.properties: Array<Object>)) {
if (prop.kind === "get" || prop.kind === "set") {
this.raise(prop.key.start, "Object pattern can't contain getter or setter");
} else if (prop.method) {
this.raise(prop.key.start, "Object pattern can't contain methods");
} else {
this.toAssignable(prop, isBinding, "object destructuring pattern");
}
}
return node;
}
return inner.call(this, node, isBinding, ...args);
};
});
}
return super.toAssignable(node, isBinding, ...args);
}
};

File diff suppressed because it is too large Load Diff

View File

@ -395,72 +395,66 @@ pp.jsxParseElement = function() {
return this.jsxParseElementAt(startPos, startLoc);
};
export default function(instance) {
instance.extend("parseExprAtom", function(inner) {
return function(refShortHandDefaultPos) {
if (this.match(tt.jsxText)) {
return this.parseLiteral(this.state.value, "JSXText");
} else if (this.match(tt.jsxTagStart)) {
return this.jsxParseElement();
} else {
return inner.call(this, refShortHandDefaultPos);
}
};
});
export default (superClass) => class extends superClass {
parseExprAtom(refShortHandDefaultPos) {
if (this.match(tt.jsxText)) {
return this.parseLiteral(this.state.value, "JSXText");
} else if (this.match(tt.jsxTagStart)) {
return this.jsxParseElement();
} else {
return super.parseExprAtom(refShortHandDefaultPos);
}
}
instance.extend("readToken", function(inner) {
return function(code) {
if (this.state.inPropertyName) return inner.call(this, code);
readToken(code) {
if (this.state.inPropertyName) return super.readToken(code);
const context = this.curContext();
const context = this.curContext();
if (context === tc.j_expr) {
return this.jsxReadToken();
if (context === tc.j_expr) {
return this.jsxReadToken();
}
if (context === tc.j_oTag || context === tc.j_cTag) {
if (isIdentifierStart(code)) {
return this.jsxReadWord();
}
if (context === tc.j_oTag || context === tc.j_cTag) {
if (isIdentifierStart(code)) {
return this.jsxReadWord();
}
if (code === 62) {
++this.state.pos;
return this.finishToken(tt.jsxTagEnd);
}
if ((code === 34 || code === 39) && context === tc.j_oTag) {
return this.jsxReadString(code);
}
}
if (code === 60 && this.state.exprAllowed) {
if (code === 62) {
++this.state.pos;
return this.finishToken(tt.jsxTagStart);
return this.finishToken(tt.jsxTagEnd);
}
return inner.call(this, code);
};
});
if ((code === 34 || code === 39) && context === tc.j_oTag) {
return this.jsxReadString(code);
}
}
instance.extend("updateContext", function(inner) {
return function(prevType) {
if (this.match(tt.braceL)) {
const curContext = this.curContext();
if (curContext === tc.j_oTag) {
this.state.context.push(tc.braceExpression);
} else if (curContext === tc.j_expr) {
this.state.context.push(tc.templateQuasi);
} else {
inner.call(this, prevType);
}
this.state.exprAllowed = true;
} else if (this.match(tt.slash) && prevType === tt.jsxTagStart) {
this.state.context.length -= 2; // do not consider JSX expr -> JSX open tag -> ... anymore
this.state.context.push(tc.j_cTag); // reconsider as closing tag context
this.state.exprAllowed = false;
if (code === 60 && this.state.exprAllowed) {
++this.state.pos;
return this.finishToken(tt.jsxTagStart);
}
return super.readToken(code);
}
updateContext(prevType) {
if (this.match(tt.braceL)) {
const curContext = this.curContext();
if (curContext === tc.j_oTag) {
this.state.context.push(tc.braceExpression);
} else if (curContext === tc.j_expr) {
this.state.context.push(tc.templateQuasi);
} else {
return inner.call(this, prevType);
super.updateContext(prevType);
}
};
});
}
this.state.exprAllowed = true;
} else if (this.match(tt.slash) && prevType === tt.jsxTagStart) {
this.state.context.length -= 2; // do not consider JSX expr -> JSX open tag -> ... anymore
this.state.context.push(tc.j_cTag); // reconsider as closing tag context
this.state.exprAllowed = false;
} else {
return super.updateContext(prevType);
}
}
};