Skip to content

Commit

Permalink
Transform destructuring private (#14304)
Browse files Browse the repository at this point in the history
Co-authored-by: Nicol貌 Ribaudo <nicolo.ribaudo@gmail.com>
Co-authored-by: Justin Ridgewell <justin@ridgewell.name>
  • Loading branch information
3 people committed May 17, 2022
1 parent 6415f09 commit 1daded5
Show file tree
Hide file tree
Showing 283 changed files with 4,338 additions and 76 deletions.
1 change: 1 addition & 0 deletions Gulpfile.mjs
Expand Up @@ -459,6 +459,7 @@ function copyDts(packages) {

const libBundles = [
"packages/babel-parser",
"packages/babel-plugin-proposal-destructuring-private",
"packages/babel-plugin-proposal-object-rest-spread",
"packages/babel-plugin-proposal-optional-chaining",
"packages/babel-preset-react",
Expand Down
Expand Up @@ -181,7 +181,7 @@ export function shouldTransform(path: NodePath<t.Class>, file: File): boolean {
if (privateMethodPath && !hasFeature(file, FEATURES.privateMethods)) {
throw privateMethodPath.buildCodeFrameError(
"Class private methods are not enabled. " +
"Please add `@babel/plugin-proposal-private-method` to your configuration.",
"Please add `@babel/plugin-proposal-private-methods` to your configuration.",
);
}

Expand Down
6 changes: 5 additions & 1 deletion packages/babel-helper-define-map/src/index.ts
Expand Up @@ -121,7 +121,11 @@ export function toComputedObjectFromClass(obj: any) {
const prop = obj.properties[i];
const val = prop.value;
val.properties.unshift(
objectProperty(identifier("key"), toComputedKey(prop)),
objectProperty(
identifier("key"),
// @ts-expect-error toComputedObjectFromClass is not used, maybe we can remove it
toComputedKey(prop),
),
);
objExpr.elements.push(val);
}
Expand Down
@@ -1,7 +1,7 @@
{
"name": "@babel/plugin-proposal-class-static-block",
"version": "7.17.12",
"description": "Allow parsing of class static blocks",
"description": "Transform class static blocks",
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
Expand Down
@@ -0,0 +1,3 @@
src
test
*.log
19 changes: 19 additions & 0 deletions packages/babel-plugin-proposal-destructuring-private/README.md
@@ -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
```
42 changes: 42 additions & 0 deletions packages/babel-plugin-proposal-destructuring-private/package.json
@@ -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"
}
}
214 changes: 214 additions & 0 deletions packages/babel-plugin-proposal-destructuring-private/src/index.ts
@@ -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.
},
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();
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,
};
});

0 comments on commit 1daded5

Please sign in to comment.