add support for class decorators
This commit is contained in:
parent
3e26511fb2
commit
e04ecc79c7
@ -51,6 +51,8 @@ export function Parser(options, input, startPos) {
|
||||
// Labels in scope.
|
||||
this.labels = []
|
||||
|
||||
this.decorators = []
|
||||
|
||||
// If enabled, skip leading hashbang line.
|
||||
if (this.pos === 0 && this.options.allowHashBang && this.input.slice(0, 2) === '#!')
|
||||
this.skipLineComment(2)
|
||||
|
||||
@ -51,9 +51,23 @@ pp.parseStatement = function(declaration, topLevel) {
|
||||
case tt._function:
|
||||
if (!declaration && this.options.ecmaVersion >= 6) this.unexpected()
|
||||
return this.parseFunctionStatement(node)
|
||||
|
||||
case tt.at:
|
||||
while (this.type === tt.at) {
|
||||
this.decorators.push(this.parseDecorator());
|
||||
}
|
||||
if (this.type !== tt._class) {
|
||||
this.raise(this.start, "Leading decorators must be attached to a class declaration");
|
||||
}
|
||||
|
||||
case tt._class:
|
||||
if (!declaration) this.unexpected()
|
||||
if (this.decorators.length) {
|
||||
node.decorators = this.decorators
|
||||
this.decorators = []
|
||||
}
|
||||
return this.parseClass(node, true)
|
||||
|
||||
case tt._if: return this.parseIfStatement(node)
|
||||
case tt._return: return this.parseReturnStatement(node)
|
||||
case tt._switch: return this.parseSwitchStatement(node)
|
||||
@ -99,6 +113,13 @@ pp.parseStatement = function(declaration, topLevel) {
|
||||
}
|
||||
}
|
||||
|
||||
pp.parseDecorator = function() {
|
||||
let node = this.startNode()
|
||||
this.next()
|
||||
node.expression = this.parseMaybeAssign()
|
||||
return this.finishNode(node, "Decorator")
|
||||
}
|
||||
|
||||
pp.parseBreakContinueStatement = function(node, keyword) {
|
||||
let isBreak = keyword == "break"
|
||||
this.next()
|
||||
@ -423,52 +444,6 @@ pp.parseFunctionParams = function(node) {
|
||||
// Parse a class declaration or literal (depending on the
|
||||
// `isStatement` parameter).
|
||||
|
||||
pp.parseClass = function(node, isStatement) {
|
||||
this.next()
|
||||
node.id = this.type === tt.name ? this.parseIdent() : isStatement ? this.unexpected() : null
|
||||
node.superClass = this.eat(tt._extends) ? this.parseExprSubscripts() : null
|
||||
let classBody = this.startNode()
|
||||
classBody.body = []
|
||||
this.expect(tt.braceL)
|
||||
while (!this.eat(tt.braceR)) {
|
||||
if (this.eat(tt.semi)) continue
|
||||
let method = this.startNode()
|
||||
let isGenerator = this.eat(tt.star)
|
||||
this.parsePropertyName(method)
|
||||
if (this.type !== tt.parenL && !method.computed && method.key.type === "Identifier" &&
|
||||
method.key.name === "static") {
|
||||
if (isGenerator) this.unexpected()
|
||||
method['static'] = true
|
||||
isGenerator = this.eat(tt.star)
|
||||
this.parsePropertyName(method)
|
||||
} else {
|
||||
method['static'] = false
|
||||
}
|
||||
if (this.options.features["es7.asyncFunctions"] && this.type !== tt.parenL &&
|
||||
!method.computed && method.key.type === "Identifier" && method.key.name === "async") {
|
||||
isAsync = true;
|
||||
this.parsePropertyName(method);
|
||||
}
|
||||
method.kind = "method"
|
||||
if (!method.computed && !isGenerator) {
|
||||
if (method.key.type === "Identifier") {
|
||||
if (this.type !== tt.parenL && (method.key.name === "get" || method.key.name === "set")) {
|
||||
method.kind = method.key.name
|
||||
this.parsePropertyName(method)
|
||||
} else if (!method['static'] && method.key.name === "constructor") {
|
||||
method.kind = "constructor"
|
||||
}
|
||||
} else if (!method['static'] && method.key.type === "Literal" && method.key.value === "constructor") {
|
||||
method.kind = "constructor"
|
||||
}
|
||||
}
|
||||
method.value = this.parseMethod(isGenerator)
|
||||
classBody.body.push(this.finishNode(method, "MethodDefinition"))
|
||||
}
|
||||
node.body = this.finishNode(classBody, "ClassBody")
|
||||
return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression")
|
||||
}
|
||||
|
||||
pp.parseClass = function(node, isStatement) {
|
||||
this.next()
|
||||
this.parseClassId(node, isStatement)
|
||||
@ -476,8 +451,13 @@ pp.parseClass = function(node, isStatement) {
|
||||
var classBody = this.startNode()
|
||||
classBody.body = []
|
||||
this.expect(tt.braceL)
|
||||
var decorators = []
|
||||
while (!this.eat(tt.braceR)) {
|
||||
if (this.eat(tt.semi)) continue
|
||||
if (this.options.features["es7.decorators"] && this.type === tt.at) {
|
||||
decorators.push(this.parseDecorator());
|
||||
continue;
|
||||
}
|
||||
var method = this.startNode()
|
||||
var isGenerator = this.eat(tt.star), isAsync = false
|
||||
this.parsePropertyName(method)
|
||||
@ -508,8 +488,15 @@ pp.parseClass = function(node, isStatement) {
|
||||
method.kind = "constructor"
|
||||
}
|
||||
}
|
||||
if (this.options.features["es7.decorators"] && decorators.length) {
|
||||
method.decorators = decorators
|
||||
decorators = []
|
||||
}
|
||||
this.parseClassMethod(classBody, method, isGenerator, isAsync)
|
||||
}
|
||||
if (decorators.length) {
|
||||
raise(this.start, "You have trailing decorators with no method");
|
||||
}
|
||||
node.body = this.finishNode(classBody, "ClassBody")
|
||||
return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression")
|
||||
}
|
||||
@ -540,11 +527,11 @@ pp.parseExport = function(node) {
|
||||
}
|
||||
if (this.eat(tt._default)) { // export default ...
|
||||
let expr = this.parseMaybeAssign()
|
||||
let needsSemi = false
|
||||
let needsSemi = true
|
||||
if (expr.id) switch (expr.type) {
|
||||
case "FunctionExpression": expr.type = "FunctionDeclaration"; break
|
||||
case "ClassExpression": expr.type = "ClassDeclaration"; break
|
||||
default: needsSemi = true
|
||||
default: needsSemi = false
|
||||
}
|
||||
node.declaration = expr
|
||||
if (needsSemi) this.semicolon()
|
||||
|
||||
@ -322,6 +322,7 @@ pp.getTokenFromCode = function(code) {
|
||||
case 125: ++this.pos; return this.finishToken(tt.braceR)
|
||||
case 58: ++this.pos; return this.finishToken(tt.colon)
|
||||
case 63: ++this.pos; return this.finishToken(tt.question)
|
||||
case 64: ++this.pos; return this.finishToken(tt.at)
|
||||
|
||||
case 96: // '`'
|
||||
if (this.options.ecmaVersion < 6) break
|
||||
|
||||
@ -61,6 +61,7 @@ export const types = {
|
||||
ellipsis: new TokenType("...", beforeExpr),
|
||||
backQuote: new TokenType("`", startsExpr),
|
||||
dollarBraceL: new TokenType("${", {beforeExpr: true, startsExpr: true}),
|
||||
at: new TokenType("@"),
|
||||
|
||||
// Operators. These carry several kinds of properties to help the
|
||||
// parser use them properly (the presence of these properties is
|
||||
|
||||
@ -2056,3 +2056,5 @@ test('export async function foo(){}', {
|
||||
sourceType: "module",
|
||||
features: { "es7.asyncFunctions": true }
|
||||
});
|
||||
|
||||
// ES7 decorators
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
export function ClassDeclaration(node, print) {
|
||||
if (node.decorators && node.decorators.length) {
|
||||
print.list(node.decorators);
|
||||
}
|
||||
|
||||
this.push("class");
|
||||
|
||||
if (node.id) {
|
||||
@ -41,6 +45,10 @@ export function ClassBody(node, print) {
|
||||
}
|
||||
|
||||
export function MethodDefinition(node, print) {
|
||||
if (node.decorators && node.decorators.length) {
|
||||
print.list(node.decorators);
|
||||
}
|
||||
|
||||
if (node.static) {
|
||||
this.push("static ");
|
||||
}
|
||||
|
||||
@ -61,6 +61,11 @@ export function Super() {
|
||||
this.push("super");
|
||||
}
|
||||
|
||||
export function Decorator(node, print) {
|
||||
this.push("@");
|
||||
print(node.expression);
|
||||
}
|
||||
|
||||
export function CallExpression(node, print) {
|
||||
print(node.callee);
|
||||
|
||||
|
||||
@ -57,7 +57,7 @@ export default class File {
|
||||
"inherits",
|
||||
"defaults",
|
||||
"create-class",
|
||||
"create-computed-class",
|
||||
"create-decorated-class",
|
||||
"apply-constructor",
|
||||
"tagged-template-literal",
|
||||
"tagged-template-literal-loose",
|
||||
|
||||
1
src/babel/transformation/templates/class-decorator.js
Normal file
1
src/babel/transformation/templates/class-decorator.js
Normal file
@ -0,0 +1 @@
|
||||
CLASS_REF = DECORATOR(CLASS_REF) || CLASS_REF;
|
||||
@ -1,11 +1,11 @@
|
||||
(function() {
|
||||
function defineProperties(target, props) {
|
||||
for (var key in props) {
|
||||
var prop = props[key];
|
||||
for (var i = 0; i < props.length; i ++) {
|
||||
var prop = props[i];
|
||||
prop.configurable = true;
|
||||
if (prop.value) prop.writable = true;
|
||||
Object.defineProperty(target, prop.key, prop);
|
||||
}
|
||||
Object.defineProperties(target, props);
|
||||
}
|
||||
|
||||
return function (Constructor, protoProps, staticProps) {
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
(function() {
|
||||
function defineProperties(target, props) {
|
||||
for (var i = 0; i < props.length; i ++) {
|
||||
var prop = props[i];
|
||||
prop.configurable = true;
|
||||
if (prop.value) prop.writable = true;
|
||||
Object.defineProperty(target, prop.key, prop);
|
||||
}
|
||||
}
|
||||
|
||||
return function (Constructor, protoProps, staticProps) {
|
||||
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
||||
if (staticProps) defineProperties(Constructor, staticProps);
|
||||
return Constructor;
|
||||
};
|
||||
})()
|
||||
30
src/babel/transformation/templates/create-decorated-class.js
Normal file
30
src/babel/transformation/templates/create-decorated-class.js
Normal file
@ -0,0 +1,30 @@
|
||||
(function() {
|
||||
function defineProperties(target, props) {
|
||||
for (var i = 0; i < props.length; i ++) {
|
||||
var descriptor = props[i];
|
||||
|
||||
descriptor.enumerable = false;
|
||||
descriptor.configurable = true;
|
||||
if (descriptor.value) descriptor.writable = true;
|
||||
|
||||
if (descriptor.decorators) {
|
||||
for (var i = 0; i < descriptor.decorators.length; i++) {
|
||||
var decorator = descriptor.decorators[i];
|
||||
if (typeof decorator === "function") {
|
||||
descriptor = decorator(target, descriptor.key, descriptor) || descriptor;
|
||||
} else {
|
||||
throw new TypeError("The decorator for method " + descriptor.key + " is of the invalid type " + typeof decorator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(target, descriptor.key, descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
return function (Constructor, protoProps, staticProps) {
|
||||
if (protoProps) defineProperties(Constructor.prototype, protoProps);
|
||||
if (staticProps) defineProperties(Constructor, staticProps);
|
||||
return Constructor;
|
||||
};
|
||||
})()
|
||||
@ -67,6 +67,7 @@ class ClassTransformer {
|
||||
|
||||
this.hasInstanceMutators = false;
|
||||
this.hasStaticMutators = false;
|
||||
this.hasDecorators = false;
|
||||
|
||||
this.instanceMutatorMap = {};
|
||||
this.staticMutatorMap = {};
|
||||
@ -139,6 +140,16 @@ class ClassTransformer {
|
||||
|
||||
this.buildBody();
|
||||
|
||||
var decorators = this.node.decorators;
|
||||
if (decorators) {
|
||||
for (var i = 0; i < decorators.length; i++) {
|
||||
var decorator = decorators[i];
|
||||
body.push(util.template("class-decorator", {
|
||||
DECORATOR: decorator.expression,
|
||||
CLASS_REF: classRef
|
||||
}, true));
|
||||
}
|
||||
}
|
||||
|
||||
if (this.className) {
|
||||
// named class with only a constructor
|
||||
@ -218,6 +229,7 @@ class ClassTransformer {
|
||||
var instanceProps;
|
||||
var staticProps;
|
||||
var classHelper = "create-class";
|
||||
if (this.hasDecorators) classHelper = "create-decorated-class";
|
||||
|
||||
if (this.hasInstanceMutators) {
|
||||
instanceProps = defineMap.toClassObject(this.instanceMutatorMap);
|
||||
@ -228,11 +240,8 @@ class ClassTransformer {
|
||||
}
|
||||
|
||||
if (instanceProps || staticProps) {
|
||||
if (defineMap.hasComputed(this.instanceMutatorMap) || defineMap.hasComputed(this.staticMutatorMap)) {
|
||||
if (instanceProps) instanceProps = defineMap.toComputedObjectFromClass(instanceProps);
|
||||
if (staticProps) staticProps = defineMap.toComputedObjectFromClass(staticProps);
|
||||
classHelper = "create-computed-class";
|
||||
}
|
||||
if (instanceProps) instanceProps = defineMap.toComputedObjectFromClass(instanceProps);
|
||||
if (staticProps) staticProps = defineMap.toComputedObjectFromClass(staticProps);
|
||||
|
||||
instanceProps ||= t.literal(null);
|
||||
|
||||
@ -300,6 +309,15 @@ class ClassTransformer {
|
||||
}
|
||||
|
||||
defineMap.push(mutatorMap, methodName, kind, node.computed, node);
|
||||
|
||||
var decorators = node.decorators;
|
||||
if (decorators && decorators.length) {
|
||||
for (var i = 0; i < decorators.length; i++) {
|
||||
decorators[i] = decorators[i].expression;
|
||||
}
|
||||
defineMap.push(mutatorMap, methodName, "decorators", node.computed, t.arrayExpression(decorators));
|
||||
this.hasDecorators = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
4
src/babel/transformation/transformers/es7/decorators.js
Normal file
4
src/babel/transformation/transformers/es7/decorators.js
Normal file
@ -0,0 +1,4 @@
|
||||
export var metadata = {
|
||||
experimental: true,
|
||||
optional: true
|
||||
};
|
||||
@ -1,5 +1,6 @@
|
||||
export default {
|
||||
"es7.asyncFunctions": require("./es7/async-functions"),
|
||||
"es7.decorators": require("./es7/decorators"),
|
||||
|
||||
strict: require("./other/strict"),
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
"ComprehensionExpression": ["filter", "blocks", "body"],
|
||||
"ConditionalExpression": ["test", "consequent", "alternate"],
|
||||
"ContinueStatement": ["label"],
|
||||
"Decorator": ["expression"],
|
||||
"DebuggerStatement": [],
|
||||
"DoWhileStatement": ["body", "test"],
|
||||
"DoExpression": ["body"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user