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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions tsc/internal/checker/emitresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package checker
import (
"maps"
"slices"
"strings"
"sync"

"github.com/microsoft/TypeScript/tsc/internal/ast"
"github.com/microsoft/TypeScript/tsc/internal/binder"
"github.com/microsoft/TypeScript/tsc/internal/core"
"github.com/microsoft/TypeScript/tsc/internal/evaluator"
"github.com/microsoft/TypeScript/tsc/internal/jsnum"
"github.com/microsoft/TypeScript/tsc/internal/modulespecifiers"
"github.com/microsoft/TypeScript/tsc/internal/nodebuilder"
"github.com/microsoft/TypeScript/tsc/internal/printer"
)
Expand Down Expand Up @@ -127,6 +129,96 @@ func (r *EmitResolver) IsDeclarationVisible(node *ast.Node) bool {
return r.checker.isDeclarationVisible(node)
}

func (r *EmitResolver) IsUniqueESSymbol(symbol *ast.Symbol) bool {
if r.checker.getTypeOfSymbol(symbol).flags&TypeFlagsUniqueESSymbol != 0 {
return true
}
for _, declaration := range symbol.Declarations() {
file := ast.GetSourceFileOfNode(declaration)
if file != nil && file.Symbol != nil {
if exported := file.Symbol.Exports()[symbol.Name()]; exported != nil && r.checker.getTypeOfSymbol(exported).flags&TypeFlagsUniqueESSymbol != 0 {
return true
}
}
}
return false
}

func (r *EmitResolver) IsNameResolvableToSymbol(location *ast.Node, name string, target *ast.Symbol) bool {
resolved := r.checker.resolveName(location, name, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace, nil, false, false)
if resolved == nil {
return false
}
if resolved.Flags()&ast.SymbolFlagsAlias != 0 {
resolved = r.checker.GetAliasedSymbol(resolved)
}
for _, declaration := range target.Declarations() {
if slices.Contains(resolved.Declarations(), declaration) {
return true
}
}
return false
}

func (r *EmitResolver) GetModuleSpecifierForSymbol(symbol *ast.Symbol, enclosingDeclaration *ast.Node) string {
// Symbol tracker calls occur while the checker lock is already held.
contextFile := ast.GetSourceFileOfNode(enclosingDeclaration)
moduleSymbol := symbol.Parent()
if contextFile == nil || moduleSymbol == nil {
return ""
}
resolutionMode := r.checker.program.GetDefaultResolutionModeForFile(contextFile)
endingPreference := modulespecifiers.ImportModuleSpecifierEndingPreferenceNone
if resolutionMode == core.ResolutionModeESM {
endingPreference = modulespecifiers.ImportModuleSpecifierEndingPreferenceJs
}
result := modulespecifiers.GetModuleSpecifiers(
moduleSymbol,
r.checker,
r.checker.compilerOptions,
contextFile,
r.checker.program,
modulespecifiers.UserPreferences{
ImportModuleSpecifierPreference: modulespecifiers.ImportModuleSpecifierPreferenceProjectRelative,
ImportModuleSpecifierEnding: endingPreference,
},
modulespecifiers.ModuleSpecifierOptions{OverrideImportMode: resolutionMode},
false,
)
if len(result.Specifiers) == 0 {
return ""
}
specifier := result.Specifiers[0].AsString()
if (r.checker.compilerOptions.GetModuleResolutionKind() == core.ModuleResolutionKindNode16 || r.checker.compilerOptions.GetModuleResolutionKind() == core.ModuleResolutionKindNodeNext) && strings.Contains(specifier, "/node_modules/") {
swappedMode := core.ModuleKindESNext
if r.checker.program.GetEmitModuleFormatOfFile(contextFile) == core.ModuleKindESNext {
swappedMode = core.ModuleKindCommonJS
}
endingPreference := modulespecifiers.ImportModuleSpecifierEndingPreferenceNone
if swappedMode == core.ModuleKindESNext {
endingPreference = modulespecifiers.ImportModuleSpecifierEndingPreferenceJs
}
alternate := modulespecifiers.GetModuleSpecifiers(
moduleSymbol,
r.checker,
r.checker.compilerOptions,
contextFile,
r.checker.program,
modulespecifiers.UserPreferences{
ImportModuleSpecifierPreference: modulespecifiers.ImportModuleSpecifierPreferenceProjectRelative,
ImportModuleSpecifierEnding: endingPreference,
},
modulespecifiers.ModuleSpecifierOptions{OverrideImportMode: swappedMode},
false,
)
if len(alternate.Specifiers) == 0 || strings.Contains(alternate.Specifiers[0].AsString(), "/node_modules/") {
return ""
}
specifier = alternate.Specifiers[0].AsString()
}
return specifier
}

func (r *EmitResolver) PrecalculateDeclarationEmitVisibility(file *ast.SourceFile) {
r.checkerMu.Lock()
defer r.checkerMu.Unlock()
Expand Down
21 changes: 20 additions & 1 deletion tsc/internal/checker/nodebuilderimpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -848,9 +848,18 @@ func (b *NodeBuilderImpl) createAccessFromSymbolChain(chain []*ast.Symbol, index

func (b *NodeBuilderImpl) symbolToExpression(symbol *ast.Symbol, mask ast.SymbolFlags) *ast.Expression {
b.ctx.tracker.TrackSymbol(symbol, b.ctx.enclosingDeclaration, mask)
b.recordRemappedSymbolReference(symbol)
return b.symbolToExpressionWorker(symbol, mask)
}

func (b *NodeBuilderImpl) recordRemappedSymbolReference(symbol *ast.Symbol) {
if remapper, ok := b.ctx.tracker.(nodebuilder.SymbolRemapper); ok {
if remapped := remapper.GetRemappedSymbol(symbol); remapped != nil {
b.ctx.remappedSymbolReferences[ast.GetSymbolId(symbol)] = remapped
}
}
}

func (b *NodeBuilderImpl) symbolToExpressionWorker(symbol *ast.Symbol, mask ast.SymbolFlags) *ast.Expression {
chain := b.lookupSymbolChainWorker(symbol, mask, false)
return b.createExpressionFromSymbolChain(chain, len(chain)-1)
Expand Down Expand Up @@ -2450,6 +2459,14 @@ func (b *NodeBuilderImpl) trackComputedName(accessExpression *ast.Node, enclosin
// get symbol of the first identifier of the entityName
firstIdentifier := ast.GetFirstIdentifier(accessExpression)
name := b.ch.resolveName(enclosingDeclaration, firstIdentifier.Text(), ast.SymbolFlagsValue|ast.SymbolFlagsExportValue, nil /*nameNotFoundMessage*/, true /*isUse*/, false)
sourceSymbol := b.ch.getSymbolOfNameOrPropertyAccessExpression(ast.GetReparsedNodeForNode(firstIdentifier))
if sourceSymbol != nil && sourceSymbol != name && b.ch.getTypeOfSymbol(sourceSymbol).flags&TypeFlagsUniqueESSymbol != 0 {
// If the destination spelling resolves to a same-named local, track the
// symbol from the source declaration so declaration emit can import it
// under a collision-free local name.
b.ctx.tracker.TrackSymbol(sourceSymbol, enclosingDeclaration, ast.SymbolFlagsValue)
return
}
if name != nil {
b.ctx.tracker.TrackSymbol(name, enclosingDeclaration, ast.SymbolFlagsValue)
} else {
Expand Down Expand Up @@ -2586,7 +2603,9 @@ func (b *NodeBuilderImpl) getPropertyNameNodeForSymbolFromNameType(symbol *ast.S
if nameType.flags&TypeFlagsUniqueESSymbol != 0 {
// The reference was tracked in the destination scope by trackComputedName.
// Reconstructing its spelling in the source scope must not paint that scope's declarations visible.
return b.f.NewComputedPropertyName(b.symbolToExpressionWorker(nameType.AsUniqueESSymbolType().symbol, ast.SymbolFlagsValue))
symbol := nameType.AsUniqueESSymbolType().symbol
b.recordRemappedSymbolReference(symbol)
return b.f.NewComputedPropertyName(b.symbolToExpressionWorker(symbol, ast.SymbolFlagsValue))
}
return nil
}
Expand Down
7 changes: 7 additions & 0 deletions tsc/internal/checker/nodecopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,13 @@ func (w *wrappingTracker) TrackSymbol(symbol *ast.Symbol, enclosingDeclaration *
return false
}

func (w *wrappingTracker) GetRemappedSymbol(symbol *ast.Symbol) *ast.Symbol {
if remapper, ok := w.wrapped.(nodebuilder.SymbolRemapper); ok {
return remapper.GetRemappedSymbol(symbol)
}
return nil
}

func newWrappingTracker(inner nodebuilder.SymbolTracker, bound *recoveryBoundary) *wrappingTracker {
return &wrappingTracker{
wrapped: inner,
Expand Down
7 changes: 7 additions & 0 deletions tsc/internal/checker/symboltracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ func (this *SymbolTrackerImpl) TrackSymbol(symbol *ast.Symbol, enclosingDeclarat
return false
}

func (this *SymbolTrackerImpl) GetRemappedSymbol(symbol *ast.Symbol) *ast.Symbol {
if remapper, ok := this.inner.(nodebuilder.SymbolRemapper); ok {
return remapper.GetRemappedSymbol(symbol)
}
return nil
}

func (this *SymbolTrackerImpl) ReportInaccessibleThisError() {
this.onDiagnosticReported()
if this.inner == nil {
Expand Down
6 changes: 6 additions & 0 deletions tsc/internal/nodebuilder/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ type SymbolTracker interface {
PopErrorFallbackNode()
}

// SymbolRemapper optionally provides the name to use when a referenced symbol
// is represented by a generated binding during declaration emit.
type SymbolRemapper interface {
GetRemappedSymbol(symbol *ast.Symbol) *ast.Symbol
}

// NOTE: If modifying this enum, must modify `TypeFormatFlags` too!
type Flags uint32

Expand Down
3 changes: 3 additions & 0 deletions tsc/internal/printer/emitresolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ type EmitResolver interface {
// declaration emit checker functionality projections
PrecalculateDeclarationEmitVisibility(file *ast.SourceFile)
IsSymbolAccessible(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags, shouldComputeAliasToMarkVisible bool) SymbolAccessibilityResult
IsUniqueESSymbol(symbol *ast.Symbol) bool
IsNameResolvableToSymbol(location *ast.Node, name string, target *ast.Symbol) bool
GetModuleSpecifierForSymbol(symbol *ast.Symbol, enclosingDeclaration *ast.Node) string
IsEntityNameVisible(entityName *ast.Node, enclosingDeclaration *ast.Node) SymbolAccessibilityResult // previously SymbolVisibilityResult in strada - ErrorModuleName never set
IsExpandoFunctionDeclaration(node *ast.Node) bool
IsExpandoFunctionDeclarationUnsafe(node *ast.Node) bool
Expand Down
16 changes: 15 additions & 1 deletion tsc/internal/transformers/declarations/tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,22 @@ func (s *SymbolTrackerImpl) TrackSymbol(symbol *ast.Symbol, enclosingDeclaration
s.classSymbolTracked = true
return false
}
issuedDiagnostic := s.handleSymbolAccessibilityError(s.resolver.IsSymbolAccessible(symbol, enclosingDeclaration, meaning /*shouldComputeAliasToMarkVisible*/, true))
accessibility := s.resolver.IsSymbolAccessible(symbol, enclosingDeclaration, meaning /*shouldComputeAliasToMarkVisible*/, true)
if meaning&ast.SymbolFlagsValue != 0 && s.resolver.IsUniqueESSymbol(symbol) &&
!s.resolver.IsNameResolvableToSymbol(enclosingDeclaration, symbol.Name(), symbol) && s.state.addUniqueSymbolImport != nil && s.state.addUniqueSymbolImport(symbol) {
return false
}
issuedDiagnostic := s.handleSymbolAccessibilityError(accessibility)
return issuedDiagnostic
}

func (s *SymbolTrackerImpl) GetRemappedSymbol(symbol *ast.Symbol) *ast.Symbol {
if s.state.remappedSymbols == nil {
return nil
}
return s.state.remappedSymbols[ast.GetSymbolId(symbol)]
}

func (s *SymbolTrackerImpl) handleSymbolAccessibilityError(symbolAccessibilityResult printer.SymbolAccessibilityResult) bool {
if symbolAccessibilityResult.Accessibility == printer.SymbolAccessibilityAccessible {
// Add aliases back onto the possible imports list if they're not there so we can try them again with updated visibility info
Expand Down Expand Up @@ -246,6 +258,8 @@ type SymbolTrackerSharedState struct {
currentSourceFile *ast.SourceFile
resolver printer.EmitResolver
reportExpandoFunctionErrors func(node *ast.Node)
addUniqueSymbolImport func(symbol *ast.Symbol) bool
remappedSymbols map[ast.SymbolId]*ast.Symbol
}

func (s *SymbolTrackerSharedState) addDiagnostic(diag *ast.Diagnostic) {
Expand Down
66 changes: 66 additions & 0 deletions tsc/internal/transformers/declarations/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ type DeclarationTransformer struct {
expandoHosts map[ast.NodeId]*ast.Node // store the result of transforming expando hosts so they can be inserted later if the host is actually referenced
expandoMembers map[ast.NodeId][]*ast.Node // store any found expando _members_ after transforming them so *if* the host is referenced, they can be emitted alongside it
deferredExpandoAssignments map[ast.NodeId][]*ast.BinaryExpression // expando assignments whose host wasn't visible when collected, processed if the host is late-marked visible
generatedSymbolImports []*ast.Node
generatedSymbolImportIds map[ast.SymbolId]*ast.Symbol
generatedSymbolImportNames map[string]bool
seenProperties collections.Set[thisPropertyAssignmentKey]
thisPropertyAssignmentsCollected []*ast.Node
rawReferencedFiles []ReferencedFilePair
Expand Down Expand Up @@ -113,6 +116,7 @@ func NewDeclarationTransformer(host DeclarationEmitHost, resolver printer.EmitRe
resolver: resolver,
declarationFilePath: declarationFilePath,
}
tx.state.addUniqueSymbolImport = tx.addUniqueSymbolImport
tx.state.reportExpandoFunctionErrors = func(node *ast.Node) {
if !tx.state.isolatedDeclarations {
return
Expand Down Expand Up @@ -290,6 +294,10 @@ func (tx *DeclarationTransformer) visitSourceFile(node *ast.SourceFile) *ast.Nod
tx.resultHasExternalModuleIndicator = false
tx.suppressNewDiagnosticContexts = false
tx.state.lateMarkedStatements = make([]*ast.Node, 0)
tx.generatedSymbolImports = nil
tx.generatedSymbolImportIds = make(map[ast.SymbolId]*ast.Symbol)
tx.generatedSymbolImportNames = make(map[string]bool)
tx.state.remappedSymbols = make(map[ast.SymbolId]*ast.Symbol)
tx.lateStatementReplacementMap = make(map[ast.NodeId]*ast.Node)
tx.expandoHosts = make(map[ast.NodeId]*ast.Node)
tx.expandoMembers = make(map[ast.NodeId][]*ast.Node)
Expand Down Expand Up @@ -351,6 +359,10 @@ func (tx *DeclarationTransformer) transformSourceFile(node *ast.SourceFile) *ast
var combinedStatements *ast.StatementList
statements := tx.Visitor().VisitNodes(node.Statements)
combinedStatements = tx.transformAndReplaceLatePaintedStatements(statements)
if len(tx.generatedSymbolImports) > 0 {
combinedStatements = tx.Factory().NewNodeList(append(tx.generatedSymbolImports, combinedStatements.Nodes...))
tx.resultHasExternalModuleIndicator = true
}
combinedStatements = tx.appendCjsExports(combinedStatements)
combinedStatements.Loc = statements.Loc // setTextRange
if ast.IsExternalOrCommonJSModule(node) {
Expand All @@ -377,6 +389,60 @@ func (tx *DeclarationTransformer) transformSourceFile(node *ast.SourceFile) *ast
return result.AsNode()
}

func (tx *DeclarationTransformer) addUniqueSymbolImport(symbol *ast.Symbol) bool {
if symbol == nil {
return false
}
var exportedSymbol *ast.Symbol
for _, declaration := range symbol.Declarations() {
file := ast.GetSourceFileOfNode(declaration)
if file != nil && file.Symbol != nil {
if candidate := file.Symbol.Exports()[symbol.Name()]; candidate != nil {
if slices.Contains(candidate.Declarations(), declaration) {
exportedSymbol = candidate
break
}
}
}
}
if exportedSymbol == nil {
return false
}
symbolId := ast.GetSymbolId(symbol)
exportedSymbolId := ast.GetSymbolId(exportedSymbol)
if importedAlias := tx.generatedSymbolImportIds[exportedSymbolId]; importedAlias != nil {
tx.state.remappedSymbols[symbolId] = importedAlias
return true
}
specifier := tx.resolver.GetModuleSpecifierForSymbol(exportedSymbol, tx.enclosingDeclaration)
if specifier == "" {
return false
}
localName := exportedSymbol.Name()
for suffix := 1; tx.state.currentSourceFile.Locals[localName] != nil || tx.generatedSymbolImportNames[localName]; suffix++ {
localName = fmt.Sprintf("%s_%d", exportedSymbol.Name(), suffix)
}
importedName := tx.Factory().NewIdentifier(exportedSymbol.Name())
localIdentifier := tx.Factory().NewIdentifier(localName)
var propertyName *ast.Node
if localName != exportedSymbol.Name() {
propertyName = importedName.AsNode()
}
importSpecifier := tx.Factory().NewImportSpecifier(false, propertyName, localIdentifier)
namedImports := tx.Factory().NewNamedImports(tx.Factory().NewNodeList([]*ast.Node{importSpecifier}))
importClause := tx.Factory().NewImportClause(ast.KindTypeKeyword, nil, namedImports)
moduleSpecifier := tx.Factory().NewStringLiteral(specifier, ast.TokenFlagsNone)
importDeclaration := tx.Factory().NewImportDeclaration(nil, importClause, moduleSpecifier, nil)
tx.generatedSymbolImports = append(tx.generatedSymbolImports, importDeclaration)
tx.generatedSymbolImportNames[localName] = true
alias := ast.NewSymbol()
alias.SetName(localName)
tx.generatedSymbolImportIds[exportedSymbolId] = alias
tx.state.remappedSymbols[symbolId] = alias
tx.state.remappedSymbols[exportedSymbolId] = alias
return true
}

func createEmptyExports(factory *ast.NodeFactory) *ast.Node {
return factory.NewExportDeclaration(nil /*isTypeOnly*/, false, factory.NewNamedExports(factory.NewNodeList([]*ast.Node{})), nil, nil)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,8 @@ export interface Interface {
readonly [SYMBOL]: string;
}
export declare function createInstance(): Interface;
//// [index.d.ts]
import type { SYMBOL } from "./bug";
export declare const spread: {
[SYMBOL]: string;
};
Loading