Skip to content

Add stubs for missing downlevel feature transforms, organize transforms by purpose #1270

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

Merged
merged 2 commits into from
Jun 24, 2025
Merged
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
74 changes: 73 additions & 1 deletion internal/compiler/emitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/base64"

"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/binder"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
"github.com/microsoft/typescript-go/internal/outputpaths"
Expand All @@ -12,6 +13,10 @@ import (
"github.com/microsoft/typescript-go/internal/stringutil"
"github.com/microsoft/typescript-go/internal/transformers"
"github.com/microsoft/typescript-go/internal/transformers/declarations"
"github.com/microsoft/typescript-go/internal/transformers/estransforms"
"github.com/microsoft/typescript-go/internal/transformers/jsxtransforms"
"github.com/microsoft/typescript-go/internal/transformers/moduletransforms"
"github.com/microsoft/typescript-go/internal/transformers/tstransforms"
"github.com/microsoft/typescript-go/internal/tspath"
)

Expand Down Expand Up @@ -49,6 +54,73 @@ func (e *emitter) getDeclarationTransformers(emitContext *printer.EmitContext, s
return []*declarations.DeclarationTransformer{transform}
}

func getModuleTransformer(emitContext *printer.EmitContext, options *core.CompilerOptions, resolver binder.ReferenceResolver, getEmitModuleFormatOfFile func(file ast.HasFileName) core.ModuleKind) *transformers.Transformer {
switch options.GetEmitModuleKind() {
case core.ModuleKindPreserve:
// `ESModuleTransformer` contains logic for preserving CJS input syntax in `--module preserve`
return moduletransforms.NewESModuleTransformer(emitContext, options, resolver, getEmitModuleFormatOfFile)

case core.ModuleKindESNext,
core.ModuleKindES2022,
core.ModuleKindES2020,
core.ModuleKindES2015,
core.ModuleKindNode18,
core.ModuleKindNode16,
core.ModuleKindNodeNext,
core.ModuleKindCommonJS:
return moduletransforms.NewImpliedModuleTransformer(emitContext, options, resolver, getEmitModuleFormatOfFile)

default:
return moduletransforms.NewCommonJSModuleTransformer(emitContext, options, resolver, getEmitModuleFormatOfFile)
}
}

func getScriptTransformers(emitContext *printer.EmitContext, host printer.EmitHost, sourceFile *ast.SourceFile) []*transformers.Transformer {
var tx []*transformers.Transformer
options := host.Options()

// JS files don't use reference calculations as they don't do import elision, no need to calculate it
importElisionEnabled := !options.VerbatimModuleSyntax.IsTrue() && !ast.IsInJSFile(sourceFile.AsNode())

var emitResolver printer.EmitResolver
var referenceResolver binder.ReferenceResolver
if importElisionEnabled || options.GetJSXTransformEnabled() {
emitResolver = host.GetEmitResolver(sourceFile, false /*skipDiagnostics*/) // !!! conditionally skip diagnostics
emitResolver.MarkLinkedReferencesRecursively(sourceFile)
referenceResolver = emitResolver
} else {
referenceResolver = binder.NewReferenceResolver(options, binder.ReferenceResolverHooks{})
}

// transform TypeScript syntax
{
// erase types
tx = append(tx, tstransforms.NewTypeEraserTransformer(emitContext, options))

// elide imports
if importElisionEnabled {
tx = append(tx, tstransforms.NewImportElisionTransformer(emitContext, options, emitResolver))
}

// transform `enum`, `namespace`, and parameter properties
tx = append(tx, tstransforms.NewRuntimeSyntaxTransformer(emitContext, options, referenceResolver))
}

// !!! transform legacy decorator syntax
if options.GetJSXTransformEnabled() {
tx = append(tx, jsxtransforms.NewJSXTransformer(emitContext, options, emitResolver))
}

downleveler := estransforms.GetESTransformer(options, emitContext)
if downleveler != nil {
tx = append(tx, downleveler)
}

// transform module syntax
tx = append(tx, getModuleTransformer(emitContext, options, referenceResolver, host.GetEmitModuleFormatOfFile))
return tx
}

func (e *emitter) emitJSFile(sourceFile *ast.SourceFile, jsFilePath string, sourceMapFilePath string) {
options := e.host.Options()

Expand All @@ -61,7 +133,7 @@ func (e *emitter) emitJSFile(sourceFile *ast.SourceFile, jsFilePath string, sour
}

emitContext := printer.NewEmitContext()
for _, transformer := range transformers.GetScriptTransformers(emitContext, e.host, sourceFile) {
for _, transformer := range getScriptTransformers(emitContext, e.host, sourceFile) {
sourceFile = transformer.TransformSourceFile(sourceFile)
}

Expand Down
4 changes: 2 additions & 2 deletions internal/printer/printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/testutil/emittestutil"
"github.com/microsoft/typescript-go/internal/testutil/parsetestutil"
"github.com/microsoft/typescript-go/internal/transformers"
"github.com/microsoft/typescript-go/internal/transformers/tstransforms"
)

func TestEmit(t *testing.T) {
Expand Down Expand Up @@ -2504,7 +2504,7 @@ func TestPartiallyEmittedExpression(t *testing.T) {
.expression;`, false /*jsx*/)

emitContext := printer.NewEmitContext()
file = transformers.NewTypeEraserTransformer(emitContext, compilerOptions).TransformSourceFile(file)
file = tstransforms.NewTypeEraserTransformer(emitContext, compilerOptions).TransformSourceFile(file)
emittestutil.CheckEmit(t, emitContext, file.AsSourceFile(), `return container.parent
.left
.expression
Expand Down
44 changes: 44 additions & 0 deletions internal/transformers/chain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package transformers

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

type chainedTransformer struct {
Transformer
components []*Transformer
}

func (ch *chainedTransformer) visit(node *ast.Node) *ast.Node {
if node.Kind != ast.KindSourceFile {
panic("Chained transform passed non-sourcefile initial node")
}
result := node.AsSourceFile()
for _, t := range ch.components {
result = t.TransformSourceFile(result)
}
return result.AsNode()
}

type TransformerFactory = func(emitContext *printer.EmitContext) *Transformer

// Chains transforms in left-to-right order, running them one at a time in order (as opposed to interleaved at each node)
// - the resulting combined transform only operates on SourceFile nodes
func Chain(transforms ...TransformerFactory) TransformerFactory {
if len(transforms) < 2 {
if len(transforms) == 0 {
panic("Expected some number of transforms to chain, but got none")
}
return transforms[0]
}
return func(emitContext *printer.EmitContext) *Transformer {
constructed := make([]*Transformer, 0, len(transforms))
for _, t := range transforms {
// TODO: flatten nested chains?
constructed = append(constructed, t(emitContext))
}
ch := &chainedTransformer{components: constructed}
return ch.NewTransformer(ch.visit, emitContext)
}
}
20 changes: 20 additions & 0 deletions internal/transformers/estransforms/async.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package estransforms

import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/transformers"
)

type asyncTransformer struct {
transformers.Transformer
}

func (ch *asyncTransformer) visit(node *ast.Node) *ast.Node {
return node // !!!
}

func newAsyncTransformer(emitContext *printer.EmitContext) *transformers.Transformer {
tx := &asyncTransformer{}
return tx.NewTransformer(tx.visit, emitContext)
}
20 changes: 20 additions & 0 deletions internal/transformers/estransforms/classfields.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package estransforms

import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/transformers"
)

type classFieldsTransformer struct {
transformers.Transformer
}

func (ch *classFieldsTransformer) visit(node *ast.Node) *ast.Node {
return node // !!!
}

func newClassFieldsTransformer(emitContext *printer.EmitContext) *transformers.Transformer {
tx := &classFieldsTransformer{}
return tx.NewTransformer(tx.visit, emitContext)
}
20 changes: 20 additions & 0 deletions internal/transformers/estransforms/classstatic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package estransforms

import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/transformers"
)

type classStaticBlockTransformer struct {
transformers.Transformer
}

func (ch *classStaticBlockTransformer) visit(node *ast.Node) *ast.Node {
return node // !!!
}

func newClassStaticBlockTransformer(emitContext *printer.EmitContext) *transformers.Transformer {
tx := &classStaticBlockTransformer{}
return tx.NewTransformer(tx.visit, emitContext)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package transformers
package estransforms

import (
"github.com/microsoft/typescript-go/internal/ast"
Expand Down
46 changes: 46 additions & 0 deletions internal/transformers/estransforms/definitions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package estransforms

import (
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/transformers"
)

// !!! TODO: This fixed layering scheme assumes you can't swap out the es decorator transform for the legacy one,
// or the proper es class field transform for the legacy one
var (
NewESNextTransformer = transformers.Chain(newESDecoratorTransformer, newUsingDeclarationTransformer)
// 2025: only module system syntax (import attributes, json modules), untransformed regex modifiers
// 2024: no new downlevel syntax
// 2023: no new downlevel syntax
NewES2022Transformer = transformers.Chain(NewESNextTransformer, newClassStaticBlockTransformer, newClassFieldsTransformer) // !!! top level await? not transformed, just errored on at lower targets - also more of a module system feature anyway
NewES2021Transformer = transformers.Chain(NewES2022Transformer, newLogicalAssignmentTransformer) // !!! numeric seperators? always elided by printer?
NewES2020Transformer = transformers.Chain(NewES2021Transformer, newNullishCoalescingTransformer, newOptionalChainTransformer) // also dynamic import - module system feature
NewES2019Transformer = transformers.Chain(NewES2020Transformer, newOptionalCatchTransformer)
NewES2018Transformer = transformers.Chain(NewES2019Transformer, newObjectRestSpreadTransformer, newforawaitTransformer)
NewES2017Transformer = transformers.Chain(NewES2018Transformer, newAsyncTransformer)
NewES2016Transformer = transformers.Chain(NewES2017Transformer, newExponentiationTransformer)
)

func GetESTransformer(options *core.CompilerOptions, emitContext *printer.EmitContext) *transformers.Transformer {
switch options.GetEmitScriptTarget() {
case core.ScriptTargetESNext:
return nil // no transforms needed
case /*core.ScriptTargetES2025,*/ core.ScriptTargetES2024, core.ScriptTargetES2023, core.ScriptTargetES2022:
return NewESNextTransformer(emitContext)
case core.ScriptTargetES2021:
return NewES2022Transformer(emitContext)
case core.ScriptTargetES2020:
return NewES2021Transformer(emitContext)
case core.ScriptTargetES2019:
return NewES2020Transformer(emitContext)
case core.ScriptTargetES2018:
return NewES2019Transformer(emitContext)
case core.ScriptTargetES2017:
return NewES2018Transformer(emitContext)
case core.ScriptTargetES2016:
return NewES2017Transformer(emitContext)
default: // other, older, option, transform maximally
return NewES2016Transformer(emitContext)
}
}
20 changes: 20 additions & 0 deletions internal/transformers/estransforms/esdecorator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package estransforms

import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/transformers"
)

type esDecoratorTransformer struct {
transformers.Transformer
}

func (ch *esDecoratorTransformer) visit(node *ast.Node) *ast.Node {
return node // !!!
}

func newESDecoratorTransformer(emitContext *printer.EmitContext) *transformers.Transformer {
tx := &esDecoratorTransformer{}
return tx.NewTransformer(tx.visit, emitContext)
}
20 changes: 20 additions & 0 deletions internal/transformers/estransforms/exponentiation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package estransforms

import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/transformers"
)

type exponentiationTransformer struct {
transformers.Transformer
}

func (ch *exponentiationTransformer) visit(node *ast.Node) *ast.Node {
return node // !!!
}

func newExponentiationTransformer(emitContext *printer.EmitContext) *transformers.Transformer {
tx := &exponentiationTransformer{}
return tx.NewTransformer(tx.visit, emitContext)
}
20 changes: 20 additions & 0 deletions internal/transformers/estransforms/forawait.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package estransforms

import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/transformers"
)

type forawaitTransformer struct {
transformers.Transformer
}

func (ch *forawaitTransformer) visit(node *ast.Node) *ast.Node {
return node // !!!
}

func newforawaitTransformer(emitContext *printer.EmitContext) *transformers.Transformer {
tx := &forawaitTransformer{}
return tx.NewTransformer(tx.visit, emitContext)
}
20 changes: 20 additions & 0 deletions internal/transformers/estransforms/logicalassignment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package estransforms

import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/transformers"
)

type logicalAssignmentTransformer struct {
transformers.Transformer
}

func (ch *logicalAssignmentTransformer) visit(node *ast.Node) *ast.Node {
return node // !!!
}

func newLogicalAssignmentTransformer(emitContext *printer.EmitContext) *transformers.Transformer {
tx := &logicalAssignmentTransformer{}
return tx.NewTransformer(tx.visit, emitContext)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package transformers
package estransforms

import (
"slices"
Expand Down
20 changes: 20 additions & 0 deletions internal/transformers/estransforms/nullishcoalescing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package estransforms

import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/transformers"
)

type nullishCoalescingTransformer struct {
transformers.Transformer
}

func (ch *nullishCoalescingTransformer) visit(node *ast.Node) *ast.Node {
return node // !!!
}

func newNullishCoalescingTransformer(emitContext *printer.EmitContext) *transformers.Transformer {
tx := &nullishCoalescingTransformer{}
return tx.NewTransformer(tx.visit, emitContext)
}
Loading