forked from babel/babel
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request babel#7734 from nicolo-ribaudo/decorators-legacy-o…
…ption Decorators legacy option
- Loading branch information
Showing
25 changed files
with
447 additions
and
320 deletions.
There are no files selected for viewing
5 changes: 4 additions & 1 deletion
5
packages/babel-core/test/fixtures/transformation/misc/regression-4855/options.json
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,4 +1,7 @@ | ||
{ | ||
"compact": false, | ||
"presets": ["es2015","stage-2"] | ||
"presets": [ | ||
"es2015", | ||
["stage-2", { "decoratorsLegacy": true }] | ||
] | ||
} |
5 changes: 4 additions & 1 deletion
5
...ges/babel-plugin-proposal-class-properties/test/fixtures/public-loose/foobar/options.json
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,4 +1,7 @@ | ||
{ | ||
"plugins": ["external-helpers", ["proposal-class-properties", {"loose": true}]], | ||
"presets": ["stage-0", "es2015"] | ||
"presets": [ | ||
["stage-0", { "decoratorsLegacy": true }], | ||
"es2015" | ||
] | ||
} |
5 changes: 4 additions & 1 deletion
5
packages/babel-plugin-proposal-class-properties/test/fixtures/public/foobar/options.json
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,4 +1,7 @@ | ||
{ | ||
"plugins": ["external-helpers", "proposal-class-properties"], | ||
"presets": ["stage-0", "es2015"] | ||
"presets": [ | ||
["stage-0", { "decoratorsLegacy": true }], | ||
"es2015" | ||
] | ||
} |
5 changes: 4 additions & 1 deletion
5
packages/babel-plugin-proposal-class-properties/test/fixtures/regression/6154/options.json
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,6 @@ | ||
{ | ||
"presets": ["es2015", "stage-2"] | ||
"presets": [ | ||
"es2015", | ||
["stage-2", { "decoratorsLegacy": true }] | ||
] | ||
} |
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
275 changes: 12 additions & 263 deletions
275
packages/babel-plugin-proposal-decorators/src/index.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,278 +1,27 @@ | ||
// Fork of https://github.com/loganfsmyth/babel-plugin-proposal-decorators-legacy | ||
|
||
import { declare } from "@babel/helper-plugin-utils"; | ||
import syntaxDecorators from "@babel/plugin-syntax-decorators"; | ||
import { template, types as t } from "@babel/core"; | ||
|
||
const buildClassDecorator = template(` | ||
DECORATOR(CLASS_REF = INNER) || CLASS_REF; | ||
`); | ||
|
||
const buildClassPrototype = template(` | ||
CLASS_REF.prototype; | ||
`); | ||
import visitor from "./transformer"; | ||
import legacyVisitor from "./transformer-legacy"; | ||
|
||
const buildGetDescriptor = template(` | ||
Object.getOwnPropertyDescriptor(TARGET, PROPERTY); | ||
`); | ||
|
||
const buildGetObjectInitializer = template(` | ||
(TEMP = Object.getOwnPropertyDescriptor(TARGET, PROPERTY), (TEMP = TEMP ? TEMP.value : undefined), { | ||
enumerable: true, | ||
configurable: true, | ||
writable: true, | ||
initializer: function(){ | ||
return TEMP; | ||
} | ||
}) | ||
`); | ||
|
||
export default declare(api => { | ||
export default declare((api, options) => { | ||
api.assertVersion(7); | ||
|
||
const WARNING_CALLS = new WeakSet(); | ||
|
||
/** | ||
* If the decorator expressions are non-identifiers, hoist them to before the class so we can be sure | ||
* that they are evaluated in order. | ||
*/ | ||
function applyEnsureOrdering(path) { | ||
// TODO: This should probably also hoist computed properties. | ||
const decorators = (path.isClass() | ||
? [path].concat(path.get("body.body")) | ||
: path.get("properties") | ||
).reduce((acc, prop) => acc.concat(prop.node.decorators || []), []); | ||
|
||
const identDecorators = decorators.filter( | ||
decorator => !t.isIdentifier(decorator.callee), | ||
); | ||
if (identDecorators.length === 0) return; | ||
|
||
return t.sequenceExpression( | ||
identDecorators | ||
.map(decorator => { | ||
const callee = decorator.callee; | ||
const id = (decorator.callee = path.scope.generateDeclaredUidIdentifier( | ||
"dec", | ||
)); | ||
return t.assignmentExpression("=", id, callee); | ||
}) | ||
.concat([path.node]), | ||
); | ||
} | ||
|
||
/** | ||
* Given a class expression with class-level decorators, create a new expression | ||
* with the proper decorated behavior. | ||
*/ | ||
function applyClassDecorators(classPath) { | ||
if (!hasClassDecorators(classPath.node)) return; | ||
|
||
const decorators = classPath.node.decorators || []; | ||
classPath.node.decorators = null; | ||
|
||
const name = classPath.scope.generateDeclaredUidIdentifier("class"); | ||
|
||
return decorators | ||
.map(dec => dec.callee) | ||
.reverse() | ||
.reduce(function(acc, decorator) { | ||
return buildClassDecorator({ | ||
CLASS_REF: t.cloneNode(name), | ||
DECORATOR: t.cloneNode(decorator), | ||
INNER: acc, | ||
}).expression; | ||
}, classPath.node); | ||
} | ||
|
||
function hasClassDecorators(classNode) { | ||
return !!(classNode.decorators && classNode.decorators.length); | ||
} | ||
|
||
/** | ||
* Given a class expression with method-level decorators, create a new expression | ||
* with the proper decorated behavior. | ||
*/ | ||
function applyMethodDecorators(path, state) { | ||
if (!hasMethodDecorators(path.node.body.body)) return; | ||
|
||
return applyTargetDecorators(path, state, path.node.body.body); | ||
} | ||
|
||
function hasMethodDecorators(body) { | ||
return body.some(node => node.decorators && node.decorators.length); | ||
const { legacy = false } = options; | ||
if (typeof legacy !== "boolean") { | ||
throw new Error("'legacy' must be a boolean."); | ||
} | ||
|
||
/** | ||
* Given an object expression with property decorators, create a new expression | ||
* with the proper decorated behavior. | ||
*/ | ||
function applyObjectDecorators(path, state) { | ||
if (!hasMethodDecorators(path.node.properties)) return; | ||
|
||
return applyTargetDecorators(path, state, path.node.properties); | ||
} | ||
|
||
/** | ||
* A helper to pull out property decorators into a sequence expression. | ||
*/ | ||
function applyTargetDecorators(path, state, decoratedProps) { | ||
const name = path.scope.generateDeclaredUidIdentifier( | ||
path.isClass() ? "class" : "obj", | ||
if (legacy !== true) { | ||
throw new Error( | ||
"The new decorators proposal is not supported yet." + | ||
' You muse pass the `"legacy": true` option to' + | ||
" @babel/plugin-proposal-decorators", | ||
); | ||
|
||
const exprs = decoratedProps.reduce(function(acc, node) { | ||
const decorators = node.decorators || []; | ||
node.decorators = null; | ||
|
||
if (decorators.length === 0) return acc; | ||
|
||
if (node.computed) { | ||
throw path.buildCodeFrameError( | ||
"Computed method/property decorators are not yet supported.", | ||
); | ||
} | ||
|
||
const property = t.isLiteral(node.key) | ||
? node.key | ||
: t.stringLiteral(node.key.name); | ||
|
||
const target = | ||
path.isClass() && !node.static | ||
? buildClassPrototype({ | ||
CLASS_REF: name, | ||
}).expression | ||
: name; | ||
|
||
if (t.isClassProperty(node, { static: false })) { | ||
const descriptor = path.scope.generateDeclaredUidIdentifier( | ||
"descriptor", | ||
); | ||
|
||
const initializer = node.value | ||
? t.functionExpression( | ||
null, | ||
[], | ||
t.blockStatement([t.returnStatement(node.value)]), | ||
) | ||
: t.nullLiteral(); | ||
|
||
node.value = t.callExpression( | ||
state.addHelper("initializerWarningHelper"), | ||
[descriptor, t.thisExpression()], | ||
); | ||
|
||
WARNING_CALLS.add(node.value); | ||
|
||
acc = acc.concat([ | ||
t.assignmentExpression( | ||
"=", | ||
descriptor, | ||
t.callExpression(state.addHelper("applyDecoratedDescriptor"), [ | ||
t.cloneNode(target), | ||
t.cloneNode(property), | ||
t.arrayExpression(decorators.map(dec => t.cloneNode(dec.callee))), | ||
t.objectExpression([ | ||
t.objectProperty( | ||
t.identifier("enumerable"), | ||
t.booleanLiteral(true), | ||
), | ||
t.objectProperty(t.identifier("initializer"), initializer), | ||
]), | ||
]), | ||
), | ||
]); | ||
} else { | ||
acc = acc.concat( | ||
t.callExpression(state.addHelper("applyDecoratedDescriptor"), [ | ||
t.cloneNode(target), | ||
t.cloneNode(property), | ||
t.arrayExpression(decorators.map(dec => t.cloneNode(dec.callee))), | ||
t.isObjectProperty(node) || | ||
t.isClassProperty(node, { static: true }) | ||
? buildGetObjectInitializer({ | ||
TEMP: path.scope.generateDeclaredUidIdentifier("init"), | ||
TARGET: t.cloneNode(target), | ||
PROPERTY: t.cloneNode(property), | ||
}).expression | ||
: buildGetDescriptor({ | ||
TARGET: t.cloneNode(target), | ||
PROPERTY: t.cloneNode(property), | ||
}).expression, | ||
t.cloneNode(target), | ||
]), | ||
); | ||
} | ||
|
||
return acc; | ||
}, []); | ||
|
||
return t.sequenceExpression([ | ||
t.assignmentExpression("=", t.cloneNode(name), path.node), | ||
t.sequenceExpression(exprs), | ||
t.cloneNode(name), | ||
]); | ||
} | ||
|
||
return { | ||
inherits: syntaxDecorators, | ||
|
||
visitor: { | ||
ClassDeclaration(path) { | ||
const { node } = path; | ||
|
||
if (!hasClassDecorators(node) && !hasMethodDecorators(node.body.body)) { | ||
return; | ||
} | ||
|
||
const ref = node.id | ||
? t.cloneNode(node.id) | ||
: path.scope.generateUidIdentifier("class"); | ||
const letDeclaration = t.variableDeclaration("let", [ | ||
t.variableDeclarator(ref, t.toExpression(node)), | ||
]); | ||
|
||
if (path.parentPath.isExportDefaultDeclaration()) { | ||
// Split the class declaration and the export into two separate statements. | ||
path.parentPath.replaceWithMultiple([ | ||
letDeclaration, | ||
t.exportNamedDeclaration(null, [ | ||
t.exportSpecifier(t.cloneNode(ref), t.identifier("default")), | ||
]), | ||
]); | ||
} else { | ||
path.replaceWith(letDeclaration); | ||
} | ||
}, | ||
ClassExpression(path, state) { | ||
// Create a replacement for the class node if there is one. We do one pass to replace classes with | ||
// class decorators, and a second pass to process method decorators. | ||
const decoratedClass = | ||
applyEnsureOrdering(path) || | ||
applyClassDecorators(path, state) || | ||
applyMethodDecorators(path, state); | ||
|
||
if (decoratedClass) path.replaceWith(decoratedClass); | ||
}, | ||
ObjectExpression(path, state) { | ||
const decoratedObject = | ||
applyEnsureOrdering(path) || applyObjectDecorators(path, state); | ||
|
||
if (decoratedObject) path.replaceWith(decoratedObject); | ||
}, | ||
|
||
AssignmentExpression(path, state) { | ||
if (!WARNING_CALLS.has(path.node.right)) return; | ||
|
||
path.replaceWith( | ||
t.callExpression(state.addHelper("initializerDefineProperty"), [ | ||
t.cloneNode(path.get("left.object").node), | ||
t.stringLiteral(path.get("left.property").node.name), | ||
t.cloneNode(path.get("right.arguments")[0].node), | ||
t.cloneNode(path.get("right.arguments")[1].node), | ||
]), | ||
); | ||
}, | ||
}, | ||
visitor: legacy ? legacyVisitor : visitor, | ||
}; | ||
}); |
Oops, something went wrong.