vendor tsgo

This commit is contained in:
2026-07-09 16:50:43 -04:00
parent c06ea2e5a4
commit 98978e4930
5804 changed files with 1556156 additions and 101 deletions

View File

@@ -0,0 +1,153 @@
package tstransforms
import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/transformers"
)
type ImportElisionTransformer struct {
transformers.Transformer
compilerOptions *core.CompilerOptions
currentSourceFile *ast.SourceFile
emitResolver printer.EmitResolver
}
func NewImportElisionTransformer(opt *transformers.TransformOptions) *transformers.Transformer {
compilerOptions := opt.CompilerOptions
emitContext := opt.Context
if compilerOptions.VerbatimModuleSyntax.IsTrue() {
panic("ImportElisionTransformer should not be used with VerbatimModuleSyntax")
}
tx := &ImportElisionTransformer{compilerOptions: compilerOptions, emitResolver: opt.EmitResolver}
return tx.NewTransformer(tx.visit, emitContext)
}
func (tx *ImportElisionTransformer) visit(node *ast.Node) *ast.Node {
if ast.IsSourceFile(node) && tx.emitResolver != nil {
tx.emitResolver.MarkLinkedReferencesRecursively(tx.EmitContext().MostOriginal(node).AsSourceFile())
}
switch node.Kind {
case ast.KindImportEqualsDeclaration:
if ast.IsExternalModuleImportEqualsDeclaration(node) {
if !tx.shouldEmitAliasDeclaration(node) {
return nil
}
} else {
if !tx.shouldEmitImportEqualsDeclaration(node.AsImportEqualsDeclaration()) {
return nil
}
}
return tx.Visitor().VisitEachChild(node)
case ast.KindImportDeclaration:
n := node.AsImportDeclaration()
// Do not elide a side-effect only import declaration.
// import "foo";
if n.ImportClause != nil {
importClause := tx.Visitor().VisitNode(n.ImportClause)
if importClause == nil {
return nil
}
return tx.Factory().UpdateImportDeclaration(n, n.Modifiers(), importClause, n.ModuleSpecifier, tx.Visitor().VisitNode(n.Attributes))
}
return tx.Visitor().VisitEachChild(node)
case ast.KindImportClause:
n := node.AsImportClause()
name := core.IfElse(tx.shouldEmitAliasDeclaration(node), n.Name(), nil)
namedBindings := tx.Visitor().VisitNode(n.NamedBindings)
if name == nil && namedBindings == nil {
// all import bindings were elided
return nil
}
return tx.Factory().UpdateImportClause(n, n.PhaseModifier, name, namedBindings)
case ast.KindNamespaceImport:
if !tx.shouldEmitAliasDeclaration(node) {
// elide unused imports
return nil
}
return node
case ast.KindNamedImports:
n := node.AsNamedImports()
elements := tx.Visitor().VisitNodes(n.Elements)
if len(elements.Nodes) == 0 {
// all import specifiers were elided
return nil
}
return tx.Factory().UpdateNamedImports(n, elements)
case ast.KindImportSpecifier:
if !tx.shouldEmitAliasDeclaration(node) {
// elide type-only or unused imports
return nil
}
return node
case ast.KindExportAssignment:
if !tx.compilerOptions.VerbatimModuleSyntax.IsTrue() && !tx.isValueAliasDeclaration(node) {
// elide unused import
return nil
}
return tx.Visitor().VisitEachChild(node)
case ast.KindExportDeclaration:
n := node.AsExportDeclaration()
var exportClause *ast.Node
if n.ExportClause != nil {
exportClause = tx.Visitor().VisitNode(n.ExportClause)
if exportClause == nil {
// all export bindings were elided
return nil
}
}
return tx.Factory().UpdateExportDeclaration(n, nil /*modifiers*/, false /*isTypeOnly*/, exportClause, tx.Visitor().VisitNode(n.ModuleSpecifier), tx.Visitor().VisitNode(n.Attributes))
case ast.KindNamedExports:
n := node.AsNamedExports()
elements := tx.Visitor().VisitNodes(n.Elements)
if len(elements.Nodes) == 0 {
// all export specifiers were elided
return nil
}
return tx.Factory().UpdateNamedExports(n, elements)
case ast.KindExportSpecifier:
if !tx.isValueAliasDeclaration(node) {
// elide unused export
return nil
}
return node
case ast.KindSourceFile:
savedCurrentSourceFile := tx.currentSourceFile
tx.currentSourceFile = node.AsSourceFile()
node = tx.Visitor().VisitEachChild(node)
tx.currentSourceFile = savedCurrentSourceFile
return node
case ast.KindModuleDeclaration, ast.KindModuleBlock:
return tx.Visitor().VisitEachChild(node)
default:
return node
}
}
func (tx *ImportElisionTransformer) shouldEmitAliasDeclaration(node *ast.Node) bool {
return ast.IsInJSFile(node) || tx.isReferencedAliasDeclaration(node)
}
func (tx *ImportElisionTransformer) shouldEmitImportEqualsDeclaration(node *ast.ImportEqualsDeclaration) bool {
// preserve old compiler's behavior: emit import declaration (even if we do not consider them referenced) when
// - current file is not external module
// - import declaration is top level and target is value imported by entity name
return tx.shouldEmitAliasDeclaration(node.AsNode()) || (!ast.IsExternalModule(tx.currentSourceFile) && tx.isTopLevelValueImportEqualsWithEntityName(node.AsNode()))
}
func (tx *ImportElisionTransformer) isReferencedAliasDeclaration(node *ast.Node) bool {
node = tx.EmitContext().ParseNode(node)
return node == nil || tx.emitResolver.IsReferencedAliasDeclaration(node)
}
func (tx *ImportElisionTransformer) isValueAliasDeclaration(node *ast.Node) bool {
node = tx.EmitContext().ParseNode(node)
return node == nil || tx.emitResolver.IsValueAliasDeclaration(node)
}
func (tx *ImportElisionTransformer) isTopLevelValueImportEqualsWithEntityName(node *ast.Node) bool {
node = tx.EmitContext().ParseNode(node)
return node != nil && tx.emitResolver.IsTopLevelValueImportEqualsWithEntityName(node)
}

View File

@@ -0,0 +1,270 @@
package tstransforms_test
import (
"testing"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/binder"
"github.com/microsoft/typescript-go/internal/checker"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/module"
"github.com/microsoft/typescript-go/internal/packagejson"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/symlinks"
"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"
"github.com/microsoft/typescript-go/internal/tsoptions"
"github.com/microsoft/typescript-go/internal/tspath"
)
type fakeProgram struct {
singleThreaded bool
compilerOptions *core.CompilerOptions
files []*ast.SourceFile
getEmitModuleFormatOfFile func(sourceFile ast.HasFileName) core.ModuleKind
getImpliedNodeFormatForEmit func(sourceFile ast.HasFileName) core.ModuleKind
getResolvedModule func(currentSourceFile ast.HasFileName, moduleReference string, mode core.ResolutionMode) *module.ResolvedModule
getSourceFile func(FileName string) *ast.SourceFile
getSourceFileForResolvedModule func(FileName string) *ast.SourceFile
}
// GetRedirectForResolution implements checker.Program.
func (p *fakeProgram) GetRedirectForResolution(file ast.HasFileName) *tsoptions.ParsedCommandLine {
panic("unimplemented")
}
// SourceFileMayBeEmitted implements checker.Program.
func (p *fakeProgram) SourceFileMayBeEmitted(sourceFile *ast.SourceFile, forceDtsEmit bool) bool {
panic("unimplemented")
}
// GetEmitSyntaxForUsageLocation implements checker.Program.
func (p *fakeProgram) GetEmitSyntaxForUsageLocation(sourceFile ast.HasFileName, usageLocation *ast.StringLiteralLike) core.ResolutionMode {
panic("unimplemented")
}
// CommonSourceDirectory implements checker.Program.
func (p *fakeProgram) CommonSourceDirectory() string {
panic("unimplemented")
}
func (p *fakeProgram) GetResolvedModuleFromModuleSpecifier(file ast.HasFileName, moduleSpecifier *ast.StringLiteralLike) *module.ResolvedModule {
panic("unimplemented")
}
func (p *fakeProgram) FileExists(path string) bool {
return false
}
func (p *fakeProgram) GetCurrentDirectory() string {
return ""
}
func (p *fakeProgram) GetGlobalTypingsCacheLocation() string {
return ""
}
func (p *fakeProgram) GetNearestAncestorDirectoryWithPackageJson(dirname string) string {
return ""
}
func (p *fakeProgram) GetSymlinkCache() *symlinks.KnownSymlinks {
return nil
}
func (p *fakeProgram) ResolveModuleName(moduleName string, containingFile string, resolutionMode core.ResolutionMode) *module.ResolvedModule {
return nil
}
func (p *fakeProgram) GetPackageJsonInfo(pkgJsonPath string) *packagejson.InfoCacheEntry {
return nil
}
func (p *fakeProgram) GetRedirectTargets(path tspath.Path) []string {
return nil
}
func (p *fakeProgram) GetSourceOfProjectReferenceIfOutputIncluded(file ast.HasFileName) string {
return ""
}
func (p *fakeProgram) GetProjectReferenceFromSource(path tspath.Path) *tsoptions.SourceOutputAndProjectReference {
return nil
}
func (p *fakeProgram) IsSourceFromProjectReference(path tspath.Path) bool {
return false
}
func (p *fakeProgram) GetPackagesMap() map[string]bool {
return nil
}
func (p *fakeProgram) GetProjectReferenceFromOutputDts(path tspath.Path) *tsoptions.SourceOutputAndProjectReference {
return nil
}
func (p *fakeProgram) UseCaseSensitiveFileNames() bool {
return true
}
func (p *fakeProgram) Options() *core.CompilerOptions {
return p.compilerOptions
}
func (p *fakeProgram) SourceFiles() []*ast.SourceFile {
return p.files
}
func (p *fakeProgram) BindSourceFiles() {
wg := core.NewWorkGroup(p.singleThreaded)
for _, file := range p.files {
if !file.IsBound() {
wg.Queue(func() {
binder.BindSourceFile(file)
})
}
}
wg.RunAndWait()
}
func (p *fakeProgram) GetEmitModuleFormatOfFile(sourceFile ast.HasFileName) core.ModuleKind {
return p.getEmitModuleFormatOfFile(sourceFile)
}
func (p *fakeProgram) GetImpliedNodeFormatForEmit(sourceFile ast.HasFileName) core.ModuleKind {
return p.getImpliedNodeFormatForEmit(sourceFile)
}
func (p *fakeProgram) GetDefaultResolutionModeForFile(sourceFile ast.HasFileName) core.ResolutionMode {
return p.getEmitModuleFormatOfFile(sourceFile)
}
func (p *fakeProgram) GetModeForUsageLocation(sourceFile ast.HasFileName, location *ast.Node) core.ResolutionMode {
return p.getEmitModuleFormatOfFile(sourceFile)
}
func (p *fakeProgram) GetResolvedModule(currentSourceFile ast.HasFileName, moduleReference string, mode core.ResolutionMode) *module.ResolvedModule {
return p.getResolvedModule(currentSourceFile, moduleReference, mode)
}
func (p *fakeProgram) GetSourceFile(FileName string) *ast.SourceFile {
return p.getSourceFile(FileName)
}
func (p *fakeProgram) GetSourceFileForResolvedModule(FileName string) *ast.SourceFile {
return p.getSourceFileForResolvedModule(FileName)
}
func (p *fakeProgram) GetSourceFileMetaData(path tspath.Path) ast.SourceFileMetaData {
return ast.SourceFileMetaData{}
}
func (p *fakeProgram) GetImportHelpersImportSpecifier(path tspath.Path) *ast.Node {
return nil
}
func (p *fakeProgram) GetJSXRuntimeImportSpecifier(path tspath.Path) (moduleReference string, specifier *ast.Node) {
return "", nil
}
func (p *fakeProgram) GetResolvedModules() map[tspath.Path]module.ModeAwareCache[*module.ResolvedModule] {
panic("unimplemented")
}
func (p *fakeProgram) IsSourceFileDefaultLibrary(path tspath.Path) bool {
return false
}
func TestImportElision(t *testing.T) {
t.Parallel()
data := []struct {
title string
input string
output string
other string
jsx bool
}{
{title: "ImportEquals#1", input: "import x = require(\"other\"); x;", output: "import x = require(\"other\");\nx;"},
{title: "ImportEquals#2", input: "import x = require(\"other\");", output: ""},
{title: "ImportDeclaration#1", input: `import "m";`, output: `import "m";`},
{title: "ImportDeclaration#2", input: "import * as x from \"other\"; x;", output: "import * as x from \"other\";\nx;"},
{title: "ImportDeclaration#3", input: "import x from \"other\"; x;", output: "import x from \"other\";\nx;"},
{title: "ImportDeclaration#4", input: "import { x } from \"other\"; x;", output: "import { x } from \"other\";\nx;"},
{title: "ImportDeclaration#5", input: "import * as x from \"other\";", output: ""},
{title: "ImportDeclaration#6", input: "import x from \"other\";", output: ""},
{title: "ImportDeclaration#7", input: "import { x } from \"other\";", output: ""},
{title: "ExportDeclaration#1", input: "export * from \"other\";", other: "export let x;", output: "export * from \"other\";"},
{title: "ExportDeclaration#2", input: "export * as x from \"other\";", other: "export let x;", output: "export * as x from \"other\";"},
{title: "ExportDeclaration#3", input: "export * from \"other\";", other: "export let x;", output: "export * from \"other\";"},
{title: "ExportDeclaration#4", input: "export * as x from \"other\";", other: "export let x;", output: "export * as x from \"other\";"},
{title: "ExportDeclaration#5", input: "export { x } from \"other\";", other: "export let x;", output: "export { x } from \"other\";"},
{title: "ExportDeclaration#6", input: "export { x } from \"other\";", other: "export type x = any;", output: ""},
{title: "ExportDeclaration#7", input: "export { x }; let x;", output: "export { x };\nlet x;"},
{title: "ExportDeclaration#8", input: "export { x }; type x = any;", output: ""},
{title: "ExportDeclaration#9", input: "import { x } from \"other\"; export { x };", other: "export type x = any;", output: ""},
{title: "ExportAssignment#1", input: "let x; export default x;", output: "let x;\nexport default x;"},
{title: "ExportAssignment#2", input: "type x = any; export default x;", output: ""},
}
for _, rec := range data {
t.Run(rec.title, func(t *testing.T) {
t.Parallel()
file := parsetestutil.ParseTypeScript(rec.input, rec.jsx)
parsetestutil.CheckDiagnostics(t, file)
files := []*ast.SourceFile{file}
var other *ast.SourceFile
if len(rec.other) > 0 {
other = parsetestutil.ParseTypeScript(rec.other, rec.jsx)
parsetestutil.CheckDiagnostics(t, other)
files = append(files, other)
}
compilerOptions := &core.CompilerOptions{}
c, _ := checker.NewChecker(&fakeProgram{
singleThreaded: true,
compilerOptions: compilerOptions,
files: files,
getEmitModuleFormatOfFile: func(sourceFile ast.HasFileName) core.ModuleKind {
return core.ModuleKindESNext
},
getImpliedNodeFormatForEmit: func(sourceFile ast.HasFileName) core.ModuleKind {
return core.ModuleKindESNext
},
getSourceFile: func(fileName string) *ast.SourceFile {
if fileName == "other.ts" {
return other
}
return nil
},
getSourceFileForResolvedModule: func(fileName string) *ast.SourceFile {
if fileName == "other.ts" {
return other
}
return nil
},
getResolvedModule: func(currentSourceFile ast.HasFileName, moduleReference string, mode core.ResolutionMode) *module.ResolvedModule {
if currentSourceFile == file && moduleReference == "other" {
return &module.ResolvedModule{
ResolvedFileName: "other.ts",
Extension: tspath.ExtensionTs,
}
}
return nil
},
}, nil)
emitResolver := c.GetEmitResolver()
opts := &transformers.TransformOptions{CompilerOptions: compilerOptions, Context: printer.NewEmitContext(), EmitResolver: emitResolver, Resolver: emitResolver}
file = tstransforms.NewTypeEraserTransformer(opts).TransformSourceFile(file)
file = tstransforms.NewImportElisionTransformer(opts).TransformSourceFile(file)
emittestutil.CheckEmit(t, nil, file, rec.output)
})
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,390 @@
package tstransforms
import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/transformers"
)
const USE_NEW_TYPE_METADATA_FORMAT = false
type MetadataTransformer struct {
transformers.Transformer
legacyDecorators bool
resolver printer.EmitResolver
serializer *metadataSerializer
languageVersion core.ScriptTarget
strictNullChecks bool
parent *ast.Node
currentLexicalScope *ast.Node
}
func NewMetadataTransformer(opt *transformers.TransformOptions) *transformers.Transformer {
tx := &MetadataTransformer{
legacyDecorators: opt.CompilerOptions.ExperimentalDecorators.IsTrue(),
resolver: opt.EmitResolver,
languageVersion: opt.CompilerOptions.GetEmitScriptTarget(),
strictNullChecks: opt.CompilerOptions.GetStrictOptionValue(opt.CompilerOptions.StrictNullChecks),
}
return tx.NewTransformer(tx.visit, opt.Context)
}
func (tx *MetadataTransformer) visit(node *ast.Node) *ast.Node {
if (node.SubtreeFacts() & ast.SubtreeContainsDecorators) == 0 {
return node
}
switch node.Kind {
case ast.KindClassDeclaration:
return tx.visitClassDeclaration(node.AsClassDeclaration())
case ast.KindClassExpression:
return tx.visitClassExpression(node.AsClassExpression())
case ast.KindPropertyDeclaration:
return tx.visitPropertyDeclaration(node.AsPropertyDeclaration())
case ast.KindMethodDeclaration:
return tx.visitMethodDeclaration(node.AsMethodDeclaration())
case ast.KindSetAccessor:
return tx.visitSetAccessor(node.AsSetAccessorDeclaration())
case ast.KindGetAccessor:
return tx.visitGetAccessor(node.AsGetAccessorDeclaration())
case ast.KindSourceFile:
tx.parent = nil
defer tx.setParent(nil)
tx.currentLexicalScope = node
defer tx.setCurrentLexicalScope(nil)
tx.serializer = newMetadataSerializer(tx.resolver, tx.Factory(), tx.EmitContext(), tx.languageVersion, tx.strictNullChecks)
updated := tx.Visitor().VisitEachChild(node)
tx.EmitContext().AddEmitHelper(updated, tx.EmitContext().ReadEmitHelpers()...)
return updated
case ast.KindModuleBlock, ast.KindBlock, ast.KindCaseBlock:
oldScope := tx.currentLexicalScope
tx.currentLexicalScope = node
defer tx.setCurrentLexicalScope(oldScope)
return tx.Visitor().VisitEachChild(node)
default:
return tx.Visitor().VisitEachChild(node)
}
}
func (tx *MetadataTransformer) setParent(node *ast.Node) {
tx.parent = node
}
func (tx *MetadataTransformer) setCurrentLexicalScope(node *ast.Node) {
tx.currentLexicalScope = node
}
func (tx *MetadataTransformer) visitClassExpression(node *ast.ClassExpression) *ast.Node {
oldParent := tx.parent
tx.parent = node.AsNode()
defer tx.setParent(oldParent)
if !ast.ClassOrConstructorParameterIsDecorated(tx.legacyDecorators, node.AsNode()) {
return tx.Visitor().VisitEachChild(node.AsNode())
}
modifiers := tx.injectClassTypeMetadata(tx.Visitor().VisitModifiers(node.Modifiers()), node.AsNode())
return tx.Factory().UpdateClassExpression(
node,
modifiers,
tx.Visitor().VisitNode(node.Name()),
tx.Visitor().VisitNodes(node.TypeParameters),
tx.Visitor().VisitNodes(node.HeritageClauses),
tx.Visitor().VisitNodes(node.Members),
)
}
func (tx *MetadataTransformer) visitClassDeclaration(node *ast.ClassDeclaration) *ast.Node {
oldParent := tx.parent
tx.parent = node.AsNode()
defer tx.setParent(oldParent)
if !ast.ClassOrConstructorParameterIsDecorated(tx.legacyDecorators, node.AsNode()) {
return tx.Visitor().VisitEachChild(node.AsNode())
}
modifiers := tx.injectClassTypeMetadata(tx.Visitor().VisitModifiers(node.Modifiers()), node.AsNode())
return tx.Factory().UpdateClassDeclaration(
node,
modifiers,
tx.Visitor().VisitNode(node.Name()),
tx.Visitor().VisitNodes(node.TypeParameters),
tx.Visitor().VisitNodes(node.HeritageClauses),
tx.Visitor().VisitNodes(node.Members),
)
}
func (tx *MetadataTransformer) visitPropertyDeclaration(node *ast.PropertyDeclaration) *ast.Node {
if !ast.HasDecorators(node.AsNode()) {
return tx.Visitor().VisitEachChild(node.AsNode())
}
modifiers := tx.injectClassElementTypeMetadata(tx.Visitor().VisitModifiers(node.Modifiers()), node.AsNode(), tx.parent)
return tx.Factory().UpdatePropertyDeclaration(
node,
modifiers,
tx.Visitor().VisitNode(node.Name()),
tx.Visitor().VisitNode(node.PostfixToken),
tx.Visitor().VisitNode(node.Type),
tx.Visitor().VisitNode(node.Initializer),
)
}
func (tx *MetadataTransformer) visitMethodDeclaration(node *ast.MethodDeclaration) *ast.Node {
if !ast.HasDecorators(node.AsNode()) && len(getDecoratorsOfParameters(node.AsNode())) == 0 {
return tx.Visitor().VisitEachChild(node.AsNode())
}
modifiers := tx.injectClassElementTypeMetadata(tx.Visitor().VisitModifiers(node.Modifiers()), node.AsNode(), tx.parent)
return tx.Factory().UpdateMethodDeclaration(
node,
modifiers,
tx.Visitor().VisitNode(node.AsteriskToken),
tx.Visitor().VisitNode(node.Name()),
tx.Visitor().VisitNode(node.PostfixToken),
tx.Visitor().VisitNodes(node.TypeParameters),
tx.Visitor().VisitNodes(node.Parameters),
tx.Visitor().VisitNode(node.Type),
tx.Visitor().VisitNode(node.FullSignature),
tx.Visitor().VisitNode(node.Body),
)
}
func (tx *MetadataTransformer) visitSetAccessor(node *ast.SetAccessorDeclaration) *ast.Node {
if !ast.HasDecorators(node.AsNode()) && len(getDecoratorsOfParameters(node.AsNode())) == 0 {
return tx.Visitor().VisitEachChild(node.AsNode())
}
modifiers := tx.injectClassElementTypeMetadata(tx.Visitor().VisitModifiers(node.Modifiers()), node.AsNode(), tx.parent)
return tx.Factory().UpdateSetAccessorDeclaration(
node,
modifiers,
tx.Visitor().VisitNode(node.Name()),
tx.Visitor().VisitNodes(node.TypeParameters),
tx.Visitor().VisitNodes(node.Parameters),
tx.Visitor().VisitNode(node.Type),
tx.Visitor().VisitNode(node.FullSignature),
tx.Visitor().VisitNode(node.Body),
)
}
func (tx *MetadataTransformer) visitGetAccessor(node *ast.GetAccessorDeclaration) *ast.Node {
if !ast.HasDecorators(node.AsNode()) {
return tx.Visitor().VisitEachChild(node.AsNode())
}
modifiers := tx.injectClassElementTypeMetadata(tx.Visitor().VisitModifiers(node.Modifiers()), node.AsNode(), tx.parent)
return tx.Factory().UpdateGetAccessorDeclaration(
node,
modifiers,
tx.Visitor().VisitNode(node.Name()),
tx.Visitor().VisitNodes(node.TypeParameters),
tx.Visitor().VisitNodes(node.Parameters),
tx.Visitor().VisitNode(node.Type),
tx.Visitor().VisitNode(node.FullSignature),
tx.Visitor().VisitNode(node.Body),
)
}
func (tx *MetadataTransformer) injectClassTypeMetadata(list *ast.ModifierList, node *ast.Node) *ast.ModifierList {
metadata := tx.getTypeMetadata(node, node)
if len(metadata) > 0 {
var originalNodes []*ast.Node
if list != nil {
originalNodes = list.Nodes
}
if len(originalNodes) == 0 {
res := tx.Factory().NewModifierList(metadata)
if list != nil {
res.Loc = list.Loc
}
return res
}
var modifiersArray []*ast.Node
if ast.IsModifier(originalNodes[0]) && (originalNodes[0].Kind == ast.KindDefaultKeyword || originalNodes[0].Kind == ast.KindExportKeyword) {
modifiersArray = append(modifiersArray, originalNodes[0])
if len(originalNodes) > 1 && (originalNodes[1].Kind == ast.KindDefaultKeyword || originalNodes[1].Kind == ast.KindExportKeyword) {
modifiersArray = append(modifiersArray, originalNodes[1])
}
}
restStart := len(modifiersArray)
decos := core.Filter(originalNodes, ast.IsDecorator)
modifiersArray = append(modifiersArray, decos...)
modifiersArray = append(modifiersArray, metadata...)
otherModifiers := core.Filter(originalNodes[restStart:], ast.IsModifier)
modifiersArray = append(modifiersArray, otherModifiers...)
res := tx.Factory().NewModifierList(modifiersArray)
res.Loc = list.Loc
return res
}
return list
}
func (tx *MetadataTransformer) injectClassElementTypeMetadata(list *ast.ModifierList, node *ast.Node, container *ast.Node) *ast.ModifierList {
if !ast.IsClassLike(container) {
return list
}
if !ast.ClassElementOrClassElementParameterIsDecorated(tx.legacyDecorators, node, container) {
return list
}
metadata := tx.getTypeMetadata(node, container)
if len(metadata) > 0 {
var originalNodes []*ast.Node
if list != nil {
originalNodes = list.Nodes
}
if len(originalNodes) == 0 {
res := tx.Factory().NewModifierList(metadata)
if list != nil {
res.Loc = list.Loc
}
return res
}
var modifiersArray []*ast.Node
decos := core.Filter(originalNodes, ast.IsDecorator)
modifiersArray = append(modifiersArray, decos...)
modifiersArray = append(modifiersArray, metadata...)
modifiers := core.Filter(originalNodes, ast.IsModifier)
modifiersArray = append(modifiersArray, modifiers...)
res := tx.Factory().NewModifierList(modifiersArray)
res.Loc = list.Loc
return res
}
return list
}
/**
* Gets optional type metadata for a declaration.
*
* @param node The declaration node.
*/
func (tx *MetadataTransformer) getTypeMetadata(node *ast.Node, container *ast.Node) []*ast.Node {
// Decorator metadata is not yet supported for ES decorators.
if !tx.legacyDecorators {
return nil
}
if USE_NEW_TYPE_METADATA_FORMAT {
return tx.getNewTypeMetadata(node, container)
}
return tx.getOldTypeMetadata(node, container)
}
func (tx *MetadataTransformer) getOldTypeMetadata(node *ast.Node, container *ast.Node) []*ast.Node {
var decorators []*ast.Node
if tx.shouldAddTypeMetadata(node) {
typeMetadata := tx.Factory().NewMetadataHelper("design:type", tx.serializer.SerializeTypeOfNode(metadataSerializerContext{currentLexicalScope: tx.currentLexicalScope, currentNameScope: container}, node, container))
decorators = append(decorators, tx.Factory().NewDecorator(typeMetadata))
}
if tx.shouldAddParamTypesMetadata(node) {
paramTypesMetadata := tx.Factory().NewMetadataHelper("design:paramtypes", tx.serializer.SerializeParameterTypesOfNode(metadataSerializerContext{currentLexicalScope: tx.currentLexicalScope, currentNameScope: container}, node, container))
decorators = append(decorators, tx.Factory().NewDecorator(paramTypesMetadata))
}
if tx.shouldAddReturnTypeMetadata(node) {
returnTypeMetadata := tx.Factory().NewMetadataHelper("design:returntype", tx.serializer.SerializeReturnTypeOfNode(metadataSerializerContext{currentLexicalScope: tx.currentLexicalScope, currentNameScope: container}, node))
decorators = append(decorators, tx.Factory().NewDecorator(returnTypeMetadata))
}
return decorators
}
func (tx *MetadataTransformer) getNewTypeMetadata(node *ast.Node, container *ast.Node) []*ast.Node {
var properties []*ast.Node
if tx.shouldAddTypeMetadata(node) {
properties = append(properties, tx.Factory().NewPropertyAssignment(
nil,
tx.Factory().NewIdentifier("type"),
nil,
nil,
tx.Factory().NewArrowFunction(
nil,
nil,
tx.Factory().NewNodeList([]*ast.Node{}),
nil,
nil,
tx.Factory().NewToken(ast.KindEqualsGreaterThanToken),
tx.serializer.SerializeTypeOfNode(metadataSerializerContext{currentLexicalScope: tx.currentLexicalScope, currentNameScope: container}, node, container),
),
))
}
if tx.shouldAddParamTypesMetadata(node) {
properties = append(properties, tx.Factory().NewPropertyAssignment(
nil,
tx.Factory().NewIdentifier("paramTypes"),
nil,
nil,
tx.Factory().NewArrowFunction(
nil,
nil,
tx.Factory().NewNodeList([]*ast.Node{}),
nil,
nil,
tx.Factory().NewToken(ast.KindEqualsGreaterThanToken),
tx.serializer.SerializeParameterTypesOfNode(metadataSerializerContext{currentLexicalScope: tx.currentLexicalScope, currentNameScope: container}, node, container),
),
))
}
if tx.shouldAddReturnTypeMetadata(node) {
properties = append(properties, tx.Factory().NewPropertyAssignment(
nil,
tx.Factory().NewIdentifier("returnType"),
nil,
nil,
tx.Factory().NewArrowFunction(
nil,
nil,
tx.Factory().NewNodeList([]*ast.Node{}),
nil,
nil,
tx.Factory().NewToken(ast.KindEqualsGreaterThanToken),
tx.serializer.SerializeReturnTypeOfNode(metadataSerializerContext{currentLexicalScope: tx.currentLexicalScope, currentNameScope: container}, node),
),
))
}
if len(properties) > 0 {
typeInfoMetadata := tx.Factory().NewMetadataHelper("design:typeinfo", tx.Factory().NewObjectLiteralExpression(tx.Factory().NewNodeList(properties), true))
return []*ast.Node{tx.Factory().NewDecorator(typeInfoMetadata)}
}
return nil
}
/**
* Determines whether to emit the "design:type" metadata based on the node's kind.
* The caller should have already tested whether the node has decorators and whether the
* emitDecoratorMetadata compiler option is set.
*
* @param node The node to test.
*/
func (tx *MetadataTransformer) shouldAddTypeMetadata(node *ast.Node) bool {
switch node.Kind {
case ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor, ast.KindPropertyDeclaration:
return true
}
return false
}
/**
* Determines whether to emit the "design:returntype" metadata based on the node's kind.
* The caller should have already tested whether the node has decorators and whether the
* emitDecoratorMetadata compiler option is set.
*
* @param node The node to test.
*/
func (tx *MetadataTransformer) shouldAddReturnTypeMetadata(node *ast.Node) bool {
return node.Kind == ast.KindMethodDeclaration
}
/**
* Determines whether to emit the "design:paramtypes" metadata based on the node's kind.
* The caller should have already tested whether the node has decorators and whether the
* emitDecoratorMetadata compiler option is set.
*
* @param node The node to test.
*/
func (tx *MetadataTransformer) shouldAddParamTypesMetadata(node *ast.Node) bool {
switch node.Kind {
case ast.KindClassDeclaration, ast.KindClassExpression:
return ast.GetFirstConstructorWithBody(node) != nil
case ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor:
return true
}
return false
}

View File

@@ -0,0 +1,995 @@
package tstransforms
// !!! SourceMaps and Comments need to be validated
import (
"slices"
"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/jsnum"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/transformers"
)
// Transforms TypeScript-specific runtime syntax into JavaScript-compatible syntax.
type RuntimeSyntaxTransformer struct {
transformers.Transformer
compilerOptions *core.CompilerOptions
parentNode *ast.Node
currentNode *ast.Node
currentSourceFile *ast.Node
currentScope *ast.Node // SourceFile | Block | ModuleBlock | CaseBlock
currentScopeFirstDeclarationsOfName map[string]*ast.Node
currentEnum *ast.EnumDeclarationNode
currentNamespace *ast.ModuleDeclarationNode
resolver binder.ReferenceResolver
emitResolver printer.EmitResolver
}
func NewRuntimeSyntaxTransformer(opt *transformers.TransformOptions) *transformers.Transformer {
compilerOptions := opt.CompilerOptions
emitContext := opt.Context
tx := &RuntimeSyntaxTransformer{compilerOptions: compilerOptions, resolver: opt.Resolver, emitResolver: opt.EmitResolver}
return tx.NewTransformer(tx.visit, emitContext)
}
// Pushes a new child node onto the ancestor tracking stack, returning the grandparent node to be restored later via `popNode`.
func (tx *RuntimeSyntaxTransformer) pushNode(node *ast.Node) (grandparentNode *ast.Node) {
grandparentNode = tx.parentNode
tx.parentNode = tx.currentNode
tx.currentNode = node
return grandparentNode
}
// Pops the last child node off the ancestor tracking stack, restoring the grandparent node.
func (tx *RuntimeSyntaxTransformer) popNode(grandparentNode *ast.Node) {
tx.currentNode = tx.parentNode
tx.parentNode = grandparentNode
}
func (tx *RuntimeSyntaxTransformer) pushScope(node *ast.Node) (savedCurrentScope *ast.Node, savedCurrentScopeFirstDeclarationsOfName map[string]*ast.Node) {
savedCurrentScope = tx.currentScope
savedCurrentScopeFirstDeclarationsOfName = tx.currentScopeFirstDeclarationsOfName
switch node.Kind {
case ast.KindSourceFile:
tx.currentScope = node
tx.currentSourceFile = node
tx.currentScopeFirstDeclarationsOfName = nil
case ast.KindCaseBlock, ast.KindModuleBlock, ast.KindBlock:
tx.currentScope = node
tx.currentScopeFirstDeclarationsOfName = nil
case ast.KindFunctionDeclaration, ast.KindClassDeclaration, ast.KindVariableStatement:
tx.recordDeclarationInScope(node)
}
return savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName
}
func (tx *RuntimeSyntaxTransformer) popScope(savedCurrentScope *ast.Node, savedCurrentScopeFirstDeclarationsOfName map[string]*ast.Node) {
if tx.currentScope != savedCurrentScope {
// only reset the first declaration for a name if we are exiting the scope in which it was declared
tx.currentScopeFirstDeclarationsOfName = savedCurrentScopeFirstDeclarationsOfName
}
tx.currentScope = savedCurrentScope
}
// Visits each node in the AST
func (tx *RuntimeSyntaxTransformer) visit(node *ast.Node) *ast.Node {
grandparentNode := tx.pushNode(node)
defer tx.popNode(grandparentNode)
savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName := tx.pushScope(node)
defer tx.popScope(savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName)
if node.SubtreeFacts()&ast.SubtreeContainsTypeScript == 0 && (tx.currentNamespace == nil && tx.currentEnum == nil || node.SubtreeFacts()&ast.SubtreeContainsIdentifier == 0) {
return node
}
switch node.Kind {
// TypeScript parameter property modifiers are elided
case ast.KindPublicKeyword,
ast.KindPrivateKeyword,
ast.KindProtectedKeyword,
ast.KindReadonlyKeyword,
ast.KindOverrideKeyword:
node = nil
case ast.KindEnumDeclaration:
node = tx.visitEnumDeclaration(node.AsEnumDeclaration())
case ast.KindModuleDeclaration:
node = tx.visitModuleDeclaration(node.AsModuleDeclaration())
case ast.KindClassDeclaration:
node = tx.visitClassDeclaration(node.AsClassDeclaration())
case ast.KindClassExpression:
node = tx.visitClassExpression(node.AsClassExpression())
case ast.KindConstructor:
node = tx.visitConstructorDeclaration(node.AsConstructorDeclaration())
case ast.KindFunctionDeclaration:
node = tx.visitFunctionDeclaration(node.AsFunctionDeclaration())
case ast.KindVariableStatement:
node = tx.visitVariableStatement(node.AsVariableStatement())
case ast.KindExportDeclaration, ast.KindImportDeclaration, ast.KindImportClause:
if tx.currentNamespace != nil && tx.currentScope != nil && tx.currentScope.Kind != ast.KindBlock {
// do not emit ES6 imports and exports since they are illegal inside a namespace
node = nil
} else {
node = tx.Visitor().VisitEachChild(node)
}
case ast.KindImportEqualsDeclaration:
if tx.currentNamespace != nil && tx.currentScope != nil && tx.currentScope.Kind != ast.KindBlock && node.AsImportEqualsDeclaration().ModuleReference.Kind == ast.KindExternalModuleReference {
// do not emit ES6 imports and exports since they are illegal inside a namespace
node = nil
} else if tx.currentNamespace != nil && tx.currentScope != nil && tx.currentScope.Kind == ast.KindBlock && node.AsImportEqualsDeclaration().ModuleReference.Kind != ast.KindExternalModuleReference {
// inside a block within a namespace, elide internal import aliases
node = nil
} else {
node = tx.visitImportEqualsDeclaration(node.AsImportEqualsDeclaration())
}
case ast.KindIdentifier:
node = tx.visitIdentifier(node)
case ast.KindShorthandPropertyAssignment:
node = tx.visitShorthandPropertyAssignment(node.AsShorthandPropertyAssignment())
default:
node = tx.Visitor().VisitEachChild(node)
}
return node
}
// Records that a declaration was emitted in the current scope, if it was the first declaration for the provided symbol.
func (tx *RuntimeSyntaxTransformer) recordDeclarationInScope(node *ast.Node) {
switch node.Kind {
case ast.KindVariableStatement:
tx.recordDeclarationInScope(node.AsVariableStatement().DeclarationList)
return
case ast.KindVariableDeclarationList:
for _, decl := range node.AsVariableDeclarationList().Declarations.Nodes {
tx.recordDeclarationInScope(decl)
}
return
case ast.KindArrayBindingPattern, ast.KindObjectBindingPattern:
for _, element := range node.Elements() {
tx.recordDeclarationInScope(element)
}
return
}
name := node.Name()
if name != nil {
if ast.IsIdentifier(name) {
if tx.currentScopeFirstDeclarationsOfName == nil {
tx.currentScopeFirstDeclarationsOfName = make(map[string]*ast.Node)
}
text := name.Text()
if _, found := tx.currentScopeFirstDeclarationsOfName[text]; !found {
tx.currentScopeFirstDeclarationsOfName[text] = node
}
} else if ast.IsBindingPattern(name) {
tx.recordDeclarationInScope(name)
}
}
}
// Determines whether a declaration is the first declaration with the same name emitted in the current scope.
func (tx *RuntimeSyntaxTransformer) isFirstDeclarationInScope(node *ast.Node) bool {
name := node.Name()
if name != nil && ast.IsIdentifier(name) {
text := name.Text()
if firstDeclaration, found := tx.currentScopeFirstDeclarationsOfName[text]; found {
return firstDeclaration == node
}
}
return false
}
func (tx *RuntimeSyntaxTransformer) isExportOfNamespace(node *ast.Node) bool {
return tx.currentNamespace != nil && (tx.currentScope == nil || tx.currentScope.Kind != ast.KindBlock) && node.ModifierFlags()&ast.ModifierFlagsExport != 0
}
// Gets an expression that represents a property name, such as `"foo"` for the identifier `foo`.
func (tx *RuntimeSyntaxTransformer) getExpressionForPropertyName(member *ast.EnumMember) *ast.Expression {
name := member.Name()
switch name.Kind {
case ast.KindPrivateIdentifier:
return tx.Factory().NewIdentifier("")
case ast.KindComputedPropertyName:
n := name.AsComputedPropertyName()
// enums don't support computed properties so we always generate the 'expression' part of the name as-is.
return tx.Visitor().VisitNode(n.Expression)
case ast.KindIdentifier:
return tx.Factory().NewStringLiteral(name.Text(), ast.TokenFlagsNone)
case ast.KindStringLiteral: // !!! propagate token flags (will produce new diffs)
return tx.Factory().NewStringLiteral(name.Text(), ast.TokenFlagsNone)
case ast.KindNumericLiteral:
return tx.Factory().NewNumericLiteral(name.Text(), ast.TokenFlagsNone)
default:
return name
}
}
// Gets an expression like `E["A"]` that references an enum member.
func (tx *RuntimeSyntaxTransformer) getEnumQualifiedElement(enum *ast.EnumDeclaration, member *ast.EnumMember) *ast.Expression {
prop := tx.getNamespaceQualifiedElement(tx.getNamespaceContainerName(enum.AsNode()), tx.getExpressionForPropertyName(member))
tx.EmitContext().AddEmitFlags(prop, printer.EFNoComments|printer.EFNoNestedComments|printer.EFNoSourceMap|printer.EFNoNestedSourceMaps)
return prop
}
// Gets an expression used to refer to a namespace or enum from within the body of its declaration.
func (tx *RuntimeSyntaxTransformer) getNamespaceContainerName(node *ast.Node) *ast.IdentifierNode {
return tx.Factory().NewGeneratedNameForNode(node)
}
// Gets an expression used to refer to an export of a namespace or a member of an enum by property name.
func (tx *RuntimeSyntaxTransformer) getNamespaceQualifiedProperty(ns *ast.IdentifierNode, name *ast.IdentifierNode) *ast.Expression {
return tx.Factory().GetNamespaceMemberName(ns, name, printer.NameOptions{AllowSourceMaps: true})
}
// Gets an expression used to refer to an export of a namespace or a member of an enum by indexed access.
func (tx *RuntimeSyntaxTransformer) getNamespaceQualifiedElement(ns *ast.IdentifierNode, expression *ast.Expression) *ast.Expression {
qualifiedName := tx.EmitContext().Factory.NewElementAccessExpression(ns, nil /*questionDotToken*/, expression, ast.NodeFlagsNone)
tx.EmitContext().AssignCommentAndSourceMapRanges(qualifiedName, expression)
return qualifiedName
}
// Gets an expression used within the provided node's container for any exported references.
func (tx *RuntimeSyntaxTransformer) getExportQualifiedReferenceToDeclaration(node *ast.Declaration) *ast.Expression {
if tx.isExportOfNamespace(node.AsNode()) {
return tx.Factory().GetExternalModuleOrNamespaceExportName(tx.getNamespaceContainerName(tx.currentNamespace), node, false /*allowComments*/, true /*allowSourceMaps*/)
}
return tx.Factory().GetDeclarationNameEx(node.AsNode(), printer.NameOptions{AllowSourceMaps: true})
}
func (tx *RuntimeSyntaxTransformer) addVarForDeclaration(statements []*ast.Statement, node *ast.Declaration) ([]*ast.Statement, bool) {
tx.recordDeclarationInScope(node)
if !tx.isFirstDeclarationInScope(node) {
return statements, false
}
// var name;
name := tx.Factory().GetLocalNameEx(node, printer.AssignedNameOptions{AllowSourceMaps: true})
varDecl := tx.Factory().NewVariableDeclaration(name, nil, nil, nil)
varFlags := core.IfElse(tx.currentScope == tx.currentSourceFile, ast.NodeFlagsNone, ast.NodeFlagsLet)
varDecls := tx.Factory().NewVariableDeclarationList(tx.Factory().NewNodeList([]*ast.Node{varDecl}), varFlags)
// Replicate modifierVisitor: strip decorators, TypeScript modifiers, and export when in namespace.
modifierMask := ^(ast.ModifierFlagsTypeScriptModifier | ast.ModifierFlagsDecorator)
if tx.currentNamespace != nil {
modifierMask &^= ast.ModifierFlagsExport
}
modifiers := transformers.ExtractModifiers(tx.EmitContext(), node.Modifiers(), modifierMask)
varStatement := tx.Factory().NewVariableStatement(modifiers, varDecls)
tx.EmitContext().SetOriginal(varDecl, node)
// !!! synthetic comments
tx.EmitContext().SetOriginal(varStatement, node)
// Adjust the source map emit to match the old emitter.
if ast.IsEnumDeclaration(node) {
tx.EmitContext().SetSourceMapRange(varDecls, node.Loc)
} else {
tx.EmitContext().SetSourceMapRange(varStatement, node.Loc)
}
// Trailing comments for enum declaration should be emitted after the function closure
// instead of the variable statement:
//
// /** Leading comment*/
// enum E {
// A
// } // trailing comment
//
// Should emit:
//
// /** Leading comment*/
// var E;
// (function (E) {
// E[E["A"] = 0] = "A";
// })(E || (E = {})); // trailing comment
//
tx.EmitContext().SetCommentRange(varStatement, node.Loc)
tx.EmitContext().AddEmitFlags(varStatement, printer.EFNoTrailingComments)
statements = append(statements, varStatement)
return statements, true
}
func (tx *RuntimeSyntaxTransformer) visitEnumDeclaration(node *ast.EnumDeclaration) *ast.Node {
if !tx.shouldEmitEnumDeclaration(node) {
return tx.EmitContext().NewNotEmittedStatement(node.AsNode())
}
statements := []*ast.Statement{}
// If needed, we should emit a variable declaration for the enum:
// var name;
statements, varAdded := tx.addVarForDeclaration(statements, node.AsNode())
// If we emit a leading variable declaration, we should not emit leading comments for the enum body, but we should
// still emit the comments if we are emitting to a System module.
emitFlags := printer.EFNone
if varAdded && (tx.compilerOptions.GetEmitModuleKind() != core.ModuleKindSystem || tx.currentScope != tx.currentSourceFile) {
emitFlags |= printer.EFNoLeadingComments
}
// x || (x = {})
// exports.x || (exports.x = {})
enumArg := tx.Factory().NewLogicalORExpression(
tx.getExportQualifiedReferenceToDeclaration(node.AsNode()),
tx.Factory().NewAssignmentExpression(
tx.getExportQualifiedReferenceToDeclaration(node.AsNode()),
tx.Factory().NewObjectLiteralExpression(tx.Factory().NewNodeList([]*ast.Node{}), false),
),
)
if tx.isExportOfNamespace(node.AsNode()) {
// `localName` is the expression used within this node's containing scope for any local references.
localName := tx.Factory().GetLocalNameEx(node.AsNode(), printer.AssignedNameOptions{AllowSourceMaps: true})
// x = (exports.x || (exports.x = {}))
enumArg = tx.Factory().NewAssignmentExpression(localName, enumArg)
}
// (function (name) { ... })(name || (name = {}))
enumParamName := tx.Factory().NewGeneratedNameForNode(node.AsNode())
tx.EmitContext().SetSourceMapRange(enumParamName, node.Name().Loc)
enumParam := tx.Factory().NewParameterDeclaration(nil, nil, enumParamName, nil, nil, nil)
enumBody := tx.transformEnumBody(node)
enumFunc := tx.Factory().NewFunctionExpression(nil, nil, nil, nil, tx.Factory().NewNodeList([]*ast.Node{enumParam}), nil, nil, enumBody)
enumCall := tx.Factory().NewCallExpression(tx.Factory().NewParenthesizedExpression(enumFunc), nil, nil, tx.Factory().NewNodeList([]*ast.Node{enumArg}), ast.NodeFlagsNone)
enumStatement := tx.Factory().NewExpressionStatement(enumCall)
tx.EmitContext().SetOriginal(enumStatement, node.AsNode())
tx.EmitContext().AssignCommentAndSourceMapRanges(enumStatement, node.AsNode())
tx.EmitContext().AddEmitFlags(enumStatement, emitFlags)
return tx.Factory().NewSyntaxList(append(statements, enumStatement))
}
// Transforms the body of an enum declaration.
func (tx *RuntimeSyntaxTransformer) transformEnumBody(node *ast.EnumDeclaration) *ast.BlockNode {
savedCurrentEnum := tx.currentEnum
tx.currentEnum = node.AsNode()
// visit the children of `node` in advance to capture any references to enum members
node = tx.Visitor().VisitEachChild(node.AsNode()).AsEnumDeclaration()
statements := []*ast.Statement{}
for i := range len(node.Members.Nodes) {
// E[E["A"] = 0] = "A";
statements = tx.transformEnumMember(
statements,
node,
i,
)
}
statementList := tx.Factory().NewNodeList(statements)
statementList.Loc = node.Members.Loc
tx.currentEnum = savedCurrentEnum
return tx.Factory().NewBlock(statementList, true /*multiline*/)
}
// Transforms an enum member into a statement. It is expected that `enum` has already been visited.
func (tx *RuntimeSyntaxTransformer) transformEnumMember(
statements []*ast.Statement,
enum *ast.EnumDeclaration,
index int,
) []*ast.Statement {
memberNode := enum.Members.Nodes[index]
member := memberNode.AsEnumMember()
savedParent := tx.parentNode
tx.parentNode = tx.currentNode
tx.currentNode = memberNode
// E[E["A"] = x] = "A";
// ^
expression := member.Initializer // NOTE: already visited
var useExplicitReverseMapping bool
parseNode := tx.EmitContext().ParseNode(memberNode)
result := tx.emitResolver.GetEnumMemberValue(parseNode)
switch value := result.Value.(type) {
case jsnum.Number:
expression = core.Coalesce(constantExpression(value, tx.Factory()), expression)
useExplicitReverseMapping = true
case string:
expression = core.Coalesce(constantExpression(value, tx.Factory()), expression)
default:
if expression == nil {
expression = tx.Factory().NewVoidZeroExpression()
}
useExplicitReverseMapping = !result.IsSyntacticallyString
}
// Define the enum member property:
// E[E["A"] = 0] = "A";
// ^^^^^^^^--_____
expression = tx.Factory().NewAssignmentExpression(
tx.getEnumQualifiedElement(enum, member),
expression,
)
if useExplicitReverseMapping {
// E[E["A"] = 0] = "A";
// ^^--------------^^^^^
expression = tx.Factory().NewAssignmentExpression(
tx.Factory().NewElementAccessExpression(
tx.getNamespaceContainerName(enum.AsNode()),
nil, /*questionDotToken*/
expression,
ast.NodeFlagsNone,
),
tx.getExpressionForPropertyName(member),
)
}
memberStatement := tx.Factory().NewExpressionStatement(expression)
tx.EmitContext().AssignCommentAndSourceMapRanges(expression, member.AsNode())
tx.EmitContext().AssignCommentAndSourceMapRanges(memberStatement, member.AsNode())
statements = append(statements, memberStatement)
tx.currentNode = tx.parentNode
tx.parentNode = savedParent
return statements
}
func (tx *RuntimeSyntaxTransformer) visitModuleDeclaration(node *ast.ModuleDeclaration) *ast.Node {
if !tx.shouldEmitModuleDeclaration(node) {
return tx.EmitContext().NewNotEmittedStatement(node.AsNode())
}
statements := []*ast.Statement{}
// If needed, we should emit a variable declaration for the module:
// var name;
statements, varAdded := tx.addVarForDeclaration(statements, node.AsNode())
// If we emit a leading variable declaration, we should not emit leading comments for the module body, but we should
// still emit the comments if we are emitting to a System module.
emitFlags := printer.EFNone
if varAdded && (tx.compilerOptions.GetEmitModuleKind() != core.ModuleKindSystem || tx.currentScope != tx.currentSourceFile) {
emitFlags |= printer.EFNoLeadingComments
}
// x || (x = {})
// exports.x || (exports.x = {})
moduleArg := tx.Factory().NewLogicalORExpression(
tx.getExportQualifiedReferenceToDeclaration(node.AsNode()),
tx.Factory().NewAssignmentExpression(
tx.getExportQualifiedReferenceToDeclaration(node.AsNode()),
tx.Factory().NewObjectLiteralExpression(tx.Factory().NewNodeList([]*ast.Node{}), false),
),
)
if tx.isExportOfNamespace(node.AsNode()) {
// `localName` is the expression used within this node's containing scope for any local references.
localName := tx.Factory().GetLocalNameEx(node.AsNode(), printer.AssignedNameOptions{AllowSourceMaps: true})
// x = (exports.x || (exports.x = {}))
moduleArg = tx.Factory().NewAssignmentExpression(localName, moduleArg)
}
// (function (name) { ... })(name || (name = {}))
moduleParamName := tx.Factory().NewGeneratedNameForNode(node.AsNode())
tx.EmitContext().SetSourceMapRange(moduleParamName, node.Name().Loc)
moduleParam := tx.Factory().NewParameterDeclaration(nil, nil, moduleParamName, nil, nil, nil)
moduleBody := tx.transformModuleBody(node, tx.getNamespaceContainerName(node.AsNode()))
moduleFunc := tx.Factory().NewFunctionExpression(nil, nil, nil, nil, tx.Factory().NewNodeList([]*ast.Node{moduleParam}), nil, nil, moduleBody)
moduleCall := tx.Factory().NewCallExpression(tx.Factory().NewParenthesizedExpression(moduleFunc), nil, nil, tx.Factory().NewNodeList([]*ast.Node{moduleArg}), ast.NodeFlagsNone)
moduleStatement := tx.Factory().NewExpressionStatement(moduleCall)
tx.EmitContext().SetOriginal(moduleStatement, node.AsNode())
tx.EmitContext().AssignCommentAndSourceMapRanges(moduleStatement, node.AsNode())
tx.EmitContext().AddEmitFlags(moduleStatement, emitFlags)
return tx.Factory().NewSyntaxList(append(statements, moduleStatement))
}
func (tx *RuntimeSyntaxTransformer) transformModuleBody(node *ast.ModuleDeclaration, namespaceLocalName *ast.IdentifierNode) *ast.BlockNode {
savedCurrentNamespace := tx.currentNamespace
savedCurrentScope := tx.currentScope
savedCurrentScopeFirstDeclarationsOfName := tx.currentScopeFirstDeclarationsOfName
tx.currentNamespace = node.AsNode()
tx.currentScopeFirstDeclarationsOfName = nil
var statements []*ast.Statement
tx.EmitContext().StartVariableEnvironment()
var statementsLocation core.TextRange
var blockLocation core.TextRange
if node.Body != nil {
if node.Body.Kind == ast.KindModuleBlock {
// visit the children of `node` in advance to capture any references to namespace members
node = tx.Visitor().VisitEachChild(node.AsNode()).AsModuleDeclaration()
body := node.Body.AsModuleBlock()
statements = body.Statements.Nodes
statementsLocation = body.Statements.Loc
blockLocation = body.Loc
} else { // node.Body.Kind == ast.KindModuleDeclaration
// !!! Strada didn't do this; why?
// tx.currentScope = node.AsNode()
statements, _ = tx.Visitor().VisitSlice([]*ast.Node{node.Body})
moduleBlock := getInnermostModuleDeclarationFromDottedModule(node).Body.AsModuleBlock()
statementsLocation = moduleBlock.Statements.Loc.WithPos(-1)
}
}
tx.currentNamespace = savedCurrentNamespace
tx.currentScope = savedCurrentScope
tx.currentScopeFirstDeclarationsOfName = savedCurrentScopeFirstDeclarationsOfName
statements = tx.EmitContext().EndAndMergeVariableEnvironment(statements)
statementList := tx.Factory().NewNodeList(statements)
statementList.Loc = statementsLocation
block := tx.Factory().NewBlock(statementList, true /*multiline*/)
block.Loc = blockLocation
// namespace hello.hi.world {
// function foo() {}
//
// // TODO, blah
// }
//
// should be emitted as
//
// var hello;
// (function (hello) {
// var hi;
// (function (hi) {
// var world;
// (function (world) {
// function foo() { }
// // TODO, blah
// })(world = hi.world || (hi.world = {}));
// })(hi = hello.hi || (hello.hi = {}));
// })(hello || (hello = {}));
//
// We only want to emit comment on the namespace which contains block body itself, not the containing namespaces.
if node.Body == nil || node.Body.Kind != ast.KindModuleBlock {
tx.EmitContext().AddEmitFlags(block, printer.EFNoComments)
}
return block
}
func (tx *RuntimeSyntaxTransformer) visitImportEqualsDeclaration(node *ast.ImportEqualsDeclaration) *ast.Node {
if node.ModuleReference.Kind == ast.KindExternalModuleReference {
return tx.Visitor().VisitEachChild(node.AsNode())
}
moduleReference := tx.Factory().CreateExpressionFromEntityName(node.ModuleReference)
tx.EmitContext().SetEmitFlags(moduleReference, printer.EFNoComments|printer.EFNoNestedComments)
if !tx.isExportOfNamespace(node.AsNode()) {
// export var ${name} = ${moduleReference};
// var ${name} = ${moduleReference};
varDecl := tx.Factory().NewVariableDeclaration(node.Name(), nil /*exclamationToken*/, nil /*type*/, moduleReference)
tx.EmitContext().SetOriginal(varDecl, node.AsNode())
varList := tx.Factory().NewVariableDeclarationList(tx.Factory().NewNodeList([]*ast.Node{varDecl}), ast.NodeFlagsNone)
varModifiers := transformers.ExtractModifiers(tx.EmitContext(), node.Modifiers(), ast.ModifierFlagsExport)
varStatement := tx.Factory().NewVariableStatement(varModifiers, varList)
tx.EmitContext().SetOriginal(varStatement, node.AsNode())
tx.EmitContext().AssignCommentAndSourceMapRanges(varStatement, node.AsNode())
return varStatement
} else {
// exports.${name} = ${moduleReference};
statement := tx.createExportStatement(node.Name(), moduleReference, node.Loc, node.Loc, node.AsNode())
statement.Loc = node.Loc
return statement
}
}
func (tx *RuntimeSyntaxTransformer) visitVariableStatement(node *ast.VariableStatement) *ast.Node {
if tx.isExportOfNamespace(node.AsNode()) {
expressions := []*ast.Expression{}
for _, declaration := range node.DeclarationList.AsVariableDeclarationList().Declarations.Nodes {
v := declaration.AsVariableDeclaration()
if v.Initializer == nil {
continue
}
if ast.IsBindingPattern(v.Name()) {
expression := transformers.FlattenDestructuringAssignment(
&tx.Transformer,
tx.Visitor().VisitNode(declaration),
false, /*needsValue*/
transformers.FlattenLevelAll,
tx.createNamespaceExportExpression,
)
if expression != nil {
expressions = append(expressions, expression)
}
} else {
expression := transformers.ConvertVariableDeclarationToAssignmentExpression(tx.EmitContext(), v)
if expression != nil {
expressions = append(expressions, expression)
}
}
}
if len(expressions) == 0 {
return nil
}
expression := tx.Factory().InlineExpressions(expressions)
statement := tx.Factory().NewExpressionStatement(expression)
tx.EmitContext().SetOriginal(statement, node.AsNode())
tx.EmitContext().AssignCommentAndSourceMapRanges(statement, node.AsNode())
// re-visit as the new node
savedCurrent := tx.currentNode
tx.currentNode = statement
statement = tx.Visitor().VisitEachChild(statement)
tx.currentNode = savedCurrent
return statement
}
return tx.Visitor().VisitEachChild(node.AsNode())
}
// createNamespaceExportExpression creates an assignment to a namespace member for use as a
// callback during destructuring flattening.
func (tx *RuntimeSyntaxTransformer) createNamespaceExportExpression(exportName *ast.IdentifierNode, exportValue *ast.Expression, location *core.TextRange) *ast.Expression {
memberName := tx.getNamespaceQualifiedProperty(tx.getNamespaceContainerName(tx.currentNamespace), exportName)
expression := tx.Factory().NewAssignmentExpression(memberName, exportValue)
if location != nil {
expression.Loc = *location
}
return expression
}
func (tx *RuntimeSyntaxTransformer) visitFunctionDeclaration(node *ast.FunctionDeclaration) *ast.Node {
if tx.isExportOfNamespace(node.AsNode()) {
updated := tx.Factory().UpdateFunctionDeclaration(
node,
tx.Visitor().VisitModifiers(transformers.ExtractModifiers(tx.EmitContext(), node.Modifiers(), ^ast.ModifierFlagsExport)),
node.AsteriskToken,
tx.Visitor().VisitNode(node.Name()),
nil, /*typeParameters*/
tx.Visitor().VisitNodes(node.Parameters),
nil, /*returnType*/
nil, /*fullSignature*/
tx.Visitor().VisitNode(node.Body),
)
export := tx.createExportStatementForDeclaration(node.AsNode())
if export != nil {
return tx.Factory().NewSyntaxList([]*ast.Node{updated, export})
}
return updated
}
return tx.Visitor().VisitEachChild(node.AsNode())
}
func (tx *RuntimeSyntaxTransformer) getParameterProperties(constructor *ast.Node) []*ast.ParameterDeclaration {
var parameterProperties []*ast.ParameterDeclaration
if constructor != nil {
for _, parameter := range constructor.Parameters() {
if ast.IsParameterPropertyDeclaration(parameter, constructor) {
parameterProperties = append(parameterProperties, parameter.AsParameterDeclaration())
}
}
}
return parameterProperties
}
func (tx *RuntimeSyntaxTransformer) visitClassDeclaration(node *ast.ClassDeclaration) *ast.Node {
exported := tx.isExportOfNamespace(node.AsNode())
var modifiers *ast.ModifierList
if exported {
modifiers = tx.Visitor().VisitModifiers(transformers.ExtractModifiers(tx.EmitContext(), node.Modifiers(), ^ast.ModifierFlagsExportDefault))
} else {
modifiers = tx.Visitor().VisitModifiers(node.Modifiers())
}
name := tx.Visitor().VisitNode(node.Name())
if name == nil && (exported || ast.ChildIsDecorated(tx.compilerOptions.ExperimentalDecorators.IsTrue(), node.AsNode(), nil)) {
name = tx.Factory().NewGeneratedNameForNode(node.AsNode())
}
heritageClauses := tx.Visitor().VisitNodes(node.HeritageClauses)
members := tx.Visitor().VisitNodes(node.Members)
parameterProperties := tx.getParameterProperties(core.Find(node.Members.Nodes, ast.IsConstructorDeclaration))
if len(parameterProperties) > 0 {
var newMembers []*ast.ClassElement
for _, parameter := range parameterProperties {
if ast.IsIdentifier(parameter.Name()) {
parameterProperty := tx.Factory().NewPropertyDeclaration(
nil, /*modifiers*/
parameter.Name().Clone(tx.Factory()),
nil, /*questionOrExclamationToken*/
nil, /*type*/
nil, /*initializer*/
)
tx.EmitContext().SetOriginal(parameterProperty, parameter.AsNode())
newMembers = append(newMembers, parameterProperty)
}
}
if len(newMembers) > 0 {
newMembers = append(newMembers, members.Nodes...)
members = tx.Factory().NewNodeList(newMembers)
members.Loc = node.Members.Loc
}
}
updated := tx.Factory().UpdateClassDeclaration(node, modifiers, name, nil /*typeParameters*/, heritageClauses, members)
if exported {
export := tx.createExportStatementForDeclaration(node.AsNode())
if export != nil {
return tx.Factory().NewSyntaxList([]*ast.Node{updated, export})
}
}
return updated
}
func (tx *RuntimeSyntaxTransformer) visitClassExpression(node *ast.ClassExpression) *ast.Node {
modifiers := tx.Visitor().VisitModifiers(transformers.ExtractModifiers(tx.EmitContext(), node.Modifiers(), ^ast.ModifierFlagsExportDefault))
name := tx.Visitor().VisitNode(node.Name())
heritageClauses := tx.Visitor().VisitNodes(node.HeritageClauses)
members := tx.Visitor().VisitNodes(node.Members)
parameterProperties := tx.getParameterProperties(core.Find(node.Members.Nodes, ast.IsConstructorDeclaration))
if len(parameterProperties) > 0 {
var newMembers []*ast.ClassElement
for _, parameter := range parameterProperties {
if ast.IsIdentifier(parameter.Name()) {
parameterProperty := tx.Factory().NewPropertyDeclaration(
nil, /*modifiers*/
parameter.Name().Clone(tx.Factory()),
nil, /*questionOrExclamationToken*/
nil, /*type*/
nil, /*initializer*/
)
tx.EmitContext().SetOriginal(parameterProperty, parameter.AsNode())
newMembers = append(newMembers, parameterProperty)
}
}
if len(newMembers) > 0 {
newMembers = append(newMembers, members.Nodes...)
members = tx.Factory().NewNodeList(newMembers)
members.Loc = node.Members.Loc
}
}
return tx.Factory().UpdateClassExpression(node, modifiers, name, nil /*typeParameters*/, heritageClauses, members)
}
func (tx *RuntimeSyntaxTransformer) visitConstructorDeclaration(node *ast.ConstructorDeclaration) *ast.Node {
modifiers := tx.Visitor().VisitModifiers(node.Modifiers())
parameters := tx.EmitContext().VisitParameters(node.ParameterList(), tx.Visitor())
body := tx.visitConstructorBody(node.Body.AsBlock(), node.AsNode())
return tx.Factory().UpdateConstructorDeclaration(node, modifiers, nil /*typeParameters*/, parameters, nil /*returnType*/, nil /*fullSignature*/, body)
}
func (tx *RuntimeSyntaxTransformer) visitConstructorBody(body *ast.Block, constructor *ast.Node) *ast.Node {
parameterProperties := tx.getParameterProperties(constructor)
if len(parameterProperties) == 0 {
return tx.EmitContext().VisitFunctionBody(body.AsNode(), tx.Visitor())
}
grandparentOfBody := tx.pushNode(body.AsNode())
savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName := tx.pushScope(body.AsNode())
tx.EmitContext().StartVariableEnvironment()
prologue, rest := tx.Factory().SplitStandardPrologue(body.Statements.Nodes)
statements := slices.Clone(prologue)
// Transform parameters into property assignments. Transforms this:
//
// constructor (public x, public y) {
// }
//
// Into this:
//
// constructor (x, y) {
// this.x = x;
// this.y = y;
// }
//
var parameterPropertyAssignments []*ast.Statement
for _, parameter := range parameterProperties {
if ast.IsIdentifier(parameter.Name()) {
propertyName := parameter.Name().Clone(tx.Factory())
propertyName.Parent = parameter.Name().Parent //nolint:customlint // .Parent set to get node to printback using text from original file instead of processed text; TODO: this should be achievable via EmitFlags instead
tx.EmitContext().AddEmitFlags(propertyName, printer.EFNoComments|printer.EFNoSourceMap)
localName := parameter.Name().Clone(tx.Factory())
localName.Parent = parameter.Name().Parent //nolint:customlint // .Parent set to get node to printback using text from original file instead of processed text; TODO: this should be achievable via EmitFlags instead
tx.EmitContext().AddEmitFlags(localName, printer.EFNoComments)
parameterProperty := tx.Factory().NewExpressionStatement(
tx.Factory().NewAssignmentExpression(
tx.Factory().NewPropertyAccessExpression(
tx.Factory().NewThisExpression(),
nil, /*questionDotToken*/
propertyName,
ast.NodeFlagsNone,
),
localName,
),
)
tx.EmitContext().SetOriginal(parameterProperty, parameter.AsNode())
tx.EmitContext().AddEmitFlags(parameterProperty, printer.EFStartOnNewLine)
parameterPropertyAssignments = append(parameterPropertyAssignments, parameterProperty)
}
}
superPath := transformers.FindSuperStatementIndexPath(rest, 0)
if len(superPath) > 0 {
statements = append(statements, tx.transformConstructorBodyWorker(rest, superPath, parameterPropertyAssignments)...)
} else {
statements = append(statements, parameterPropertyAssignments...)
statements = append(statements, core.FirstResult(tx.Visitor().VisitSlice(rest))...)
}
statements = tx.EmitContext().EndAndMergeVariableEnvironment(statements)
statementList := tx.Factory().NewNodeList(statements)
statementList.Loc = body.Statements.Loc
tx.popScope(savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName)
tx.popNode(grandparentOfBody)
updated := tx.Factory().NewBlock(statementList /*multiline*/, true)
tx.EmitContext().SetOriginal(updated, body.AsNode())
updated.Loc = body.Loc
return updated
}
func (tx *RuntimeSyntaxTransformer) transformConstructorBodyWorker(statementsIn []*ast.Statement, superPath []int, initializerStatements []*ast.Statement) []*ast.Statement {
var statementsOut []*ast.Statement
superStatementIndex := superPath[0]
superStatement := statementsIn[superStatementIndex]
// visit up to the statement containing `super`
statementsOut = append(statementsOut, core.FirstResult(tx.Visitor().VisitSlice(statementsIn[:superStatementIndex]))...)
// if the statement containing `super` is a `try` statement, transform the body of the `try` block
if ast.IsTryStatement(superStatement) {
tryStatement := superStatement.AsTryStatement()
tryBlock := tryStatement.TryBlock.AsBlock()
// keep track of hierarchy as we descend
grandparentOfTryStatement := tx.pushNode(tryStatement.AsNode())
grandparentOfTryBlock := tx.pushNode(tryBlock.AsNode())
savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName := tx.pushScope(tryBlock.AsNode())
// visit the `try` block
tryBlockStatements := tx.transformConstructorBodyWorker(
tryBlock.Statements.Nodes,
superPath[1:],
initializerStatements,
)
// restore hierarchy as we ascend to the `try` statement
tx.popScope(savedCurrentScope, savedCurrentScopeFirstDeclarationsOfName)
tx.popNode(grandparentOfTryBlock)
tryBlockStatementList := tx.Factory().NewNodeList(tryBlockStatements)
tryBlockStatementList.Loc = tryBlock.Statements.Loc
statementsOut = append(statementsOut, tx.Factory().UpdateTryStatement(
tryStatement,
tx.Factory().UpdateBlock(tryBlock, tryBlockStatementList, tryBlock.MultiLine),
tx.Visitor().VisitNode(tryStatement.CatchClause),
tx.Visitor().VisitNode(tryStatement.FinallyBlock),
))
// restore hierarchy as we ascend to the parent of the `try` statement
tx.popNode(grandparentOfTryStatement)
} else {
// visit the statement containing `super`
statementsOut = append(statementsOut, core.FirstResult(tx.Visitor().VisitSlice(statementsIn[superStatementIndex:superStatementIndex+1]))...)
// insert the initializer statements
statementsOut = append(statementsOut, initializerStatements...)
}
// visit the statements after `super`
statementsOut = append(statementsOut, core.FirstResult(tx.Visitor().VisitSlice(statementsIn[superStatementIndex+1:]))...)
return statementsOut
}
func (tx *RuntimeSyntaxTransformer) visitShorthandPropertyAssignment(node *ast.ShorthandPropertyAssignment) *ast.Node {
name := node.Name()
exportedOrImportedName := tx.visitExpressionIdentifier(name)
if exportedOrImportedName != name {
expression := exportedOrImportedName
if node.ObjectAssignmentInitializer != nil {
equalsToken := node.EqualsToken
if equalsToken == nil {
equalsToken = tx.Factory().NewToken(ast.KindEqualsToken)
}
expression = tx.Factory().NewBinaryExpression(
nil, /*modifiers*/
expression,
nil, /*typeNode*/
equalsToken,
tx.Visitor().VisitNode(node.ObjectAssignmentInitializer),
)
}
updated := tx.Factory().NewPropertyAssignment(nil /*modifiers*/, node.Name(), nil /*postfixToken*/, nil /*typeNode*/, expression)
updated.Loc = node.Loc
tx.EmitContext().SetOriginal(updated, node.AsNode())
tx.EmitContext().AssignCommentAndSourceMapRanges(updated, node.AsNode())
return updated
}
return tx.Factory().UpdateShorthandPropertyAssignment(
node,
nil, /*modifiers*/
exportedOrImportedName,
nil, /*postfixToken*/
nil, /*typeNode*/
node.EqualsToken,
tx.Visitor().VisitNode(node.ObjectAssignmentInitializer),
)
}
func (tx *RuntimeSyntaxTransformer) visitIdentifier(node *ast.IdentifierNode) *ast.Node {
if transformers.IsIdentifierReference(node, tx.parentNode) {
return tx.visitExpressionIdentifier(node)
}
return node
}
func (tx *RuntimeSyntaxTransformer) visitExpressionIdentifier(node *ast.IdentifierNode) *ast.Node {
if (tx.currentEnum != nil || tx.currentNamespace != nil) && !transformers.IsGeneratedIdentifier(tx.EmitContext(), node) && !transformers.IsLocalName(tx.EmitContext(), node) {
location := tx.EmitContext().MostOriginal(node.AsNode())
container := tx.resolver.GetReferencedExportContainer(location, false /*prefixLocals*/)
if container != nil && (ast.IsEnumDeclaration(container) || ast.IsModuleDeclaration(container)) {
containerName := tx.getNamespaceContainerName(container)
memberName := node.Clone(tx.Factory())
tx.EmitContext().SetEmitFlags(memberName, printer.EFNoComments|printer.EFNoSourceMap)
expression := tx.Factory().GetNamespaceMemberName(containerName, memberName, printer.NameOptions{AllowSourceMaps: true})
tx.EmitContext().AssignCommentAndSourceMapRanges(expression, node.AsNode())
return expression
}
}
return node
}
func (tx *RuntimeSyntaxTransformer) createExportStatementForDeclaration(node *ast.Declaration) *ast.Statement {
exportName := tx.Factory().GetExternalModuleOrNamespaceExportName(tx.getNamespaceContainerName(tx.currentNamespace), node, false /*allowComments*/, true /*allowSourceMaps*/)
localName := tx.Factory().GetLocalName(node)
expression := tx.Factory().NewAssignmentExpression(exportName, localName)
exportAssignmentSourceMapRange := node.Loc
if node.Name() != nil {
exportAssignmentSourceMapRange = exportAssignmentSourceMapRange.WithPos(node.Name().Pos())
}
tx.EmitContext().SetSourceMapRange(expression, exportAssignmentSourceMapRange)
statement := tx.Factory().NewExpressionStatement(expression)
exportStatementSourceMapRange := node.Loc.WithPos(-1)
tx.EmitContext().SetSourceMapRange(statement, exportStatementSourceMapRange)
return statement
}
func (tx *RuntimeSyntaxTransformer) createExportAssignment(name *ast.IdentifierNode, expression *ast.Expression, exportAssignmentSourceMapRange core.TextRange, original *ast.Node) *ast.Expression {
exportName := tx.getNamespaceQualifiedProperty(tx.getNamespaceContainerName(tx.currentNamespace), name)
exportAssignment := tx.Factory().NewAssignmentExpression(exportName, expression)
tx.EmitContext().SetOriginal(exportAssignment, original)
tx.EmitContext().SetSourceMapRange(exportAssignment, exportAssignmentSourceMapRange)
return exportAssignment
}
func (tx *RuntimeSyntaxTransformer) createExportStatement(name *ast.IdentifierNode, expression *ast.Expression, exportAssignmentSourceMapRange core.TextRange, exportStatementSourceMapRange core.TextRange, original *ast.Node) *ast.Statement {
exportStatement := tx.Factory().NewExpressionStatement(tx.createExportAssignment(name, expression, exportAssignmentSourceMapRange, original))
tx.EmitContext().SetOriginal(exportStatement, original)
tx.EmitContext().SetSourceMapRange(exportStatement, exportStatementSourceMapRange)
return exportStatement
}
func (tx *RuntimeSyntaxTransformer) shouldEmitEnumDeclaration(node *ast.EnumDeclaration) bool {
return !ast.IsEnumConst(node.AsNode()) || tx.compilerOptions.ShouldPreserveConstEnums()
}
func (tx *RuntimeSyntaxTransformer) shouldEmitModuleDeclaration(node *ast.ModuleDeclaration) bool {
pn := tx.EmitContext().ParseNode(node.AsNode())
if pn == nil {
// If we can't find a parse tree node, assume the node is instantiated.
return true
}
return ast.IsInstantiatedModule(pn, tx.compilerOptions.ShouldPreserveConstEnums())
}
func getInnermostModuleDeclarationFromDottedModule(moduleDeclaration *ast.ModuleDeclaration) *ast.ModuleDeclaration {
for moduleDeclaration.Body != nil && moduleDeclaration.Body.Kind == ast.KindModuleDeclaration {
moduleDeclaration = moduleDeclaration.Body.AsModuleDeclaration()
}
return moduleDeclaration
}

View File

@@ -0,0 +1,393 @@
package tstransforms
import (
"slices"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/transformers"
)
type TypeEraserTransformer struct {
transformers.Transformer
compilerOptions *core.CompilerOptions
parentNode *ast.Node
currentNode *ast.Node
}
func NewTypeEraserTransformer(opt *transformers.TransformOptions) *transformers.Transformer {
compilerOptions := opt.CompilerOptions
emitContext := opt.Context
tx := &TypeEraserTransformer{compilerOptions: compilerOptions}
return tx.NewTransformer(tx.visit, emitContext)
}
// Pushes a new child node onto the ancestor tracking stack, returning the grandparent node to be restored later via `popNode`.
func (tx *TypeEraserTransformer) pushNode(node *ast.Node) (grandparentNode *ast.Node) {
grandparentNode = tx.parentNode
tx.parentNode = tx.currentNode
tx.currentNode = node
return grandparentNode
}
// Pops the last child node off the ancestor tracking stack, restoring the grandparent node.
func (tx *TypeEraserTransformer) popNode(grandparentNode *ast.Node) {
tx.currentNode = tx.parentNode
tx.parentNode = grandparentNode
}
func (tx *TypeEraserTransformer) elide(node *ast.Statement) *ast.Statement {
return tx.EmitContext().NewNotEmittedStatement(node.AsNode())
}
func (tx *TypeEraserTransformer) visit(node *ast.Node) *ast.Node {
if node.SubtreeFacts()&ast.SubtreeContainsTypeScript == 0 {
return node
}
if ast.IsStatement(node) && ast.HasSyntacticModifier(node, ast.ModifierFlagsAmbient) {
return tx.elide(node)
}
grandparentNode := tx.pushNode(node)
defer tx.popNode(grandparentNode)
switch node.Kind {
case
// TypeScript accessibility and readonly modifiers are elided
ast.KindPublicKeyword,
ast.KindPrivateKeyword,
ast.KindProtectedKeyword,
ast.KindAbstractKeyword,
ast.KindOverrideKeyword,
ast.KindConstKeyword,
ast.KindDeclareKeyword,
ast.KindReadonlyKeyword,
// TypeScript type nodes are elided.
ast.KindArrayType,
ast.KindTupleType,
ast.KindOptionalType,
ast.KindRestType,
ast.KindTypeLiteral,
ast.KindTypePredicate,
ast.KindTypeParameter,
ast.KindAnyKeyword,
ast.KindUnknownKeyword,
ast.KindBooleanKeyword,
ast.KindStringKeyword,
ast.KindNumberKeyword,
ast.KindNeverKeyword,
ast.KindVoidKeyword,
ast.KindSymbolKeyword,
ast.KindConstructorType,
ast.KindFunctionType,
ast.KindTypeQuery,
ast.KindTypeReference,
ast.KindUnionType,
ast.KindIntersectionType,
ast.KindConditionalType,
ast.KindParenthesizedType,
ast.KindThisType,
ast.KindTypeOperator,
ast.KindIndexedAccessType,
ast.KindMappedType,
ast.KindLiteralType,
// TypeScript index signatures are elided.
ast.KindIndexSignature:
return nil
case ast.KindInKeyword, ast.KindOutKeyword:
// TypeScript `in`/`out` variance modifiers are elided. These keywords are only
// meaningful as modifiers on type parameters (which are themselves elided), but they may
// appear as a grammar error on other declarations and must not leak into the emitted JS.
// The `in` binary operator shares this token kind, so only elide when used as a modifier.
if tx.parentNode == nil || !ast.IsBinaryExpression(tx.parentNode) {
return nil
}
return tx.Visitor().VisitEachChild(node)
case ast.KindJSImportDeclaration:
// reparsed commonjs are elided
return nil
case ast.KindTypeAliasDeclaration,
ast.KindJSTypeAliasDeclaration,
ast.KindInterfaceDeclaration:
// TypeScript type-only declarations are elided.
return tx.elide(node)
case ast.KindNamespaceExportDeclaration:
// TypeScript namespace export declarations are elided.
return nil
case ast.KindModuleDeclaration:
if !ast.IsIdentifier(node.Name()) ||
!ast.IsInstantiatedModule(node, tx.compilerOptions.ShouldPreserveConstEnums()) ||
getInnermostModuleDeclarationFromDottedModule(node.AsModuleDeclaration()).Body == nil {
// TypeScript module declarations are elided if they are not instantiated or have no body
return tx.elide(node)
}
return tx.Visitor().VisitEachChild(node)
case ast.KindExpressionWithTypeArguments:
n := node.AsExpressionWithTypeArguments()
return tx.Factory().UpdateExpressionWithTypeArguments(n, tx.Visitor().VisitNode(n.Expression), nil)
case ast.KindPropertyDeclaration:
if tx.compilerOptions.ExperimentalDecorators.IsTrue() && ast.HasSyntacticModifier(node, ast.ModifierFlagsAmbient|ast.ModifierFlagsAbstract) && ast.HasDecorators(node) {
// declare/abstract props with decorators must be preserved until the decorator transform can process them and remove them
n := node.AsPropertyDeclaration()
return tx.Factory().UpdatePropertyDeclaration(n, tx.Visitor().VisitModifiers(n.Modifiers()), tx.Visitor().VisitNode(n.Name()), nil, nil, tx.Visitor().VisitNode(n.Initializer))
}
if ast.HasSyntacticModifier(node, ast.ModifierFlagsAmbient|ast.ModifierFlagsAbstract) {
// TypeScript `declare` fields are elided
return nil
}
n := node.AsPropertyDeclaration()
return tx.Factory().UpdatePropertyDeclaration(n, tx.Visitor().VisitModifiers(n.Modifiers()), tx.Visitor().VisitNode(n.Name()), nil, nil, tx.Visitor().VisitNode(n.Initializer))
case ast.KindConstructor:
n := node.AsConstructorDeclaration()
if ast.NodeIsMissing(n.Body) {
// TypeScript overloads are elided
return nil
}
return tx.Factory().UpdateConstructorDeclaration(n, nil, nil, tx.Visitor().VisitNodes(n.Parameters), nil, nil, tx.Visitor().VisitNode(n.Body))
case ast.KindMethodDeclaration:
n := node.AsMethodDeclaration()
if ast.NodeIsMissing(n.Body) {
// TypeScript overloads are elided
return nil
}
return tx.Factory().UpdateMethodDeclaration(n, tx.Visitor().VisitModifiers(n.Modifiers()), n.AsteriskToken, tx.Visitor().VisitNode(n.Name()), nil, nil, tx.Visitor().VisitNodes(n.Parameters), nil, nil, tx.Visitor().VisitNode(n.Body))
case ast.KindGetAccessor:
n := node.AsGetAccessorDeclaration()
if ast.NodeIsMissing(n.Body) && ast.HasSyntacticModifier(node, ast.ModifierFlagsAbstract) {
// Abstract accessors are elided
return nil
}
body := tx.Visitor().VisitNode(n.Body)
if body == nil {
body = tx.Factory().NewBlock(tx.Factory().NewNodeList(nil), false)
}
return tx.Factory().UpdateGetAccessorDeclaration(n, tx.Visitor().VisitModifiers(n.Modifiers()), tx.Visitor().VisitNode(n.Name()), nil, tx.Visitor().VisitNodes(n.Parameters), nil, nil, body)
case ast.KindSetAccessor:
n := node.AsSetAccessorDeclaration()
if ast.NodeIsMissing(n.Body) && ast.HasSyntacticModifier(node, ast.ModifierFlagsAbstract) {
// Abstract accessors are elided
return nil
}
body := tx.Visitor().VisitNode(n.Body)
if body == nil {
body = tx.Factory().NewBlock(tx.Factory().NewNodeList(nil), false)
}
return tx.Factory().UpdateSetAccessorDeclaration(n, tx.Visitor().VisitModifiers(n.Modifiers()), tx.Visitor().VisitNode(n.Name()), nil, tx.Visitor().VisitNodes(n.Parameters), nil, nil, body)
case ast.KindVariableDeclaration:
n := node.AsVariableDeclaration()
updated := tx.Factory().UpdateVariableDeclaration(n, tx.Visitor().VisitNode(n.Name()), nil, nil, tx.Visitor().VisitNode(n.Initializer))
if n.Type != nil {
tx.EmitContext().SetTypeNode(updated.AsVariableDeclaration().Name(), n.Type)
}
return updated
case ast.KindHeritageClause:
n := node.AsHeritageClause()
if n.Token == ast.KindImplementsKeyword {
// TypeScript `implements` clauses are elided
return nil
}
return tx.Factory().UpdateHeritageClause(n, n.Token, tx.Visitor().VisitNodes(n.Types))
case ast.KindClassDeclaration:
n := node.AsClassDeclaration()
return tx.Factory().UpdateClassDeclaration(n, tx.Visitor().VisitModifiers(n.Modifiers()), tx.Visitor().VisitNode(n.Name()), nil, tx.Visitor().VisitNodes(n.HeritageClauses), tx.Visitor().VisitNodes(n.Members))
case ast.KindClassExpression:
n := node.AsClassExpression()
return tx.Factory().UpdateClassExpression(n, tx.Visitor().VisitModifiers(n.Modifiers()), tx.Visitor().VisitNode(n.Name()), nil, tx.Visitor().VisitNodes(n.HeritageClauses), tx.Visitor().VisitNodes(n.Members))
case ast.KindFunctionDeclaration:
n := node.AsFunctionDeclaration()
if ast.NodeIsMissing(n.Body) {
// TypeScript overloads are elided
return tx.elide(node)
}
return tx.Factory().UpdateFunctionDeclaration(n, tx.Visitor().VisitModifiers(n.Modifiers()), n.AsteriskToken, tx.Visitor().VisitNode(n.Name()), nil, tx.Visitor().VisitNodes(n.Parameters), nil, nil, tx.Visitor().VisitNode(n.Body))
case ast.KindFunctionExpression:
n := node.AsFunctionExpression()
return tx.Factory().UpdateFunctionExpression(n, tx.Visitor().VisitModifiers(n.Modifiers()), n.AsteriskToken, tx.Visitor().VisitNode(n.Name()), nil, tx.Visitor().VisitNodes(n.Parameters), nil, nil, tx.Visitor().VisitNode(n.Body))
case ast.KindArrowFunction:
n := node.AsArrowFunction()
return tx.Factory().UpdateArrowFunction(n, tx.Visitor().VisitModifiers(n.Modifiers()), nil, tx.Visitor().VisitNodes(n.Parameters), nil, nil, n.EqualsGreaterThanToken, tx.Visitor().VisitNode(n.Body))
case ast.KindParameter:
if ast.IsThisParameter(node) {
// TypeScript `this` parameters are elided
return nil
}
n := node.AsParameterDeclaration()
// preserve parameter property modifiers to be handled by the runtime transformer
var modifiers *ast.ModifierList
if ast.IsParameterPropertyDeclaration(node, tx.parentNode) {
modifiers = transformers.ExtractModifiers(tx.EmitContext(), n.Modifiers(), ast.ModifierFlagsParameterPropertyModifier)
}
// preserve decorators for the decorator transforms
if ast.HasDecorators(node) {
decorators := node.Decorators()
visited, _ := tx.Visitor().VisitSlice(decorators)
if modifiers == nil {
modifiers = tx.Factory().NewModifierList(visited)
} else {
modifiers = tx.Factory().NewModifierList(slices.Concat(modifiers.Nodes, visited))
}
}
return tx.Factory().UpdateParameterDeclaration(n, modifiers, n.DotDotDotToken, tx.Visitor().VisitNode(n.Name()), nil, nil, tx.Visitor().VisitNode(n.Initializer))
case ast.KindCallExpression:
n := node.AsCallExpression()
return tx.Factory().UpdateCallExpression(n, tx.Visitor().VisitNode(n.Expression), n.QuestionDotToken, nil, tx.Visitor().VisitNodes(n.Arguments), n.Flags)
case ast.KindNewExpression:
n := node.AsNewExpression()
return tx.Factory().UpdateNewExpression(n, tx.Visitor().VisitNode(n.Expression), nil, tx.Visitor().VisitNodes(n.Arguments))
case ast.KindTaggedTemplateExpression:
n := node.AsTaggedTemplateExpression()
return tx.Factory().UpdateTaggedTemplateExpression(n, tx.Visitor().VisitNode(n.Tag), n.QuestionDotToken, nil, tx.Visitor().VisitNode(n.Template), n.Flags)
case ast.KindNonNullExpression, ast.KindTypeAssertionExpression, ast.KindAsExpression, ast.KindSatisfiesExpression:
partial := tx.Factory().NewPartiallyEmittedExpression(tx.Visitor().VisitNode(node.Expression()))
tx.EmitContext().SetOriginal(partial, node)
partial.Loc = node.Loc
return partial
case ast.KindParenthesizedExpression:
if !ast.IsJSDocTypeAssertion(node) {
n := node.AsParenthesizedExpression()
expression := ast.SkipOuterExpressions(n.Expression, ast.OEKAllExceptAssertionsOrExpressionsWithTypeArguments)
if ast.IsAssertionExpression(expression) || ast.IsSatisfiesExpression(expression) {
partial := tx.Factory().NewPartiallyEmittedExpression(tx.Visitor().VisitNode(n.Expression))
tx.EmitContext().SetOriginal(partial, node)
partial.Loc = node.Loc
return partial
}
}
return tx.Visitor().VisitEachChild(node)
case ast.KindJsxSelfClosingElement:
n := node.AsJsxSelfClosingElement()
return tx.Factory().UpdateJsxSelfClosingElement(n, tx.Visitor().VisitNode(n.TagName), nil, tx.Visitor().VisitNode(n.Attributes))
case ast.KindJsxOpeningElement:
n := node.AsJsxOpeningElement()
return tx.Factory().UpdateJsxOpeningElement(n, tx.Visitor().VisitNode(n.TagName), nil, tx.Visitor().VisitNode(n.Attributes))
case ast.KindImportEqualsDeclaration:
n := node.AsImportEqualsDeclaration()
if n.IsTypeOnly {
// elide type-only imports
return nil
}
return tx.Visitor().VisitEachChild(node)
case ast.KindImportDeclaration:
n := node.AsImportDeclaration()
if n.ImportClause == nil {
// Do not elide a side-effect only import declaration.
// import "foo";
return node
}
importClause := tx.Visitor().VisitNode(n.ImportClause)
if importClause == nil {
return nil
}
return tx.Factory().UpdateImportDeclaration(n, n.Modifiers(), importClause, n.ModuleSpecifier, n.Attributes)
case ast.KindImportClause:
n := node.AsImportClause()
if n.IsTypeOnly() {
// Always elide type-only imports
return nil
}
name := n.Name()
namedBindings := tx.Visitor().VisitNode(n.NamedBindings)
if name == nil && namedBindings == nil {
// all import bindings were elided
return nil
}
return tx.Factory().UpdateImportClause(n, n.PhaseModifier, name, namedBindings)
case ast.KindNamedImports:
n := node.AsNamedImports()
if len(n.Elements.Nodes) == 0 {
// Do not elide a side-effect only import declaration.
return node
}
elements := tx.Visitor().VisitNodes(n.Elements)
if !tx.compilerOptions.VerbatimModuleSyntax.IsTrue() && len(elements.Nodes) == 0 {
// all import specifiers were elided
return nil
}
return tx.Factory().UpdateNamedImports(n, elements)
case ast.KindImportSpecifier:
n := node.AsImportSpecifier()
if n.IsTypeOnly {
// elide type-only or unused imports
return nil
}
return node
case ast.KindExportDeclaration:
n := node.AsExportDeclaration()
if n.IsTypeOnly {
// elide type-only exports
return nil
}
var exportClause *ast.Node
if n.ExportClause != nil {
exportClause = tx.Visitor().VisitNode(n.ExportClause)
if exportClause == nil {
// all export bindings were elided
return nil
}
}
return tx.Factory().UpdateExportDeclaration(n, nil /*modifiers*/, false /*isTypeOnly*/, exportClause, tx.Visitor().VisitNode(n.ModuleSpecifier), tx.Visitor().VisitNode(n.Attributes))
case ast.KindNamedExports:
n := node.AsNamedExports()
if len(n.Elements.Nodes) == 0 {
// Do not elide an empty export declaration.
return node
}
elements := tx.Visitor().VisitNodes(n.Elements)
if !tx.compilerOptions.VerbatimModuleSyntax.IsTrue() && len(elements.Nodes) == 0 {
// all export specifiers were elided
return nil
}
return tx.Factory().UpdateNamedExports(n, elements)
case ast.KindExportSpecifier:
n := node.AsExportSpecifier()
if n.IsTypeOnly {
// elide unused export
return nil
}
return node
case ast.KindEnumDeclaration:
if ast.IsEnumConst(node) {
return node
}
return tx.Visitor().VisitEachChild(node)
default:
return tx.Visitor().VisitEachChild(node)
}
}

View File

@@ -0,0 +1,106 @@
package tstransforms_test
import (
"testing"
"github.com/microsoft/typescript-go/internal/core"
"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 TestTypeEraser(t *testing.T) {
t.Parallel()
data := []struct {
title string
input string
output string
jsx bool
vms bool
}{
{title: "Modifiers", input: "class C { public x; private y }", output: "class C {\n x;\n y;\n}"},
{title: "InterfaceDeclaration", input: "interface I { }", output: ""},
{title: "TypeAliasDeclaration", input: "type T = U;", output: ""},
{title: "NamespaceExportDeclaration", input: "export as namespace N;", output: ""},
{title: "UninstantiatedNamespace1", input: "namespace N {}", output: ""},
{title: "UninstantiatedNamespace2", input: "namespace N { export interface I {} }", output: ""},
{title: "UninstantiatedNamespace3", input: "namespace N { export type T = U; }", output: ""},
{title: "ExpressionWithTypeArguments", input: "F<T>", output: "F;"},
{title: "PropertyDeclaration1", input: "class C { declare x; }", output: "class C {\n}"},
{title: "PropertyDeclaration2", input: "class C { public x: number; }", output: "class C {\n x;\n}"},
{title: "PropertyDeclaration3", input: "class C { public static x: number; }", output: "class C {\n static x;\n}"},
{title: "ConstructorDeclaration1", input: "class C { constructor(); }", output: "class C {\n}"},
{title: "ConstructorDeclaration2", input: "class C { public constructor() {} }", output: "class C {\n constructor() { }\n}"},
{title: "MethodDeclaration1", input: "class C { m(); }", output: "class C {\n}"},
{title: "MethodDeclaration2", input: "class C { public m<T>(): U {} }", output: "class C {\n m() { }\n}"},
{title: "MethodDeclaration3", input: "class C { public static m<T>(): U {} }", output: "class C {\n static m() { }\n}"},
{title: "GetAccessorDeclaration1", input: "class C { get m(); }", output: "class C {\n get m() { }\n}"},
{title: "GetAccessorDeclaration2", input: "class C { public get m<T>(): U {} }", output: "class C {\n get m() { }\n}"},
{title: "GetAccessorDeclaration3", input: "class C { public static get m<T>(): U {} }", output: "class C {\n static get m() { }\n}"},
{title: "SetAccessorDeclaration1", input: "class C { set m(v); }", output: "class C {\n set m(v) { }\n}"},
{title: "SetAccessorDeclaration2", input: "class C { public set m<T>(v): U {} }", output: "class C {\n set m(v) { }\n}"},
{title: "SetAccessorDeclaration3", input: "class C { public static set m<T>(v): U {} }", output: "class C {\n static set m(v) { }\n}"},
{title: "IndexSignature", input: "class C { [key: string]: number; }", output: "class C {\n}"},
{title: "VariableDeclaration1", input: "declare var a;", output: ""},
{title: "VariableDeclaration2", input: "var a: number", output: "var a;"},
{title: "HeritageClause", input: "class C implements I {}", output: "class C {\n}"},
{title: "ClassDeclaration1", input: "declare class C {}", output: ""},
{title: "ClassDeclaration2", input: "class C<T> {}", output: "class C {\n}"},
{title: "ClassExpression", input: "(class C<T> {})", output: "(class C {\n});"},
{title: "FunctionDeclaration1", input: "declare function f() {}", output: ""},
{title: "FunctionDeclaration2", input: "function f();", output: ""},
{title: "FunctionDeclaration3", input: "function f<T>(): U {}", output: "function f() { }"},
{title: "FunctionExpression", input: "(function f<T>(): U {})", output: "(function f() { });"},
{title: "ArrowFunction", input: "(<T>(): U => {})", output: "(() => { });"},
{title: "ParameterDeclaration", input: "function f(this: x, a: number, b?: boolean) {}", output: "function f(a, b) { }"},
{title: "CallExpression", input: "f<T>()", output: "f();"},
{title: "NewExpression1", input: "new f<T>()", output: "new f();"},
{title: "NewExpression2", input: "new f<T>", output: "new f;"},
{title: "TaggedTemplateExpression", input: "f<T>``", output: "f ``;"},
{title: "NonNullExpression", input: "x!", output: "x;"},
{title: "TypeAssertionExpression#1", input: "<T>x", output: "x;"},
{title: "TypeAssertionExpression#2", input: "(<T>x).c", output: "x.c;"},
{title: "AsExpression#1", input: "x as T", output: "x;"},
{title: "AsExpression#2", input: "(x as T).c", output: "x.c;"},
{title: "SatisfiesExpression#1", input: "x satisfies T", output: "x;"},
{title: "SatisfiesExpression#2", input: "(x satisfies T).c", output: "x.c;"},
{title: "JsxSelfClosingElement", input: "<x<T> />", output: "<x />;", jsx: true},
{title: "JsxOpeningElement", input: "<x<T>></x>", output: "<x></x>;", jsx: true},
{title: "ImportEqualsDeclaration#1", input: "import x = require(\"m\");", output: "import x = require(\"m\");"},
{title: "ImportEqualsDeclaration#2", input: "import type x = require(\"m\");", output: ""},
{title: "ImportEqualsDeclaration#3", input: "import x = y;", output: "import x = y;"},
{title: "ImportEqualsDeclaration#4", input: "import type x = y;", output: ""},
{title: "ImportDeclaration#1", input: "import \"m\";", output: "import \"m\";"},
{title: "ImportDeclaration#2", input: "import * as x from \"m\"; x;", output: "import * as x from \"m\";\nx;"},
{title: "ImportDeclaration#3", input: "import x from \"m\"; x;", output: "import x from \"m\";\nx;"},
{title: "ImportDeclaration#4", input: "import { x } from \"m\"; x;", output: "import { x } from \"m\";\nx;"},
{title: "ImportDeclaration#5", input: "import type * as x from \"m\";", output: ""},
{title: "ImportDeclaration#6", input: "import type x from \"m\";", output: ""},
{title: "ImportDeclaration#7", input: "import type { x } from \"m\";", output: ""},
{title: "ImportDeclaration#8", input: "import { type x } from \"m\";", output: ""},
{title: "ImportDeclaration#9", input: "import { type x } from \"m\";", output: "import {} from \"m\";", vms: true},
{title: "ExportDeclaration#1", input: "export * from \"m\";", output: "export * from \"m\";"},
{title: "ExportDeclaration#2", input: "export * as x from \"m\";", output: "export * as x from \"m\";"},
{title: "ExportDeclaration#3", input: "export { x } from \"m\";", output: "export { x } from \"m\";"},
{title: "ExportDeclaration#4", input: "export type * from \"m\";", output: ""},
{title: "ExportDeclaration#5", input: "export type * as x from \"m\";", output: ""},
{title: "ExportDeclaration#6", input: "export type { x } from \"m\";", output: ""},
{title: "ExportDeclaration#7", input: "export { type x } from \"m\";", output: ""},
{title: "ExportDeclaration#7", input: "export { type x } from \"m\";", output: "export {} from \"m\";", vms: true},
}
for _, rec := range data {
t.Run(rec.title, func(t *testing.T) {
t.Parallel()
file := parsetestutil.ParseTypeScript(rec.input, rec.jsx)
parsetestutil.CheckDiagnostics(t, file)
compilerOptions := &core.CompilerOptions{}
if rec.vms {
compilerOptions.VerbatimModuleSyntax = core.TSTrue
}
emittestutil.CheckEmit(t, nil, tstransforms.NewTypeEraserTransformer(&transformers.TransformOptions{CompilerOptions: compilerOptions, Context: printer.NewEmitContext()}).TransformSourceFile(file), rec.output)
})
}
}

View File

@@ -0,0 +1,511 @@
package tstransforms
import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/debug"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/transformers"
)
type metadataSerializer struct {
resolver printer.EmitResolver
languageVersion core.ScriptTarget
strictNullChecks bool
f *printer.NodeFactory
ec *printer.EmitContext
c metadataSerializerContext
}
type metadataSerializerContext struct {
currentLexicalScope *ast.Node
currentNameScope *ast.Node
serializingConditionalTypeBranch bool
}
func newMetadataSerializer(resolver printer.EmitResolver, f *printer.NodeFactory, ec *printer.EmitContext, languageVersion core.ScriptTarget, strictNullChecks bool) *metadataSerializer {
return &metadataSerializer{resolver: resolver, languageVersion: languageVersion, f: f, ec: ec, strictNullChecks: strictNullChecks}
}
func (s *metadataSerializer) setContext(ctx metadataSerializerContext) {
s.c = ctx
}
func (s *metadataSerializer) SerializeTypeOfNode(ctx metadataSerializerContext, node *ast.Node, container *ast.Node) *ast.Node {
oldCtx := s.c
s.c = ctx
defer s.setContext(oldCtx)
return s.serializeTypeOfNode(node, container)
}
func (s *metadataSerializer) SerializeParameterTypesOfNode(ctx metadataSerializerContext, node *ast.Node, container *ast.Node) *ast.Node {
oldCtx := s.c
s.c = ctx
defer s.setContext(oldCtx)
return s.serializeParameterTypesOfNode(node, container)
}
func (s *metadataSerializer) SerializeReturnTypeOfNode(ctx metadataSerializerContext, node *ast.Node) *ast.Node {
oldCtx := s.c
s.c = ctx
defer s.setContext(oldCtx)
return s.serializeReturnTypeOfNode(node)
}
func GetSetAccessorValueParameter(node *ast.SetAccessorDeclaration) *ast.Node {
if node != nil && len(node.Parameters.Nodes) > 0 {
if len(node.Parameters.Nodes) >= 2 && ast.IsThisParameter(node.Parameters.Nodes[0]) {
return node.Parameters.Nodes[1]
}
return node.Parameters.Nodes[0]
}
return nil
}
/**
* Get the type annotation for the value parameter.
*
* @internal
*/
func getSetAccessorTypeAnnotationNode(node *ast.SetAccessorDeclaration) *ast.Node {
p := GetSetAccessorValueParameter(node)
if p != nil && p.Type() != nil {
return p.Type()
}
return nil
}
func getAccessorTypeNode(node *ast.Node, container *ast.Node) *ast.Node {
accessors := ast.GetAllAccessorDeclarations(container.Members(), node)
if accessors.SetAccessor != nil {
return getSetAccessorTypeAnnotationNode(accessors.SetAccessor)
}
if accessors.GetAccessor != nil {
return accessors.GetAccessor.Type
}
return nil
}
/**
* Serializes the type of a node for use with decorator type metadata.
* @param node The node that should have its type serialized.
*/
func (s *metadataSerializer) serializeTypeOfNode(node *ast.Node, container *ast.Node) *ast.Node {
switch node.Kind {
case ast.KindPropertyDeclaration, ast.KindParameter:
return s.serializeTypeNode(node.Type())
case ast.KindGetAccessor, ast.KindSetAccessor:
return s.serializeTypeNode(getAccessorTypeNode(node, container))
case ast.KindClassDeclaration, ast.KindClassExpression, ast.KindMethodDeclaration:
return s.f.NewIdentifier("Function")
default:
return s.f.NewVoidZeroExpression()
}
}
/**
* Serializes the type of a node for use with decorator type metadata.
* @param node The node that should have its type serialized.
*/
func (s *metadataSerializer) serializeParameterTypesOfNode(node *ast.Node, container *ast.Node) *ast.Node {
var valueDeclaration *ast.Node
if ast.IsClassLike(node) {
valueDeclaration = ast.GetFirstConstructorWithBody(node)
} else if ast.IsFunctionLike(node) && ast.NodeIsPresent(node.Body()) {
valueDeclaration = node
}
if valueDeclaration == nil {
return s.f.NewArrayLiteralExpression(s.f.NewNodeList([]*ast.Node{}), false)
}
var expressions []*ast.Node
parameters := getParametersOfDecoratedDeclaration(valueDeclaration, container)
for i, parameter := range parameters.Nodes {
if i == 0 && ast.IsIdentifier(parameter.Name()) && parameter.Name().Text() == "this" {
continue
}
if parameter.AsParameterDeclaration().DotDotDotToken != nil {
expressions = append(expressions, s.serializeTypeNode(ast.GetRestParameterElementType(parameter.Type())))
} else {
expressions = append(expressions, s.serializeTypeOfNode(parameter, container))
}
}
return s.f.NewArrayLiteralExpression(s.f.NewNodeList(expressions), false)
}
func getParametersOfDecoratedDeclaration(node *ast.Node, container *ast.Node) *ast.NodeList {
if container != nil && node.Kind == ast.KindGetAccessor {
acc := ast.GetAllAccessorDeclarations(container.Members(), node)
if acc.SetAccessor != nil {
return acc.SetAccessor.Parameters
}
}
return node.ParameterList()
}
/**
* Serializes the return type of a node for use with decorator type metadata.
* @param node The node that should have its return type serialized.
*/
func (s *metadataSerializer) serializeReturnTypeOfNode(node *ast.Node) *ast.Node {
if ast.IsFunctionLike(node) && node.Type() != nil {
return s.serializeTypeNode(node.Type())
} else if ast.IsAsyncFunction(node) {
return s.f.NewIdentifier("Promise")
}
return s.f.NewVoidZeroExpression()
}
/**
* Serializes a type node for use with decorator type metadata.
*
* Types are serialized in the following fashion:
* - Void types point to "undefined" (e.g. "void 0")
* - Function and Constructor types point to the global "Function" constructor.
* - Interface types with a call or construct signature types point to the global
* "Function" constructor.
* - Array and Tuple types point to the global "Array" constructor.
* - Type predicates and booleans point to the global "Boolean" constructor.
* - String literal types and strings point to the global "String" constructor.
* - Enum and number types point to the global "Number" constructor.
* - Symbol types point to the global "Symbol" constructor.
* - Type references to classes (or class-like variables) point to the constructor for the class.
* - Anything else points to the global "Object" constructor.
*
* @param node The type node to serialize.
*/
func (s *metadataSerializer) serializeTypeNode(node *ast.Node) *ast.Node {
if node == nil {
return s.f.NewIdentifier("Object")
}
node = ast.SkipTypeParentheses(node)
switch node.Kind {
case ast.KindVoidKeyword, ast.KindUndefinedKeyword, ast.KindNeverKeyword:
return s.f.NewVoidZeroExpression()
case ast.KindFunctionType, ast.KindConstructorType:
return s.f.NewIdentifier("Function")
case ast.KindArrayType, ast.KindTupleType:
return s.f.NewIdentifier("Array")
case ast.KindTypePredicate:
if node.AsTypePredicateNode().AssertsModifier != nil {
return s.f.NewVoidZeroExpression()
}
return s.f.NewIdentifier("Boolean")
case ast.KindBooleanKeyword:
return s.f.NewIdentifier("Boolean")
case ast.KindTemplateLiteralType, ast.KindStringKeyword:
return s.f.NewIdentifier("String")
case ast.KindObjectKeyword:
return s.f.NewIdentifier("Object")
case ast.KindLiteralType:
return s.serializeLiteralOfLiteralTypeNode(node.AsLiteralTypeNode().Literal)
case ast.KindNumberKeyword:
return s.f.NewIdentifier("Number")
case ast.KindBigIntKeyword:
return s.serializeBigIntConstructor()
case ast.KindSymbolKeyword:
return s.f.NewIdentifier("Symbol")
case ast.KindTypeReference:
return s.serializeTypeReferenceNode(node.AsTypeReferenceNode())
case ast.KindIntersectionType:
return s.serializeUnionOrIntersectionConstituents(node.AsIntersectionTypeNode().Types.Nodes, true)
case ast.KindUnionType:
return s.serializeUnionOrIntersectionConstituents(node.AsUnionTypeNode().Types.Nodes, false)
case ast.KindConditionalType:
oldState := s.c.serializingConditionalTypeBranch
s.c.serializingConditionalTypeBranch = true
defer func() { s.c.serializingConditionalTypeBranch = oldState }()
return s.serializeUnionOrIntersectionConstituents([]*ast.Node{node.AsConditionalTypeNode().TrueType, node.AsConditionalTypeNode().FalseType}, false)
case ast.KindTypeOperator:
if node.AsTypeOperatorNode().Operator == ast.KindReadonlyKeyword {
return s.serializeTypeNode(node.Type())
}
// TODO: why is `unique symbol` not handled as `Symbol`? This falls back to `Object`
case ast.KindTypeQuery, ast.KindIndexedAccessType, ast.KindMappedType, ast.KindTypeLiteral, ast.KindAnyKeyword, ast.KindUnknownKeyword, ast.KindThisType, ast.KindImportType:
break
// handle JSDoc types from an invalid parse
case ast.KindJSDocAllType, ast.KindJSDocVariadicType:
break
case ast.KindJSDocNullableType, ast.KindJSDocNonNullableType, ast.KindJSDocOptionalType:
return s.serializeTypeNode(node.Type())
default:
debug.FailBadSyntaxKind(node)
return nil
}
return s.f.NewIdentifier("Object")
}
func (s *metadataSerializer) serializeUnionOrIntersectionConstituents(types []*ast.Node, isIntersection bool) *ast.Node {
// Note when updating logic here also update `getEntityNameForDecoratorMetadata` in checker.ts so that aliases can be marked as referenced
var serializedType *ast.Node
for _, typeNode := range types {
typeNode = ast.SkipTypeParentheses(typeNode)
if typeNode.Kind == ast.KindNeverKeyword {
if isIntersection {
return s.f.NewVoidZeroExpression() // Reduce to `never` in an intersection
}
continue // Elide `never` in a union
}
if typeNode.Kind == ast.KindUnknownKeyword {
if !isIntersection {
return s.f.NewIdentifier("Object") // Reduce to `unknown` in a union
}
continue // Elide `unknown` in an intersection
}
if typeNode.Kind == ast.KindAnyKeyword {
return s.f.NewIdentifier("Object") // Reduce to `any` in a union or intersection
}
if !s.strictNullChecks && ((ast.IsLiteralTypeNode(typeNode) && typeNode.AsLiteralTypeNode().Literal.Kind == ast.KindNullKeyword) || typeNode.Kind == ast.KindUndefinedKeyword) {
continue // Elide null and undefined from unions for metadata, just like what we did prior to the implementation of strict null checks
}
serializedConstituent := s.serializeTypeNode(typeNode)
if ast.IsIdentifier(serializedConstituent) && serializedConstituent.AsIdentifier().Text == "Object" {
// One of the individual is global object, return immediately
return serializedConstituent
}
// If there exists union that is not `void 0` expression, check if the the common type is identifier.
// anything more complex and we will just default to Object
if serializedType != nil {
// Different types
if !s.equateSerializedTypeNodes(serializedType, serializedConstituent) {
return s.f.NewIdentifier("Object")
}
} else {
// Initialize the union type
serializedType = serializedConstituent
}
}
// If we were able to find common type, use it
if serializedType != nil {
return serializedType
}
return s.f.NewVoidZeroExpression() // Fallback is only hit if all union constituents are null/undefined/never
}
func (s *metadataSerializer) serializeLiteralOfLiteralTypeNode(node *ast.Node) *ast.Node {
switch node.Kind {
case ast.KindStringLiteral, ast.KindNoSubstitutionTemplateLiteral:
return s.f.NewIdentifier("String")
case ast.KindPrefixUnaryExpression:
operand := node.AsPrefixUnaryExpression().Operand
switch operand.Kind {
case ast.KindNumericLiteral, ast.KindBigIntLiteral:
return s.serializeLiteralOfLiteralTypeNode(operand)
default:
debug.FailBadSyntaxKind(operand)
}
case ast.KindNumericLiteral:
return s.f.NewIdentifier("Number")
case ast.KindBigIntLiteral:
return s.serializeBigIntConstructor()
case ast.KindTrueKeyword, ast.KindFalseKeyword:
return s.f.NewIdentifier("Boolean")
case ast.KindNullKeyword:
return s.f.NewVoidZeroExpression()
default:
debug.FailBadSyntaxKind(node)
return nil
}
return nil
}
/**
* Serializes a TypeReferenceNode to an appropriate JS constructor value for use with decorator type metadata.
* @param node The type reference node.
*/
func (s *metadataSerializer) serializeTypeReferenceNode(node *ast.TypeReferenceNode) *ast.Node {
serialScope := s.c.currentNameScope
if serialScope == nil {
serialScope = s.c.currentLexicalScope
}
kind := s.resolver.GetTypeReferenceSerializationKind(s.ec.ParseNode(node.TypeName), s.ec.ParseNode(serialScope))
switch kind {
case printer.TypeReferenceSerializationKindUnknown:
// From conditional type type reference that cannot be resolved is Similar to any or unknown
if s.c.serializingConditionalTypeBranch {
return s.f.NewIdentifier("Object")
}
serialized := s.serializeEntityNameAsExpressionFallback(node.TypeName)
temp := s.f.NewTempVariable()
s.ec.AddVariableDeclaration(temp)
return s.f.NewConditionalExpression(
s.f.NewTypeCheck(s.f.NewAssignmentExpression(temp, serialized), "function"),
s.f.NewToken(ast.KindQuestionToken),
temp,
s.f.NewToken(ast.KindColonToken),
s.f.NewIdentifier("Object"),
)
case printer.TypeReferenceSerializationKindTypeWithConstructSignatureAndValue:
return s.serializeEntityNameAsExpression(node.TypeName)
case printer.TypeReferenceSerializationKindVoidNullableOrNeverType:
return s.f.NewVoidZeroExpression()
case printer.TypeReferenceSerializationKindBigIntLikeType:
return s.serializeBigIntConstructor()
case printer.TypeReferenceSerializationKindBooleanType:
return s.f.NewIdentifier("Boolean")
case printer.TypeReferenceSerializationKindNumberLikeType:
return s.f.NewIdentifier("Number")
case printer.TypeReferenceSerializationKindStringLikeType:
return s.f.NewIdentifier("String")
case printer.TypeReferenceSerializationKindArrayLikeType:
return s.f.NewIdentifier("Array")
case printer.TypeReferenceSerializationKindESSymbolType:
return s.f.NewIdentifier("Symbol")
case printer.TypeReferenceSerializationKindTypeWithCallSignature:
return s.f.NewIdentifier("Function")
case printer.TypeReferenceSerializationKindPromise:
return s.f.NewIdentifier("Promise")
case printer.TypeReferenceSerializationKindObjectType:
return s.f.NewIdentifier("Object")
default:
debug.AssertNever(kind, "unknown type reference serialization kind")
return nil
}
}
func (s *metadataSerializer) serializeBigIntConstructor() *ast.Node {
if s.languageVersion >= core.ScriptTargetES2020 {
return s.f.NewIdentifier("BigInt")
}
return s.f.NewConditionalExpression(
s.f.NewTypeCheck(s.f.NewIdentifier("BigInt"), "function"),
s.f.NewToken(ast.KindQuestionToken),
s.f.NewIdentifier("BigInt"),
s.f.NewToken(ast.KindColonToken),
s.f.NewIdentifier("Object"),
)
}
/**
* Serializes an entity name as an expression for decorator type metadata.
* @param node The entity name to serialize.
*/
func (s *metadataSerializer) serializeEntityNameAsExpression(node *ast.EntityName) *ast.Node {
switch node.Kind {
case ast.KindIdentifier:
// Create a clone of the name with a new parent, and treat it as if it were
// a source tree node for the purposes of the checker.
name := node.Clone(s.f)
name.Loc = node.Loc
s.ec.UnsetOriginal(name) // make this identifier emulate a parse node, making it behave correctly when inspected by the module transforms
name.Parent = s.ec.ParseNode(s.c.currentLexicalScope) //nolint:customlint // ensure the parent is set to a parse tree node.
return name
case ast.KindQualifiedName:
return s.serializeQualifiedNameAsExpression(node.AsQualifiedName())
}
return nil
}
/**
* Serializes an qualified name as an expression for decorator type metadata.
* @param node The qualified name to serialize.
*/
func (s *metadataSerializer) serializeQualifiedNameAsExpression(node *ast.QualifiedName) *ast.Node {
return s.f.NewPropertyAccessExpression(s.serializeEntityNameAsExpression(node.Left), nil, node.Right, ast.NodeFlagsNone)
}
/**
* Serializes an entity name which may not exist at runtime, but whose access shouldn't throw
* @param node The entity name to serialize.
*/
func (s *metadataSerializer) serializeEntityNameAsExpressionFallback(node *ast.EntityName) *ast.Node {
if node.Kind == ast.KindIdentifier {
// A -> typeof A !== "undefined" && A
copied := s.serializeEntityNameAsExpression(node)
return s.createCheckedValue(copied, copied)
}
if node.AsQualifiedName().Left.Kind == ast.KindIdentifier {
// A.B -> typeof A !== "undefined" && A.B
return s.createCheckedValue(s.serializeEntityNameAsExpression(node.AsQualifiedName().Left), s.serializeEntityNameAsExpression(node))
}
// A.B.C -> typeof A !== "undefined" && (_a = A.B) !== void 0 && _a.C
left := s.serializeEntityNameAsExpressionFallback(node.AsQualifiedName().Left)
temp := s.f.NewTempVariable()
s.ec.AddVariableDeclaration(temp)
return s.f.NewLogicalANDExpression(
s.f.NewLogicalANDExpression(
left.AsBinaryExpression().Left,
s.f.NewStrictInequalityExpression(s.f.NewAssignmentExpression(temp, left.AsBinaryExpression().Right), s.f.NewVoidZeroExpression()),
),
s.f.NewPropertyAccessExpression(temp, nil, node.AsQualifiedName().Right, ast.NodeFlagsNone),
)
}
/**
* Produces an expression that results in `right` if `left` is not undefined at runtime:
*
* ```
* typeof left !== "undefined" && right
* ```
*
* We use `typeof L !== "undefined"` (rather than `L !== undefined`) since `L` may not be declared.
* It's acceptable for this expression to result in `false` at runtime, as the result is intended to be
* further checked by any containing expression.
*/
func (s *metadataSerializer) createCheckedValue(left *ast.Node, right *ast.Node) *ast.Node {
return s.f.NewLogicalANDExpression(
s.f.NewStrictInequalityExpression(s.f.NewTypeOfExpression(left), s.f.NewStringLiteral("undefined", ast.TokenFlagsNone)),
right,
)
}
func (s *metadataSerializer) equateSerializedTypeNodes(left *ast.Node, right *ast.Node) bool {
// temp vars used in fallback
if transformers.IsGeneratedIdentifier(s.ec, left) {
return transformers.IsGeneratedIdentifier(s.ec, right)
}
// entity names
if ast.IsIdentifier(left) {
return ast.IsIdentifier(right) && left.Text() == right.Text()
}
if ast.IsPropertyAccessExpression(left) {
return ast.IsPropertyAccessExpression(right) && s.equateSerializedTypeNodes(left.Expression(), right.Expression()) && s.equateSerializedTypeNodes(left.Name(), right.Name())
}
// `void 0`
if ast.IsVoidExpression(left) {
return ast.IsVoidExpression(right) && ast.IsNumericLiteral(left.Expression()) && ast.IsNumericLiteral(right.Expression()) && left.Expression().Text() == "0" && right.Expression().Text() == "0"
}
// `"undefined"` or `"function"` in `typeof` checks
if ast.IsStringLiteral(left) {
return ast.IsStringLiteral(right) && left.Text() == right.Text()
}
// used in `typeof` checks for fallback
if ast.IsTypeOfExpression(left) {
return ast.IsTypeOfExpression(right) && s.equateSerializedTypeNodes(left.Expression(), right.Expression())
}
// parens in `typeof` checks with temps
if ast.IsParenthesizedExpression(left) {
return ast.IsParenthesizedExpression(right) && s.equateSerializedTypeNodes(left.Expression(), right.Expression())
}
// conditionals used in fallback
if ast.IsConditionalExpression(left) {
return ast.IsConditionalExpression(right) && s.equateSerializedTypeNodes(left.AsConditionalExpression().Condition, right.AsConditionalExpression().Condition) && s.equateSerializedTypeNodes(left.AsConditionalExpression().WhenTrue, right.AsConditionalExpression().WhenTrue) && s.equateSerializedTypeNodes(left.AsConditionalExpression().WhenFalse, right.AsConditionalExpression().WhenFalse)
}
// logical binary and assignments used in fallback
if ast.IsBinaryExpression(left) {
return ast.IsBinaryExpression(right) && left.AsBinaryExpression().OperatorToken.Kind == right.AsBinaryExpression().OperatorToken.Kind && s.equateSerializedTypeNodes(left.AsBinaryExpression().Left, right.AsBinaryExpression().Left) && s.equateSerializedTypeNodes(left.AsBinaryExpression().Right, right.AsBinaryExpression().Right)
}
return false
}

View File

@@ -0,0 +1,29 @@
package tstransforms
import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/jsnum"
"github.com/microsoft/typescript-go/internal/printer"
)
func constantExpression(value any, factory *printer.NodeFactory) *ast.Expression {
switch value := value.(type) {
case string:
return factory.NewStringLiteral(value, ast.TokenFlagsNone)
case jsnum.Number:
if value.IsInf() {
if value > 0 {
return factory.NewIdentifier("Infinity")
}
return factory.NewPrefixUnaryExpression(ast.KindMinusToken, factory.NewIdentifier("Infinity"))
}
if value.IsNaN() {
return factory.NewIdentifier("NaN")
}
if value < 0 {
return factory.NewPrefixUnaryExpression(ast.KindMinusToken, constantExpression(-value, factory))
}
return factory.NewNumericLiteral(value.String(), ast.TokenFlagsNone)
}
return nil
}