-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for the new decorators proposal
- Loading branch information
1 parent
d9149aa
commit b304993
Showing
77 changed files
with
2,073 additions
and
28 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
213 changes: 211 additions & 2 deletions
213
packages/babel-plugin-proposal-decorators/src/transformer.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,212 @@ | ||
// Not implemented yet | ||
import { types as t, template } from "@babel/core"; | ||
import splitExportDeclaration from "@babel/helper-split-export-declaration"; | ||
import ReplaceSupers from "@babel/helper-replace-supers"; | ||
|
||
export default {}; | ||
function prop(key, value) { | ||
if (!value) return null; | ||
return t.objectProperty(t.identifier(key), value); | ||
} | ||
|
||
function value(body, params = []) { | ||
return t.objectMethod("method", t.identifier("value"), params, body); | ||
} | ||
|
||
function hasDecorators({ node }) { | ||
if (node.decorators && node.decorators.length > 0) return true; | ||
|
||
const body = node.body.body; | ||
for (let i = 0; i < body.length; i++) { | ||
const method = body[i]; | ||
if (method.decorators && method.decorators.length > 0) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
function extractDecorators({ node }) { | ||
let result; | ||
if (node.decorators && node.decorators.length > 0) { | ||
result = t.arrayExpression( | ||
node.decorators.map(decorator => decorator.expression), | ||
); | ||
} | ||
node.decorators = undefined; | ||
return result; | ||
} | ||
|
||
function getKey(node) { | ||
if (node.computed) { | ||
return node.key; | ||
} else { | ||
return t.stringLiteral(node.key.name || String(node.key.value)); | ||
} | ||
} | ||
|
||
function getSingleElementDefinition(path, superRef, classRef, file) { | ||
const { node, scope } = path; | ||
const isMethod = path.isClassMethod(); | ||
|
||
if (path.isPrivate()) { | ||
throw path.buildCodeFrameError( | ||
`Private ${ | ||
isMethod ? "methods" : "fields" | ||
} in decorated classes are not supported yet.`, | ||
); | ||
} | ||
|
||
new ReplaceSupers( | ||
{ | ||
methodPath: path, | ||
methodNode: node, | ||
objectRef: classRef, | ||
isStatic: node.static, | ||
superRef, | ||
scope, | ||
file, | ||
}, | ||
true, | ||
).replace(); | ||
|
||
const properties = [ | ||
prop("kind", t.stringLiteral(isMethod ? node.kind : "field")), | ||
prop("decorators", extractDecorators(path)), | ||
prop("static", node.static && t.booleanLiteral(true)), | ||
prop("key", getKey(node)), | ||
isMethod | ||
? value(node.body, node.params) | ||
: node.value | ||
? value(template.ast`{ return ${node.value} }`) | ||
: prop("value", scope.buildUndefinedNode()), | ||
].filter(Boolean); | ||
|
||
return t.objectExpression(properties); | ||
} | ||
|
||
function getElementsDefinitions(path, fId, file) { | ||
const superRef = path.node.superClass || t.identifier("Function"); | ||
|
||
const elements = []; | ||
for (const p of path.get("body.body")) { | ||
if (!p.isClassMethod({ kind: "constructor" })) { | ||
elements.push(getSingleElementDefinition(p, superRef, fId, file)); | ||
p.remove(); | ||
} | ||
} | ||
|
||
return t.arrayExpression(elements); | ||
} | ||
|
||
function getConstructorPath(path) { | ||
return path | ||
.get("body.body") | ||
.find(path => path.isClassMethod({ kind: "constructor" })); | ||
} | ||
|
||
const bareSupersVisitor = { | ||
CallExpression(path, { initializeInstanceElements }) { | ||
if (path.get("callee").isSuper()) { | ||
path.insertAfter(t.cloneNode(initializeInstanceElements)); | ||
} | ||
}, | ||
Function(path) { | ||
if (!path.isArrowFunctionExpression()) path.skip(); | ||
}, | ||
}; | ||
|
||
function insertInitializeInstanceElements(path, initializeInstanceId) { | ||
const isBase = !path.node.superClass; | ||
const initializeInstanceElements = t.callExpression(initializeInstanceId, [ | ||
t.thisExpression(), | ||
]); | ||
|
||
const constructorPath = getConstructorPath(path); | ||
if (constructorPath) { | ||
if (isBase) { | ||
constructorPath | ||
.get("body") | ||
.unshiftContainer("body", [ | ||
t.expressionStatement(initializeInstanceElements), | ||
]); | ||
} else { | ||
constructorPath.traverse(bareSupersVisitor, { | ||
initializeInstanceElements, | ||
}); | ||
} | ||
} else { | ||
const constructor = isBase | ||
? t.classMethod( | ||
"constructor", | ||
t.identifier("constructor"), | ||
[], | ||
t.blockStatement([t.expressionStatement(initializeInstanceElements)]), | ||
) | ||
: t.classMethod( | ||
"constructor", | ||
t.identifier("constructor"), | ||
[t.restElement(t.identifier("args"))], | ||
t.blockStatement([ | ||
t.expressionStatement( | ||
t.callExpression(t.Super(), [ | ||
t.spreadElement(t.identifier("args")), | ||
]), | ||
), | ||
t.expressionStatement(initializeInstanceElements), | ||
]), | ||
); | ||
path.node.body.body.push(constructor); | ||
} | ||
} | ||
|
||
function transformClass(path, file) { | ||
const isDeclaration = path.node.id && path.isDeclaration(); | ||
const isStrict = path.isInStrictMode(); | ||
const { superClass } = path.node; | ||
|
||
path.node.type = "ClassDeclaration"; | ||
if (!path.node.id) path.node.id = path.scope.generateUidIdentifier("class"); | ||
|
||
const initializeId = path.scope.generateUidIdentifier("initialize"); | ||
const superId = | ||
superClass && | ||
path.scope.generateUidIdentifierBasedOnNode(path.node.superClass, "super"); | ||
|
||
if (superClass) path.node.superClass = superId; | ||
|
||
const classDecorators = extractDecorators(path); | ||
const definitions = getElementsDefinitions(path, path.node.id, file); | ||
|
||
insertInitializeInstanceElements(path, initializeId); | ||
|
||
const expr = template.expression.ast` | ||
${file.addHelper("decorate")}( | ||
${classDecorators || t.nullLiteral()}, | ||
function (${initializeId}, ${superClass ? superId : null}) { | ||
${isStrict ? null : t.stringLiteral("use strict")} | ||
${path.node} | ||
return { F: ${t.cloneNode(path.node.id)}, d: ${definitions} }; | ||
}, | ||
${superClass} | ||
) | ||
`; | ||
|
||
return isDeclaration ? template.ast`let ${path.node.id} = ${expr}` : expr; | ||
} | ||
|
||
export default { | ||
ExportDefaultDeclaration(path) { | ||
let decl = path.get("declaration"); | ||
if (!decl.isClassDeclaration() || !hasDecorators(decl)) return; | ||
|
||
if (decl.node.id) decl = splitExportDeclaration(path); | ||
|
||
decl.replaceWith(transformClass(decl, this.file)); | ||
}, | ||
|
||
Class(path) { | ||
if (hasDecorators(path)) { | ||
path.replaceWith(transformClass(path, this.file)); | ||
} | ||
}, | ||
}; |
22 changes: 22 additions & 0 deletions
22
...s/babel-plugin-proposal-decorators/test/fixtures/duplicated-keys/coalesce-get-set/exec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
var el, el1; | ||
|
||
@(_ => el = _) | ||
class A { | ||
@(_ => el1 = _) | ||
get foo() { return 1; } | ||
|
||
set foo(x) { return 2; } | ||
} | ||
|
||
expect(el.elements).toHaveLength(1); | ||
|
||
expect(el1).toEqual(expect.objectContaining({ | ||
descriptor: expect.objectContaining({ | ||
get: expect.any(Function), | ||
set: expect.any(Function) | ||
}) | ||
})); | ||
|
||
var desc = Object.getOwnPropertyDescriptor(A.prototype, "foo"); | ||
expect(desc.get()).toBe(1); | ||
expect(desc.set()).toBe(2); |
28 changes: 28 additions & 0 deletions
28
...l-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-ast/exec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
var i = 0; | ||
|
||
function getKey() { | ||
return (i++).toString(); | ||
} | ||
|
||
var desc; | ||
|
||
@(_ => desc = _) | ||
class Foo { | ||
[getKey()]() { | ||
return 1; | ||
} | ||
|
||
[getKey()]() { | ||
return 2; | ||
} | ||
} | ||
|
||
expect(desc.elements).toHaveLength(2); | ||
|
||
expect(desc.elements[0].key).toBe("0"); | ||
expect(desc.elements[0].descriptor.value()).toBe(1); | ||
|
||
expect(desc.elements[1].key).toBe("1"); | ||
expect(desc.elements[1].descriptor.value()).toBe(2); | ||
|
||
expect(i).toBe(2); |
10 changes: 10 additions & 0 deletions
10
...-plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-ast/input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
@(_ => desc = _) | ||
class Foo { | ||
[getKey()]() { | ||
return 1; | ||
} | ||
|
||
[getKey()]() { | ||
return 2; | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
...plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-ast/output.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
let Foo = babelHelpers.decorate([_ => desc = _], function (_initialize) { | ||
"use strict"; | ||
|
||
class Foo { | ||
constructor() { | ||
_initialize(this); | ||
} | ||
|
||
} | ||
|
||
return { | ||
F: Foo, | ||
d: [{ | ||
kind: "method", | ||
key: getKey(), | ||
|
||
value() { | ||
return 1; | ||
} | ||
|
||
}, { | ||
kind: "method", | ||
key: getKey(), | ||
|
||
value() { | ||
return 2; | ||
} | ||
|
||
}] | ||
}; | ||
}); |
30 changes: 30 additions & 0 deletions
30
...plugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-value/exec.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
var i = 0; | ||
var j = 0; | ||
|
||
function getKeyI() { | ||
return (i++).toString(); | ||
} | ||
function getKeyJ() { | ||
return (j++).toString(); | ||
} | ||
|
||
var desc; | ||
|
||
@(_ => desc = _) | ||
class Foo { | ||
[getKeyI()]() { | ||
return 1; | ||
} | ||
|
||
[getKeyJ()]() { | ||
return 2; | ||
} | ||
} | ||
|
||
expect(desc.elements).toHaveLength(1); | ||
|
||
expect(desc.elements[0].key).toBe("0"); | ||
expect(desc.elements[0].descriptor.value()).toBe(2); | ||
|
||
expect(i).toBe(1); | ||
expect(j).toBe(1); |
10 changes: 10 additions & 0 deletions
10
...lugin-proposal-decorators/test/fixtures/duplicated-keys/computed-keys-same-value/input.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
@(_ => desc = _) | ||
class Foo { | ||
[getKeyI()]() { | ||
return 1; | ||
} | ||
|
||
[getKeyJ()]() { | ||
return 2; | ||
} | ||
} |
Oops, something went wrong.