Normalize Phase
The Normalize phase reserves all TypeScript identifiers and resolves naming conflicts.
Entry Point
File: Normalize/NameReservation.cs
public static void ReserveAll(BuildContext ctx, SymbolGraph graph)
{
// 1. Reserve type names in namespace scopes
foreach (var ns in graph.Namespaces)
{
var scope = ScopeFactory.Namespace(ns.Name);
foreach (var type in ns.Types)
{
ctx.Renamer.ReserveTypeName(
type.StableId,
type.TsEmitName,
scope,
"TypeReservation");
}
}
// 2. Reserve member names in type scopes
foreach (var type in graph.AllTypes)
{
ReserveMembers(ctx, type);
}
}
Scope System
Names are reserved in hierarchical scopes:
Namespace Scope: "ns:System.Collections.Generic#internal"
└── Type names (List_1, Dictionary_2, ...)
Type Scope: "type:System.Collections.Generic.List`1#instance"
└── Instance member names (add, remove, count, ...)
Type Scope: "type:System.Collections.Generic.List`1#static"
└── Static member names (empty, ...)
View Scope: "view:System.Collections.Generic.List`1:IEnumerable`1#instance"
└── View member names (getEnumerator, ...)
Conflict Resolution
When a name is already taken in a scope, numeric suffixes are added:
// First reservation
"add" -> "add"
// Conflict - add suffix
"add" -> "add2"
// Another conflict
"add" -> "add3"
Naming Style Transform
The --naming js option applies camelCase transform:
ctx.Renamer.AdoptMemberStyleTransform(name => ToCamelCase(name));
// GetEnumerator -> getEnumerator
// WriteLine -> writeLine
// XMLReader -> xmlReader
Type names are NOT transformed (always PascalCase).
Reserved Word Handling
TypeScript reserved words get trailing underscore:
public static class TypeScriptReservedWords
{
public static (string Sanitized, bool WasSanitized) Sanitize(string name)
{
if (IsReserved(name))
return (name + "_", true);
return (name, false);
}
}
// Examples:
// "default" -> "default_"
// "class" -> "class_"
// "function" -> "function_"
Rename Decisions
Every rename is recorded for bindings generation:
public sealed record RenameDecision
{
public StableId Id { get; }
public string Requested { get; } // Original name
public string Final { get; } // Final TypeScript name
public string Reason { get; } // Why renamed
public string Strategy { get; } // None, NumericSuffix, ReservedWord
public string ScopeKey { get; }
public bool? IsStatic { get; }
}
Querying Final Names
After normalization, all names are queryable:
// Type names
string finalName = ctx.Renamer.GetFinalTypeName(type);
string instanceName = ctx.Renamer.GetInstanceTypeName(type); // T$instance
string staticName = ctx.Renamer.GetStaticInterfaceName(type); // T$static
// Member names
var scope = ScopeFactory.ClassSurface(type, isStatic: false);
string memberName = ctx.Renamer.GetFinalMemberName(member.StableId, scope);
Explicit Interface Members
Explicit interface implementations get interface-suffixed names:
// C#: void ICollection.Clear() { }
// Requested: "clear" (conflicts with own Clear method)
// Final: "clear_ICollection"