Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3585,6 +3585,10 @@ namespace ts {
let excludeFlags = TransformFlags.NodeExcludes;

switch (kind) {
case SyntaxKind.PrivateName:
// Private names are ESNext syntax.
transformFlags |= TransformFlags.AssertESNext;
break;
case SyntaxKind.AsyncKeyword:
case SyntaxKind.AwaitExpression:
// async/await is ES2017 syntax, but may be ESNext syntax (for async generators)
Expand Down
54 changes: 45 additions & 9 deletions src/compiler/transformers/esnext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,14 +362,15 @@ namespace ts {
}

function transformClassMembers(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) {
// Declare private names.
const privateProperties = filter(node.members, isPrivatePropertyDeclaration);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since private-named-fields aren't properties, what about privateNamedField?

privateProperties.forEach(property => addPrivateNameToEnvironment(property.name));

const members: ClassElement[] = [];
const constructor = transformConstructor(node, isDerivedClass);
if (constructor) {
members.push(constructor);
}
// Declare private names.
const privateProperties = filter(node.members, isPrivatePropertyDeclaration);
privateProperties.forEach(property => addPrivateNameToEnvironment(property.name));

addRange(members, visitNodes(node.members, classElementVisitor, isClassElement));
return setTextRange(createNodeArray(members), /*location*/ node.members);
Expand Down Expand Up @@ -403,9 +404,9 @@ namespace ts {
}

function transformConstructorBody(node: ClassDeclaration | ClassExpression, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) {
const properties = getInitializedProperties(node, /*isStatic*/ false);
const properties = filter(node.members, (node): node is PropertyDeclaration => isPropertyDeclaration(node) && !hasStaticModifier(node));

// Only generate synthetic constructor when there are property initializers to move.
// Only generate synthetic constructor when there are property declarations to move.
if (!constructor && !some(properties)) {
return visitFunctionBody(/*node*/ undefined, visitor, context);
}
Expand Down Expand Up @@ -474,7 +475,11 @@ namespace ts {
*/
function addInitializedPropertyStatements(statements: Statement[], properties: ReadonlyArray<PropertyDeclaration>, receiver: LeftHandSideExpression) {
for (const property of properties) {
const statement = createExpressionStatement(transformInitializedProperty(property, receiver));
const expression = transformProperty(property, receiver);
if (!expression) {
continue;
}
const statement = createExpressionStatement(expression);
setSourceMapRange(statement, moveRangePastModifiers(property));
setCommentRange(statement, property);
setOriginalNode(statement, property);
Expand All @@ -491,7 +496,10 @@ namespace ts {
function generateInitializedPropertyExpressions(properties: ReadonlyArray<PropertyDeclaration>, receiver: LeftHandSideExpression) {
const expressions: Expression[] = [];
for (const property of properties) {
const expression = transformInitializedProperty(property, receiver);
const expression = transformProperty(property, receiver);
if (!expression) {
continue;
}
startOnNewLine(expression);
setSourceMapRange(expression, moveRangePastModifiers(property));
setCommentRange(expression, property);
Expand All @@ -508,12 +516,30 @@ namespace ts {
* @param property The property declaration.
* @param receiver The object receiving the property assignment.
*/
function transformInitializedProperty(property: PropertyDeclaration, receiver: LeftHandSideExpression) {
function transformProperty(property: PropertyDeclaration, receiver: LeftHandSideExpression) {
// We generate a name here in order to reuse the value cached by the relocated computed name expression (which uses the same generated name)
const propertyName = isComputedPropertyName(property.name) && !isSimpleInlineableExpression(property.name.expression)
? updateComputedPropertyName(property.name, getGeneratedNameForNode(property.name))
: property.name;
const initializer = visitNode(property.initializer!, visitor, isExpression);
const initializer = visitNode(property.initializer, visitor, isExpression);

if (isPrivateName(propertyName)) {
const privateNameInfo = accessPrivateName(propertyName);
if (privateNameInfo) {
switch (privateNameInfo.type) {
case PrivateNameType.InstanceField: {
return createCall(
createPropertyAccess(privateNameInfo.weakMapName, "set"),
/*typeArguments*/ undefined,
[receiver, initializer || createVoidZero()]
);
}
}
}
}
if (!initializer) {
return undefined;
}
const memberAccess = createMemberAccessForPropertyName(receiver, propertyName, /*location*/ propertyName);

return createAssignment(memberAccess, initializer);
Expand Down Expand Up @@ -547,6 +573,16 @@ namespace ts {
);
}

function accessPrivateName(name: PrivateName) {
for (let i = privateNameEnvironmentStack.length - 1; i >= 0; --i) {
const env = privateNameEnvironmentStack[i];
if (env.has(name.escapedText)) {
return env.get(name.escapedText);
}
}
return undefined;
}

function enableSubstitutionForClassAliases() {
if ((enabledSubstitutions & ESNextSubstitutionFlags.ClassAliases) === 0) {
enabledSubstitutions |= ESNextSubstitutionFlags.ClassAliases;
Expand Down
17 changes: 6 additions & 11 deletions src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -929,17 +929,12 @@ namespace ts {
const parameters = transformConstructorParameters(constructor);
const body = transformConstructorBody(node.members, constructor, parametersWithPropertyAssignments);
members.push(startOnNewLine(
setOriginalNode(
setTextRange(
createConstructor(
/*decorators*/ undefined,
/*modifiers*/ undefined,
parameters,
body
),
constructor
),
constructor
updateConstructor(
constructor,
/*decorators*/ undefined,
/*modifiers*/ undefined,
parameters,
body
)
));
addRange(
Expand Down
17 changes: 17 additions & 0 deletions tests/baselines/reference/privateNameFieldInitializer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//// [privateNameFieldInitializer.ts]
class A {
#field = 10;
#uninitialized;
}


//// [privateNameFieldInitializer.js]
var _field, _uninitialized;
var A = /** @class */ (function () {
function A() {
_field.set(this, 10);
_uninitialized.set(this, void 0);
}
return A;
}());
_field = new WeakMap(), _uninitialized = new WeakMap();
11 changes: 11 additions & 0 deletions tests/baselines/reference/privateNameFieldInitializer.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
=== tests/cases/conformance/classes/members/privateNames/privateNameFieldInitializer.ts ===
class A {
>A : Symbol(A, Decl(privateNameFieldInitializer.ts, 0, 0))

#field = 10;
>#field : Symbol(A[#field], Decl(privateNameFieldInitializer.ts, 0, 9))

#uninitialized;
>#uninitialized : Symbol(A[#uninitialized], Decl(privateNameFieldInitializer.ts, 1, 16))
}

12 changes: 12 additions & 0 deletions tests/baselines/reference/privateNameFieldInitializer.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
=== tests/cases/conformance/classes/members/privateNames/privateNameFieldInitializer.ts ===
class A {
>A : A

#field = 10;
>#field : number
>10 : 10

#uninitialized;
>#uninitialized : any
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class A {
#field = 10;
#uninitialized;
}