New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Transform destructuring private #14304
Changes from all commits
284bcc1
32ec061
e4e90e5
36b6ae1
98dbb05
cfc8c50
65575c2
9af24c7
291987a
14cd6df
f1bbd3b
a0ca19b
9df6e7c
1577980
701ff4c
60f681f
a607850
66261a1
828a9f0
f8c059e
14d0309
73447bd
3a5848c
d2b6815
b5768c1
39a26f1
acbd8a6
d3da6fb
993febb
eeb77b6
c8d22de
2a7b8bc
603192c
ce7d90d
e57ff74
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
src | ||
test | ||
*.log |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# @babel/plugin-proposal-destructuring-private | ||
|
||
> Transform destructuring private proposal | ||
See our website [@babel/plugin-proposal-destructuring-private](https://babeljs.io/docs/en/babel-plugin-proposal-destructuring-private) for more information. | ||
|
||
## Install | ||
|
||
Using npm: | ||
|
||
```sh | ||
npm install --save-dev @babel/plugin-proposal-destructuring-private | ||
``` | ||
|
||
or using yarn: | ||
|
||
```sh | ||
yarn add @babel/plugin-proposal-destructuring-private --dev | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
{ | ||
"name": "@babel/plugin-proposal-destructuring-private", | ||
"version": "0.0.0", | ||
"description": "Transform destructuring private proposal", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/babel/babel.git", | ||
"directory": "packages/babel-plugin-proposal-destructuring-private" | ||
}, | ||
"license": "MIT", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"main": "./lib/index.js", | ||
"keywords": [ | ||
"babel-plugin" | ||
], | ||
"dependencies": { | ||
"@babel/helper-plugin-utils": "workspace:^", | ||
"@babel/plugin-syntax-destructuring-private": "workspace:^", | ||
"@babel/plugin-transform-destructuring": "workspace:^", | ||
"@babel/plugin-transform-parameters": "workspace:^" | ||
}, | ||
"peerDependencies": { | ||
"@babel/core": "^7.17.0" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "workspace:^", | ||
"@babel/helper-plugin-test-runner": "workspace:^", | ||
"@babel/traverse": "workspace:^", | ||
"@babel/types": "workspace:^" | ||
}, | ||
"homepage": "https://babel.dev/docs/en/next/babel-plugin-proposal-destructuring-private", | ||
"engines": { | ||
"node": ">=6.9.0" | ||
}, | ||
"author": "The Babel Team (https://babel.dev/team)", | ||
"exports": { | ||
".": "./lib/index.js", | ||
"./package.json": "./package.json" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
@@ -0,0 +1,214 @@ | ||||
import { declare } from "@babel/helper-plugin-utils"; | ||||
import syntaxDestructuringPrivate from "@babel/plugin-syntax-destructuring-private"; | ||||
import { | ||||
hasPrivateKeys, | ||||
hasPrivateClassElement, | ||||
transformPrivateKeyDestructuring, | ||||
buildVariableDeclarationFromParams, | ||||
} from "./util"; | ||||
import { convertFunctionParams } from "@babel/plugin-transform-parameters"; | ||||
import { unshiftForXStatementBody } from "@babel/plugin-transform-destructuring"; | ||||
|
||||
import type { PluginPass } from "@babel/core"; | ||||
import type { Visitor } from "@babel/traverse"; | ||||
|
||||
export default declare(function ({ assertVersion, assumption, types: t }) { | ||||
assertVersion("^7.17.0"); | ||||
const { | ||||
assignmentExpression, | ||||
assignmentPattern, | ||||
cloneNode, | ||||
expressionStatement, | ||||
isExpressionStatement, | ||||
isIdentifier, | ||||
isSequenceExpression, | ||||
sequenceExpression, | ||||
variableDeclaration, | ||||
variableDeclarator, | ||||
} = t; | ||||
|
||||
const ignoreFunctionLength = assumption("ignoreFunctionLength"); | ||||
const objectRestNoSymbols = assumption("objectRestNoSymbols"); | ||||
|
||||
const privateKeyDestructuringVisitor: Visitor<PluginPass> = { | ||||
Function(path) { | ||||
// (b, { #x: x } = I) => body | ||||
// transforms to: | ||||
// (b, p1) => { var { #x: x } = p1 === undefined ? I : p1; body; } | ||||
const firstPrivateIndex = path.node.params.findIndex(param => | ||||
hasPrivateKeys(param), | ||||
); | ||||
if (firstPrivateIndex === -1) return; | ||||
// wrap function body within IIFE if any param is shadowed | ||||
convertFunctionParams(path, ignoreFunctionLength, () => false, false); | ||||
// invariant: path.body is always a BlockStatement after `convertFunctionParams` | ||||
const { node, scope } = path; | ||||
const { params } = node; | ||||
const firstAssignmentPatternIndex = ignoreFunctionLength | ||||
? -1 | ||||
: params.findIndex(param => param.type === "AssignmentPattern"); | ||||
const paramsAfterIndex = params.splice(firstPrivateIndex); | ||||
const { params: transformedParams, variableDeclaration } = | ||||
buildVariableDeclarationFromParams(paramsAfterIndex, scope); | ||||
|
||||
path.get("body").unshiftContainer("body", variableDeclaration); | ||||
params.push(...transformedParams); | ||||
// preserve function.length | ||||
// (b, p1) => {} | ||||
// transforms to | ||||
// (b, p1 = void 0) => {} | ||||
if (firstAssignmentPatternIndex >= firstPrivateIndex) { | ||||
params[firstAssignmentPatternIndex] = assignmentPattern( | ||||
// @ts-ignore The transformed assignment pattern must not be a RestElement | ||||
params[firstAssignmentPatternIndex], | ||||
scope.buildUndefinedNode(), | ||||
); | ||||
} | ||||
scope.crawl(); | ||||
// the pattern will be handled by VariableDeclaration visitor. | ||||
}, | ||||
CatchClause(path) { | ||||
// catch({ #x: x }) { body } | ||||
// transforms to: | ||||
// catch(_e) { var {#x: x } = _e; body } | ||||
const { node, scope } = path; | ||||
if (!hasPrivateKeys(node.param)) return; | ||||
// todo: handle shadowed param as we did in convertFunctionParams | ||||
const ref = scope.generateUidIdentifier("e"); | ||||
path | ||||
.get("body") | ||||
.unshiftContainer( | ||||
"body", | ||||
variableDeclaration("let", [variableDeclarator(node.param, ref)]), | ||||
); | ||||
node.param = cloneNode(ref); | ||||
scope.crawl(); | ||||
// the pattern will be handled by VariableDeclaration visitor. | ||||
JLHwung marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
}, | ||||
ForXStatement(path) { | ||||
const { node, scope } = path; | ||||
const leftPath = path.get("left"); | ||||
if (leftPath.isVariableDeclaration()) { | ||||
const left = leftPath.node; | ||||
if (!hasPrivateKeys(left.declarations[0].id)) return; | ||||
// for (const { #x: x } of cls) body; | ||||
// transforms to: | ||||
// for (const ref of cls) { const { #x: x } = ref; body; } | ||||
// todo: the transform here assumes that any expression within | ||||
// the destructuring pattern (`{ #x: x }`), when evluated, do not interfere | ||||
// with the iterator of cls. Otherwise we have to pause the iterator and | ||||
// interleave the expressions. | ||||
// See also https://gist.github.com/nicolo-ribaudo/f8ac7916f89450f2ead77d99855b2098 | ||||
const temp = scope.generateUidIdentifier("ref"); | ||||
node.left = variableDeclaration(left.kind, [ | ||||
variableDeclarator(temp, null), | ||||
]); | ||||
left.declarations[0].init = cloneNode(temp); | ||||
unshiftForXStatementBody(path, [left]); | ||||
scope.crawl(); | ||||
// the pattern will be handled by VariableDeclaration visitor. | ||||
} else if (leftPath.isPattern()) { | ||||
if (!hasPrivateKeys(leftPath.node)) return; | ||||
// for ({ #x: x } of cls); | ||||
// transforms to: | ||||
// for (const ref of cls) { ({ #x: x } = ref); body; } | ||||
// This transform assumes that any expression within the pattern | ||||
// does not interfere with the iterable `cls`. | ||||
const temp = scope.generateUidIdentifier("ref"); | ||||
node.left = variableDeclaration("const", [ | ||||
variableDeclarator(temp, null), | ||||
]); | ||||
const assignExpr = expressionStatement( | ||||
assignmentExpression("=", leftPath.node, cloneNode(temp)), | ||||
); | ||||
unshiftForXStatementBody(path, [assignExpr]); | ||||
scope.crawl(); | ||||
} | ||||
}, | ||||
VariableDeclaration(path, state) { | ||||
const { scope, node } = path; | ||||
const { declarations } = node; | ||||
if (!declarations.some(declarator => hasPrivateKeys(declarator.id))) { | ||||
return; | ||||
} | ||||
const newDeclarations = []; | ||||
for (const declarator of declarations) { | ||||
for (const { left, right } of transformPrivateKeyDestructuring( | ||||
// @ts-expect-error The id of a variable declarator must not be a RestElement | ||||
declarator.id, | ||||
declarator.init, | ||||
scope, | ||||
/* isAssignment */ false, | ||||
/* shouldPreserveCompletion */ false, | ||||
name => state.addHelper(name), | ||||
objectRestNoSymbols, | ||||
/* useBuiltIns */ true, | ||||
)) { | ||||
newDeclarations.push(variableDeclarator(left, right)); | ||||
} | ||||
} | ||||
node.declarations = newDeclarations; | ||||
scope.crawl(); | ||||
}, | ||||
|
||||
AssignmentExpression(path, state) { | ||||
const { node, scope, parent } = path; | ||||
if (!hasPrivateKeys(node.left)) return; | ||||
const assignments = []; | ||||
const shouldPreserveCompletion = | ||||
(!isExpressionStatement(parent) && !isSequenceExpression(parent)) || | ||||
path.isCompletionRecord(); | ||||
Comment on lines
+158
to
+160
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we exclude There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is copied from
|
||||
for (const { left, right } of transformPrivateKeyDestructuring( | ||||
// @ts-expect-error The left of an assignment expression must not be a RestElement | ||||
node.left, | ||||
node.right, | ||||
scope, | ||||
/* isAssignment */ true, | ||||
shouldPreserveCompletion, | ||||
name => state.addHelper(name), | ||||
objectRestNoSymbols, | ||||
/* useBuiltIns */ true, | ||||
)) { | ||||
assignments.push(assignmentExpression("=", left, right)); | ||||
} | ||||
// preserve completion record | ||||
if (shouldPreserveCompletion) { | ||||
const { left, right } = assignments[0]; | ||||
// If node.right is right and left is an identifier, then the left is an effectively-constant memoised id | ||||
if (isIdentifier(left) && right === node.right) { | ||||
if ( | ||||
!isIdentifier(assignments[assignments.length - 1].right, { | ||||
name: left.name, | ||||
}) | ||||
) { | ||||
// If the last assignment does not end with left, then we push `left` as the completion value | ||||
assignments.push(cloneNode(left)); | ||||
} | ||||
// do nothing as `left` is already at the end of assignments | ||||
} else { | ||||
const tempId = scope.generateDeclaredUidIdentifier("m"); | ||||
assignments.unshift( | ||||
assignmentExpression("=", tempId, cloneNode(node.right)), | ||||
); | ||||
assignments.push(cloneNode(tempId)); | ||||
} | ||||
} | ||||
|
||||
path.replaceWith(sequenceExpression(assignments)); | ||||
scope.crawl(); | ||||
}, | ||||
}; | ||||
|
||||
const visitor: Visitor<PluginPass> = { | ||||
Class(path, state) { | ||||
if (!hasPrivateClassElement(path.node.body)) return; | ||||
path.traverse(privateKeyDestructuringVisitor, state); | ||||
}, | ||||
}; | ||||
|
||||
return { | ||||
name: "proposal-destructuring-private", | ||||
inherits: syntaxDestructuringPrivate, | ||||
visitor: visitor, | ||||
}; | ||||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Since this is the only real use for
firstAssignmentPatternIndex
, wouldn't it be clearer to check!ignoreFunctionLength && …
and then do the specific assignment code here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can not assign the
firstAssignmentPatternIndex
here because the transform will convert any params after the first private destructuring. In other words,firstAssignmentPatternIndex
will always be less thanfirstPrivateIndex
if we assignfirstAssignmentPatternIndex
here.