Skip to content

Port class fields transformer #1542

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
29 changes: 27 additions & 2 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ func IsClassElement(node *Node) bool {
return false
}

func isMethodOrAccessor(node *Node) bool {
func IsMethodOrAccessor(node *Node) bool {
switch node.Kind {
case KindMethodDeclaration, KindGetAccessor, KindSetAccessor:
return true
Expand All @@ -575,7 +575,7 @@ func isMethodOrAccessor(node *Node) bool {
}

func IsPrivateIdentifierClassElementDeclaration(node *Node) bool {
return (IsPropertyDeclaration(node) || isMethodOrAccessor(node)) && IsPrivateIdentifier(node.Name())
return (IsPropertyDeclaration(node) || IsMethodOrAccessor(node)) && IsPrivateIdentifier(node.Name())
}

func IsObjectLiteralOrClassExpressionMethodOrAccessor(node *Node) bool {
Expand Down Expand Up @@ -1467,6 +1467,18 @@ func getAssignedName(node *Node) *Node {
return nil
}

func IsStaticPropertyDeclaration(member *ClassElement) bool {
return IsPropertyDeclaration(member) && HasStaticModifier(member)
}

func IsStaticPropertyDeclarationOrClassStaticBlockDeclaration(element *ClassElement) bool {
return IsStaticPropertyDeclaration(element) || IsClassStaticBlockDeclaration(element)
}

func GetStaticPropertiesAndClassStaticBlock(node *ClassLikeDeclaration) []*Node {
return core.Filter(node.Members(), IsStaticPropertyDeclarationOrClassStaticBlockDeclaration)
}

type JSDeclarationKind int

const (
Expand Down Expand Up @@ -3635,3 +3647,16 @@ func GetSemanticJsxChildren(children []*JsxChild) []*JsxChild {
}
})
}

func GetEffectiveBaseTypeNode(node *Node) *Node {
baseType := GetClassExtendsHeritageElement(node)
// !!! TODO: JSDoc support
// if (baseType && isInJSFile(node)) {
// // Prefer an @augments tag because it may have type parameters.
// const tag = getJSDocAugmentsTag(node);
// if (tag) {
// return tag.class;
// }
// }
return baseType
}
10 changes: 10 additions & 0 deletions internal/core/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,13 @@ func (s *Stack[T]) Peek() T {
func (s *Stack[T]) Len() int {
return len(s.data)
}

func (s *Stack[T]) FindFromTop(predicate func(item T) bool) T {
for i := len(s.data) - 1; i >= 0; i-- {
if predicate(s.data[i]) {
return s.data[i]
}
}
var zero T
return zero
}
12 changes: 12 additions & 0 deletions internal/printer/emitcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,14 @@ func (c *EmitContext) SourceMapRange(node *ast.Node) core.TextRange {
return node.Loc
}

// Sets `EFNoComments` on a node and removes any leading and trailing synthetic comments.
func (c *EmitContext) RemoveAllComments(node *ast.Node) {
c.AddEmitFlags(node, EFNoComments)
// !!! TODO: Also remove synthetic trailing/leading comments added by transforms
// emitNode.leadingComments = undefined;
// emitNode.trailingComments = undefined;
}

// Sets the range to use for a node when emitting source maps.
func (c *EmitContext) SetSourceMapRange(node *ast.Node, loc core.TextRange) {
emitNode := c.emitNodes.Get(node)
Expand Down Expand Up @@ -616,6 +624,10 @@ func (c *EmitContext) SetTokenSourceMapRange(node *ast.Node, kind ast.Kind, loc
emitNode.tokenSourceMapRanges[kind] = loc
}

func (c *EmitContext) TextSource(node *ast.Node) *ast.Node {
return c.textSource[node]
}

func (c *EmitContext) AssignedName(node *ast.Node) *ast.Expression {
return c.assignedName[node]
}
Expand Down
43 changes: 43 additions & 0 deletions internal/printer/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,22 @@ func (f *NodeFactory) getName(node *ast.Declaration, emitFlags EmitFlags, opts A
return f.NewGeneratedNameForNode(node)
}

// Gets the internal name of a declaration. This is primarily used for declarations that can be referred to by name in
// the body of an ES5 class function body. An internal name will *never* be prefixed with an module or namespace export
// modifier like "exports." when emitted as an expression. An internal name will also *never* be renamed due to a
// collision with a block scoped variable.
func (f *NodeFactory) GetInternalName(node *ast.Declaration) *ast.IdentifierNode {
return f.GetInternalNameEx(node, AssignedNameOptions{})
}

// Gets the internal name of a declaration. This is primarily used for declarations that can be referred to by name in
// the body of an ES5 class function body. An internal name will *never* be prefixed with an module or namespace export
// modifier like "exports." when emitted as an expression. An internal name will also *never* be renamed due to a
// collision with a block scoped variable.
func (f *NodeFactory) GetInternalNameEx(node *ast.Declaration, opts AssignedNameOptions) *ast.IdentifierNode {
return f.getName(node, EFLocalName|EFInternalName, opts)
}

// Gets the local name of a declaration. This is primarily used for declarations that can be referred to by name in the
// declaration's immediate scope (classes, enums, namespaces). A local name will *never* be prefixed with a module or
// namespace export modifier like "exports." when emitted as an expression.
Expand Down Expand Up @@ -639,3 +655,30 @@ func (f *NodeFactory) NewRewriteRelativeImportExtensionsHelper(firstArgument *as
ast.NodeFlagsNone,
)
}

// Allocate a new call expression to the `__classPrivateFieldGet` helper
func (f *NodeFactory) NewClassPrivateFieldGetHelper(receiver *ast.Expression, state *ast.Identifier, kind PrivateIdentifierKind, farg *ast.Identifier) *ast.Expression {
f.emitContext.RequestEmitHelper(classPrivateFieldGetHelper)
var arguments []*ast.Expression
if farg == nil {
arguments = []*ast.Expression{
receiver,
state.AsNode(),
f.NewStringLiteral(kind.String()),
}
} else {
arguments = []*ast.Expression{
receiver,
state.AsNode(),
f.NewStringLiteral(kind.String()),
farg.AsNode(),
}
}
return f.NewCallExpression(
f.NewUnscopedHelperName("__classPrivateFieldGet"),
nil, /*questionDotToken*/
nil, /*typeArguments*/
f.NewNodeList(arguments),
ast.NodeFlagsNone,
)
}
11 changes: 11 additions & 0 deletions internal/printer/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,14 @@ var rewriteRelativeImportExtensionsHelper = &EmitHelper{
return path;
};`,
}

var classPrivateFieldGetHelper = &EmitHelper{
Name: "typescript:classPrivateFieldGet",
ImportName: "__classPrivateFieldGet",
Scoped: false,
Text: `var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};`,
}
206 changes: 206 additions & 0 deletions internal/printer/privateidentifierinfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package printer

import "github.com/microsoft/typescript-go/internal/ast"

type PrivateIdentifierKind int

const (
PrivateIdentifierKindMethod PrivateIdentifierKind = iota
PrivateIdentifierKindField
PrivateIdentifierKindAccessor
PrivateIdentifierKindUntransformed
)

func (k PrivateIdentifierKind) String() string {
switch k {
case PrivateIdentifierKindMethod:
return "m"
case PrivateIdentifierKindField:
return "f"
case PrivateIdentifierKindAccessor:
return "a"
case PrivateIdentifierKindUntransformed:
return "untransformed"
default:
panic("Unhandled PrivateIdentifierKind")
}
}

type PrivateIdentifierInfo interface {
Kind() PrivateIdentifierKind
IsValid() bool
IsStatic() bool
BrandCheckIdentifier() *ast.Identifier
}

type PrivateIdentifierInfoBase struct {
/**
* brandCheckIdentifier can contain:
* - For instance field: The WeakMap that will be the storage for the field.
* - For instance methods or accessors: The WeakSet that will be used for brand checking.
* - For static members: The constructor that will be used for brand checking.
*/
brandCheckIdentifier *ast.Identifier
// Stores if the identifier is static or not
isStatic bool
// Stores if the identifier declaration is valid or not. Reserved names (e.g. #constructor)
// or duplicate identifiers are considered invalid.
isValid bool
}

type PrivateIdentifierAccessorInfo struct {
PrivateIdentifierInfoBase
kind PrivateIdentifierKind
// Identifier for a variable that will contain the private get accessor implementation, if any.
GetterName *ast.Identifier
// Identifier for a variable that will contain the private set accessor implementation, if any.
SetterName *ast.Identifier
}

func (p *PrivateIdentifierAccessorInfo) Kind() PrivateIdentifierKind {
return p.kind
}
func (p *PrivateIdentifierAccessorInfo) IsValid() bool {
return p.PrivateIdentifierInfoBase.isValid
}
func (p *PrivateIdentifierAccessorInfo) IsStatic() bool {
return p.PrivateIdentifierInfoBase.isStatic
}
func (p *PrivateIdentifierAccessorInfo) BrandCheckIdentifier() *ast.Identifier {
return p.PrivateIdentifierInfoBase.brandCheckIdentifier
}

func NewPrivateIdentifierAccessorInfo(brandCheckIdentifier *ast.Identifier, getterName *ast.Identifier, setterName *ast.Identifier, isValid bool, isStatc bool) *PrivateIdentifierAccessorInfo {
return &PrivateIdentifierAccessorInfo{
kind: PrivateIdentifierKindAccessor,
PrivateIdentifierInfoBase: PrivateIdentifierInfoBase{
brandCheckIdentifier: brandCheckIdentifier,
isStatic: isStatc,
Comment on lines +73 to +78
Copy link
Preview

Copilot AI Aug 8, 2025

Choose a reason for hiding this comment

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

Parameter name 'isStatc' appears to be a typo. It should be 'isStatic' to match the field name and other similar parameters.

Suggested change
func NewPrivateIdentifierAccessorInfo(brandCheckIdentifier *ast.Identifier, getterName *ast.Identifier, setterName *ast.Identifier, isValid bool, isStatc bool) *PrivateIdentifierAccessorInfo {
return &PrivateIdentifierAccessorInfo{
kind: PrivateIdentifierKindAccessor,
PrivateIdentifierInfoBase: PrivateIdentifierInfoBase{
brandCheckIdentifier: brandCheckIdentifier,
isStatic: isStatc,
func NewPrivateIdentifierAccessorInfo(brandCheckIdentifier *ast.Identifier, getterName *ast.Identifier, setterName *ast.Identifier, isValid bool, isStatic bool) *PrivateIdentifierAccessorInfo {
return &PrivateIdentifierAccessorInfo{
kind: PrivateIdentifierKindAccessor,
PrivateIdentifierInfoBase: PrivateIdentifierInfoBase{
brandCheckIdentifier: brandCheckIdentifier,
isStatic: isStatic,

Copilot uses AI. Check for mistakes.

isValid: isValid,
},
GetterName: getterName,
SetterName: setterName,
}
}

type PrivateIdentifierMethodInfo struct {
PrivateIdentifierInfoBase
kind PrivateIdentifierKind
// Identifier for a variable that will contain the private method implementation.
MethodName *ast.Identifier
}

func (p *PrivateIdentifierMethodInfo) Kind() PrivateIdentifierKind {
return p.kind
}
func (p *PrivateIdentifierMethodInfo) IsValid() bool {
return p.PrivateIdentifierInfoBase.isValid
}
func (p *PrivateIdentifierMethodInfo) IsStatic() bool {
return p.PrivateIdentifierInfoBase.isStatic
}
func (p *PrivateIdentifierMethodInfo) BrandCheckIdentifier() *ast.Identifier {
return p.PrivateIdentifierInfoBase.brandCheckIdentifier
}

func NewPrivateIdentifierMethodInfo(brandCheckIdentifier *ast.Identifier, methodName *ast.Identifier, isValid bool, isStatc bool) *PrivateIdentifierMethodInfo {
return &PrivateIdentifierMethodInfo{
kind: PrivateIdentifierKindMethod,
PrivateIdentifierInfoBase: PrivateIdentifierInfoBase{
brandCheckIdentifier: brandCheckIdentifier,
isStatic: isStatc,
Comment on lines +106 to +111
Copy link
Preview

Copilot AI Aug 8, 2025

Choose a reason for hiding this comment

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

Parameter name 'isStatc' appears to be a typo. It should be 'isStatic' to match the field name and other similar parameters.

Suggested change
func NewPrivateIdentifierMethodInfo(brandCheckIdentifier *ast.Identifier, methodName *ast.Identifier, isValid bool, isStatc bool) *PrivateIdentifierMethodInfo {
return &PrivateIdentifierMethodInfo{
kind: PrivateIdentifierKindMethod,
PrivateIdentifierInfoBase: PrivateIdentifierInfoBase{
brandCheckIdentifier: brandCheckIdentifier,
isStatic: isStatc,
func NewPrivateIdentifierMethodInfo(brandCheckIdentifier *ast.Identifier, methodName *ast.Identifier, isValid bool, isStatic bool) *PrivateIdentifierMethodInfo {
return &PrivateIdentifierMethodInfo{
kind: PrivateIdentifierKindMethod,
PrivateIdentifierInfoBase: PrivateIdentifierInfoBase{
brandCheckIdentifier: brandCheckIdentifier,
isStatic: isStatic,

Copilot uses AI. Check for mistakes.

isValid: isValid,
},
MethodName: methodName,
}
}

type PrivateIdentifierInstanceFieldInfo struct {
PrivateIdentifierInfoBase
kind PrivateIdentifierKind
}

func NewPrivateIdentifierInstanceFieldInfo(brandCheckIdentifier *ast.Identifier, isValid bool) *PrivateIdentifierInstanceFieldInfo {
return &PrivateIdentifierInstanceFieldInfo{
kind: PrivateIdentifierKindField,
PrivateIdentifierInfoBase: PrivateIdentifierInfoBase{
brandCheckIdentifier: brandCheckIdentifier,
isStatic: false,
isValid: isValid,
},
}
}

func (p *PrivateIdentifierInstanceFieldInfo) Kind() PrivateIdentifierKind {
return p.kind
}
func (p *PrivateIdentifierInstanceFieldInfo) IsValid() bool {
return p.PrivateIdentifierInfoBase.isValid
}
func (p *PrivateIdentifierInstanceFieldInfo) IsStatic() bool {
return p.PrivateIdentifierInfoBase.isStatic
}
func (p *PrivateIdentifierInstanceFieldInfo) BrandCheckIdentifier() *ast.Identifier {
return p.PrivateIdentifierInfoBase.brandCheckIdentifier
}

type PrivateIdentifierStaticFieldInfo struct {
PrivateIdentifierInfoBase
kind PrivateIdentifierKind
// Contains the variable that will serve as the storage for the field.
VariableName *ast.Identifier
}

func NewPrivateIdentifierStaticFieldInfo(brandCheckIdentifier *ast.Identifier, variableName *ast.Identifier, isValid bool) *PrivateIdentifierStaticFieldInfo {
return &PrivateIdentifierStaticFieldInfo{
kind: PrivateIdentifierKindField,
PrivateIdentifierInfoBase: PrivateIdentifierInfoBase{
brandCheckIdentifier: brandCheckIdentifier,
isStatic: true,
isValid: isValid,
},
VariableName: variableName,
}
}

func (p *PrivateIdentifierStaticFieldInfo) Kind() PrivateIdentifierKind {
return p.kind
}
func (p *PrivateIdentifierStaticFieldInfo) IsValid() bool {
return p.PrivateIdentifierInfoBase.isValid
}
func (p *PrivateIdentifierStaticFieldInfo) IsStatic() bool {
return p.PrivateIdentifierInfoBase.isStatic
}
func (p *PrivateIdentifierStaticFieldInfo) BrandCheckIdentifier() *ast.Identifier {
return p.PrivateIdentifierInfoBase.brandCheckIdentifier
}

type PrivateIdentifierUntransformedInfo struct {
PrivateIdentifierInfoBase
kind PrivateIdentifierKind
}

func NewPrivateIdentifierUntransformedInfo(brandCheckIdentifier *ast.Identifier, isValid bool, isStatic bool) *PrivateIdentifierUntransformedInfo {
return &PrivateIdentifierUntransformedInfo{
kind: PrivateIdentifierKindUntransformed,
PrivateIdentifierInfoBase: PrivateIdentifierInfoBase{
brandCheckIdentifier: brandCheckIdentifier,
isStatic: isStatic,
isValid: isValid,
},
}
}

func (p *PrivateIdentifierUntransformedInfo) Kind() PrivateIdentifierKind {
return p.kind
}
func (p *PrivateIdentifierUntransformedInfo) IsValid() bool {
return p.PrivateIdentifierInfoBase.isValid
}
func (p *PrivateIdentifierUntransformedInfo) IsStatic() bool {
return p.PrivateIdentifierInfoBase.isStatic
}
func (p *PrivateIdentifierUntransformedInfo) BrandCheckIdentifier() *ast.Identifier {
return p.PrivateIdentifierInfoBase.brandCheckIdentifier
}
Loading
Loading