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,247 @@
package printer
import (
"unicode/utf8"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/scanner"
"github.com/microsoft/typescript-go/internal/stringutil"
)
type ChangeTrackerWriter struct {
textWriter
lastNonTriviaPosition int
pos map[triviaPositionKey]int
end map[triviaPositionKey]int
}
type triviaPositionKey interface { // *astNode | *ast.NodeList
Pos() int
End() int
}
func NewChangeTrackerWriter(newline string, indentSize int) *ChangeTrackerWriter {
// TODO: Callers passing -1 should pass actual indent options once indent-related formatting is ported.
if indentSize < 0 {
indentSize = defaultIndentSize
}
ctw := &ChangeTrackerWriter{
textWriter: textWriter{newLine: newline, indentSize: indentSize},
lastNonTriviaPosition: 0,
pos: map[triviaPositionKey]int{},
end: map[triviaPositionKey]int{},
}
ctw.textWriter.Clear()
return ctw
}
func (ct *ChangeTrackerWriter) GetPrintHandlers() PrintHandlers {
return PrintHandlers{
OnBeforeEmitNode: func(nodeOpt *ast.Node) {
if nodeOpt != nil {
ct.setPos(nodeOpt)
}
},
OnAfterEmitNode: func(nodeOpt *ast.Node) {
if nodeOpt != nil {
ct.setEnd(nodeOpt)
}
},
OnBeforeEmitNodeList: func(nodesOpt *ast.NodeList) {
if nodesOpt != nil {
ct.setPos(nodesOpt)
}
},
OnAfterEmitNodeList: func(nodesOpt *ast.NodeList) {
if nodesOpt != nil {
ct.setEnd(nodesOpt)
}
},
OnBeforeEmitToken: func(nodeOpt *ast.TokenNode) {
if nodeOpt != nil {
ct.setPos(nodeOpt)
}
},
OnAfterEmitToken: func(nodeOpt *ast.TokenNode) {
if nodeOpt != nil {
ct.setEnd(nodeOpt)
}
},
}
}
func (ct *ChangeTrackerWriter) setPos(node triviaPositionKey) {
ct.pos[node] = ct.lastNonTriviaPosition
}
func (ct *ChangeTrackerWriter) setEnd(node triviaPositionKey) {
ct.end[node] = ct.lastNonTriviaPosition
}
func (ct *ChangeTrackerWriter) getPos(node triviaPositionKey) int {
return ct.pos[node]
}
func (ct *ChangeTrackerWriter) getEnd(node triviaPositionKey) int {
return ct.end[node]
}
func (ct *ChangeTrackerWriter) setLastNonTriviaPosition(s string, force bool) {
if force || scanner.SkipTrivia(s, 0) != len(s) {
ct.lastNonTriviaPosition = ct.textWriter.GetTextPos()
// trim trailing whitespaces
pos := len(s)
for pos > 0 {
r, size := utf8.DecodeLastRuneInString(s[:pos])
if stringutil.IsWhiteSpaceLike(r) {
pos -= size
} else {
break
}
}
ct.lastNonTriviaPosition -= len(s) - pos
}
}
func (ct *ChangeTrackerWriter) AssignPositionsToNode(node *ast.Node, factory *ast.NodeFactory) *ast.Node {
var visitor *ast.NodeVisitor
visitor = &ast.NodeVisitor{
Visit: func(n *ast.Node) *ast.Node { return ct.assignPositionsToNodeWorker(n, visitor) },
Factory: factory,
Hooks: ast.NodeVisitorHooks{
VisitNode: ct.assignPositionsToNodeWorker,
VisitNodes: ct.assignPositionsToNodeArray,
VisitToken: ct.assignPositionsToNodeWorker,
VisitModifiers: func(modifiers *ast.ModifierList, v *ast.NodeVisitor) *ast.ModifierList {
if modifiers != nil {
newNodeList := ct.assignPositionsToNodeArray(&modifiers.NodeList, v)
// Return a new ModifierList so that VisitEachChild/Update detects the
// change and creates a new node with reassigned child positions.
return factory.NewModifierList(newNodeList.Nodes)
}
return modifiers
},
},
}
return ct.assignPositionsToNodeWorker(node, visitor)
}
func (ct *ChangeTrackerWriter) assignPositionsToNodeWorker(
node *ast.Node,
v *ast.NodeVisitor,
) *ast.Node {
if node == nil {
return node
}
visited := node.VisitEachChild(v)
// create proxy node for non synthesized nodes
newNode := visited
if !ast.NodeIsSynthesized(visited) {
newNode = visited.Clone(v.Factory)
}
newNode.ForEachChild(func(child *ast.Node) bool {
child.Parent = newNode
return true
})
newNode.Loc = core.NewTextRange(ct.getPos(node), ct.getEnd(node))
return newNode
}
func (ct *ChangeTrackerWriter) assignPositionsToNodeArray(
nodes *ast.NodeList,
v *ast.NodeVisitor,
) *ast.NodeList {
visited := v.VisitNodes(nodes)
if visited == nil {
return visited
}
if nodes == nil {
// Debug.assert(nodes);
panic("if nodes is nil, visited should not be nil")
}
// clone nodearray if necessary
nodeArray := visited
if visited == nodes {
nodeArray = visited.Clone(v.Factory)
}
nodeArray.Loc = core.NewTextRange(ct.getPos(nodes), ct.getEnd(nodes))
return nodeArray
}
func (ct *ChangeTrackerWriter) Write(text string) {
ct.textWriter.Write(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteTrailingSemicolon(text string) {
ct.textWriter.WriteTrailingSemicolon(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteComment(text string) { ct.textWriter.WriteComment(text) }
func (ct *ChangeTrackerWriter) WriteKeyword(text string) {
ct.textWriter.WriteKeyword(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteOperator(text string) {
ct.textWriter.WriteOperator(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WritePunctuation(text string) {
ct.textWriter.WritePunctuation(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteSpace(text string) {
ct.textWriter.WriteSpace(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteStringLiteral(text string) {
ct.textWriter.WriteStringLiteral(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteParameter(text string) {
ct.textWriter.WriteParameter(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteProperty(text string) {
ct.textWriter.WriteProperty(text)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteSymbol(text string, symbol *ast.Symbol) {
ct.textWriter.WriteSymbol(text, symbol)
ct.setLastNonTriviaPosition(text, false)
}
func (ct *ChangeTrackerWriter) WriteLine() { ct.textWriter.WriteLine() }
func (ct *ChangeTrackerWriter) WriteLineForce(force bool) { ct.textWriter.WriteLineForce(force) }
func (ct *ChangeTrackerWriter) IncreaseIndent() { ct.textWriter.IncreaseIndent() }
func (ct *ChangeTrackerWriter) DecreaseIndent() { ct.textWriter.DecreaseIndent() }
func (ct *ChangeTrackerWriter) Clear() { ct.textWriter.Clear(); ct.lastNonTriviaPosition = 0 }
func (ct *ChangeTrackerWriter) String() string { return ct.textWriter.String() }
func (ct *ChangeTrackerWriter) RawWrite(s string) {
ct.textWriter.RawWrite(s)
ct.setLastNonTriviaPosition(s, false)
}
func (ct *ChangeTrackerWriter) WriteLiteral(s string) {
ct.textWriter.WriteLiteral(s)
ct.setLastNonTriviaPosition(s, true)
}
func (ct *ChangeTrackerWriter) GetTextPos() int { return ct.textWriter.GetTextPos() }
func (ct *ChangeTrackerWriter) GetLine() int { return ct.textWriter.GetLine() }
func (ct *ChangeTrackerWriter) GetColumn() core.UTF16Offset { return ct.textWriter.GetColumn() }
func (ct *ChangeTrackerWriter) GetIndent() int { return ct.textWriter.GetIndent() }
func (ct *ChangeTrackerWriter) IsAtStartOfLine() bool { return ct.textWriter.IsAtStartOfLine() }
func (ct *ChangeTrackerWriter) HasTrailingComment() bool { return ct.textWriter.HasTrailingComment() }
func (ct *ChangeTrackerWriter) HasTrailingWhitespace() bool {
return ct.textWriter.HasTrailingWhitespace()
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
package printer
type EmitFlags uint32
const (
EFSingleLine EmitFlags = 1 << iota // The contents of this node should be emitted on a single line.
EFMultiLine // The contents of this node should be emitted on multiple lines.
EFNoLeadingSourceMap // Do not emit a leading source map location for this node.
EFNoTrailingSourceMap // Do not emit a trailing source map location for this node.
EFNoNestedSourceMaps // Do not emit source map locations for children of this node.
EFNoTokenLeadingSourceMaps // Do not emit leading source map location for token nodes.
EFNoTokenTrailingSourceMaps // Do not emit trailing source map location for token nodes.
EFNoLeadingComments // Do not emit leading comments for this node.
EFNoTrailingComments // Do not emit trailing comments for this node.
EFNoNestedComments // Do not emit nested comments for children of this node.
EFHelperName // The Identifier refers to an *unscoped* emit helper (one that is emitted at the top of the file)
EFExportName // Ensure an export prefix is added for an identifier that points to an exported declaration with a local name (see SymbolFlags.ExportHasLocal).
EFLocalName // Ensure an export prefix is not added for an identifier that points to an exported declaration.
EFIndented // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter).
EFNoIndentation // Do not indent the node.
EFReuseTempVariableScope // Reuse the existing temp variable scope during emit.
EFCustomPrologue // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed).
EFNoAsciiEscaping // When synthesizing nodes that lack an original node or textSourceNode, we want to write the text on the node with ASCII escaping substitutions.
EFExternalHelpers // This source file has external helpers
EFStartOnNewLine // Start this node on a new line
EFIndirectCall // Emit CallExpression as an indirect call: `(0, f)()`
EFAsyncFunctionBody // The node was originally an async function body.
EFNoLexicalArguments // Do not capture `arguments` for this arrow function. Set on arrows lowered from class static blocks, where `arguments` is an error; preserves Strada's emit behavior.
EFTransformPrivateStaticElements // Indicates static private elements in a file or class should be transformed regardless of --target (used by esDecorators transform).
EFNoLexicalThis // Do not capture `this` for this node's subtree. Set on relocated static initializers, where `this` is handled by the class fields transform.
)
const (
EFNone EmitFlags = 0
EFNoSourceMap = EFNoLeadingSourceMap | EFNoTrailingSourceMap // Do not emit a source map location for this node.
EFNoTokenSourceMaps = EFNoTokenLeadingSourceMaps | EFNoTokenTrailingSourceMaps // Do not emit source map locations for tokens of this node.
EFNoComments = EFNoLeadingComments | EFNoTrailingComments // Do not emit comments for this node.
)

View File

@@ -0,0 +1,23 @@
package printer
import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/tsoptions"
"github.com/microsoft/typescript-go/internal/tspath"
)
// NOTE: EmitHost operations must be thread-safe
type EmitHost interface {
Options() *core.CompilerOptions
SourceFiles() []*ast.SourceFile
UseCaseSensitiveFileNames() bool
GetCurrentDirectory() string
CommonSourceDirectory() string
IsEmitBlocked(file string) bool
WriteFile(fileName string, text string) error
GetEmitModuleFormatOfFile(file ast.HasFileName) core.ModuleKind
GetEmitResolver() EmitResolver
GetProjectReferenceFromSource(path tspath.Path) *tsoptions.SourceOutputAndProjectReference
IsSourceFileFromExternalLibrary(file *ast.SourceFile) bool
}

View File

@@ -0,0 +1,129 @@
package printer
import (
"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/evaluator"
"github.com/microsoft/typescript-go/internal/nodebuilder"
)
type SymbolAccessibility int32
const (
SymbolAccessibilityAccessible SymbolAccessibility = iota
SymbolAccessibilityNotAccessible
SymbolAccessibilityCannotBeNamed
SymbolAccessibilityNotResolved
)
type SymbolAccessibilityResult struct {
Accessibility SymbolAccessibility
AliasesToMakeVisible []*ast.Node // aliases that need to have this symbol visible
ErrorSymbolName string // Optional - symbol name that results in error
ErrorNode *ast.Node // Optional - node that results in error
ErrorModuleName string // Optional - If the symbol is not visible from module, module's name
}
/**
* Indicates how to serialize the name for a TypeReferenceNode when emitting decorator metadata
*
* @internal
*/
type TypeReferenceSerializationKind int32
const (
// The TypeReferenceNode could not be resolved.
// The type name should be emitted using a safe fallback.
TypeReferenceSerializationKindUnknown = iota
// The TypeReferenceNode resolves to a type with a constructor
// function that can be reached at runtime (e.g. a `class`
// declaration or a `var` declaration for the static side
// of a type, such as the global `Promise` type in lib.d.ts).
TypeReferenceSerializationKindTypeWithConstructSignatureAndValue
// The TypeReferenceNode resolves to a Void-like, Nullable, or Never type.
TypeReferenceSerializationKindVoidNullableOrNeverType
// The TypeReferenceNode resolves to a Number-like type.
TypeReferenceSerializationKindNumberLikeType
// The TypeReferenceNode resolves to a BigInt-like type.
TypeReferenceSerializationKindBigIntLikeType
// The TypeReferenceNode resolves to a String-like type.
TypeReferenceSerializationKindStringLikeType
// The TypeReferenceNode resolves to a Boolean-like type.
TypeReferenceSerializationKindBooleanType
// The TypeReferenceNode resolves to an Array-like type.
TypeReferenceSerializationKindArrayLikeType
// The TypeReferenceNode resolves to the ESSymbol type.
TypeReferenceSerializationKindESSymbolType
// The TypeReferenceNode resolved to the global Promise constructor symbol.
TypeReferenceSerializationKindPromise
// The TypeReferenceNode resolves to a Function type or a type with call signatures.
TypeReferenceSerializationKindTypeWithCallSignature
// The TypeReferenceNode resolves to any other type.
TypeReferenceSerializationKindObjectType
)
type EmitResolver interface {
binder.ReferenceResolver
IsReferencedAliasDeclaration(node *ast.Node) bool
IsValueAliasDeclaration(node *ast.Node) bool
IsTopLevelValueImportEqualsWithEntityName(node *ast.Node) bool
MarkLinkedReferencesRecursively(file *ast.SourceFile)
GetExternalModuleFileFromDeclaration(node *ast.Node) *ast.SourceFile
GetEffectiveDeclarationFlags(node *ast.Node, flags ast.ModifierFlags) ast.ModifierFlags
GetResolutionModeOverride(node *ast.Node) core.ResolutionMode
// decorator metadata
GetTypeReferenceSerializationKind(name *ast.EntityName, serialScope *ast.Node) TypeReferenceSerializationKind
// const enum inlining
GetConstantValue(node *ast.Node) any
// JSX Emit
GetJsxFactoryEntity(location *ast.Node) *ast.Node
GetJsxFragmentFactoryEntity(location *ast.Node) *ast.Node
SetReferencedImportDeclaration(node *ast.IdentifierNode, ref *ast.Declaration) // for overriding the reference resolver behavior for generated identifiers
// declaration emit checker functionality projections
PrecalculateDeclarationEmitVisibility(file *ast.SourceFile)
IsSymbolAccessible(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags, shouldComputeAliasToMarkVisible bool) SymbolAccessibilityResult
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
IsLiteralConstDeclaration(node *ast.Node) bool
RequiresAddingImplicitUndefined(node *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool
IsDeclarationVisible(node *ast.Node) bool
IsNameResolvable(location *ast.Node, name string) bool
IsImportRequiredByAugmentation(decl *ast.ImportDeclaration) bool
IsDefinitelyReferenceToGlobalSymbolObject(node *ast.Node) bool
IsImplementationOfOverload(node *ast.SignatureDeclaration) bool
GetEnumMemberValue(node *ast.Node) evaluator.Result
IsLateBound(node *ast.Node) bool
IsOptionalParameter(node *ast.Node) bool
IsThisPropertyAssignmentDeclarationRedundant(node *ast.Node) bool
// isolatedDeclarations-specific declaration emit
GetPropertiesOfContainerFunction(node *ast.Node) []*ast.Symbol
RequiresAddingImplicitUndefinedUnsafe(node *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool
GetReferencedValueDeclarationUnsafe(node *ast.IdentifierNode) *ast.Declaration
// Node construction for declaration emit
CreateTypeOfDeclaration(emitContext *EmitContext, declaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node
CreateReturnTypeOfSignatureDeclaration(emitContext *EmitContext, signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node
CreateTypeParametersOfSignatureDeclaration(emitContext *EmitContext, signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node
CreateLiteralConstValue(emitContext *EmitContext, node *ast.Node, tracker nodebuilder.SymbolTracker) *ast.Node
CreateTypeOfExpression(emitContext *EmitContext, expression *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node
CreateLateBoundIndexSignatures(emitContext *EmitContext, container *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node
TryJSTypeNodeToTypeNode(emitContext *EmitContext, typeNode *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node
}

View File

@@ -0,0 +1,36 @@
package printer
import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
)
// Externally opaque interface for printing text
type EmitTextWriter interface {
Write(s string)
WriteTrailingSemicolon(text string)
WriteComment(text string)
WriteKeyword(text string)
WriteOperator(text string)
WritePunctuation(text string)
WriteSpace(text string)
WriteStringLiteral(text string)
WriteParameter(text string)
WriteProperty(text string)
WriteSymbol(text string, symbol *ast.Symbol)
WriteLine()
WriteLineForce(force bool)
IncreaseIndent()
DecreaseIndent()
Clear()
String() string
RawWrite(s string)
WriteLiteral(s string)
GetTextPos() int
GetLine() int
GetColumn() core.UTF16Offset
GetIndent() int
IsAtStartOfLine() bool
HasTrailingComment() bool
HasTrailingWhitespace() bool
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
package printer
type GeneratedIdentifierFlags int
const (
// Kind
GeneratedIdentifierFlagsNone = iota // Not automatically generated.
GeneratedIdentifierFlagsAuto // Automatically generated identifier.
GeneratedIdentifierFlagsLoop // Automatically generated identifier with a preference for '_i'.
GeneratedIdentifierFlagsUnique // Unique name based on the 'text' property.
GeneratedIdentifierFlagsNode // Unique name based on the node in the 'Node' property.
GeneratedIdentifierFlagsKindMask = 7 // Mask to extract the kind of identifier from its flags.
// Flags
GeneratedIdentifierFlagsReservedInNestedScopes = 1 << 3 // Reserve the generated name in nested scopes
GeneratedIdentifierFlagsOptimistic = 1 << 4 // First instance won't use '_#' if there's no conflict
GeneratedIdentifierFlagsFileLevel = 1 << 5 // Use only the file identifiers list and not generated names to search for conflicts
GeneratedIdentifierFlagsAllowNameSubstitution = 1 << 6 // Used by `module.ts` to indicate generated nodes which can have substitutions performed upon them (as they were generated by an earlier transform phase)
)
func (f GeneratedIdentifierFlags) Kind() GeneratedIdentifierFlags {
return f & GeneratedIdentifierFlagsKindMask
}
func (f GeneratedIdentifierFlags) IsAuto() bool {
return f.Kind() == GeneratedIdentifierFlagsAuto
}
func (f GeneratedIdentifierFlags) IsLoop() bool {
return f.Kind() == GeneratedIdentifierFlagsLoop
}
func (f GeneratedIdentifierFlags) IsUnique() bool {
return f.Kind() == GeneratedIdentifierFlagsUnique
}
func (f GeneratedIdentifierFlags) IsNode() bool {
return f.Kind() == GeneratedIdentifierFlagsNode
}
func (f GeneratedIdentifierFlags) IsReservedInNestedScopes() bool {
return f&GeneratedIdentifierFlagsReservedInNestedScopes != 0
}
func (f GeneratedIdentifierFlags) IsOptimistic() bool {
return f&GeneratedIdentifierFlagsOptimistic != 0
}
func (f GeneratedIdentifierFlags) IsFileLevel() bool {
return f&GeneratedIdentifierFlagsFileLevel != 0
}
func (f GeneratedIdentifierFlags) HasAllowNameSubstitution() bool {
return f&GeneratedIdentifierFlagsAllowNameSubstitution != 0
}

View File

@@ -0,0 +1,558 @@
package printer
type Priority struct {
Value int
}
type EmitHelper struct {
Name string // A unique name for this helper.
Scoped bool // Indicates whether the helper MUST be emitted in the current scope.
Text string // ES3-compatible raw script text
TextCallback func(makeUniqueName func(string) string) string // A function yielding an ES3-compatible raw script text.
Priority *Priority // Helpers with a higher priority are emitted earlier than other helpers on the node.
Dependencies []*EmitHelper // Emit helpers this helper depends on
ImportName string // The name of the helper to use when importing via `--importHelpers`.
}
func compareEmitHelpers(x *EmitHelper, y *EmitHelper) int {
if x == y {
return 0
}
if x.Priority == y.Priority {
return 0
}
if x.Priority == nil {
return 1
}
if y.Priority == nil {
return -1
}
return x.Priority.Value - y.Priority.Value
}
// TypeScript Helpers
var decorateHelper = &EmitHelper{
Name: "typescript:decorate",
ImportName: "__decorate",
Scoped: false,
Priority: &Priority{2},
Text: `var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};`,
}
var metadataHelper = &EmitHelper{
Name: "typescript:metadata",
ImportName: "__metadata",
Scoped: false,
Priority: &Priority{3},
Text: `var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};`,
}
var paramHelper = &EmitHelper{
Name: "typescript:param",
ImportName: "__param",
Scoped: false,
Priority: &Priority{4},
Text: `var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};`,
}
// ESNext Helpers
var addDisposableResourceHelper = &EmitHelper{
Name: "typescript:addDisposableResource",
ImportName: "__addDisposableResource",
Scoped: false,
Text: `var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
if (value !== null && value !== void 0) {
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
var dispose, inner;
if (async) {
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
dispose = value[Symbol.asyncDispose];
}
if (dispose === void 0) {
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
dispose = value[Symbol.dispose];
if (async) inner = dispose;
}
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
env.stack.push({ value: value, dispose: dispose, async: async });
}
else if (async) {
env.stack.push({ async: true });
}
return value;
};`,
}
var disposeResourcesHelper = &EmitHelper{
Name: "typescript:disposeResources",
ImportName: "__disposeResources",
Scoped: false,
Text: `var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
return function (env) {
function fail(e) {
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
env.hasError = true;
}
var r, s = 0;
function next() {
while (r = env.stack.pop()) {
try {
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
if (r.dispose) {
var result = r.dispose.call(r.value);
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
}
else s |= 1;
}
catch (e) {
fail(e);
}
}
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
if (env.hasError) throw env.error;
}
return next();
};
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
});`,
}
// Class Fields Helpers
/**
* Parameters:
* @param receiver — The object from which the private member will be read.
* @param state — One of the following:
* - A WeakMap used to read a private instance field.
* - A WeakSet used as an instance brand for private instance methods and accessors.
* - A function value that should be the undecorated class constructor used to brand check private static fields, methods, and accessors.
* @param kind — (optional pre TS 4.3, required for TS 4.3+) One of the following values:
* - undefined — Indicates a private instance field (pre TS 4.3).
* - "f" — Indicates a private field (instance or static).
* - "m" — Indicates a private method (instance or static).
* - "a" — Indicates a private accessor (instance or static).
* @param f — (optional pre TS 4.3) Depends on the arguments for state and kind:
* - If kind is "m", this should be the function corresponding to the static or instance method.
* - If kind is "a", this should be the function corresponding to the getter method, or undefined if the getter was not defined.
* - If kind is "f" and state is a function, this should be an object holding the value of a static field, or undefined if the static field declaration has not yet been evaluated.
* Usage:
* This helper will only ever be used by the compiler in the following ways:
*
* Reading from a private instance field (pre TS 4.3):
* __classPrivateFieldGet(<any>, <WeakMap>)
*
* Reading from a private instance field (TS 4.3+):
* __classPrivateFieldGet(<any>, <WeakMap>, "f")
*
* Reading from a private instance get accessor (when defined, TS 4.3+):
* __classPrivateFieldGet(<any>, <WeakSet>, "a", <function>)
*
* Reading from a private instance get accessor (when not defined, TS 4.3+):
* __classPrivateFieldGet(<any>, <WeakSet>, "a", void 0)
* NOTE: This always results in a runtime error.
*
* Reading from a private instance method (TS 4.3+):
* __classPrivateFieldGet(<any>, <WeakSet>, "m", <function>)
*
* Reading from a private static field (TS 4.3+):
* __classPrivateFieldGet(<any>, <constructor>, "f", <{ value: any }>)
*
* Reading from a private static get accessor (when defined, TS 4.3+):
* __classPrivateFieldGet(<any>, <constructor>, "a", <function>)
*
* Reading from a private static get accessor (when not defined, TS 4.3+):
* __classPrivateFieldGet(<any>, <constructor>, "a", void 0)
* NOTE: This always results in a runtime error.
*
* Reading from a private static method (TS 4.3+):
* __classPrivateFieldGet(<any>, <constructor>, "m", <function>)
*/
var classPrivateFieldGetHelper = &EmitHelper{
Name: "typescript:classPrivateFieldGet",
ImportName: "__classPrivateFieldGet",
Scoped: false,
Text: `var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};`,
}
/**
* Parameters:
* @param receiver — The object on which the private member will be set.
* @param state — One of the following:
* - A WeakMap used to store a private instance field.
* - A WeakSet used as an instance brand for private instance methods and accessors.
* - A function value that should be the undecorated class constructor used to brand check private static fields, methods, and accessors.
* @param value — The value to set.
* @param kind — (optional pre TS 4.3, required for TS 4.3+) One of the following values:
* - undefined — Indicates a private instance field (pre TS 4.3).
* - "f" — Indicates a private field (instance or static).
* - "m" — Indicates a private method (instance or static).
* - "a" — Indicates a private accessor (instance or static).
* @param f — (optional pre TS 4.3) Depends on the arguments for state and kind:
* - If kind is "m", this should be the function corresponding to the static or instance method.
* - If kind is "a", this should be the function corresponding to the setter method, or undefined if the setter was not defined.
* - If kind is "f" and state is a function, this should be an object holding the value of a static field, or undefined if the static field declaration has not yet been evaluated.
* Usage:
* This helper will only ever be used by the compiler in the following ways:
*
* Writing to a private instance field (pre TS 4.3):
* __classPrivateFieldSet(<any>, <WeakMap>, <any>)
*
* Writing to a private instance field (TS 4.3+):
* __classPrivateFieldSet(<any>, <WeakMap>, <any>, "f")
*
* Writing to a private instance set accessor (when defined, TS 4.3+):
* __classPrivateFieldSet(<any>, <WeakSet>, <any>, "a", <function>)
*
* Writing to a private instance set accessor (when not defined, TS 4.3+):
* __classPrivateFieldSet(<any>, <WeakSet>, <any>, "a", void 0)
* NOTE: This always results in a runtime error.
*
* Writing to a private instance method (TS 4.3+):
* __classPrivateFieldSet(<any>, <WeakSet>, <any>, "m", <function>)
* NOTE: This always results in a runtime error.
*
* Writing to a private static field (TS 4.3+):
* __classPrivateFieldSet(<any>, <constructor>, <any>, "f", <{ value: any }>)
*
* Writing to a private static set accessor (when defined, TS 4.3+):
* __classPrivateFieldSet(<any>, <constructor>, <any>, "a", <function>)
*
* Writing to a private static set accessor (when not defined, TS 4.3+):
* __classPrivateFieldSet(<any>, <constructor>, <any>, "a", void 0)
* NOTE: This always results in a runtime error.
*
* Writing to a private static method (TS 4.3+):
* __classPrivateFieldSet(<any>, <constructor>, <any>, "m", <function>)
* NOTE: This always results in a runtime error.
*/
var classPrivateFieldSetHelper = &EmitHelper{
Name: "typescript:classPrivateFieldSet",
ImportName: "__classPrivateFieldSet",
Scoped: false,
Text: `var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};`,
}
/**
* Parameters:
* @param state — One of the following:
* - A WeakMap when the member is a private instance field.
* - A WeakSet when the member is a private instance method or accessor.
* - A function value that should be the undecorated class constructor when the member is a private static field, method, or accessor.
* @param receiver — The object being checked if it has the private member.
*
* Usage:
* This helper is used to transform `#field in expression` to
* `__classPrivateFieldIn(<weakMap/weakSet/constructor>, expression)`
*/
var classPrivateFieldInHelper = &EmitHelper{
Name: "typescript:classPrivateFieldIn",
ImportName: "__classPrivateFieldIn",
Scoped: false,
Text: `var __classPrivateFieldIn = (this && this.__classPrivateFieldIn) || function(state, receiver) {
if (receiver === null || (typeof receiver !== "object" && typeof receiver !== "function")) throw new TypeError("Cannot use 'in' operator on non-object");
return typeof state === "function" ? receiver === state : state.has(receiver);
};`,
}
// ES2018 Helpers
var awaitHelper = &EmitHelper{
Name: "typescript:await",
ImportName: "__await",
Scoped: false,
Text: `var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }`,
}
var asyncGeneratorHelper = &EmitHelper{
Name: "typescript:asyncGenerator",
ImportName: "__asyncGenerator",
Scoped: false,
Dependencies: []*EmitHelper{awaitHelper},
Text: `var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};`,
}
var asyncDelegatorHelper = &EmitHelper{
Name: "typescript:asyncDelegator",
ImportName: "__asyncDelegator",
Scoped: false,
Dependencies: []*EmitHelper{awaitHelper},
Text: `var __asyncDelegator = (this && this.__asyncDelegator) || function (o) {
var i, p;
return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }
};`,
}
var asyncValuesHelper = &EmitHelper{
Name: "typescript:asyncValues",
ImportName: "__asyncValues",
Scoped: false,
Text: `var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};`,
}
// ES2018 Destructuring Helpers
var restHelper = &EmitHelper{
Name: "typescript:rest",
ImportName: "__rest",
Scoped: false,
Text: `var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};`,
}
var awaiterHelper = &EmitHelper{
Name: "typescript:awaiter",
ImportName: "__awaiter",
Scoped: false,
Priority: &Priority{5},
Text: `var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};`,
}
var AsyncSuperHelper = &EmitHelper{
Name: "typescript:async-super",
Scoped: true,
TextCallback: func(makeUniqueName func(string) string) string {
return "\nconst " + makeUniqueName("_superIndex") + " = name => super[name];"
},
}
var AdvancedAsyncSuperHelper = &EmitHelper{
Name: "typescript:advanced-async-super",
Scoped: true,
TextCallback: func(makeUniqueName func(string) string) string {
return "\nconst " + makeUniqueName("_superIndex") + " = (function (geti, seti) {\n" +
" const cache = Object.create(null);\n" +
" return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } });\n" +
"})(name => super[name], (name, value) => super[name] = value);"
},
}
// ES Decorator Helpers
var esDecorateHelper = &EmitHelper{
Name: "typescript:esDecorate",
ImportName: "__esDecorate",
Scoped: false,
Priority: &Priority{2},
Text: `var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
var _, done = false;
for (var i = decorators.length - 1; i >= 0; i--) {
var context = {};
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
if (kind === "accessor") {
if (result === void 0) continue;
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
if (_ = accept(result.get)) descriptor.get = _;
if (_ = accept(result.set)) descriptor.set = _;
if (_ = accept(result.init)) initializers.unshift(_);
}
else if (_ = accept(result)) {
if (kind === "field") initializers.unshift(_);
else descriptor[key] = _;
}
}
if (target) Object.defineProperty(target, contextIn.name, descriptor);
done = true;
};`,
}
var runInitializersHelper = &EmitHelper{
Name: "typescript:runInitializers",
ImportName: "__runInitializers",
Scoped: false,
Priority: &Priority{2},
Text: `var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
var useValue = arguments.length > 2;
for (var i = 0; i < initializers.length; i++) {
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
}
return useValue ? value : void 0;
};`,
}
// ES2015 Helpers
var makeTemplateObjectHelper = &EmitHelper{
Name: "typescript:makeTemplateObject",
ImportName: "__makeTemplateObject",
Scoped: false,
Priority: &Priority{0},
Text: `var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
return cooked;
};`,
}
var propKeyHelper = &EmitHelper{
Name: "typescript:propKey",
ImportName: "__propKey",
Scoped: false,
Text: `var __propKey = (this && this.__propKey) || function (x) {
return typeof x === "symbol" ? x : "".concat(x);
};`,
}
// https://tc39.es/ecma262/#sec-setfunctionname
var setFunctionNameHelper = &EmitHelper{
Name: "typescript:setFunctionName",
ImportName: "__setFunctionName",
Scoped: false,
Text: `var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
};`,
}
// ES Module Helpers
var createBindingHelper = &EmitHelper{
Name: "typescript:commonjscreatebinding",
ImportName: "__createBinding",
Scoped: false,
Priority: &Priority{1},
Text: `var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));`,
}
var setModuleDefaultHelper = &EmitHelper{
Name: "typescript:commonjscreatevalue",
ImportName: "__setModuleDefault",
Scoped: false,
Priority: &Priority{1},
Text: `var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});`,
}
var importStarHelper = &EmitHelper{
Name: "typescript:commonjsimportstar",
ImportName: "__importStar",
Scoped: false,
Dependencies: []*EmitHelper{createBindingHelper, setModuleDefaultHelper},
Priority: &Priority{2},
Text: `var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();`,
}
var importDefaultHelper = &EmitHelper{
Name: "typescript:commonjsimportdefault",
ImportName: "__importDefault",
Scoped: false,
Text: `var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};`,
}
var exportStarHelper = &EmitHelper{
Name: "typescript:export-star",
ImportName: "__exportStar",
Scoped: false,
Dependencies: []*EmitHelper{createBindingHelper},
Priority: &Priority{2},
Text: `var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};`,
}
var rewriteRelativeImportExtensionsHelper = &EmitHelper{
Name: "typescript:rewriteRelativeImportExtensions",
ImportName: "__rewriteRelativeImportExtension",
Scoped: false,
Text: `var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
if (typeof path === "string" && /^\.\.?\//.test(path)) {
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
});
}
return path;
};`,
}

View File

@@ -0,0 +1,405 @@
package printer
import (
"fmt"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/debug"
)
// Flags enum to track count of temp variables and a few dedicated names
type tempFlags int
const (
tempFlagsAuto tempFlags = 0x00000000 // No preferred name
tempFlagsCountMask tempFlags = 0x0FFFFFFF // Temp variable counter
tempFlags_i tempFlags = 0x10000000 // Use/preference flag for '_i'
)
type NameGenerator struct {
Context *EmitContext
IsFileLevelUniqueNameInCurrentFile func(string, bool) bool // callback for Printer.isFileLevelUniqueNameInCurrentFile
GetTextOfNode func(*ast.Node) string // callback for Printer.getTextOfNode
nodeIdToGeneratedName map[ast.NodeId]string // Map of generated names for specific nodes
nodeIdToGeneratedPrivateName map[ast.NodeId]string // Map of generated private names for specific nodes
autoGeneratedIdToGeneratedName map[AutoGenerateId]string // Map of generated names for temp and loop variables
nameGenerationScope *nameGenerationScope
privateNameGenerationScope *nameGenerationScope
generatedNames collections.Set[string] // NOTE: Used to match Strada, but should be moved to nameGenerationScope after port is complete.
}
type nameGenerationScope struct {
next *nameGenerationScope // The next nameGenerationScope in the stack
tempFlags tempFlags // TempFlags for the current name generation scope.
formattedNameTempFlags map[string]tempFlags // TempFlags for the current name generation scope.
reservedNames collections.Set[string] // Names reserved in nested name generation scopes.
// generatedNames collections.Set[string] // NOTE: generated names should be scoped after Strada port is complete.
}
func (g *NameGenerator) PushScope(reuseTempVariableScope bool) {
g.privateNameGenerationScope = &nameGenerationScope{next: g.privateNameGenerationScope}
if !reuseTempVariableScope {
g.nameGenerationScope = &nameGenerationScope{next: g.nameGenerationScope}
}
}
func (g *NameGenerator) PopScope(reuseTempVariableScope bool) {
if g.privateNameGenerationScope != nil {
g.privateNameGenerationScope = g.privateNameGenerationScope.next
}
if !reuseTempVariableScope && g.nameGenerationScope != nil {
g.nameGenerationScope = g.nameGenerationScope.next
}
}
func (g *NameGenerator) getScope(privateName bool) **nameGenerationScope {
return core.IfElse(privateName, &g.privateNameGenerationScope, &g.nameGenerationScope)
}
func (g *NameGenerator) getTempFlags(privateName bool) tempFlags {
scope := g.getScope(privateName)
if *scope != nil {
return (*scope).tempFlags
}
return tempFlagsAuto
}
func (g *NameGenerator) setTempFlags(privateName bool, flags tempFlags) {
scope := g.getScope(privateName)
if *scope == nil {
*scope = &nameGenerationScope{}
}
(*scope).tempFlags = flags
}
// Gets the TempFlags to use in the current nameGenerationScope for the given key
func (g *NameGenerator) getTempFlagsForFormattedName(privateName bool, formattedNameKey string) tempFlags {
scope := g.getScope(privateName)
if *scope != nil {
if flags, ok := (*scope).formattedNameTempFlags[formattedNameKey]; ok {
return flags
}
}
return tempFlagsAuto
}
// Sets the TempFlags to use in the current nameGenerationScope for the given key
func (g *NameGenerator) setTempFlagsForFormattedName(privateName bool, formattedNameKey string, flags tempFlags) {
scope := g.getScope(privateName)
if *scope == nil {
*scope = &nameGenerationScope{}
}
if (*scope).formattedNameTempFlags == nil {
(*scope).formattedNameTempFlags = make(map[string]tempFlags)
}
(*scope).formattedNameTempFlags[formattedNameKey] = flags
}
func (g *NameGenerator) reserveName(name string, privateName bool, scoped bool, temp bool) {
scope := g.getScope(privateName)
if *scope == nil {
*scope = &nameGenerationScope{}
}
if privateName || scoped {
(*scope).reservedNames.Add(name)
} else if !temp {
g.generatedNames.Add(name) // NOTE: Matches Strada, but is incorrect.
// (*scope).generatedNames.Add(name) // TODO: generated names should be scoped after Strada port is complete.
}
}
// Generate the text for a generated identifier or private identifier
func (g *NameGenerator) GenerateName(name *ast.MemberName) string {
if g.Context != nil {
if autoGenerate, ok := g.Context.autoGenerate[name]; ok {
if autoGenerate.Flags.IsNode() {
// Node names generate unique names based on their original node
// and are cached based on that node's id.
return g.generateNameForNodeCached(g.Context.GetNodeForGeneratedName(name), ast.IsPrivateIdentifier(name), autoGenerate.Flags, autoGenerate.Prefix, autoGenerate.Suffix)
} else {
// Auto, Loop, and Unique names are cached based on their unique autoGenerateId.
if autoGeneratedName, ok := g.autoGeneratedIdToGeneratedName[autoGenerate.Id]; ok {
return autoGeneratedName
}
if g.autoGeneratedIdToGeneratedName == nil {
g.autoGeneratedIdToGeneratedName = make(map[AutoGenerateId]string)
}
autoGeneratedName := g.makeName(name)
g.autoGeneratedIdToGeneratedName[autoGenerate.Id] = autoGeneratedName
return autoGeneratedName
}
}
}
return g.GetTextOfNode(name)
}
func (g *NameGenerator) generateNameForNodeCached(node *ast.Node, privateName bool, flags GeneratedIdentifierFlags, prefix string, suffix string) string {
nodeId := ast.GetNodeId(node)
cache := core.IfElse(privateName, &g.nodeIdToGeneratedPrivateName, &g.nodeIdToGeneratedName)
if *cache == nil {
*cache = make(map[ast.NodeId]string)
}
if name, ok := (*cache)[nodeId]; ok {
return name
}
name := g.generateNameForNode(node, privateName, flags, prefix, suffix)
(*cache)[nodeId] = name
return name
}
func (g *NameGenerator) generateNameForNode(node *ast.Node, privateName bool, flags GeneratedIdentifierFlags, prefix string, suffix string) string {
switch node.Kind {
case ast.KindIdentifier, ast.KindPrivateIdentifier:
return g.makeUniqueName(g.GetTextOfNode(node), nil /*checkFn*/, flags.IsOptimistic(), flags.IsReservedInNestedScopes(), privateName, prefix, suffix)
case ast.KindModuleDeclaration, ast.KindEnumDeclaration:
if privateName || len(prefix) > 0 || len(suffix) > 0 {
panic("Generated name for a module or enum cannot be private and may have neither a prefix nor suffix")
}
return g.generateNameForModuleOrEnum(node)
case ast.KindImportDeclaration, ast.KindJSImportDeclaration, ast.KindExportDeclaration:
if privateName || len(prefix) > 0 || len(suffix) > 0 {
panic("Generated name for an import or export cannot be private and may have neither a prefix nor suffix")
}
return g.generateNameForImportOrExportDeclaration(node)
case ast.KindFunctionDeclaration, ast.KindClassDeclaration:
if privateName || len(prefix) > 0 || len(suffix) > 0 {
panic("Generated name for a class or function declaration cannot be private and may have neither a prefix nor suffix")
}
name := node.Name()
if name != nil && !(g.Context == nil && g.Context.HasAutoGenerateInfo(name)) {
return g.generateNameForNode(name, false /*privateName*/, flags, "" /*prefix*/, "" /*suffix*/)
}
return g.generateNameForExportDefault()
case ast.KindExportAssignment:
if privateName || len(prefix) > 0 || len(suffix) > 0 {
panic("Generated name for an export assignment cannot be private and may have neither a prefix nor suffix")
}
return g.generateNameForExportDefault()
case ast.KindClassExpression:
if privateName || len(prefix) > 0 || len(suffix) > 0 {
panic("Generated name for a class expression cannot be private and may have neither a prefix nor suffix")
}
return g.generateNameForClassExpression()
case ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor:
return g.generateNameForMethodOrAccessor(node, privateName, prefix, suffix)
case ast.KindComputedPropertyName:
return g.makeTempVariableName(tempFlagsAuto, true /*reservedInNestedScopes*/, privateName, prefix, suffix)
default:
return g.makeTempVariableName(tempFlagsAuto, false /*reservedInNestedScopes*/, privateName, prefix, suffix)
}
}
func (g *NameGenerator) generateNameForModuleOrEnum(node *ast.Node /* ModuleDeclaration | EnumDeclaration */) string {
name := g.GetTextOfNode(node.Name())
// Use module/enum name itself if it is unique, otherwise make a unique variation
if isUniqueLocalName(name, node) {
return name
} else {
return g.makeUniqueName(name, nil /*checkFn*/, false /*optimistic*/, false /*scoped*/, false /*privateName*/, "" /*prefix*/, "" /*suffix*/)
}
}
func (g *NameGenerator) generateNameForImportOrExportDeclaration(node *ast.Node /* ImportDeclaration | ExportDeclaration */) string {
expr := ast.GetExternalModuleName(node)
baseName := "module"
if ast.IsStringLiteral(expr) {
baseName = makeIdentifierFromModuleName(expr.Text())
}
return g.makeUniqueName(baseName, nil /*checkFn*/, false /*optimistic*/, false /*scoped*/, false /*privateName*/, "" /*prefix*/, "" /*suffix*/)
}
func (g *NameGenerator) generateNameForExportDefault() string {
return g.makeUniqueName("default", nil /*checkFn*/, false /*optimistic*/, false /*scoped*/, false /*privateName*/, "" /*prefix*/, "" /*suffix*/)
}
func (g *NameGenerator) generateNameForClassExpression() string {
return g.makeUniqueName("class", nil /*checkFn*/, false /*optimistic*/, false /*scoped*/, false /*privateName*/, "" /*prefix*/, "" /*suffix*/)
}
func (g *NameGenerator) generateNameForMethodOrAccessor(node *ast.Node /* MethodDeclaration | AccessorDeclaration */, privateName bool, prefix string, suffix string) string {
if ast.IsIdentifier(node.Name()) {
return g.generateNameForNodeCached(node.Name(), privateName, GeneratedIdentifierFlagsNone, prefix, suffix)
}
return g.makeTempVariableName(tempFlagsAuto, false /*reservedInNestedScopes*/, privateName, prefix, suffix)
}
func (g *NameGenerator) makeName(name *ast.Node) string {
if g.Context != nil {
if autoGenerate, ok := g.Context.autoGenerate[name]; ok {
switch autoGenerate.Flags.Kind() {
case GeneratedIdentifierFlagsAuto:
return g.makeTempVariableName(tempFlagsAuto, autoGenerate.Flags.IsReservedInNestedScopes(), ast.IsPrivateIdentifier(name), autoGenerate.Prefix, autoGenerate.Suffix)
case GeneratedIdentifierFlagsLoop:
debug.Assert(ast.IsIdentifier(name))
return g.makeTempVariableName(tempFlags_i, autoGenerate.Flags.IsReservedInNestedScopes(), false /*privateName*/, autoGenerate.Prefix, autoGenerate.Suffix)
case GeneratedIdentifierFlagsUnique:
return g.makeUniqueName(
name.Text(),
core.IfElse(autoGenerate.Flags.IsFileLevel(), g.IsFileLevelUniqueNameInCurrentFile, nil),
autoGenerate.Flags.IsOptimistic(),
autoGenerate.Flags.IsReservedInNestedScopes(),
ast.IsPrivateIdentifier(name),
autoGenerate.Prefix,
autoGenerate.Suffix,
)
}
}
}
return g.GetTextOfNode(name)
}
// Return the next available name in the pattern _a ... _z, _0, _1, ...
// TempFlags._i may be used to express a preference for that dedicated name.
// Note that names generated by makeTempVariableName and makeUniqueName will never conflict.
func (g *NameGenerator) makeTempVariableName(flags tempFlags, reservedInNestedScopes bool, privateName bool, prefix string, suffix string) string {
var tempFlags tempFlags
var key string
simple := len(prefix) == 0 && len(suffix) == 0
if simple {
tempFlags = g.getTempFlags(privateName)
} else {
// Generate a key to use to acquire a TempFlags counter based on the fixed portions of the generated name.
key = FormatGeneratedName(privateName, prefix, "" /*base*/, suffix)
if privateName {
key = ensureLeadingHash(key)
}
tempFlags = g.getTempFlagsForFormattedName(privateName, key)
}
if flags != 0 && tempFlags&flags == 0 {
fullName := FormatGeneratedName(privateName, prefix, "_i", suffix)
if g.isUniqueName(fullName, privateName) {
tempFlags |= flags
g.reserveName(fullName, privateName, reservedInNestedScopes, true /*temp*/)
if simple {
g.setTempFlags(privateName, tempFlags)
} else {
g.setTempFlagsForFormattedName(privateName, key, tempFlags)
}
return fullName
}
}
for {
count := tempFlags & tempFlagsCountMask
tempFlags++
// Skip over 'i' and 'n'
if count != 8 && count != 13 {
var name string
if count < 26 {
name = fmt.Sprintf("_%c", 'a'+byte(count))
} else {
name = fmt.Sprintf("_%d", count-26)
}
fullName := FormatGeneratedName(privateName, prefix, name, suffix)
if g.isUniqueName(fullName, privateName) {
g.reserveName(fullName, privateName, reservedInNestedScopes, true /*temp*/)
if simple {
g.setTempFlags(privateName, tempFlags)
} else {
g.setTempFlagsForFormattedName(privateName, key, tempFlags)
}
return fullName
}
}
}
}
// Generate a name that is unique within the current file and doesn't conflict with any names
// in global scope. The name is formed by adding an '_n' suffix to the specified base name,
// where n is a positive integer. Note that names generated by makeTempVariableName and
// makeUniqueName are guaranteed to never conflict.
// If `optimistic` is set, the first instance will use 'baseName' verbatim instead of 'baseName_1'
func (g *NameGenerator) makeUniqueName(baseName string, checkFn func(name string, privateName bool) bool, optimistic bool, scoped bool, privateName bool, prefix string, suffix string) string {
baseName = removeLeadingHash(baseName)
if optimistic {
fullName := FormatGeneratedName(privateName, prefix, baseName, suffix)
if g.checkUniqueName(fullName, privateName, checkFn) {
g.reserveName(fullName, privateName, scoped, false /*temp*/)
return fullName
}
}
// Find the first unique 'name_n', where n is a positive integer
if len(baseName) > 0 && baseName[len(baseName)-1] != '_' {
baseName += "_"
}
i := 1
for {
fullName := FormatGeneratedName(privateName, prefix, fmt.Sprintf("%s%d", baseName, i), suffix)
if g.checkUniqueName(fullName, privateName, checkFn) {
g.reserveName(fullName, privateName, scoped, false /*temp*/)
return fullName
}
i++
}
}
func (g *NameGenerator) MakeFileLevelOptimisticUniqueName(name string) string {
return g.makeUniqueName(name, g.IsFileLevelUniqueNameInCurrentFile, true /*optimistic*/, false /*scoped*/, false /*privateName*/, "" /*prefix*/, "" /*suffix*/)
}
func (g *NameGenerator) checkUniqueName(name string, privateName bool, checkFn func(name string, privateName bool) bool) bool {
if checkFn != nil {
return checkFn(name, privateName)
} else {
return g.isUniqueName(name, privateName)
}
}
func nextContainer(node *ast.Node) *ast.Node {
data := node.LocalsContainerData()
if data != nil {
return data.NextContainer
}
return nil
}
func isUniqueLocalName(name string, container *ast.Node) bool {
node := container
for node != nil && ast.IsNodeDescendantOf(node, container) && node.LocalsContainerData() != nil {
locals := node.Locals()
if locals != nil {
// We conservatively include alias symbols to cover cases where they're emitted as locals
if local, ok := locals[name]; ok && local.Flags&(ast.SymbolFlagsValue|ast.SymbolFlagsExportValue|ast.SymbolFlagsAlias) != 0 {
return false
}
}
node = nextContainer(node)
}
return true
}
func (g *NameGenerator) isUniqueName(name string, privateName bool) bool {
return (g.IsFileLevelUniqueNameInCurrentFile == nil || g.IsFileLevelUniqueNameInCurrentFile(name, privateName)) &&
!g.isReservedName(name, privateName)
}
func (g *NameGenerator) isReservedName(name string, privateName bool) bool {
scope := g.getScope(privateName)
// NOTE: The following matches Strada, but is incorrect.
if g.generatedNames.Has(name) {
return true
}
// TODO: generated names should be scoped after Strada port is complete.
////if *scope != nil {
//// if (*scope).generatedNames.Has(name) {
//// return true
//// }
////}
for *scope != nil {
if (*scope).reservedNames.Has(name) {
return true
}
scope = &(*scope).next
}
return false
}

View File

@@ -0,0 +1,640 @@
package printer_test
import (
"testing"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/binder"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/testutil/parsetestutil"
"gotest.tools/v3/assert"
)
func TestTempVariable1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewTempVariable()
name2 := ec.Factory.NewTempVariable()
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name2)
assert.Equal(t, "_a", text1)
assert.Equal(t, "_b", text2)
}
func TestTempVariable2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewTempVariableEx(printer.AutoGenerateOptions{
Prefix: "A",
Suffix: "B",
})
name2 := ec.Factory.NewTempVariableEx(printer.AutoGenerateOptions{
Prefix: "A",
Suffix: "B",
})
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name2)
assert.Equal(t, "A_aB", text1)
assert.Equal(t, "A_bB", text2)
}
func TestTempVariable3(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewTempVariable()
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name1)
assert.Equal(t, "_a", text1)
assert.Equal(t, "_a", text2)
}
func TestTempVariableScoped(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewTempVariable()
name2 := ec.Factory.NewTempVariable()
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
g.PushScope(false)
text2 := g.GenerateName(name2)
g.PopScope(false)
assert.Equal(t, "_a", text1)
assert.Equal(t, "_a", text2)
}
func TestTempVariableScopedReserved(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewTempVariableEx(printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsReservedInNestedScopes})
name2 := ec.Factory.NewTempVariable()
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
g.PushScope(false)
text2 := g.GenerateName(name2)
g.PopScope(false)
assert.Equal(t, "_a", text1)
assert.Equal(t, "_b", text2)
}
func TestLoopVariable1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewLoopVariable()
name2 := ec.Factory.NewLoopVariable()
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name2)
assert.Equal(t, "_i", text1)
assert.Equal(t, "_a", text2)
}
func TestLoopVariable2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewLoopVariableEx(printer.AutoGenerateOptions{
Prefix: "A",
Suffix: "B",
})
name2 := ec.Factory.NewLoopVariableEx(printer.AutoGenerateOptions{
Prefix: "A",
Suffix: "B",
})
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name2)
assert.Equal(t, "A_iB", text1)
assert.Equal(t, "A_aB", text2)
}
func TestLoopVariable3(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewLoopVariable()
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name1)
assert.Equal(t, "_i", text1)
assert.Equal(t, "_i", text2)
}
func TestLoopVariableScoped(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewLoopVariable()
name2 := ec.Factory.NewLoopVariable()
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
g.PushScope(false)
text2 := g.GenerateName(name2)
g.PopScope(false)
assert.Equal(t, "_i", text1)
assert.Equal(t, "_i", text2)
}
func TestUniqueName1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewUniqueName("foo")
name2 := ec.Factory.NewUniqueName("foo")
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name2)
assert.Equal(t, "foo_1", text1)
assert.Equal(t, "foo_2", text2)
}
func TestUniqueName2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewUniqueName("foo")
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name1)
assert.Equal(t, "foo_1", text1)
// Expected to be same because GenerateName goes off object identity
assert.Equal(t, "foo_1", text2)
}
func TestUniqueNameScoped(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewUniqueName("foo")
name2 := ec.Factory.NewUniqueName("foo")
g := &printer.NameGenerator{Context: ec}
assert.Equal(t, "foo_1", g.GenerateName(name1))
g.PushScope(false)
assert.Equal(t, "foo_2", g.GenerateName(name2)) // Matches Strada, but is incorrect
// assert.Equal(t, "foo_1", g.GenerateName(name2)) // TODO: Fix after Strada port is complete.
g.PopScope(false)
}
func TestUniquePrivateName1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewUniquePrivateName("#foo")
name2 := ec.Factory.NewUniquePrivateName("#foo")
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name2)
assert.Equal(t, "#foo_1", text1)
assert.Equal(t, "#foo_2", text2)
}
func TestUniquePrivateName2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewUniquePrivateName("#foo")
g := &printer.NameGenerator{Context: ec}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name1)
assert.Equal(t, "#foo_1", text1)
assert.Equal(t, "#foo_1", text2)
}
func TestUniquePrivateNameScoped(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
name1 := ec.Factory.NewUniquePrivateName("#foo")
name2 := ec.Factory.NewUniquePrivateName("#foo")
g := &printer.NameGenerator{Context: ec}
assert.Equal(t, "#foo_1", g.GenerateName(name1))
g.PushScope(false) // private names are always reserved in nested scopes
assert.Equal(t, "#foo_2", g.GenerateName(name2))
g.PopScope(false)
}
func TestGeneratedNameForIdentifier1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("function f() {}", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0].Name()
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "f_1", text1)
}
func TestGeneratedNameForIdentifier2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("function f() {}", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0].Name()
name1 := ec.Factory.NewGeneratedNameForNodeEx(n, printer.AutoGenerateOptions{
Prefix: "a",
Suffix: "b",
})
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "afb", text1)
}
func TestGeneratedNameForIdentifier3(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("function f() {}", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0].Name()
name1 := ec.Factory.NewGeneratedNameForNodeEx(n, printer.AutoGenerateOptions{
Prefix: "a",
Suffix: "b",
})
name2 := ec.Factory.NewGeneratedNameForNode(name1)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name2)
assert.Equal(t, "afb_1", text1)
}
// namespace reuses name if it does not collide with locals
func TestGeneratedNameForNamespace1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("namespace foo { }", false /*jsx*/)
binder.BindSourceFile(file)
ns1 := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(ns1)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "foo", text1)
}
// namespace uses generated name if it collides with locals
func TestGeneratedNameForNamespace2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("namespace foo { var foo; }", false /*jsx*/)
binder.BindSourceFile(file)
ns1 := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(ns1)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "foo_1", text1)
}
// avoids collisions when unscoped
func TestGeneratedNameForNamespace3(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("namespace ns1 { namespace foo { var foo; } } namespace ns2 { namespace foo { var foo; } }", false /*jsx*/)
binder.BindSourceFile(file)
ns1 := file.Statements.Nodes[0].Body().Statements()[0]
ns2 := file.Statements.Nodes[1].Body().Statements()[0]
name1 := ec.Factory.NewGeneratedNameForNode(ns1)
name2 := ec.Factory.NewGeneratedNameForNode(ns2)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name2)
assert.Equal(t, "foo_1", text1)
assert.Equal(t, "foo_2", text2)
}
// reuse name when scoped
func TestGeneratedNameForNamespace4(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("namespace ns1 { namespace foo { var foo; } } namespace ns2 { namespace foo { var foo; } }", false /*jsx*/)
binder.BindSourceFile(file)
ns1 := file.Statements.Nodes[0].Body().Statements()[0]
ns2 := file.Statements.Nodes[1].Body().Statements()[0]
name1 := ec.Factory.NewGeneratedNameForNode(ns1)
name2 := ec.Factory.NewGeneratedNameForNode(ns2)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
g.PushScope(false)
text1 := g.GenerateName(name1)
g.PopScope(false)
g.PushScope(false)
text2 := g.GenerateName(name2)
g.PopScope(false)
assert.Equal(t, "foo_1", text1)
assert.Equal(t, "foo_2", text2) // Matches Strada, but is incorrect
// assert.Equal(t, "foo_1", text2) // TODO: Fix after Strada port is complete.
}
func TestGeneratedNameForNodeCached(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("namespace foo { var foo; }", false /*jsx*/)
binder.BindSourceFile(file)
ns1 := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(ns1)
name2 := ec.Factory.NewGeneratedNameForNode(ns1)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
text2 := g.GenerateName(name2)
assert.Equal(t, "foo_1", text1)
assert.Equal(t, "foo_1", text2)
}
func TestGeneratedNameForImport(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("import * as foo from 'foo'", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "foo_1", text1)
}
func TestGeneratedNameForExport(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("export * as foo from 'foo'", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "foo_1", text1)
}
func TestGeneratedNameForFunctionDeclaration1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("export function f() {}", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "f_1", text1)
}
func TestGeneratedNameForFunctionDeclaration2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("export default function () {}", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "default_1", text1)
}
func TestGeneratedNameForClassDeclaration1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("export class C {}", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "C_1", text1)
}
func TestGeneratedNameForClassDeclaration2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("export default class {}", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "default_1", text1)
}
func TestGeneratedNameForExportAssignment(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("export default 0", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "default_1", text1)
}
func TestGeneratedNameForClassExpression(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("(class {})", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0].Expression().Expression()
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "class_1", text1)
}
func TestGeneratedNameForMethod1(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("class C { m() {} }", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0].Members()[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "m_1", text1)
}
func TestGeneratedNameForMethod2(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("class C { 0() {} }", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0].Members()[0]
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "_a", text1)
}
func TestGeneratedPrivateNameForMethod(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("class C { m() {} }", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0].Members()[0]
name1 := ec.Factory.NewGeneratedPrivateNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "#m_1", text1)
}
func TestGeneratedNameForComputedPropertyName(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("class C { [x] }", false /*jsx*/)
binder.BindSourceFile(file)
n := file.Statements.Nodes[0].Members()[0].Name()
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "_a", text1)
}
func TestGeneratedNameForOther(t *testing.T) {
t.Parallel()
ec := printer.NewEmitContext()
file := parsetestutil.ParseTypeScript("class C { [x] }", false /*jsx*/)
binder.BindSourceFile(file)
n := ec.Factory.NewObjectLiteralExpression(
ec.Factory.NewNodeList([]*ast.Node{}),
false, /*multiLine*/
)
name1 := ec.Factory.NewGeneratedNameForNode(n)
g := &printer.NameGenerator{Context: ec, GetTextOfNode: (*ast.Node).Text}
text1 := g.GenerateName(name1)
assert.Equal(t, "_a", text1)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,159 @@
package printer
import (
"strings"
"sync"
"unicode/utf8"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/stringutil"
)
var singleLineStringWriterPool sync.Pool = sync.Pool{
New: func() any {
return &singleLineStringWriter{}
},
}
var _ EmitTextWriter = &singleLineStringWriter{}
func GetSingleLineStringWriter() (EmitTextWriter, func()) {
w := singleLineStringWriterPool.Get().(*singleLineStringWriter)
w.Clear()
return w, func() {
singleLineStringWriterPool.Put(w)
}
}
type singleLineStringWriter struct {
builder strings.Builder
lastWritten string
}
func (w *singleLineStringWriter) Clear() {
w.lastWritten = ""
w.builder.Reset()
}
func (w singleLineStringWriter) DecreaseIndent() {
// Do Nothing
}
func (w singleLineStringWriter) GetColumn() core.UTF16Offset {
return 0
}
func (w singleLineStringWriter) GetIndent() int {
return 0
}
func (w singleLineStringWriter) GetLine() int {
return 0
}
func (w singleLineStringWriter) String() string {
return w.builder.String()
}
func (w singleLineStringWriter) GetTextPos() int {
return w.builder.Len()
}
func (w singleLineStringWriter) HasTrailingComment() bool {
return false
}
func (w singleLineStringWriter) HasTrailingWhitespace() bool {
if w.builder.Len() == 0 {
return false
}
ch, _ := utf8.DecodeLastRuneInString(w.lastWritten)
if ch == utf8.RuneError {
return false
}
return stringutil.IsWhiteSpaceLike(ch)
}
func (w singleLineStringWriter) IncreaseIndent() {
// Do Nothing
}
func (w singleLineStringWriter) IsAtStartOfLine() bool {
return false
}
func (w *singleLineStringWriter) RawWrite(s string) {
w.lastWritten = s
w.builder.WriteString(s)
}
func (w *singleLineStringWriter) Write(s string) {
w.lastWritten = s
w.builder.WriteString(s)
}
func (w *singleLineStringWriter) WriteComment(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WriteKeyword(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WriteLine() {
w.lastWritten = " "
w.builder.WriteString(" ")
}
func (w *singleLineStringWriter) WriteLineForce(force bool) {
w.lastWritten = " "
w.builder.WriteString(" ")
}
func (w *singleLineStringWriter) WriteLiteral(s string) {
w.lastWritten = s
w.builder.WriteString(s)
}
func (w *singleLineStringWriter) WriteOperator(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WriteParameter(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WriteProperty(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WritePunctuation(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WriteSpace(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WriteStringLiteral(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WriteSymbol(text string, symbol *ast.Symbol) {
w.lastWritten = text
w.builder.WriteString(text)
}
func (w *singleLineStringWriter) WriteTrailingSemicolon(text string) {
w.lastWritten = text
w.builder.WriteString(text)
}

View File

@@ -0,0 +1,10 @@
package printer
import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/tspath"
)
type SourceFileMetaDataProvider interface {
GetSourceFileMetaData(path tspath.Path) *ast.SourceFileMetaData
}

View File

@@ -0,0 +1,227 @@
package printer
import (
"strings"
"unicode/utf8"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/stringutil"
)
var _ EmitTextWriter = &textWriter{}
type textWriter struct {
newLine string
indentSize int
builder strings.Builder
lastWritten string
indent int
lineStart bool
lineCount int
linePos int
hasTrailingCommentState bool
}
func (w *textWriter) Clear() {
*w = textWriter{newLine: w.newLine, indentSize: w.indentSize, lineStart: true}
}
func (w *textWriter) Grow(n int) {
w.builder.Grow(n)
}
func (w *textWriter) DecreaseIndent() {
w.indent--
}
// GetColumn returns the column position measured in UTF-16 code units
// for source map compatibility.
func (w *textWriter) GetColumn() core.UTF16Offset {
if w.lineStart {
return core.UTF16Offset(w.indent * w.indentSize)
}
// Count UTF-16 code units from the last line start.
// For ASCII-only output (the common case), this equals the byte count.
return core.UTF16Len(w.builder.String()[w.linePos:])
}
func (w *textWriter) GetIndent() int {
return w.indent
}
func (w *textWriter) GetLine() int {
return w.lineCount
}
func (w *textWriter) String() string {
return w.builder.String()
}
func (w *textWriter) GetTextPos() int {
return w.builder.Len()
}
func (w textWriter) HasTrailingComment() bool {
return w.hasTrailingCommentState
}
func (w *textWriter) HasTrailingWhitespace() bool {
if w.builder.Len() == 0 {
return false
}
ch, _ := utf8.DecodeLastRuneInString(w.lastWritten)
if ch == utf8.RuneError {
return false
}
return stringutil.IsWhiteSpaceLike(ch)
}
func (w *textWriter) IncreaseIndent() {
w.indent++
}
func (w *textWriter) IsAtStartOfLine() bool {
return w.lineStart
}
func (w *textWriter) RawWrite(s string) {
if s != "" {
w.builder.WriteString(s)
w.lastWritten = s
w.hasTrailingCommentState = false
}
w.updateLineCountAndPosFor(s)
}
func (w *textWriter) updateLineCountAndPosFor(s string) {
var count int
var lastLineStart core.TextPos
for lineStart := range core.ComputeECMALineStartsSeq(s) {
count++
lastLineStart = lineStart
}
if count > 1 {
w.lineCount += count - 1
curLen := w.builder.Len()
w.linePos = curLen - len(s) + int(lastLineStart)
w.lineStart = (w.linePos - curLen) == 0
return
}
w.lineStart = false
}
const defaultIndentSize = 4
// GetDefaultIndentSize returns the default indent size (4 spaces) used when no specific indent size is configured.
func GetDefaultIndentSize() int {
return defaultIndentSize
}
func getIndentString(indent int, indentSize int) string {
if indent == 0 {
return ""
}
// TODO: This is cached in tsc - should it be cached here?
return strings.Repeat(" ", indent*indentSize)
}
func (w *textWriter) writeText(s string) {
if s != "" {
if w.lineStart {
w.builder.WriteString(getIndentString(w.indent, w.indentSize))
w.lineStart = false
}
w.builder.WriteString(s)
w.lastWritten = s
w.updateLineCountAndPosFor(s)
}
}
func (w *textWriter) Write(s string) {
if s != "" {
w.hasTrailingCommentState = false
}
w.writeText(s)
}
func (w *textWriter) WriteComment(text string) {
if text != "" {
w.hasTrailingCommentState = true
}
w.writeText(text)
}
func (w *textWriter) WriteKeyword(text string) {
w.Write(text)
}
func (w *textWriter) writeLineRaw() {
w.builder.WriteString(w.newLine)
w.lastWritten = w.newLine
w.lineCount++
w.linePos = w.builder.Len()
w.lineStart = true
w.hasTrailingCommentState = false
}
func (w *textWriter) WriteLine() {
if !w.lineStart {
w.writeLineRaw()
}
}
func (w *textWriter) WriteLineForce(force bool) {
if !w.lineStart || force {
w.writeLineRaw()
}
}
func (w *textWriter) WriteLiteral(s string) {
w.Write(s)
}
func (w *textWriter) WriteOperator(text string) {
w.Write(text)
}
func (w *textWriter) WriteParameter(text string) {
w.Write(text)
}
func (w *textWriter) WriteProperty(text string) {
w.Write(text)
}
func (w *textWriter) WritePunctuation(text string) {
w.Write(text)
}
func (w *textWriter) WriteSpace(text string) {
w.Write(text)
}
func (w *textWriter) WriteStringLiteral(text string) {
w.Write(text)
}
func (w *textWriter) WriteSymbol(text string, symbol *ast.Symbol) {
w.Write(text)
}
func (w *textWriter) WriteTrailingSemicolon(text string) {
w.Write(text)
}
func NewTextWriter(newLine string, indentSize int) EmitTextWriter {
if indentSize <= 0 {
indentSize = 4
}
var w textWriter
w.newLine = newLine
w.indentSize = indentSize
w.Clear()
return &w
}

View File

@@ -0,0 +1,944 @@
package printer
import (
"fmt"
"slices"
"strconv"
"strings"
"unicode/utf8"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/scanner"
"github.com/microsoft/typescript-go/internal/sourcemap"
"github.com/microsoft/typescript-go/internal/stringutil"
"github.com/microsoft/typescript-go/internal/tspath"
)
type getLiteralTextFlags int
const (
getLiteralTextFlagsNone getLiteralTextFlags = 0
getLiteralTextFlagsNeverAsciiEscape getLiteralTextFlags = 1 << 0
getLiteralTextFlagsJsxAttributeEscape getLiteralTextFlags = 1 << 1
getLiteralTextFlagsTerminateUnterminatedLiterals getLiteralTextFlags = 1 << 2
getLiteralTextFlagsAllowNumericSeparator getLiteralTextFlags = 1 << 3
)
type QuoteChar rune
const (
QuoteCharSingleQuote QuoteChar = '\''
QuoteCharDoubleQuote QuoteChar = '"'
QuoteCharBacktick QuoteChar = '`'
)
var jsxEscapedCharsMap = map[rune]string{
'"': "&quot;",
'\'': "&apos;",
}
var escapedCharsMap = map[rune]string{
'\t': `\t`,
'\v': `\v`,
'\f': `\f`,
'\b': `\b`,
'\r': `\r`,
'\n': `\n`,
'\\': `\\`,
'"': `\"`,
'\'': `\'`,
'`': "\\`",
'$': `\$`, // when quoteChar == '`'
'\u2028': `\u2028`, // lineSeparator
'\u2029': `\u2029`, // paragraphSeparator
'\u0085': `\u0085`, // nextLine
}
func encodeJsxCharacterEntity(b *strings.Builder, charCode rune) {
hexCharCode := strings.ToUpper(strconv.FormatUint(uint64(charCode), 16))
b.WriteString("&#x")
b.WriteString(hexCharCode)
b.WriteByte(';')
}
func encodeUtf16EscapeSequence(b *strings.Builder, charCode rune) {
hexCharCode := strings.ToUpper(strconv.FormatUint(uint64(charCode), 16))
b.WriteString(`\u`)
for i := len(hexCharCode); i < 4; i++ {
b.WriteByte('0')
}
b.WriteString(hexCharCode)
}
// Based heavily on the abstract 'Quote'/'QuoteJSONString' operation from ECMA-262 (24.3.2.2),
// but augmented for a few select characters (e.g. lineSeparator, paragraphSeparator, nextLine)
// Note that this doesn't actually wrap the input in double quotes.
func escapeStringWorker(s string, quoteChar QuoteChar, flags getLiteralTextFlags, b *strings.Builder) {
pos := 0
i := 0
for i < len(s) {
ch, size := stringutil.DecodeJSStringRune(s[i:])
escape := false
if ch >= 0xD800 && ch <= 0xDFFF {
escape = true
} else if ch == utf8.RuneError && size == 1 {
// A stray byte that is not valid UTF-8 (for example, a fragment of a
// surrogate sentinel left behind by code that sliced the string by
// byte). Escape it as the Unicode replacement character so the output
// is always well-formed rather than containing raw invalid bytes.
escape = true
}
// This consists of the first 19 unprintable ASCII characters, canonical escapes, lineSeparator,
// paragraphSeparator, and nextLine. The latter three are just desirable to suppress new lines in
// the language service. These characters should be escaped when printing, and if any characters are added,
// `escapedCharsMap` and/or `jsxEscapedCharsMap` must be updated. Note that this *does not* include the 'delete'
// character. There is no reason for this other than that JSON.stringify does not handle it either.
switch ch {
case '\\':
if flags&getLiteralTextFlagsJsxAttributeEscape == 0 {
escape = true
}
case '$':
if quoteChar == QuoteCharBacktick && i+1 < len(s) && s[i+1] == '{' {
escape = true
}
case rune(quoteChar), '\u2028', '\u2029', '\u0085', '\r':
escape = true
case '\n':
if quoteChar != QuoteCharBacktick {
// Template strings preserve simple LF newlines, still encode CRLF (or CR).
escape = true
}
default:
if ch <= '\u001f' || flags&getLiteralTextFlagsNeverAsciiEscape == 0 && ch > '\u007f' {
escape = true
}
}
if escape {
if pos < i {
// Write string up to this point
b.WriteString(s[pos:i])
}
switch {
case flags&getLiteralTextFlagsJsxAttributeEscape != 0:
if ch == 0 {
b.WriteString("&#0;")
} else if match, ok := jsxEscapedCharsMap[ch]; ok {
b.WriteString(match)
} else {
encodeJsxCharacterEntity(b, ch)
}
default:
if ch == '\r' && quoteChar == QuoteCharBacktick && i+1 < len(s) && s[i+1] == '\n' {
// Template strings preserve simple LF newlines, but still must escape CRLF. Left alone, the
// above cases for `\r` and `\n` would inadvertently escape CRLF as two independent characters.
size++
b.WriteString(`\r\n`)
} else if ch > 0xffff {
// encode as surrogate pair
ch -= 0x10000
encodeUtf16EscapeSequence(b, (ch&0b11111111110000000000>>10)+0xD800)
encodeUtf16EscapeSequence(b, (ch&0b00000000001111111111)+0xDC00)
} else if ch >= 0xD800 && ch <= 0xDFFF {
encodeUtf16EscapeSequence(b, ch)
} else if ch == 0 {
if i+1 < len(s) && stringutil.IsDigit(rune(s[i+1])) {
// If the null character is followed by digits, print as a hex escape to prevent the result from
// parsing as an octal (which is forbidden in strict mode)
b.WriteString(`\x00`)
} else {
// Otherwise, keep printing a literal \0 for the null character
b.WriteString(`\0`)
}
} else {
if match, ok := escapedCharsMap[ch]; ok {
b.WriteString(match)
} else {
encodeUtf16EscapeSequence(b, ch)
}
}
}
pos = i + size
}
i += size
}
if pos < i {
b.WriteString(s[pos:])
}
}
func EscapeString(s string, quoteChar QuoteChar) string {
var b strings.Builder
b.Grow(len(s) + 2)
escapeStringWorker(s, quoteChar, getLiteralTextFlagsNeverAsciiEscape, &b)
return b.String()
}
func escapeNonAsciiString(s string, quoteChar QuoteChar) string {
var b strings.Builder
b.Grow(len(s) + 2)
escapeStringWorker(s, quoteChar, getLiteralTextFlagsNone, &b)
return b.String()
}
func escapeJsxAttributeString(s string, quoteChar QuoteChar) string {
var b strings.Builder
b.Grow(len(s) + 2)
escapeStringWorker(s, quoteChar, getLiteralTextFlagsJsxAttributeEscape|getLiteralTextFlagsNeverAsciiEscape, &b)
return b.String()
}
func canUseOriginalText(node *ast.LiteralLikeNode, flags getLiteralTextFlags) bool {
// A synthetic node has no original text, nor does a node without a parent as we would be unable to find the
// containing SourceFile. We also cannot use the original text if the literal was unterminated and the caller has
// requested proper termination of unterminated literals
if ast.NodeIsSynthesized(node) || node.Parent == nil || flags&getLiteralTextFlagsTerminateUnterminatedLiterals != 0 && ast.IsUnterminatedLiteral(node) {
return false
}
if node.Kind == ast.KindNumericLiteral {
tokenFlags := node.AsNumericLiteral().TokenFlags
// For a numeric literal, we cannot use the original text if the original text was an invalid literal
if tokenFlags&ast.TokenFlagsIsInvalid != 0 {
return false
}
// We also cannot use the original text if the literal contains numeric separators, but numeric separators
// are not permitted
if tokenFlags&ast.TokenFlagsContainsSeparator != 0 {
return flags&getLiteralTextFlagsAllowNumericSeparator != 0
}
}
// Finally, we do not use the original text of a BigInt literal
// TODO(rbuckton): The reason as to why we do not use the original text for bigints is not mentioned in the
// original compiler source. It could be that this is no longer necessary, in which case bigint literals should
// use the same code path as numeric literals, above
return node.Kind != ast.KindBigIntLiteral
}
func getLiteralText(node *ast.LiteralLikeNode, sourceFile *ast.SourceFile, flags getLiteralTextFlags) string {
// If we don't need to downlevel and we can reach the original source text using
// the node's parent reference, then simply get the text as it was originally written.
if sourceFile != nil && canUseOriginalText(node, flags) {
return scanner.GetSourceTextOfNodeFromSourceFile(sourceFile, node, false /*includeTrivia*/)
}
// If we can't reach the original source text, use the canonical form if it's a number,
// or a (possibly escaped) quoted form of the original text if it's string-like.
switch node.Kind {
case ast.KindStringLiteral:
var b strings.Builder
var quoteChar QuoteChar
if node.AsStringLiteral().TokenFlags&ast.TokenFlagsSingleQuote != 0 {
quoteChar = QuoteCharSingleQuote
} else {
quoteChar = QuoteCharDoubleQuote
}
text := node.Text()
// Write leading quote character
b.Grow(len(text) + 2)
b.WriteRune(rune(quoteChar))
// Write text
escapeStringWorker(text, quoteChar, flags, &b)
// Write trailing quote character
b.WriteRune(rune(quoteChar))
return b.String()
case ast.KindNoSubstitutionTemplateLiteral,
ast.KindTemplateHead,
ast.KindTemplateMiddle,
ast.KindTemplateTail:
// If a NoSubstitutionTemplateLiteral appears to have a substitution in it, the original text
// had to include a backslash: `not \${a} substitution`.
var b strings.Builder
text := node.Text()
rawText := node.TemplateLiteralLikeData().RawText
raw := len(rawText) > 0 || len(text) == 0
var textLen int
if raw {
textLen = len(rawText)
} else {
textLen = len(text)
}
// Write leading quote character
switch node.Kind {
case ast.KindNoSubstitutionTemplateLiteral:
b.Grow(2 + textLen)
b.WriteRune('`')
case ast.KindTemplateHead:
b.Grow(3 + textLen)
b.WriteRune('`')
case ast.KindTemplateMiddle:
b.Grow(3 + textLen)
b.WriteRune('}')
case ast.KindTemplateTail:
b.Grow(2 + textLen)
b.WriteRune('}')
}
// Write text
switch {
case len(rawText) > 0 || len(text) == 0:
// If rawText is set, it is expected to be valid.
b.WriteString(rawText)
default:
escapeStringWorker(text, QuoteCharBacktick, flags, &b)
}
// Write trailing quote character
switch node.Kind {
case ast.KindNoSubstitutionTemplateLiteral:
b.WriteRune('`')
case ast.KindTemplateHead:
b.WriteString("${")
case ast.KindTemplateMiddle:
b.WriteString("${")
case ast.KindTemplateTail:
b.WriteRune('`')
}
return b.String()
case ast.KindNumericLiteral, ast.KindBigIntLiteral:
return node.Text()
case ast.KindRegularExpressionLiteral:
if flags&getLiteralTextFlagsTerminateUnterminatedLiterals != 0 && ast.IsUnterminatedLiteral(node) {
var b strings.Builder
text := node.Text()
if len(text) > 0 && text[len(text)-1] == '\\' {
b.Grow(2 + len(text))
b.WriteString(text)
b.WriteString(" /")
} else {
b.Grow(1 + len(text))
b.WriteString(text)
b.WriteString("/")
}
return b.String()
}
return node.Text()
default:
panic("Unsupported LiteralLikeNode")
}
}
func isNotPrologueDirective(node *ast.Node) bool {
return !ast.IsPrologueDirective(node)
}
func RangeIsOnSingleLine(r core.TextRange, sourceFile *ast.SourceFile) bool {
return rangeStartIsOnSameLineAsRangeEnd(r, r, sourceFile)
}
func RangeStartPositionsAreOnSameLine(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile) bool {
return PositionsAreOnSameLine(
getStartPositionOfRange(range1, sourceFile, false /*includeComments*/),
getStartPositionOfRange(range2, sourceFile, false /*includeComments*/),
sourceFile,
)
}
func rangeEndPositionsAreOnSameLine(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile) bool {
return PositionsAreOnSameLine(range1.End(), range2.End(), sourceFile)
}
func rangeStartIsOnSameLineAsRangeEnd(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile) bool {
return PositionsAreOnSameLine(getStartPositionOfRange(range1, sourceFile, false /*includeComments*/), range2.End(), sourceFile)
}
func rangeEndIsOnSameLineAsRangeStart(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile) bool {
return PositionsAreOnSameLine(range1.End(), getStartPositionOfRange(range2, sourceFile, false /*includeComments*/), sourceFile)
}
func getStartPositionOfRange(r core.TextRange, sourceFile *ast.SourceFile, includeComments bool) int {
if ast.PositionIsSynthesized(r.Pos()) {
return -1
}
return scanner.SkipTriviaEx(sourceFile.Text(), r.Pos(), &scanner.SkipTriviaOptions{StopAtComments: includeComments})
}
func PositionsAreOnSameLine(pos1 int, pos2 int, sourceFile *ast.SourceFile) bool {
return GetLinesBetweenPositions(sourceFile, pos1, pos2) == 0
}
func GetLinesBetweenPositions(sourceFile *ast.SourceFile, pos1 int, pos2 int) int {
if pos1 == pos2 {
return 0
}
lineStarts := scanner.GetECMALineStarts(sourceFile)
lower := core.IfElse(pos1 < pos2, pos1, pos2)
isNegative := lower == pos2
upper := core.IfElse(isNegative, pos1, pos2)
lowerLine := scanner.ComputeLineOfPosition(lineStarts, lower)
upperLine := lowerLine + scanner.ComputeLineOfPosition(lineStarts[lowerLine:], upper)
if isNegative {
return lowerLine - upperLine
} else {
return upperLine - lowerLine
}
}
func getLinesBetweenRangeEndAndRangeStart(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile, includeSecondRangeComments bool) int {
range2Start := getStartPositionOfRange(range2, sourceFile, includeSecondRangeComments)
return GetLinesBetweenPositions(sourceFile, range1.End(), range2Start)
}
func getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter(pos int, stopPos int, sourceFile *ast.SourceFile, includeComments bool) int {
startPos := scanner.SkipTriviaEx(sourceFile.Text(), pos, &scanner.SkipTriviaOptions{StopAtComments: includeComments})
prevPos := getPreviousNonWhitespacePosition(startPos, stopPos, sourceFile)
return GetLinesBetweenPositions(sourceFile, core.IfElse(prevPos >= 0, prevPos, stopPos), startPos)
}
func getLinesBetweenPositionAndNextNonWhitespaceCharacter(pos int, stopPos int, sourceFile *ast.SourceFile, includeComments bool) int {
nextPos := scanner.SkipTriviaEx(sourceFile.Text(), pos, &scanner.SkipTriviaOptions{StopAtComments: includeComments})
return GetLinesBetweenPositions(sourceFile, pos, core.IfElse(stopPos < nextPos, stopPos, nextPos))
}
func getPreviousNonWhitespacePosition(pos int, stopPos int, sourceFile *ast.SourceFile) int {
for ; pos >= stopPos; pos-- {
if !stringutil.IsWhiteSpaceLike(rune(sourceFile.Text()[pos])) {
return pos
}
}
return -1
}
func siblingNodePositionsAreComparable(emitContext *EmitContext, previousNode *ast.Node, nextNode *ast.Node) bool {
if nextNode.Pos() < previousNode.End() {
return false
}
previousNode = emitContext.MostOriginal(previousNode)
nextNode = emitContext.MostOriginal(nextNode)
parent := previousNode.Parent
if parent == nil || parent != nextNode.Parent {
return false
}
parentNodeArray := getContainingNodeArray(previousNode)
if parentNodeArray != nil {
prevNodeIndex := slices.Index(parentNodeArray.Nodes, previousNode)
return prevNodeIndex >= 0 && slices.Index(parentNodeArray.Nodes, nextNode) == prevNodeIndex+1
}
return false
}
func getContainingNodeArray(node *ast.Node) *ast.NodeList {
parent := node.Parent
if parent == nil {
return nil
}
switch node.Kind {
case ast.KindTypeParameter:
switch {
case ast.IsFunctionLike(parent) || ast.IsClassLike(parent) || ast.IsInterfaceDeclaration(parent) || ast.IsTypeOrJSTypeAliasDeclaration(parent):
return parent.TypeParameterList()
case ast.IsInferTypeNode(parent):
break
default:
panic(fmt.Sprintf("Unexpected TypeParameter parent: %#v", parent.Kind))
}
case ast.KindParameter:
return node.Parent.FunctionLikeData().Parameters
case ast.KindTemplateLiteralTypeSpan:
return node.Parent.AsTemplateLiteralTypeNode().TemplateSpans
case ast.KindTemplateSpan:
return node.Parent.AsTemplateExpression().TemplateSpans
case ast.KindDecorator:
if canHaveDecorators(node.Parent) {
if modifiers := node.Parent.Modifiers(); modifiers != nil {
return &modifiers.NodeList
}
}
return nil
case ast.KindHeritageClause:
if ast.IsClassLike(node.Parent) {
return node.Parent.ClassLikeData().HeritageClauses
} else {
return node.Parent.AsInterfaceDeclaration().HeritageClauses
}
}
// TODO(rbuckton)
// if ast.IsJSDocTag(node) {
// if ast.IsJSDocTypeLiteral(node.parent) {
// return nil
// }
// return node.parent.tags
// }
switch parent.Kind {
case ast.KindTypeLiteral, ast.KindInterfaceDeclaration:
if ast.IsTypeElement(node) {
return parent.MemberList()
}
case ast.KindUnionType:
return parent.AsUnionTypeNode().Types
case ast.KindIntersectionType:
return parent.AsIntersectionTypeNode().Types
case ast.KindArrayLiteralExpression, ast.KindTupleType, ast.KindNamedImports, ast.KindNamedExports:
return parent.ElementList()
case ast.KindObjectLiteralExpression, ast.KindJsxAttributes:
return parent.PropertyList()
case ast.KindCallExpression:
p := parent.AsCallExpression()
switch {
case ast.IsTypeNode(node):
return p.TypeArguments
case node != p.Expression:
return p.Arguments
}
case ast.KindNewExpression:
p := parent.AsNewExpression()
switch {
case ast.IsTypeNode(node):
return p.TypeArguments
case node != p.Expression:
return p.Arguments
}
case ast.KindJsxElement, ast.KindJsxFragment:
if ast.IsJsxChild(node) {
return parent.Children()
}
case ast.KindJsxOpeningElement, ast.KindJsxSelfClosingElement:
if ast.IsTypeNode(node) {
return parent.TypeArgumentList()
}
case ast.KindBlock, ast.KindModuleBlock, ast.KindCaseClause, ast.KindDefaultClause:
return parent.StatementList()
case ast.KindCaseBlock:
return parent.AsCaseBlock().Clauses
case ast.KindClassDeclaration, ast.KindClassExpression:
if ast.IsClassElement(node) {
return parent.MemberList()
}
case ast.KindEnumDeclaration:
if ast.IsEnumMember(node) {
return parent.MemberList()
}
case ast.KindSourceFile:
if ast.IsStatement(node) {
return parent.StatementList()
}
}
if ast.IsModifier(node) {
if modifiers := parent.Modifiers(); modifiers != nil {
return &modifiers.NodeList
}
}
return nil
}
func canHaveDecorators(node *ast.Node) bool {
switch node.Kind {
case ast.KindParameter,
ast.KindPropertyDeclaration,
ast.KindMethodDeclaration,
ast.KindGetAccessor,
ast.KindSetAccessor,
ast.KindClassExpression,
ast.KindClassDeclaration:
return true
}
return false
}
func originalNodesHaveSameParent(emitContext *EmitContext, nodeA *ast.Node, nodeB *ast.Node) bool {
nodeA = emitContext.MostOriginal(nodeA)
if nodeA.Parent != nil {
// For performance, do not call `MostOriginal` for `nodeB` if `nodeA` doesn't even
// have a parent node.
nodeB = emitContext.MostOriginal(nodeB)
return nodeA.Parent == nodeB.Parent
}
return false
}
func tryGetEnd(node interface{ End() int }) (int, bool) {
// avoid using reflect (via core.IsNil) for common cases
switch v := node.(type) {
case (*ast.Node):
if v != nil {
return v.End(), true
}
case (*ast.NodeList):
if v != nil {
return v.End(), true
}
case (*ast.ModifierList):
if v != nil {
return v.End(), true
}
case (*core.TextRange):
if v != nil {
return v.End(), true
}
case core.TextRange:
return v.End(), true
default:
panic(fmt.Sprintf("unhandled type: %T", node))
}
return 0, false
}
func greatestEnd(end int, nodes ...interface{ End() int }) int {
for i := len(nodes) - 1; i >= 0; i-- {
node := nodes[i]
if nodeEnd, ok := tryGetEnd(node); ok && end < nodeEnd {
end = nodeEnd
}
}
return end
}
func skipSynthesizedParentheses(node *ast.Node) *ast.Node {
for node.Kind == ast.KindParenthesizedExpression && ast.NodeIsSynthesized(node) {
node = node.Expression()
}
return node
}
func isNewExpressionWithoutArguments(node *ast.Node) bool {
return node.Kind == ast.KindNewExpression && node.ArgumentList() == nil
}
func isBinaryOperation(node *ast.Node, token ast.Kind) bool {
node = ast.SkipPartiallyEmittedExpressions(node)
return node.Kind == ast.KindBinaryExpression &&
node.AsBinaryExpression().OperatorToken.Kind == token
}
func mixingBinaryOperatorsRequiresParentheses(a ast.Kind, b ast.Kind) bool {
if a == ast.KindQuestionQuestionToken {
return b == ast.KindAmpersandAmpersandToken || b == ast.KindBarBarToken
}
if b == ast.KindQuestionQuestionToken {
return a == ast.KindAmpersandAmpersandToken || a == ast.KindBarBarToken
}
return false
}
func isImmediatelyInvokedFunctionExpressionOrArrowFunction(node *ast.Expression) bool {
node = ast.SkipPartiallyEmittedExpressions(node)
if !ast.IsCallExpression(node) {
return false
}
node = ast.SkipPartiallyEmittedExpressions(node.Expression())
return ast.IsFunctionExpression(node) || ast.IsArrowFunction(node)
}
func IsFileLevelUniqueName(sourceFile *ast.SourceFile, name string, hasGlobalName func(string) bool) bool {
if hasGlobalName != nil && hasGlobalName(name) {
return false
}
_, ok := sourceFile.Identifiers[name]
return !ok
}
func hasLeadingHash(text string) bool {
return len(text) > 0 && text[0] == '#'
}
func removeLeadingHash(text string) string {
if hasLeadingHash(text) {
return text[1:]
} else {
return text
}
}
func ensureLeadingHash(text string) string {
if hasLeadingHash(text) {
return text
} else {
return "#" + text
}
}
func FormatGeneratedName(privateName bool, prefix string, base string, suffix string) string {
name := removeLeadingHash(prefix) + removeLeadingHash(base) + removeLeadingHash(suffix)
if privateName {
return ensureLeadingHash(name)
}
return name
}
func isASCIIWordCharacter(ch rune) bool {
return stringutil.IsASCIILetter(ch) || stringutil.IsDigit(ch) || ch == '_'
}
func makeIdentifierFromModuleName(moduleName string) string {
moduleName = tspath.GetBaseFileName(moduleName)
var builder strings.Builder
start := 0
pos := 0
for pos < len(moduleName) {
ch := rune(moduleName[pos])
if pos == 0 && stringutil.IsDigit(ch) {
builder.WriteByte('_')
} else if !isASCIIWordCharacter(ch) {
if start < pos {
builder.WriteString(moduleName[start:pos])
}
builder.WriteByte('_')
start = pos + 1
}
pos++
}
if start < pos {
builder.WriteString(moduleName[start:pos])
}
return builder.String()
}
func findSpanEndWithEmitContext[T any](c *EmitContext, array []T, test func(c *EmitContext, value T) bool, start int) int {
i := start
for i < len(array) && test(c, array[i]) {
i++
}
return i
}
func findSpanEnd[T any](array []T, test func(value T) bool, start int) int {
i := start
for i < len(array) && test(array[i]) {
i++
}
return i
}
func skipWhiteSpaceSingleLine(text string, pos *int) {
for *pos < len(text) {
ch, size := utf8.DecodeRuneInString(text[*pos:])
if !stringutil.IsWhiteSpaceSingleLine(ch) {
break
}
*pos += size
}
}
func matchWhiteSpaceSingleLine(text string, pos *int) bool {
startPos := *pos
skipWhiteSpaceSingleLine(text, pos)
return *pos != startPos
}
func matchRune(text string, pos *int, expected rune) bool {
ch, size := utf8.DecodeRuneInString(text[*pos:])
if ch == expected {
*pos += size
return true
}
return false
}
func matchString(text string, pos *int, expected string) bool {
textPos := *pos
expectedPos := 0
for expectedPos < len(expected) {
if textPos >= len(text) {
return false
}
expectedRune, expectedSize := utf8.DecodeRuneInString(expected[expectedPos:])
if !matchRune(text, &textPos, expectedRune) {
return false
}
expectedPos += expectedSize
}
*pos = textPos
return true
}
func matchQuotedString(text string, pos *int) bool {
textPos := *pos
var quoteChar rune
switch {
case matchRune(text, &textPos, '\''):
quoteChar = '\''
case matchRune(text, &textPos, '"'):
quoteChar = '"'
default:
return false
}
for textPos < len(text) {
ch, size := utf8.DecodeRuneInString(text[textPos:])
textPos += size
if ch == quoteChar {
*pos = textPos
return true
}
}
return false
}
// /// <reference path="..." />
// /// <reference types="..." />
// /// <reference lib="..." />
// /// <reference no-default-lib="..." />
// /// <amd-dependency path="..." />
// /// <amd-module />
func IsRecognizedTripleSlashComment(text string, commentRange ast.CommentRange) bool {
if commentRange.Kind == ast.KindSingleLineCommentTrivia &&
commentRange.Len() > 2 &&
text[commentRange.Pos()+1] == '/' &&
text[commentRange.Pos()+2] == '/' {
text = text[commentRange.Pos()+3 : commentRange.End()]
pos := 0
skipWhiteSpaceSingleLine(text, &pos)
if !matchRune(text, &pos, '<') {
return false
}
switch {
case matchString(text, &pos, "reference"):
if !matchWhiteSpaceSingleLine(text, &pos) {
return false
}
if !matchString(text, &pos, "path") &&
!matchString(text, &pos, "types") &&
!matchString(text, &pos, "lib") &&
!matchString(text, &pos, "no-default-lib") {
return false
}
skipWhiteSpaceSingleLine(text, &pos)
if !matchRune(text, &pos, '=') {
return false
}
skipWhiteSpaceSingleLine(text, &pos)
if !matchQuotedString(text, &pos) {
return false
}
case matchString(text, &pos, "amd-dependency"):
if !matchWhiteSpaceSingleLine(text, &pos) {
return false
}
if !matchString(text, &pos, "path") {
return false
}
skipWhiteSpaceSingleLine(text, &pos)
if !matchRune(text, &pos, '=') {
return false
}
skipWhiteSpaceSingleLine(text, &pos)
if !matchQuotedString(text, &pos) {
return false
}
case matchString(text, &pos, "amd-module"):
skipWhiteSpaceSingleLine(text, &pos)
default:
return false
}
index := strings.Index(text[pos:], "/>")
return index != -1
}
return false
}
func isJSDocLikeText(text string, comment ast.CommentRange) bool {
return comment.Kind == ast.KindMultiLineCommentTrivia &&
comment.Len() >= 5 &&
text[comment.Pos()+2] == '*' &&
text[comment.Pos()+3] != '/'
}
func IsPinnedComment(text string, comment ast.CommentRange) bool {
return comment.Kind == ast.KindMultiLineCommentTrivia &&
comment.Len() > 5 &&
text[comment.Pos()+2] == '!'
}
func calculateIndent(text string, pos int, end int) int {
currentLineIndent := 0
indentSize := GetDefaultIndentSize()
for pos < end {
ch, size := utf8.DecodeRuneInString(text[pos:])
if !stringutil.IsWhiteSpaceSingleLine(ch) {
break
}
if ch == '\t' {
// Tabs = TabSize = indent size and go to next tabStop
currentLineIndent += indentSize - (currentLineIndent % indentSize)
} else {
// Single space
currentLineIndent++
}
pos += size
}
return currentLineIndent
}
// lineCharacterCache provides cached line/character lookups for a source file,
// optimized for monotonically increasing positions (e.g., during source map emit).
//
// When positions increase within the same line, only the delta between the last
// position and the new position needs to be scanned for UTF-16 code unit counts,
// turning what would be O(n²) into O(n) for long lines.
//
// Character offsets are measured in UTF-16 code units per the source map specification.
type lineCharacterCache struct {
lineMap []core.TextPos
text string
cachedLine int
cachedPos int
cachedChar core.UTF16Offset
hasCached bool
}
func newLineCharacterCache(source sourcemap.Source) *lineCharacterCache {
return &lineCharacterCache{
lineMap: source.ECMALineMap(),
text: source.Text(),
}
}
// getLineAndCharacter returns the 0-based line number and UTF-16 code unit
// offset from the start of that line for the given byte position.
func (c *lineCharacterCache) getLineAndCharacter(pos int) (line int, character core.UTF16Offset) {
line = scanner.ComputeLineOfPosition(c.lineMap, pos)
lineStart := int(c.lineMap[line])
// When pos is beyond the source text (e.g., for error-recovery tokens like
// missing closing braces), we can't slice past the text end. Compute the
// UTF-16 length up to EOF and add the remaining byte offset arithmetically,
// matching TypeScript's computeLineAndCharacterOfPosition which uses
// arithmetic (position - lineStarts[lineNumber]) and handles this implicitly.
endPos := min(pos, len(c.text))
if c.hasCached && line == c.cachedLine && endPos >= c.cachedPos {
// Incremental: only count UTF-16 code units from the last cached position.
character = c.cachedChar + core.UTF16Len(c.text[c.cachedPos:endPos])
} else {
// Full computation from line start.
character = core.UTF16Len(c.text[lineStart:endPos])
}
cachedChar := character
character += core.UTF16Offset(pos - endPos)
c.cachedLine = line
c.cachedPos = endPos
c.cachedChar = cachedChar
c.hasCached = true
return line, character
}

View File

@@ -0,0 +1,153 @@
package printer
import (
"fmt"
"testing"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"gotest.tools/v3/assert"
)
func TestEscapeString(t *testing.T) {
t.Parallel()
data := []struct {
s string
quoteChar QuoteChar
expected string
}{
{s: "", quoteChar: QuoteCharDoubleQuote, expected: ``},
{s: "abc", quoteChar: QuoteCharDoubleQuote, expected: `abc`},
{s: "ab\"c", quoteChar: QuoteCharDoubleQuote, expected: `ab\"c`},
{s: "ab\tc", quoteChar: QuoteCharDoubleQuote, expected: `ab\tc`},
{s: "ab\nc", quoteChar: QuoteCharDoubleQuote, expected: `ab\nc`},
{s: "ab'c", quoteChar: QuoteCharDoubleQuote, expected: `ab'c`},
{s: "ab'c", quoteChar: QuoteCharSingleQuote, expected: `ab\'c`},
{s: "ab\"c", quoteChar: QuoteCharSingleQuote, expected: `ab"c`},
{s: "ab`c", quoteChar: QuoteCharBacktick, expected: "ab\\`c"},
{s: "\u001f", quoteChar: QuoteCharBacktick, expected: "\\u001F"},
}
for i, rec := range data {
t.Run(fmt.Sprintf("[%d] escapeString(%q, %v)", i, rec.s, rec.quoteChar), func(t *testing.T) {
t.Parallel()
actual := EscapeString(rec.s, rec.quoteChar)
assert.Equal(t, actual, rec.expected)
})
}
}
func TestEscapeNonAsciiString(t *testing.T) {
t.Parallel()
data := []struct {
s string
quoteChar QuoteChar
expected string
}{
{s: "", quoteChar: QuoteCharDoubleQuote, expected: ``},
{s: "abc", quoteChar: QuoteCharDoubleQuote, expected: `abc`},
{s: "ab\"c", quoteChar: QuoteCharDoubleQuote, expected: `ab\"c`},
{s: "ab\tc", quoteChar: QuoteCharDoubleQuote, expected: `ab\tc`},
{s: "ab\nc", quoteChar: QuoteCharDoubleQuote, expected: `ab\nc`},
{s: "ab'c", quoteChar: QuoteCharDoubleQuote, expected: `ab'c`},
{s: "ab'c", quoteChar: QuoteCharSingleQuote, expected: `ab\'c`},
{s: "ab\"c", quoteChar: QuoteCharSingleQuote, expected: `ab"c`},
{s: "ab`c", quoteChar: QuoteCharBacktick, expected: "ab\\`c"},
{s: "ab\u008fc", quoteChar: QuoteCharDoubleQuote, expected: `ab\u008Fc`},
{s: "𝟘𝟙", quoteChar: QuoteCharDoubleQuote, expected: `\uD835\uDFD8\uD835\uDFD9`},
}
for i, rec := range data {
t.Run(fmt.Sprintf("[%d] escapeNonAsciiString(%q, %v)", i, rec.s, rec.quoteChar), func(t *testing.T) {
t.Parallel()
actual := escapeNonAsciiString(rec.s, rec.quoteChar)
assert.Equal(t, actual, rec.expected)
})
}
}
func TestEscapeJsxAttributeString(t *testing.T) {
t.Parallel()
data := []struct {
s string
quoteChar QuoteChar
expected string
}{
{s: "", quoteChar: QuoteCharDoubleQuote, expected: ""},
{s: "abc", quoteChar: QuoteCharDoubleQuote, expected: "abc"},
{s: "ab\"c", quoteChar: QuoteCharDoubleQuote, expected: "ab&quot;c"},
{s: "ab\tc", quoteChar: QuoteCharDoubleQuote, expected: "ab&#x9;c"},
{s: "ab\nc", quoteChar: QuoteCharDoubleQuote, expected: "ab&#xA;c"},
{s: "ab'c", quoteChar: QuoteCharDoubleQuote, expected: "ab'c"},
{s: "ab'c", quoteChar: QuoteCharSingleQuote, expected: "ab&apos;c"},
{s: "ab\"c", quoteChar: QuoteCharSingleQuote, expected: "ab\"c"},
{s: "ab\u008fc", quoteChar: QuoteCharDoubleQuote, expected: "ab\u008Fc"},
{s: "𝟘𝟙", quoteChar: QuoteCharDoubleQuote, expected: "𝟘𝟙"},
}
for i, rec := range data {
t.Run(fmt.Sprintf("[%d] escapeJsxAttributeString(%q, %v)", i, rec.s, rec.quoteChar), func(t *testing.T) {
t.Parallel()
actual := escapeJsxAttributeString(rec.s, rec.quoteChar)
assert.Equal(t, actual, rec.expected)
})
}
}
func TestIsRecognizedTripleSlashComment(t *testing.T) {
t.Parallel()
data := []struct {
s string
commentRange ast.CommentRange
expected bool
}{
{s: "", commentRange: ast.CommentRange{Kind: ast.KindMultiLineCommentTrivia}, expected: false},
{s: "", commentRange: ast.CommentRange{Kind: ast.KindSingleLineCommentTrivia}, expected: false},
{s: "/a", expected: false},
{s: "//", expected: false},
{s: "//a", expected: false},
{s: "///", expected: false},
{s: "///a", expected: false},
{s: "///<reference path=\"foo\" />", expected: true},
{s: "///<reference types=\"foo\" />", expected: true},
{s: "///<reference lib=\"foo\" />", expected: true},
{s: "///<reference no-default-lib=\"foo\" />", expected: true},
{s: "///<amd-dependency path=\"foo\" />", expected: true},
{s: "///<amd-module />", expected: true},
{s: "/// <reference path=\"foo\" />", expected: true},
{s: "/// <reference types=\"foo\" />", expected: true},
{s: "/// <reference lib=\"foo\" />", expected: true},
{s: "/// <reference no-default-lib=\"foo\" />", expected: true},
{s: "/// <amd-dependency path=\"foo\" />", expected: true},
{s: "/// <amd-module />", expected: true},
{s: "/// <reference path=\"foo\"/>", expected: true},
{s: "/// <reference types=\"foo\"/>", expected: true},
{s: "/// <reference lib=\"foo\"/>", expected: true},
{s: "/// <reference no-default-lib=\"foo\"/>", expected: true},
{s: "/// <amd-dependency path=\"foo\"/>", expected: true},
{s: "/// <amd-module/>", expected: true},
{s: "/// <reference path='foo' />", expected: true},
{s: "/// <reference types='foo' />", expected: true},
{s: "/// <reference lib='foo' />", expected: true},
{s: "/// <reference no-default-lib='foo' />", expected: true},
{s: "/// <amd-dependency path='foo' />", expected: true},
{s: "/// <reference path=\"foo\" /> ", expected: true},
{s: "/// <reference types=\"foo\" /> ", expected: true},
{s: "/// <reference lib=\"foo\" /> ", expected: true},
{s: "/// <reference no-default-lib=\"foo\" /> ", expected: true},
{s: "/// <amd-dependency path=\"foo\" /> ", expected: true},
{s: "/// <amd-module /> ", expected: true},
{s: "/// <foo />", expected: false},
{s: "/// <reference />", expected: false},
{s: "/// <amd-dependency />", expected: false},
}
for i, rec := range data {
t.Run(fmt.Sprintf("[%d] isRecognizedTripleSlashComment()", i), func(t *testing.T) {
t.Parallel()
commentRange := rec.commentRange
if commentRange.Kind == ast.KindUnknown {
commentRange.Kind = ast.KindSingleLineCommentTrivia
commentRange.TextRange = core.NewTextRange(0, len(rec.s))
}
actual := IsRecognizedTripleSlashComment(rec.s, commentRange)
assert.Equal(t, actual, rec.expected)
})
}
}