Ensure private UID generation takes into account all referenced private names

This commit is contained in:
Chris Hewell Garrett 2021-12-31 10:02:20 -05:00 committed by Nicolò Ribaudo
parent 3f7644823d
commit ab6d74a9cc
3 changed files with 57 additions and 69 deletions

View File

@ -17,10 +17,6 @@ type ClassElement =
| t.TSIndexSignature | t.TSIndexSignature
| t.StaticBlock; | t.StaticBlock;
type classUidGenerator = <B extends boolean>(
isPrivate: B,
) => B extends true ? t.PrivateName : t.Identifier;
function incrementId(id: number[], idx = id.length - 1): void { function incrementId(id: number[], idx = id.length - 1): void {
// If index is -1, id needs an additional character, unshift A // If index is -1, id needs an additional character, unshift A
if (idx === -1) { if (idx === -1) {
@ -54,64 +50,29 @@ function incrementId(id: number[], idx = id.length - 1): void {
* (you cannot have #x and static #x in the same class) and it's not worth the * (you cannot have #x and static #x in the same class) and it's not worth the
* extra complexity for public names. * extra complexity for public names.
*/ */
function createUidGeneratorForClass( function createPrivateUidGeneratorForClass(
body: NodePath<ClassElement>[], classPath: NodePath<t.ClassDeclaration | t.ClassExpression>,
): (isPrivate: boolean) => t.Identifier | t.PrivateName { ): () => t.PrivateName {
let currentPublicId: number[], currentPrivateId: number[]; const currentPrivateId = [charCodes.uppercaseA];
const publicNames = new Set<string>();
const privateNames = new Set<string>(); const privateNames = new Set<string>();
for (const element of body) { classPath.traverse({
if ( PrivateName(path) {
element.node.type === "TSIndexSignature" || privateNames.add(path.node.id.name);
element.node.type === "StaticBlock" },
) { });
continue;
return (): t.PrivateName => {
let reifiedId = String.fromCharCode(...currentPrivateId);
while (privateNames.has(reifiedId)) {
incrementId(currentPrivateId);
reifiedId = String.fromCharCode(...currentPrivateId);
} }
const { key } = element.node; incrementId(currentPrivateId);
if (key.type === "PrivateName") { return t.privateName(t.identifier(reifiedId));
privateNames.add(key.id.name);
} else if (key.type === "Identifier") {
publicNames.add(key.name);
}
}
return (isPrivate: boolean): t.Identifier | t.PrivateName => {
let currentId: number[], names: Set<String>;
if (isPrivate) {
if (!currentPrivateId) {
currentPrivateId = [charCodes.uppercaseA];
}
currentId = currentPrivateId;
names = privateNames;
} else {
if (!currentPublicId) {
currentPublicId = [charCodes.uppercaseA];
}
currentId = currentPublicId;
names = publicNames;
}
let reifiedId = String.fromCharCode(...currentId);
while (names.has(reifiedId)) {
incrementId(currentId);
reifiedId = String.fromCharCode(...currentId);
}
incrementId(currentId);
if (isPrivate) {
return t.privateName(t.identifier(reifiedId));
} else {
return t.identifier(reifiedId);
}
}; };
} }
@ -121,20 +82,18 @@ function createUidGeneratorForClass(
* saves iterating the class elements an additional time and allocating the space * saves iterating the class elements an additional time and allocating the space
* for the Sets of element names. * for the Sets of element names.
*/ */
function createLazyUidGeneratorForClass( function createLazyPrivateUidGeneratorForClass(
body: NodePath<ClassElement>[], classPath: NodePath<t.ClassDeclaration | t.ClassExpression>,
): classUidGenerator { ): () => t.PrivateName {
let generator: (isPrivate: boolean) => t.Identifier | t.PrivateName; let generator: () => t.PrivateName;
const lazyGenerator = (isPrivate: boolean): t.Identifier | t.PrivateName => { return (): t.PrivateName => {
if (!generator) { if (!generator) {
generator = createUidGeneratorForClass(body); generator = createPrivateUidGeneratorForClass(classPath);
} }
return generator(isPrivate); return generator();
}; };
return lazyGenerator as unknown as classUidGenerator;
} }
/** /**
@ -510,7 +469,7 @@ function transformClass(
const classDecorators = path.node.decorators; const classDecorators = path.node.decorators;
let hasElementDecorators = false; let hasElementDecorators = false;
const generateClassUid = createLazyUidGeneratorForClass(body); const generateClassPrivateUid = createLazyPrivateUidGeneratorForClass(path);
// Iterate over the class to see if we need to decorate it, and also to // Iterate over the class to see if we need to decorate it, and also to
// transform simple auto accessors which are not decorated // transform simple auto accessors which are not decorated
@ -524,7 +483,7 @@ function transformClass(
} else if (element.node.type === "ClassAccessorProperty") { } else if (element.node.type === "ClassAccessorProperty") {
const { key, value, static: isStatic } = element.node; const { key, value, static: isStatic } = element.node;
const newId = generateClassUid(true); const newId = generateClassPrivateUid();
const valueNode = value ? t.cloneNode(value) : undefined; const valueNode = value ? t.cloneNode(value) : undefined;
@ -626,7 +585,7 @@ function transformClass(
params.push(t.cloneNode(value)); params.push(t.cloneNode(value));
} }
const newId = generateClassUid(true); const newId = generateClassPrivateUid();
const newFieldInitId = generateLocalVarId(element, `init_${name}`); const newFieldInitId = generateLocalVarId(element, `init_${name}`);
const newValue = t.callExpression( const newValue = t.callExpression(
t.cloneNode(newFieldInitId), t.cloneNode(newFieldInitId),

View File

@ -0,0 +1,10 @@
class A {
#A = 1;
static B = class B extends A {
accessor a = 2;
getA() {
return this.#A;
}
}
}

View File

@ -0,0 +1,19 @@
class A {
#A = 1;
static B = class B extends A {
#B = 2;
get a() {
return this.#B;
}
set a(v) {
this.#B = v;
}
getA() {
return this.#A;
}
};
}