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:
parent
2ef436641e
commit
ad284d5c36
40
src/index.js
40
src/index.js
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user