Allow version: "legacy", use built-in Identifier generation, simplify private UID code

This commit is contained in:
Chris Hewell Garrett 2022-01-01 13:49:22 -05:00 committed by Nicolò Ribaudo
parent ab6d74a9cc
commit 46c54fbbf6
3 changed files with 46 additions and 38 deletions

View File

@ -194,7 +194,10 @@ module.exports = function (api) {
assumptions: parserAssumptions, assumptions: parserAssumptions,
}, },
{ {
test: ["packages/babel-generator"].map(normalize), test: [
"packages/babel-generator",
"packages/babel-plugin-proposal-decorators",
].map(normalize),
plugins: ["babel-plugin-transform-charcodes"], plugins: ["babel-plugin-transform-charcodes"],
}, },
convertESM && { convertESM && {

View File

@ -37,9 +37,11 @@ export default declare((api, options) => {
} }
} }
if (legacy) { if (version === "legacy" || legacy) {
if (version !== undefined) { if (version !== undefined && legacy) {
throw new Error("'version' can't be used with legacy decorators"); throw new Error(
'You can either specify `legacy: true` or `version: "legacy"` with decorators, not both.',
);
} }
return { return {

View File

@ -40,20 +40,16 @@ function incrementId(id: number[], idx = id.length - 1): void {
} }
/** /**
* Generates a new element name that is unique to the given class. This can be * Generates a new private name that is unique to the given class. This can be
* used to create extra class fields and methods for the implementation, while * used to create extra class fields and methods for the implementation, while
* keeping the length of those names as small as possible. This is important for * keeping the length of those names as small as possible. This is important for
* minification purposes, since public names cannot be safely renamed/minified. * minification purposes (though private names can generally be minified,
* * transpilations and polyfills cannot yet).
* Names are split into two namespaces, public and private. Static and non-static
* names are shared in the same namespace, because this is true for private names
* (you cannot have #x and static #x in the same class) and it's not worth the
* extra complexity for public names.
*/ */
function createPrivateUidGeneratorForClass( function createPrivateUidGeneratorForClass(
classPath: NodePath<t.ClassDeclaration | t.ClassExpression>, classPath: NodePath<t.ClassDeclaration | t.ClassExpression>,
): () => t.PrivateName { ): () => t.PrivateName {
const currentPrivateId = [charCodes.uppercaseA]; const currentPrivateId = [];
const privateNames = new Set<string>(); const privateNames = new Set<string>();
classPath.traverse({ classPath.traverse({
@ -63,14 +59,11 @@ function createPrivateUidGeneratorForClass(
}); });
return (): t.PrivateName => { return (): t.PrivateName => {
let reifiedId = String.fromCharCode(...currentPrivateId); let reifiedId;
do {
while (privateNames.has(reifiedId)) {
incrementId(currentPrivateId); incrementId(currentPrivateId);
reifiedId = String.fromCharCode(...currentPrivateId); reifiedId = String.fromCharCode(...currentPrivateId);
} } while (privateNames.has(reifiedId));
incrementId(currentPrivateId);
return t.privateName(t.identifier(reifiedId)); return t.privateName(t.identifier(reifiedId));
}; };
@ -124,16 +117,17 @@ function replaceClassWithVar(
if (path.node.id) { if (path.node.id) {
className = path.node.id.name; className = path.node.id.name;
varId = generateLocalVarId(path, className); varId = path.scope.parent.generateDeclaredUidIdentifier(className);
path.scope.rename(className, varId.name); path.scope.rename(className, varId.name);
} else if ( } else if (
path.parentPath.node.type === "VariableDeclarator" && path.parentPath.node.type === "VariableDeclarator" &&
path.parentPath.node.id.type === "Identifier" path.parentPath.node.id.type === "Identifier"
) { ) {
className = path.parentPath.node.id.name; className = path.parentPath.node.id.name;
varId = generateLocalVarId(path, className); varId = path.scope.parent.generateDeclaredUidIdentifier(className);
} else { } else {
varId = generateLocalVarId(path, "decorated_class"); varId =
path.scope.parent.generateDeclaredUidIdentifier("decorated_class");
} }
const newClassExpr = t.classExpression( const newClassExpr = t.classExpression(
@ -283,12 +277,6 @@ function getElementKind(element: NodePath<ClassDecoratableElement>): number {
} }
} }
function generateLocalVarId(path: NodePath, name: string): t.Identifier {
const varId = path.scope.generateUidIdentifier(name);
path.scope.parent.push({ id: varId });
return t.cloneNode(varId);
}
// Information about the decorators applied to an element // Information about the decorators applied to an element
interface DecoratorInfo { interface DecoratorInfo {
// The expressions of the decorators themselves // The expressions of the decorators themselves
@ -514,7 +502,8 @@ function transformClass(
classLocal: t.Identifier; classLocal: t.Identifier;
if (classDecorators) { if (classDecorators) {
classInitLocal = generateLocalVarId(path, "initClass"); classInitLocal =
path.scope.parent.generateDeclaredUidIdentifier("initClass");
const [localId, classPath] = replaceClassWithVar(path); const [localId, classPath] = replaceClassWithVar(path);
path = classPath; path = classPath;
@ -560,7 +549,8 @@ function transformClass(
if (isComputed) { if (isComputed) {
const keyPath = element.get("key"); const keyPath = element.get("key");
const localComputedNameId = generateLocalVarId(keyPath, name); const localComputedNameId =
keyPath.scope.parent.generateDeclaredUidIdentifier(name);
keyPath.replaceWith(localComputedNameId); keyPath.replaceWith(localComputedNameId);
elementDecoratorInfo.push({ elementDecoratorInfo.push({
@ -586,7 +576,8 @@ function transformClass(
} }
const newId = generateClassPrivateUid(); const newId = generateClassPrivateUid();
const newFieldInitId = generateLocalVarId(element, `init_${name}`); const newFieldInitId =
element.scope.parent.generateDeclaredUidIdentifier(`init_${name}`);
const newValue = t.callExpression( const newValue = t.callExpression(
t.cloneNode(newFieldInitId), t.cloneNode(newFieldInitId),
params, params,
@ -598,8 +589,12 @@ function transformClass(
if (isPrivate) { if (isPrivate) {
privateMethods = extractProxyAccessorsFor(newId); privateMethods = extractProxyAccessorsFor(newId);
const getId = generateLocalVarId(newPath, `get_${name}`); const getId = newPath.scope.parent.generateDeclaredUidIdentifier(
const setId = generateLocalVarId(newPath, `set_${name}`); `get_${name}`,
);
const setId = newPath.scope.parent.generateDeclaredUidIdentifier(
`set_${name}`,
);
addCallAccessorsFor(newPath, key as t.PrivateName, getId, setId); addCallAccessorsFor(newPath, key as t.PrivateName, getId, setId);
@ -609,7 +604,9 @@ function transformClass(
locals = newFieldInitId; locals = newFieldInitId;
} }
} else if (kind === FIELD) { } else if (kind === FIELD) {
const initId = generateLocalVarId(element, `init_${name}`); const initId = element.scope.parent.generateDeclaredUidIdentifier(
`init_${name}`,
);
const valuePath = ( const valuePath = (
element as NodePath<t.ClassProperty | t.ClassPrivateProperty> element as NodePath<t.ClassProperty | t.ClassPrivateProperty>
).get("value"); ).get("value");
@ -627,7 +624,9 @@ function transformClass(
privateMethods = extractProxyAccessorsFor(key as t.PrivateName); privateMethods = extractProxyAccessorsFor(key as t.PrivateName);
} }
} else if (isPrivate) { } else if (isPrivate) {
locals = generateLocalVarId(element, `call_${name}`); locals = element.scope.parent.generateDeclaredUidIdentifier(
`call_${name}`,
) as t.Identifier;
const replaceSupers = new ReplaceSupers({ const replaceSupers = new ReplaceSupers({
constantSuper, constantSuper,
@ -730,7 +729,8 @@ function transformClass(
const newDecorators: t.Identifier[] = []; const newDecorators: t.Identifier[] = [];
for (const decorator of decorators) { for (const decorator of decorators) {
const localComputedNameId = generateLocalVarId(path, "dec"); const localComputedNameId =
path.scope.parent.generateDeclaredUidIdentifier("dec");
assignments.push( assignments.push(
t.assignmentExpression("=", localComputedNameId, decorator), t.assignmentExpression("=", localComputedNameId, decorator),
); );
@ -761,7 +761,8 @@ function transformClass(
} }
if (requiresProtoInit) { if (requiresProtoInit) {
protoInitLocal = generateLocalVarId(path, "initProto"); protoInitLocal =
path.scope.parent.generateDeclaredUidIdentifier("initProto");
locals.push(protoInitLocal); locals.push(protoInitLocal);
const protoInitCall = t.callExpression(t.cloneNode(protoInitLocal), [ const protoInitCall = t.callExpression(t.cloneNode(protoInitLocal), [
@ -789,7 +790,8 @@ function transformClass(
found = true; found = true;
const prop = generateLocalVarId(path, "super"); const prop =
path.scope.parent.generateDeclaredUidIdentifier("super");
parentPath.replaceWith( parentPath.replaceWith(
t.sequenceExpression([ t.sequenceExpression([
t.assignmentExpression("=", t.cloneNode(prop), parentPath.node), t.assignmentExpression("=", t.cloneNode(prop), parentPath.node),
@ -829,7 +831,8 @@ function transformClass(
} }
if (requiresStaticInit) { if (requiresStaticInit) {
staticInitLocal = generateLocalVarId(path, "initStatic"); staticInitLocal =
path.scope.parent.generateDeclaredUidIdentifier("initStatic");
locals.push(staticInitLocal); locals.push(staticInitLocal);
} }