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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,84 @@
package checker_test
import (
"testing"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/bundled"
"github.com/microsoft/typescript-go/internal/checker"
"github.com/microsoft/typescript-go/internal/compiler"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/repo"
"github.com/microsoft/typescript-go/internal/tsoptions"
"github.com/microsoft/typescript-go/internal/tspath"
"github.com/microsoft/typescript-go/internal/vfs/osvfs"
"github.com/microsoft/typescript-go/internal/vfs/vfstest"
"gotest.tools/v3/assert"
)
func TestGetSymbolAtLocation(t *testing.T) {
t.Parallel()
content := `interface Foo {
bar: string;
}
declare const foo: Foo;
foo.bar;`
fs := vfstest.FromMap(map[string]string{
"/foo.ts": content,
"/tsconfig.json": `
{
"compilerOptions": {},
"files": ["foo.ts"]
}
`,
}, false /*useCaseSensitiveFileNames*/)
fs = bundled.WrapFS(fs)
cd := "/"
host := compiler.NewCompilerHost(cd, fs, bundled.LibPath(), nil, nil)
parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile("/tsconfig.json", &core.CompilerOptions{}, nil, host, nil)
assert.Equal(t, len(errors), 0, "Expected no errors in parsed command line")
p := compiler.NewProgram(compiler.ProgramOptions{
Config: parsed,
Host: host,
})
p.BindSourceFiles()
c, done := p.GetTypeChecker(t.Context())
defer done()
file := p.GetSourceFile("/foo.ts")
interfaceId := file.Statements.Nodes[0].Name()
varId := file.Statements.Nodes[1].AsVariableStatement().DeclarationList.AsVariableDeclarationList().Declarations.Nodes[0].Name()
propAccess := file.Statements.Nodes[2].Expression()
nodes := []*ast.Node{interfaceId, varId, propAccess}
for _, node := range nodes {
symbol := c.GetSymbolAtLocation(node)
if symbol == nil {
t.Fatalf("Expected symbol to be non-nil")
}
}
}
func BenchmarkNewChecker(b *testing.B) {
repo.SkipIfNoTypeScriptSubmodule(b)
fs := osvfs.FS()
fs = bundled.WrapFS(fs)
rootPath := tspath.CombinePaths(tspath.NormalizeSlashes(repo.TypeScriptSubmodulePath()), "src", "compiler")
host := compiler.NewCompilerHost(rootPath, fs, bundled.LibPath(), nil, nil)
parsed, errors := tsoptions.GetParsedCommandLineOfConfigFile(tspath.CombinePaths(rootPath, "tsconfig.json"), &core.CompilerOptions{}, nil, host, nil)
assert.Equal(b, len(errors), 0, "Expected no errors in parsed command line")
p := compiler.NewProgram(compiler.ProgramOptions{
Config: parsed,
Host: host,
})
b.ReportAllocs()
for b.Loop() {
checker.NewChecker(p, nil)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,359 @@
package checker
import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/diagnostics"
)
func (c *Checker) GetStringType() *Type {
return c.stringType
}
func (c *Checker) GetNumberType() *Type {
return c.numberType
}
func (c *Checker) GetBooleanType() *Type {
return c.booleanType
}
func (c *Checker) GetVoidType() *Type {
return c.voidType
}
func (c *Checker) GetUndefinedType() *Type {
return c.undefinedType
}
func (c *Checker) GetNullType() *Type {
return c.nullType
}
func (c *Checker) GetAnyType() *Type {
return c.anyType
}
func (c *Checker) GetErrorType() *Type {
return c.errorType
}
func (c *Checker) GetNeverType() *Type {
return c.neverType
}
func (c *Checker) GetUnknownType() *Type {
return c.unknownType
}
func (c *Checker) GetBigIntType() *Type {
return c.bigintType
}
func (c *Checker) GetESSymbolType() *Type {
return c.esSymbolType
}
func (c *Checker) GetBaseTypeOfLiteralType(t *Type) *Type {
return c.getBaseTypeOfLiteralType(t)
}
func (c *Checker) GetUnknownSymbol() *ast.Symbol {
return c.unknownSymbol
}
func (c *Checker) GetUndefinedSymbol() *ast.Symbol {
return c.undefinedSymbol
}
func (c *Checker) GetArgumentsSymbol() *ast.Symbol {
return c.argumentsSymbol
}
func (c *Checker) GetUnknownSignature() *Signature {
return c.unknownSignature
}
func (c *Checker) GetUnionType(types []*Type) *Type {
return c.getUnionType(types)
}
func (c *Checker) GetNameTypeOfSymbol(symbol *ast.Symbol) *Type {
if !c.valueSymbolLinks.Has(symbol) {
return nil
}
return c.valueSymbolLinks.TryGet(symbol).nameType
}
func IsTypeUsableAsPropertyName(t *Type) bool {
return isTypeUsableAsPropertyName(t)
}
func GetPropertyNameFromType(t *Type) string {
return getPropertyNameFromType(t)
}
func (c *Checker) GetGlobalSymbol(name string, meaning ast.SymbolFlags, diagnostic *diagnostics.Message) *ast.Symbol {
return c.getGlobalSymbol(name, meaning, diagnostic)
}
func (c *Checker) GetMergedSymbol(symbol *ast.Symbol) *ast.Symbol {
return c.getMergedSymbol(symbol)
}
func (c *Checker) TryFindAmbientModule(moduleName string) *ast.Symbol {
return c.tryFindAmbientModule(moduleName, true /* withAugmentations */)
}
func (c *Checker) GetImmediateAliasedSymbol(symbol *ast.Symbol) *ast.Symbol {
return c.getImmediateAliasedSymbol(symbol)
}
func (c *Checker) GetTypeOnlyAliasDeclaration(symbol *ast.Symbol) *ast.Node {
return c.getTypeOnlyAliasDeclaration(symbol)
}
func (c *Checker) ResolveExternalModuleName(moduleSpecifier *ast.Node) *ast.Symbol {
return c.resolveExternalModuleName(moduleSpecifier, moduleSpecifier, true /*ignoreErrors*/)
}
func (c *Checker) ResolveExternalModuleSymbol(moduleSymbol *ast.Symbol) *ast.Symbol {
return c.resolveExternalModuleSymbol(moduleSymbol, false /*dontResolveAlias*/)
}
func (c *Checker) GetTypeFromTypeNode(node *ast.Node) *Type {
return c.getTypeFromTypeNode(node)
}
func (c *Checker) IsArrayLikeType(t *Type) bool {
return c.isArrayLikeType(t)
}
func (c *Checker) GetPropertiesOfType(t *Type) []*ast.Symbol {
return c.getPropertiesOfType(t)
}
func (c *Checker) GetPropertyOfType(t *Type, name string) *ast.Symbol {
return c.getPropertyOfType(t, name)
}
func (c *Checker) TypeHasCallOrConstructSignatures(t *Type) bool {
return c.typeHasCallOrConstructSignatures(t)
}
// Checks if a property can be accessed in a location.
// The location is given by the `node` parameter.
// The node does not need to be a property access.
// @param node location where to check property accessibility
// @param isSuper whether to consider this a `super` property access, e.g. `super.foo`.
// @param isWrite whether this is a write access, e.g. `++foo.x`.
// @param containingType type where the property comes from.
// @param property property symbol.
func (c *Checker) IsPropertyAccessible(node *ast.Node, isSuper bool, isWrite bool, containingType *Type, property *ast.Symbol) bool {
return c.isPropertyAccessible(node, isSuper, isWrite, containingType, property)
}
func (c *Checker) GetTypeOfPropertyOfContextualType(t *Type, name string) *Type {
return c.getTypeOfPropertyOfContextualType(t, name)
}
func GetDeclarationModifierFlagsFromSymbol(s *ast.Symbol) ast.ModifierFlags {
return getDeclarationModifierFlagsFromSymbol(s)
}
func (c *Checker) WasCanceled() bool {
return c.wasCanceled
}
func (c *Checker) GetSignaturesOfType(t *Type, kind SignatureKind) []*Signature {
return c.getSignaturesOfType(t, kind)
}
func (c *Checker) GetDeclaredTypeOfSymbol(symbol *ast.Symbol) *Type {
return c.getDeclaredTypeOfSymbol(symbol)
}
func (c *Checker) GetTypeOfSymbol(symbol *ast.Symbol) *Type {
return c.getTypeOfSymbol(symbol)
}
func (c *Checker) GetConstraintOfTypeParameter(typeParameter *Type) *Type {
return c.getConstraintOfTypeParameter(typeParameter)
}
func (c *Checker) GetTrueTypeOfConditionalType(t *Type) *Type {
return c.getTrueTypeFromConditionalType(t)
}
func (c *Checker) GetFalseTypeOfConditionalType(t *Type) *Type {
return c.getFalseTypeFromConditionalType(t)
}
func (c *Checker) GetDefaultFromTypeParameter(typeParameter *Type) *Type {
return c.getDefaultFromTypeParameter(typeParameter)
}
func (c *Checker) GetResolutionModeOverride(node *ast.ImportAttributes, reportErrors bool) core.ResolutionMode {
return c.getResolutionModeOverride(node, reportErrors)
}
func (c *Checker) GetEffectiveDeclarationFlags(n *ast.Node, flagsToCheck ast.ModifierFlags) ast.ModifierFlags {
return c.getEffectiveDeclarationFlags(n, flagsToCheck)
}
func (c *Checker) GetBaseConstraintOfType(t *Type) *Type {
return c.getBaseConstraintOfType(t)
}
func (c *Checker) GetTypePredicateOfSignature(sig *Signature) *TypePredicate {
return c.getTypePredicateOfSignature(sig)
}
func IsTupleType(t *Type) bool {
return isTupleType(t)
}
func (c *Checker) IsArrayType(t *Type) bool {
return c.isArrayType(t)
}
func (c *Checker) GetReturnTypeOfSignature(sig *Signature) *Type {
return c.getReturnTypeOfSignature(sig)
}
func (c *Checker) HasEffectiveRestParameter(signature *Signature) bool {
return c.hasEffectiveRestParameter(signature)
}
func (c *Checker) GetLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol *ast.Symbol) []*Type {
return c.getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol)
}
func (c *Checker) GetContextualTypeForObjectLiteralElement(element *ast.Node, contextFlags ContextFlags) *Type {
return c.getContextualTypeForObjectLiteralElement(element, contextFlags)
}
func (c *Checker) TypePredicateToString(t *TypePredicate) string {
return c.typePredicateToString(t)
}
func (c *Checker) GetExpandedParameters(signature *Signature, skipUnionExpanding bool) [][]*ast.Symbol {
return c.getExpandedParameters(signature, skipUnionExpanding)
}
func (c *Checker) GetResolvedSignature(node *ast.Node) *Signature {
return c.getResolvedSignature(node, nil, CheckModeNormal)
}
// Return the type of the given property in the given type, or nil if no such property exists
func (c *Checker) GetTypeOfPropertyOfType(t *Type, name string) *Type {
return c.getTypeOfPropertyOfType(t, name)
}
func (c *Checker) GetContextualTypeForArgumentAtIndex(node *ast.Node, argIndex int) *Type {
return c.getContextualTypeForArgumentAtIndex(node, argIndex)
}
func (c *Checker) GetIndexSignaturesAtLocation(node *ast.Node) []*ast.Node {
return c.getIndexSignaturesAtLocation(node)
}
func (c *Checker) GetResolvedSymbol(node *ast.Node) *ast.Symbol {
return c.getResolvedSymbol(node)
}
func (c *Checker) GetJsxNamespace(location *ast.Node) string {
return c.getJsxNamespace(location)
}
func (c *Checker) GetJsxFragmentFactory(location *ast.Node) string {
entity := c.getJsxFragmentFactoryEntity(location)
if entity != nil {
return ast.GetFirstIdentifier(entity).Text()
}
return ""
}
func (c *Checker) ResolveName(name string, location *ast.Node, meaning ast.SymbolFlags, excludeGlobals bool) *ast.Symbol {
return c.resolveName(location, name, meaning, nil, true, excludeGlobals)
}
func (c *Checker) GetSymbolFlags(symbol *ast.Symbol) ast.SymbolFlags {
return c.getSymbolFlags(symbol)
}
func (c *Checker) GetBaseTypes(t *Type) []*Type {
return c.getBaseTypes(t)
}
func (c *Checker) GetApparentType(t *Type) *Type {
return c.getApparentType(t)
}
func (c *Checker) GetBaseConstructorTypeOfClass(t *Type) *Type {
return c.getBaseConstructorTypeOfClass(t)
}
func (c *Checker) GetRestTypeOfSignature(sig *Signature) *Type {
return c.getRestTypeOfSignature(sig)
}
func (c *Checker) GetTypeArguments(t *Type) []*Type {
return c.getTypeArguments(t)
}
func (c *Checker) GetIndexInfoOfType(t *Type, keyType *Type) *IndexInfo {
return c.getIndexInfoOfType(t, keyType)
}
func (c *Checker) GetIndexInfosOfType(t *Type) []*IndexInfo {
return c.getIndexInfosOfType(t)
}
func (c *Checker) IsContextSensitive(node *ast.Node) bool {
return c.isContextSensitive(node)
}
func (c *Checker) FillMissingTypeArguments(typeArguments []*Type, typeParameters []*Type, minTypeArgumentCount int, isJavaScriptImplicitAny bool) []*Type {
return c.fillMissingTypeArguments(typeArguments, typeParameters, minTypeArgumentCount, isJavaScriptImplicitAny)
}
func (c *Checker) GetMinTypeArgumentCount(typeParameters []*Type) int {
return c.getMinTypeArgumentCount(typeParameters)
}
func (c *Checker) GetWidenedLiteralType(t *Type) *Type {
return c.getWidenedLiteralType(t)
}
func (c *Checker) IsTypeAssignableTo(source *Type, target *Type) bool {
return c.isTypeAssignableTo(source, target)
}
func (c *Checker) GetUnionTypeEx(types []*Type, unionReduction UnionReduction) *Type {
return c.getUnionTypeEx(types, unionReduction, nil, nil)
}
func (c *Checker) RequiresAddingImplicitUndefined(node *ast.Node) bool {
enclosingDeclaration := ast.FindAncestor(node, ast.IsDeclaration)
if enclosingDeclaration == nil {
enclosingDeclaration = ast.GetSourceFileOfNode(node).AsNode()
}
symbol := node.Symbol()
if symbol == nil {
return false
}
return c.GetEmitResolver().RequiresAddingImplicitUndefined(node, symbol, enclosingDeclaration)
}
func (c *Checker) RemoveMissingOrUndefinedType(t *Type) *Type {
return c.removeMissingOrUndefinedType(t)
}
func (c *Checker) GetWidenedType(t *Type) *Type {
return c.getWidenedType(t)
}
func (c *Checker) CompareSymbols(s1, s2 *ast.Symbol) int {
return c.compareSymbols(s1, s2)
}

File diff suppressed because it is too large Load Diff

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,100 @@
package checker
import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/collections"
"github.com/microsoft/typescript-go/internal/diagnostics"
)
func (c *Checker) checkUnmatchedJSDocParameters(node *ast.Node) {
var jsdocParameters []*ast.Node
for _, tag := range getAllJSDocTags(node) {
if tag.Kind == ast.KindJSDocParameterTag {
name := tag.AsJSDocParameterOrPropertyTag().Name()
if ast.IsIdentifier(name) && len(name.Text()) == 0 {
continue
}
jsdocParameters = append(jsdocParameters, tag)
}
}
if len(jsdocParameters) == 0 {
return
}
isJs := ast.IsInJSFile(node)
parameters := collections.Set[string]{}
excludedParameters := collections.Set[int]{}
for i, param := range node.Parameters() {
name := param.AsParameterDeclaration().Name()
if ast.IsIdentifier(name) {
parameters.Add(name.Text())
}
if ast.IsBindingPattern(name) {
excludedParameters.Add(i)
}
}
if c.containsArgumentsReference(node) {
if isJs {
lastJSDocParamIndex := len(jsdocParameters) - 1
lastJSDocParam := jsdocParameters[lastJSDocParamIndex].AsJSDocParameterOrPropertyTag()
if lastJSDocParam == nil || !ast.IsIdentifier(lastJSDocParam.Name()) {
return
}
if excludedParameters.Has(lastJSDocParamIndex) || parameters.Has(lastJSDocParam.Name().Text()) {
return
}
if lastJSDocParam.TypeExpression == nil || lastJSDocParam.TypeExpression.Type() == nil {
return
}
if c.isArrayType(c.getTypeFromTypeNode(lastJSDocParam.TypeExpression.Type())) {
return
}
c.error(lastJSDocParam.Name(), diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name_It_would_match_arguments_if_it_had_an_array_type, lastJSDocParam.Name().Text())
}
} else {
for index, tag := range jsdocParameters {
name := tag.AsJSDocParameterOrPropertyTag().Name()
isNameFirst := tag.AsJSDocParameterOrPropertyTag().IsNameFirst
if excludedParameters.Has(index) || (ast.IsIdentifier(name) && parameters.Has(name.Text())) {
continue
}
if ast.IsQualifiedName(name) {
if isJs {
c.error(
name, diagnostics.Qualified_name_0_is_not_allowed_without_a_leading_param_object_1,
entityNameToString(name),
entityNameToString(name.AsQualifiedName().Left),
)
}
} else {
if !isNameFirst {
c.errorOrSuggestion(
isJs, name,
diagnostics.JSDoc_param_tag_has_name_0_but_there_is_no_parameter_with_that_name,
name.Text(),
)
}
}
}
}
}
func getAllJSDocTags(node *ast.Node) []*ast.Node {
if node.Flags&ast.NodeFlagsJSDoc == 0 {
for current := node; current != nil; current = ast.GetNextJSDocCommentLocation(current) {
jsdocs := current.JSDoc(nil)
if len(jsdocs) == 0 {
continue
}
lastJSDoc := jsdocs[len(jsdocs)-1].AsJSDoc()
if lastJSDoc.Tags != nil {
return lastJSDoc.Tags.Nodes
}
}
}
return nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,315 @@
package checker
import (
"slices"
"github.com/microsoft/typescript-go/internal/core"
)
// TypeMapperKind
type TypeMapperKind int32
const (
TypeMapperKindUnknown TypeMapperKind = iota
TypeMapperKindSimple
TypeMapperKindArray
TypeMapperKindMerged
)
// TypeMapper
type TypeMapper struct {
data TypeMapperData
}
func (m *TypeMapper) Map(t *Type) *Type { return m.data.Map(t) }
func (m *TypeMapper) Kind() TypeMapperKind { return m.data.Kind() }
func (m *TypeMapper) MapsThisOnly() bool { return m.data.MapsThisOnly() }
// TypeMapperData
type TypeMapperData interface {
Map(t *Type) *Type
Kind() TypeMapperKind
MapsThisOnly() bool
}
// Factory functions
func newTypeMapper(sources []*Type, targets []*Type) *TypeMapper {
if len(sources) == 1 {
return newSimpleTypeMapper(sources[0], targets[0])
}
return newArrayTypeMapper(sources, targets)
}
func (c *Checker) combineTypeMappers(m1 *TypeMapper, m2 *TypeMapper) *TypeMapper {
if m1 != nil {
return newCompositeTypeMapper(c, m1, m2)
}
return m2
}
func mergeTypeMappers(m1 *TypeMapper, m2 *TypeMapper) *TypeMapper {
if m1 != nil {
return newMergedTypeMapper(m1, m2)
}
return m2
}
func prependTypeMapping(source *Type, target *Type, mapper *TypeMapper) *TypeMapper {
if mapper == nil {
return newSimpleTypeMapper(source, target)
}
return newMergedTypeMapper(newSimpleTypeMapper(source, target), mapper)
}
func appendTypeMapping(mapper *TypeMapper, source *Type, target *Type) *TypeMapper {
if mapper == nil {
return newSimpleTypeMapper(source, target)
}
return newMergedTypeMapper(mapper, newSimpleTypeMapper(source, target))
}
// Maps forward-references to later types parameters to the empty object type.
// This is used during inference when instantiating type parameter defaults.
func (c *Checker) newBackreferenceMapper(context *InferenceContext, index int) *TypeMapper {
forwardInferences := context.inferences[index:]
typeParameters := core.Map(forwardInferences, func(i *InferenceInfo) *Type {
return i.typeParameter
})
return newArrayToSingleTypeMapper(typeParameters, c.unknownType)
}
// TypeMapperBase
type TypeMapperBase struct {
TypeMapper
}
func (m *TypeMapperBase) Map(t *Type) *Type { return t }
func (m *TypeMapperBase) Kind() TypeMapperKind { return TypeMapperKindUnknown }
func (m *TypeMapperBase) MapsThisOnly() bool { return false }
// SimpleTypeMapper
type SimpleTypeMapper struct {
TypeMapperBase
source *Type
target *Type
}
func newSimpleTypeMapper(source *Type, target *Type) *TypeMapper {
m := &SimpleTypeMapper{}
m.data = m
m.source = source
m.target = target
return &m.TypeMapper
}
func (m *SimpleTypeMapper) Map(t *Type) *Type {
if t == m.source {
return m.target
}
return t
}
func (m *SimpleTypeMapper) Kind() TypeMapperKind {
return TypeMapperKindSimple
}
func (m *SimpleTypeMapper) MapsThisOnly() bool {
return isThisTypeParameter(m.source)
}
// ArrayTypeMapper
type ArrayTypeMapper struct {
TypeMapperBase
sources []*Type
targets []*Type
}
func newArrayTypeMapper(sources []*Type, targets []*Type) *TypeMapper {
m := &ArrayTypeMapper{}
m.data = m
m.sources = sources
m.targets = targets
return &m.TypeMapper
}
func (m *ArrayTypeMapper) Map(t *Type) *Type {
for i, s := range m.sources {
if t == s {
return m.targets[i]
}
}
return t
}
func (m *ArrayTypeMapper) Kind() TypeMapperKind {
return TypeMapperKindArray
}
func (m *ArrayTypeMapper) MapsThisOnly() bool {
return len(m.sources) == 1 && isThisTypeParameter(m.sources[0])
}
// ArrayToSingleTypeMapper
type ArrayToSingleTypeMapper struct {
TypeMapperBase
sources []*Type
target *Type
}
func newArrayToSingleTypeMapper(sources []*Type, target *Type) *TypeMapper {
m := &ArrayToSingleTypeMapper{}
m.data = m
m.sources = sources
m.target = target
return &m.TypeMapper
}
func (m *ArrayToSingleTypeMapper) Map(t *Type) *Type {
if slices.Contains(m.sources, t) {
return m.target
}
return t
}
func (m *ArrayToSingleTypeMapper) MapsThisOnly() bool {
return len(m.sources) == 1 && isThisTypeParameter(m.sources[0])
}
// DeferredTypeMapper
type DeferredTypeMapper struct {
TypeMapperBase
sources []*Type
targets []func() *Type
}
func newDeferredTypeMapper(sources []*Type, targets []func() *Type) *TypeMapper {
m := &DeferredTypeMapper{}
m.data = m
m.sources = sources
m.targets = targets
return &m.TypeMapper
}
func (m *DeferredTypeMapper) Map(t *Type) *Type {
for i, s := range m.sources {
if t == s {
return m.targets[i]()
}
}
return t
}
func (m *DeferredTypeMapper) MapsThisOnly() bool {
return len(m.sources) == 1 && isThisTypeParameter(m.sources[0])
}
// FunctionTypeMapper
type FunctionTypeMapper struct {
TypeMapperBase
fn func(*Type) *Type
}
func newFunctionTypeMapper(fn func(*Type) *Type) *TypeMapper {
m := &FunctionTypeMapper{}
m.data = m
m.fn = fn
return &m.TypeMapper
}
func (m *FunctionTypeMapper) Map(t *Type) *Type {
return m.fn(t)
}
// MergedTypeMapper
type MergedTypeMapper struct {
TypeMapperBase
m1 *TypeMapper
m2 *TypeMapper
}
func newMergedTypeMapper(m1 *TypeMapper, m2 *TypeMapper) *TypeMapper {
m := &MergedTypeMapper{}
m.data = m
m.m1 = m1
m.m2 = m2
return &m.TypeMapper
}
func (m *MergedTypeMapper) Map(t *Type) *Type {
return m.m2.Map(m.m1.Map(t))
}
func (m *MergedTypeMapper) Kind() TypeMapperKind {
return TypeMapperKindMerged
}
// CompositeTypeMapper
type CompositeTypeMapper struct {
TypeMapperBase
c *Checker
m1 *TypeMapper
m2 *TypeMapper
}
func newCompositeTypeMapper(c *Checker, m1 *TypeMapper, m2 *TypeMapper) *TypeMapper {
m := &CompositeTypeMapper{}
m.data = m
m.c = c
m.m1 = m1
m.m2 = m2
return &m.TypeMapper
}
func (m *CompositeTypeMapper) Map(t *Type) *Type {
t1 := m.m1.Map(t)
if t1 != t {
return m.c.instantiateType(t1, m.m2)
}
return m.m2.Map(t)
}
// InferenceTypeMapper
type InferenceTypeMapper struct {
TypeMapperBase
c *Checker
n *InferenceContext
fixing bool
}
func (c *Checker) newInferenceTypeMapper(n *InferenceContext, fixing bool) *TypeMapper {
m := &InferenceTypeMapper{}
m.data = m
m.c = c
m.n = n
m.fixing = fixing
return &m.TypeMapper
}
func (m *InferenceTypeMapper) Map(t *Type) *Type {
for i, inference := range m.n.inferences {
if t == inference.typeParameter {
if m.fixing && !inference.isFixed {
// Before we commit to a particular inference (and thus lock out any further inferences),
// we infer from any intra-expression inference sites we have collected.
m.c.inferFromIntraExpressionSites(m.n)
clearCachedInferences(m.n.inferences)
inference.isFixed = true
}
return m.c.getInferredType(m.n, i)
}
}
return t
}

View File

@@ -0,0 +1,302 @@
package checker
import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/nodebuilder"
"github.com/microsoft/typescript-go/internal/printer"
)
type NodeBuilder struct {
ctxStack []*NodeBuilderContext
host Host
impl *NodeBuilderImpl
verbosity *VerbosityContext // nil for non-hover callers
}
// VerbosityContext controls hover-expansion behavior in the node builder.
// A nil VerbosityContext means no expansion (non-hover callers).
// Level 0 = default hover (maxExpansionDepth = 0; detects expandability without expanding).
// Level 1+ = expansion enabled (maxExpansionDepth = Level).
type VerbosityContext struct {
Level int // 0 = default (no expansion), 1+ = expansion depth
MaxTruncationLength int // 0 = use default
CanIncreaseVerbosity bool // output: whether increasing Level would reveal more
Truncated bool // output: whether output was truncated
}
// EmitContext implements NodeBuilderInterface.
func (b *NodeBuilder) EmitContext() *printer.EmitContext {
return b.impl.e
}
func (b *NodeBuilder) enterContext(enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) {
verbosityLevel := -1
maxTruncationLength := 0
if b.verbosity != nil {
verbosityLevel = b.verbosity.Level
maxTruncationLength = b.verbosity.MaxTruncationLength
}
b.ctxStack = append(b.ctxStack, b.impl.ctx)
b.impl.ctx = &NodeBuilderContext{
host: b.host,
tracker: tracker,
flags: flags,
internalFlags: internalFlags,
maxExpansionDepth: verbosityLevel,
maxTruncationLength: maxTruncationLength,
enclosingDeclaration: enclosingDeclaration,
enclosingFile: ast.GetSourceFileOfNode(enclosingDeclaration),
inferTypeParameters: make([]*Type, 0),
symbolDepth: make(map[CompositeSymbolIdentity]int),
trackedSymbols: make([]*TrackedSymbolArgs, 0),
reverseMappedStack: make([]*ast.Symbol, 0),
enclosingSymbolTypes: make(map[ast.SymbolId]*Type),
remappedSymbolReferences: make(map[ast.SymbolId]*ast.Symbol),
}
tracker = NewSymbolTrackerImpl(b.impl.ctx, tracker)
b.impl.ctx.tracker = tracker
}
// propagateVerbosityOut copies expansion signals from the context to the VerbosityContext output.
func (b *NodeBuilder) propagateVerbosityOut() {
if b.verbosity != nil {
// Only set to true, never clear — multiple calls share the same VerbosityContext
if b.impl.ctx.canIncreaseExpansionDepth {
b.verbosity.CanIncreaseVerbosity = true
}
if b.impl.ctx.expansionTruncated {
b.verbosity.Truncated = true
}
}
}
func (b *NodeBuilder) popContext() {
stackSize := len(b.ctxStack)
if stackSize == 0 {
b.impl.ctx = nil
} else {
b.impl.ctx = b.ctxStack[stackSize-1]
b.ctxStack = b.ctxStack[:stackSize-1]
}
}
func (b *NodeBuilder) exitContext(result *ast.Node) *ast.Node {
b.propagateVerbosityOut()
b.exitContextCheck()
defer b.popContext()
if b.impl.ctx.encounteredError {
return nil
}
return result
}
func (b *NodeBuilder) exitContextSlice(result []*ast.Node) []*ast.Node {
b.propagateVerbosityOut()
b.exitContextCheck()
defer b.popContext()
if b.impl.ctx.encounteredError {
return nil
}
return result
}
func (b *NodeBuilder) exitContextCheck() {
if b.impl.ctx.truncating && b.impl.ctx.flags&nodebuilder.FlagsNoTruncation != 0 {
b.impl.ctx.tracker.ReportTruncationError()
}
}
// IndexInfoToIndexSignatureDeclaration implements NodeBuilderInterface.
func (b *NodeBuilder) IndexInfoToIndexSignatureDeclaration(info *IndexInfo, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
return b.exitContext(b.impl.indexInfoToIndexSignatureDeclarationHelper(info, nil))
}
// SerializeReturnTypeForSignature implements NodeBuilderInterface.
func (b *NodeBuilder) SerializeReturnTypeForSignature(signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
signature := b.impl.ch.getSignatureFromDeclaration(signatureDeclaration)
_, cleanup := b.impl.enterSignatureScope(signature)
result := b.impl.serializeReturnTypeForSignature(signature, true)
cleanup()
return b.exitContext(result)
}
func (b *NodeBuilder) SerializeTypeParametersForSignature(signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
symbol := b.impl.ch.getSymbolOfDeclaration(signatureDeclaration)
typeParams := b.SymbolToTypeParameterDeclarations(symbol, enclosingDeclaration, flags, internalFlags, tracker)
return b.exitContextSlice(typeParams)
}
// SerializeTypeForDeclaration implements NodeBuilderInterface.
func (b *NodeBuilder) SerializeTypeForDeclaration(declaration *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
return b.exitContext(b.impl.serializeTypeForDeclaration(declaration, nil, symbol, true))
}
// SerializeTypeForExpression implements NodeBuilderInterface.
func (b *NodeBuilder) SerializeTypeForExpression(expr *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
return b.exitContext(b.impl.serializeTypeForExpression(expr))
}
// SignatureToSignatureDeclaration implements NodeBuilderInterface.
func (b *NodeBuilder) SignatureToSignatureDeclaration(signature *Signature, kind ast.Kind, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
return b.exitContext(b.impl.signatureToSignatureDeclarationHelper(signature, kind, nil))
}
// ExpandSymbolForHover produces declaration nodes for a symbol with verbosity level support.
func (b *NodeBuilder) ExpandSymbolForHover(symbol *ast.Symbol, meaning ast.SymbolFlags) []*ast.Node {
b.enterContext(nil, nodebuilder.FlagsIgnoreErrors|nodebuilder.FlagsMultilineObjectLiterals|nodebuilder.FlagsUseAliasDefinedOutsideCurrentScope, nodebuilder.InternalFlagsNone, nil)
// Push the declared type onto the type stack to prevent re-expansion.
// We push a nil sentinel after the real type so that isTypeOnStack
// (which skips the last element) still checks declaredType.
declaredType := b.impl.ch.getDeclaredTypeOfSymbol(symbol)
b.impl.ctx.typeStack = append(b.impl.ctx.typeStack, declaredType)
b.impl.ctx.typeStack = append(b.impl.ctx.typeStack, nil)
nodes := b.impl.expandSymbolForHover(symbol)
b.impl.ctx.typeStack = b.impl.ctx.typeStack[:len(b.impl.ctx.typeStack)-2]
b.propagateVerbosityOut()
// Simplify declarations by applying original modifiers
result := make([]*ast.Node, 0, len(nodes))
for _, node := range nodes {
switch node.Kind {
case ast.KindClassDeclaration:
result = append(result, simplifyClassDeclaration(b.impl.f, node, symbol))
case ast.KindEnumDeclaration:
result = append(result, simplifyModifiers(b.impl.f, node, ast.IsEnumDeclaration, symbol))
case ast.KindInterfaceDeclaration:
if meaning&ast.SymbolFlagsInterface != 0 {
result = append(result, simplifyModifiers(b.impl.f, node, ast.IsInterfaceDeclaration, symbol))
}
case ast.KindModuleDeclaration:
result = append(result, simplifyModifiers(b.impl.f, node, ast.IsModuleDeclaration, symbol))
}
}
return b.exitContextSlice(result)
}
func simplifyClassDeclaration(f *ast.NodeFactory, classDecl *ast.Node, symbol *ast.Symbol) *ast.Node {
classDeclarations := core.Filter(symbol.Declarations, ast.IsClassLike)
var originalClassDecl *ast.Node
if len(classDeclarations) > 0 {
originalClassDecl = classDeclarations[0]
} else {
originalClassDecl = classDecl
}
modifiers := originalClassDecl.ModifierFlags() & ^(ast.ModifierFlagsExport | ast.ModifierFlagsAmbient)
isAnonymous := ast.IsClassExpression(originalClassDecl)
if isAnonymous {
cd := classDecl.AsClassDeclaration()
classDecl = f.UpdateClassDeclaration(
cd,
classDecl.Modifiers(),
nil,
cd.TypeParameters,
cd.HeritageClauses,
cd.Members,
)
}
return ast.ReplaceModifiers(f, classDecl, f.NewModifierList(ast.CreateModifiersFromModifierFlags(modifiers, f.NewModifier)))
}
func simplifyModifiers(f *ast.NodeFactory, newDecl *ast.Node, isDeclKind func(*ast.Node) bool, symbol *ast.Symbol) *ast.Node {
decls := core.Filter(symbol.Declarations, isDeclKind)
var declWithModifiers *ast.Node
if len(decls) > 0 {
declWithModifiers = decls[0]
} else {
declWithModifiers = newDecl
}
modifiers := declWithModifiers.ModifierFlags() & ^(ast.ModifierFlagsExport | ast.ModifierFlagsAmbient)
return ast.ReplaceModifiers(f, newDecl, f.NewModifierList(ast.CreateModifiersFromModifierFlags(modifiers, f.NewModifier)))
}
// SymbolToEntityName implements NodeBuilderInterface.
func (b *NodeBuilder) SymbolToEntityName(symbol *ast.Symbol, meaning ast.SymbolFlags, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
return b.exitContext(b.impl.symbolToName(symbol, meaning, false))
}
// SymbolToExpression implements NodeBuilderInterface.
func (b *NodeBuilder) SymbolToExpression(symbol *ast.Symbol, meaning ast.SymbolFlags, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
return b.exitContext(b.impl.symbolToExpression(symbol, meaning))
}
// SymbolToNode implements NodeBuilderInterface.
func (b *NodeBuilder) SymbolToNode(symbol *ast.Symbol, meaning ast.SymbolFlags, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
return b.exitContext(b.impl.symbolToNode(symbol, meaning))
}
// SymbolToParameterDeclaration implements NodeBuilderInterface.
func (b NodeBuilder) SymbolToParameterDeclaration(symbol *ast.Symbol, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
return b.exitContext(b.impl.symbolToParameterDeclaration(symbol, false))
}
// SymbolToTypeParameterDeclarations implements NodeBuilderInterface.
func (b *NodeBuilder) SymbolToTypeParameterDeclarations(symbol *ast.Symbol, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
return b.exitContextSlice(b.impl.symbolToTypeParameterDeclarations(symbol))
}
// TypeParameterToDeclaration implements NodeBuilderInterface.
func (b *NodeBuilder) TypeParameterToDeclaration(parameter *Type, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
return b.exitContext(b.impl.typeParameterToDeclaration(parameter))
}
// TypePredicateToTypePredicateNode implements NodeBuilderInterface.
func (b *NodeBuilder) TypePredicateToTypePredicateNode(predicate *TypePredicate, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
return b.exitContext(b.impl.typePredicateToTypePredicateNode(predicate))
}
// TypeToTypeNode implements NodeBuilderInterface.
func (b *NodeBuilder) TypeToTypeNode(typ *Type, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
return b.exitContext(b.impl.typeToTypeNode(typ))
}
func (b *NodeBuilder) TryJSTypeNodeToTypeNode(node *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
return b.exitContext(b.impl.tryJSTypeNodeToTypeNode(node))
}
// var _ NodeBuilderInterface = NewNodeBuilderAPI(nil, nil)
func NewNodeBuilder(ch *Checker, e *printer.EmitContext) *NodeBuilder {
return NewNodeBuilderEx(ch, e, nil /*idToSymbol*/)
}
func NewNodeBuilderEx(ch *Checker, e *printer.EmitContext, idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *NodeBuilder {
impl := newNodeBuilderImpl(ch, e, idToSymbol)
return &NodeBuilder{impl: impl, ctxStack: make([]*NodeBuilderContext, 0, 1), host: ch.program}
}
func (c *Checker) getNodeBuilder() (*NodeBuilder, func()) {
releaseNodes := func() {
c.typeToStringNodebuilder.EmitContext().Factory.ReleaseArenas() // Allow any allocated nodes to be freed if they're no longer in a cache
}
if c.typeToStringNodebuilder != nil {
return c.typeToStringNodebuilder, releaseNodes
}
c.typeToStringNodebuilder = c.getNodeBuilderEx(nil /*idToSymbol*/)
return c.typeToStringNodebuilder, releaseNodes
}
func (c *Checker) getNodeBuilderEx(idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *NodeBuilder {
b := NewNodeBuilderEx(c, printer.NewEmitContext(), idToSymbol)
return b
}

View File

@@ -0,0 +1,597 @@
package checker
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/jsnum"
"github.com/microsoft/typescript-go/internal/nodebuilder"
"github.com/microsoft/typescript-go/internal/scanner"
)
// isExpanding returns whether the node builder context is operating in hover-expansion mode.
func isExpanding(ctx *NodeBuilderContext) bool {
return ctx.maxExpansionDepth != -1
}
// expandSymbolForHover produces declaration nodes (class, interface, enum, module) for a symbol
// for expandable hover. This is a focused alternative to the full symbolTableToDeclarationStatements
// machinery used by declaration emit — it directly builds the declaration nodes hover needs
// without the declaration-emit scaffolding (deferred privates, symbol name remapping, export
// modifier computation, alias resolution, visited symbols tracking).
func (b *NodeBuilderImpl) expandSymbolForHover(symbol *ast.Symbol) []*ast.Node {
var results []*ast.Node
if symbol.Flags&ast.SymbolFlagsEnum != 0 {
if node := b.expandEnumDecl(symbol); node != nil {
results = append(results, node)
}
}
if symbol.Flags&ast.SymbolFlagsClass != 0 {
if node := b.expandClassDecl(symbol); node != nil {
results = append(results, node)
}
}
// Module/namespace before interface (matching Strada ordering for merged declarations)
if symbol.Flags&(ast.SymbolFlagsValueModule|ast.SymbolFlagsNamespaceModule) != 0 {
if node := b.expandModuleDecl(symbol); node != nil {
results = append(results, node)
}
}
if symbol.Flags&ast.SymbolFlagsInterface != 0 && symbol.Flags&ast.SymbolFlagsClass == 0 {
if node := b.expandInterfaceDecl(symbol); node != nil {
results = append(results, node)
}
}
return results
}
// expandEnumDecl produces an EnumDeclaration node with all members.
func (b *NodeBuilderImpl) expandEnumDecl(symbol *ast.Symbol) *ast.Node {
name := ast.SymbolName(symbol)
b.ctx.approximateLength += 9 + len(name)
memberProps := core.Filter(b.ch.getPropertiesOfType(b.ch.getTypeOfSymbol(symbol)), func(p *ast.Symbol) bool {
return p.Flags&ast.SymbolFlagsEnumMember != 0
})
var members []*ast.Node
for i, p := range memberProps {
if b.checkTruncationLengthIfExpanding() && i+3 < len(memberProps)-1 {
b.ctx.expansionTruncated = true
members = append(members, b.f.NewEnumMember(b.f.NewStringLiteral(fmt.Sprintf(" ... %d more ... ", len(memberProps)-i-1), 0), nil))
last := memberProps[len(memberProps)-1]
members = append(members, b.f.NewEnumMember(b.f.NewIdentifier(last.Name), b.enumMemberInitializer(last)))
break
}
memberDecl := core.Find(p.Declarations, ast.IsEnumMember)
var initializer *ast.Node
if memberDecl != nil && memberDecl.AsEnumMember().Initializer != nil {
initializer = b.f.DeepCloneNode(memberDecl.AsEnumMember().Initializer)
} else {
initializer = b.enumMemberInitializer(p)
}
b.ctx.approximateLength += 4 + len(p.Name)
if initializer != nil {
b.ctx.approximateLength += 5 // " = " + value estimate
}
members = append(members, b.f.NewEnumMember(b.f.NewIdentifier(p.Name), initializer))
}
constModifier := ast.ModifierFlagsNone
if isConstEnumSymbol(symbol) {
constModifier = ast.ModifierFlagsConst
}
var mods *ast.ModifierList
if constModifier != 0 {
mods = b.f.NewModifierList(ast.CreateModifiersFromModifierFlags(constModifier, b.f.NewModifier))
}
return b.f.NewEnumDeclaration(mods, b.f.NewIdentifier(name), b.f.NewNodeList(members))
}
func (b *NodeBuilderImpl) enumMemberInitializer(p *ast.Symbol) *ast.Node {
memberDecl := core.Find(p.Declarations, ast.IsEnumMember)
if memberDecl == nil {
return nil
}
val := b.ch.GetConstantValue(memberDecl)
if val == nil {
return nil
}
switch v := val.(type) {
case string:
return b.f.NewStringLiteral(v, 0)
case jsnum.Number:
return b.f.NewNumericLiteral(v.String(), 0)
}
return nil
}
// expandClassDecl produces a ClassDeclaration node with heritage clauses and members.
func (b *NodeBuilderImpl) expandClassDecl(symbol *ast.Symbol) *ast.Node {
name := ast.SymbolName(symbol)
b.ctx.approximateLength += 9 + len(name)
classLikeDeclarations := core.Filter(symbol.Declarations, ast.IsClassLike)
originalDecl := core.FirstOrNil(classLikeDeclarations)
oldEnclosing := b.ctx.enclosingDeclaration
if originalDecl != nil {
b.ctx.enclosingDeclaration = originalDecl
}
defer func() { b.ctx.enclosingDeclaration = oldEnclosing }()
localParams := b.ch.getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol)
typeParamDecls := core.Map(localParams, func(p *Type) *ast.Node { return b.typeParameterToDeclaration(p) })
declaredType := b.ch.getDeclaredTypeOfClassOrInterface(symbol)
classType := b.ch.getTypeWithThisArgument(declaredType, nil, false)
baseTypes := b.ch.getBaseTypes(b.ch.getTargetType(classType))
staticType := b.ch.getTypeOfSymbol(symbol)
isClass := staticType.symbol != nil && staticType.symbol.ValueDeclaration != nil && ast.IsClassLike(staticType.symbol.ValueDeclaration)
var staticBaseType *Type
if isClass {
staticBaseType = b.ch.getBaseConstructorTypeOfClass(declaredType)
} else {
staticBaseType = b.ch.anyType
}
// Heritage clauses
heritageClauses := b.hoverHeritageClauses(classLikeDeclarations)
// Instance members via addPropertyToElementList (reusing existing serialization),
// then convert TypeElements to ClassElements and add class-specific modifiers
allProps := b.ch.getPropertiesOfType(classType)
symbolProps := b.filterInheritedProperties(classType, baseTypes, allProps)
publicProps := core.Filter(symbolProps, func(s *ast.Symbol) bool { return !isHashPrivate(s) })
hasPrivate := core.Some(symbolProps, isHashPrivate)
var instanceMembers []*ast.Node
instanceMembers = b.serializePropertiesWithTruncation(publicProps, instanceMembers)
instanceMembers = typeElementsToClassElements(b.f, instanceMembers)
instanceMembers = b.addClassModifiers(instanceMembers, false)
// Static members
staticProps := core.Filter(b.ch.getPropertiesOfType(staticType), func(p *ast.Symbol) bool {
return p.Flags&ast.SymbolFlagsPrototype == 0 && p.Name != "prototype" && !b.isNamespaceMember(p)
})
var staticMembers []*ast.Node
staticMembers = b.serializePropertiesWithTruncation(staticProps, staticMembers)
staticMembers = typeElementsToClassElements(b.f, staticMembers)
staticMembers = b.addClassModifiers(staticMembers, true)
// Hash-private members
var privateMembers []*ast.Node
if hasPrivate {
privateMembers = b.serializePropertiesWithTruncation(core.Filter(symbolProps, isHashPrivate), privateMembers)
privateMembers = typeElementsToClassElements(b.f, privateMembers)
}
// Constructors
constructors := b.serializeConstructors(staticType, staticBaseType, isClass, symbol)
// Index signatures
indexSigs := b.serializeIndexSignaturesOfType(classType, core.FirstOrNil(baseTypes))
allMembers := make([]*ast.Node, 0, len(indexSigs)+len(staticMembers)+len(constructors)+len(instanceMembers)+len(privateMembers))
allMembers = append(allMembers, indexSigs...)
allMembers = append(allMembers, staticMembers...)
allMembers = append(allMembers, constructors...)
allMembers = append(allMembers, instanceMembers...)
allMembers = append(allMembers, privateMembers...)
return b.f.NewClassDeclaration(nil, b.f.NewIdentifier(name), b.f.NewNodeList(typeParamDecls), b.f.NewNodeList(heritageClauses), b.f.NewNodeList(allMembers))
}
// addClassModifiers post-processes class member nodes to add class-specific modifiers
// (private, protected, public, abstract, static) based on the original symbol declarations.
func (b *NodeBuilderImpl) addClassModifiers(members []*ast.Node, isStatic bool) []*ast.Node {
for i, m := range members {
// Find the symbol for this member by matching the property name
var memberSymbol *ast.Symbol
memberName := m.Name()
if memberName != nil {
if sym, ok := b.idToSymbol[memberName]; ok {
memberSymbol = sym
}
}
if memberSymbol == nil {
continue
}
modFlags := getDeclarationModifierFlagsFromSymbol(memberSymbol) &^ ast.ModifierFlagsAsync
if isStatic {
modFlags |= ast.ModifierFlagsStatic
}
if modFlags != 0 && ast.CanHaveModifiers(m) {
existing := m.ModifierFlags()
if modFlags != existing {
members[i] = ast.ReplaceModifiers(b.f, m, b.f.NewModifierList(ast.CreateModifiersFromModifierFlags(modFlags|existing, b.f.NewModifier)))
}
}
}
return members
}
// typeElementsToClassElements converts TypeElement nodes (PropertySignature, MethodSignature)
// to their ClassElement equivalents (PropertyDeclaration, MethodDeclaration) so they can be
// used as members of a ClassDeclaration. Nodes that are already ClassElements pass through unchanged.
func typeElementsToClassElements(f *ast.NodeFactory, members []*ast.Node) []*ast.Node {
for i, m := range members {
switch m.Kind {
case ast.KindPropertySignature:
ps := m.AsPropertySignatureDeclaration()
members[i] = f.NewPropertyDeclaration(m.Modifiers(), ps.Name(), ps.QuestionToken(), ps.Type, nil)
case ast.KindMethodSignature:
ms := m.AsMethodSignatureDeclaration()
members[i] = f.NewMethodDeclaration(m.Modifiers(), nil, ms.Name(), ms.QuestionToken(), ms.TypeParameters, ms.Parameters, ms.Type, nil, nil)
}
}
return members
}
// expandInterfaceDecl produces an InterfaceDeclaration with members.
// Reuses addPropertyToElementList for property serialization and
// signatureToSignatureDeclarationHelper for signatures.
func (b *NodeBuilderImpl) expandInterfaceDecl(symbol *ast.Symbol) *ast.Node {
name := ast.SymbolName(symbol)
b.ctx.approximateLength += 14 + len(name)
interfaceType := b.ch.getDeclaredTypeOfClassOrInterface(symbol)
interfaceDeclarations := core.Filter(symbol.Declarations, ast.IsInterfaceDeclaration)
localParams := b.ch.getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol)
typeParamDecls := core.Map(localParams, func(p *Type) *ast.Node { return b.typeParameterToDeclaration(p) })
baseTypes := b.ch.getBaseTypes(interfaceType)
var baseType *Type
if len(baseTypes) > 0 {
baseType = b.ch.getIntersectionType(baseTypes)
}
// Members: reuse existing serialization functions
resolved := b.ch.resolveStructuredTypeMembers(interfaceType)
var members []*ast.Node
// Index signatures, filtering those identical to base
members = append(members, b.serializeIndexSignaturesOfType(interfaceType, baseType)...)
// Construct signatures (skip abstract)
for _, sig := range resolved.ConstructSignatures() {
if sig.flags&SignatureFlagsAbstract != 0 {
continue
}
members = append(members, b.signatureToSignatureDeclarationHelper(sig, ast.KindConstructSignature, nil))
}
// Call signatures
for _, sig := range resolved.CallSignatures() {
members = append(members, b.signatureToSignatureDeclarationHelper(sig, ast.KindCallSignature, nil))
}
// Properties, filtering inherited
filteredProps := b.filterInheritedProperties(interfaceType, baseTypes, resolved.properties)
members = b.serializePropertiesWithTruncation(filteredProps, members)
// Heritage clauses
heritageClauses := b.hoverHeritageClauses(interfaceDeclarations)
return b.f.NewInterfaceDeclaration(nil, b.f.NewIdentifier(name), b.f.NewNodeList(typeParamDecls), b.f.NewNodeList(heritageClauses), b.f.NewNodeList(members))
}
func (b *NodeBuilderImpl) hoverHeritageClauses(declarations []*ast.Node) []*ast.Node {
var extendsTypes []*ast.Node
var implementsTypes []*ast.Node
for _, declaration := range declarations {
for _, heritageElement := range ast.GetExtendsHeritageClauseElements(declaration) {
extendsTypes = append(extendsTypes, b.f.DeepCloneNode(heritageElement.AsNode()))
}
for _, heritageElement := range ast.GetImplementsHeritageClauseElements(declaration) {
implementsTypes = append(implementsTypes, b.f.DeepCloneNode(heritageElement.AsNode()))
}
}
var heritageClauses []*ast.Node
if len(extendsTypes) > 0 {
heritageClauses = append(heritageClauses, b.f.NewHeritageClause(ast.KindExtendsKeyword, b.f.NewNodeList(extendsTypes)))
}
if len(implementsTypes) > 0 {
heritageClauses = append(heritageClauses, b.f.NewHeritageClause(ast.KindImplementsKeyword, b.f.NewNodeList(implementsTypes)))
}
return heritageClauses
}
// serializePropertiesWithTruncation iterates properties using addPropertyToElementList,
// with truncation checks matching Strada's createTypeNodesFromResolvedType behavior.
func (b *NodeBuilderImpl) serializePropertiesWithTruncation(properties []*ast.Symbol, elements []*ast.Node) []*ast.Node {
properties = core.Filter(properties, func(p *ast.Symbol) bool {
return p.Flags&ast.SymbolFlagsPrototype == 0
})
for i, p := range properties {
if b.checkTruncationLengthIfExpanding() && (i+3 < len(properties)-1) {
b.ctx.expansionTruncated = true
text := fmt.Sprintf("... %d more ...", len(properties)-i-1)
elements = append(elements, b.f.NewPropertySignatureDeclaration(nil, b.f.NewIdentifier(text), nil, nil, nil))
elements = b.addPropertyToElementList(properties[len(properties)-1], elements)
break
}
elements = b.addPropertyToElementList(p, elements)
}
return elements
}
// serializeConstructors builds constructor signature(s) for a class, with base type filtering.
func (b *NodeBuilderImpl) serializeConstructors(staticType *Type, staticBaseType *Type, isClass bool, symbol *ast.Symbol) []*ast.Node {
isNonConstructable := !isClass &&
symbol.ValueDeclaration != nil &&
ast.IsInJSFile(symbol.ValueDeclaration) &&
len(b.ch.getSignaturesOfType(staticType, SignatureKindConstruct)) == 0
if isNonConstructable {
b.ctx.approximateLength += 21
modifiers := ast.CreateModifiersFromModifierFlags(ast.ModifierFlagsPrivate, b.f.NewModifier)
return []*ast.Node{b.f.NewConstructorDeclaration(b.f.NewModifierList(modifiers), nil, b.f.NewNodeList(nil), nil, nil, nil)}
}
signatures := b.ch.getSignaturesOfType(staticType, SignatureKindConstruct)
if staticBaseType != nil {
baseSigs := b.ch.getSignaturesOfType(staticBaseType, SignatureKindConstruct)
if len(baseSigs) == 0 && core.Every(signatures, func(sig *Signature) bool { return len(sig.parameters) == 0 }) {
return nil
}
if len(baseSigs) == len(signatures) {
allMatch := true
for i := range baseSigs {
if b.ch.compareSignaturesIdentical(signatures[i], baseSigs[i], false, false, true, b.ch.compareTypesIdentical) != TernaryTrue {
allMatch = false
break
}
}
if allMatch {
return nil
}
}
var privateProtected ast.ModifierFlags
for _, sig := range signatures {
if sig.declaration != nil {
privateProtected |= sig.declaration.ModifierFlags() & (ast.ModifierFlagsPrivate | ast.ModifierFlagsProtected)
}
}
if privateProtected != 0 {
return []*ast.Node{b.f.NewConstructorDeclaration(
b.f.NewModifierList(ast.CreateModifiersFromModifierFlags(privateProtected, b.f.NewModifier)),
nil, b.f.NewNodeList(nil), nil, nil, nil,
)}
}
} else if core.Every(signatures, func(sig *Signature) bool { return len(sig.parameters) == 0 }) {
return nil
}
var result []*ast.Node
for _, sig := range signatures {
b.ctx.approximateLength++
result = append(result, b.signatureToSignatureDeclarationHelper(sig, ast.KindConstructor, nil))
}
return result
}
// serializeIndexSignaturesOfType builds index signature declarations, filtering those identical to baseType.
func (b *NodeBuilderImpl) serializeIndexSignaturesOfType(input *Type, baseType *Type) []*ast.Node {
var result []*ast.Node
for _, info := range b.ch.getIndexInfosOfType(input) {
if baseType != nil {
baseInfo := b.ch.getIndexInfoOfType(baseType, info.keyType)
if baseInfo != nil && b.ch.isTypeIdenticalTo(info.valueType, baseInfo.valueType) {
continue
}
}
result = append(result, b.indexInfoToIndexSignatureDeclarationHelper(info, nil))
}
return result
}
// serializeNamespaceMember produces the appropriate declaration node for a namespace member
// based on its symbol flags (type alias, enum, class, interface, nested namespace, or variable).
func (b *NodeBuilderImpl) serializeNamespaceMember(resolved *ast.Symbol, name string) *ast.Node {
switch {
case resolved.Flags&ast.SymbolFlagsTypeAlias != 0:
return b.serializeTypeAliasForNamespace(resolved, name)
case resolved.Flags&ast.SymbolFlagsEnum != 0:
return b.expandEnumDecl(resolved)
case resolved.Flags&ast.SymbolFlagsClass != 0:
return b.expandClassDecl(resolved)
case resolved.Flags&ast.SymbolFlagsInterface != 0:
return b.expandInterfaceDecl(resolved)
case resolved.Flags&(ast.SymbolFlagsValueModule|ast.SymbolFlagsNamespaceModule) != 0:
return b.expandModuleDecl(resolved)
default:
t := b.ch.getWidenedType(b.ch.getTypeOfSymbol(resolved))
b.ctx.approximateLength += len(name) + 5
return b.f.NewVariableStatement(
nil,
b.f.NewVariableDeclarationList(
b.f.NewNodeList([]*ast.Node{
b.f.NewVariableDeclaration(b.f.NewIdentifier(name), nil, b.serializeTypeForDeclaration(nil, t, resolved, true), nil),
}),
ast.NodeFlagsLet,
),
)
}
}
// expandModuleDecl produces a ModuleDeclaration with exported members.
func (b *NodeBuilderImpl) expandModuleDecl(symbol *ast.Symbol) *ast.Node {
exports := b.ch.getExportsOfSymbol(symbol)
var members []*ast.Symbol
for _, sym := range exports {
// Filter to namespace-relevant members
if !b.isNamespaceMember(sym) {
continue
}
if !scanner.IsIdentifierText(sym.Name, core.LanguageVariantStandard) {
continue
}
members = append(members, sym)
}
b.ch.sortSymbols(members)
b.ctx.approximateLength += 14
// Use the same name as symbol display.
oldFlags := b.ctx.flags
defer func() { b.ctx.flags = oldFlags }()
b.ctx.flags |= nodebuilder.FlagsWriteTypeParametersInQualifiedName | nodebuilder.Flags(SymbolFormatFlagsUseOnlyExternalAliasing)
localName := b.symbolToNode(symbol, ast.SymbolFlagsAll)
b.ctx.flags = oldFlags
type hoverStatement struct {
node *ast.Node
isLocal bool // local declarations (e.g. alias targets) should not get export modifier
}
var bodyStmts []hoverStatement
var emittedLocals collections.Set[*ast.Symbol]
for i := 0; i < len(members); i++ {
m := members[i]
if b.checkTruncationLengthIfExpanding() && i+3 < len(members)-1 {
b.ctx.expansionTruncated = true
bodyStmts = append(bodyStmts, hoverStatement{node: b.f.NewExpressionStatement(b.f.NewIdentifier(fmt.Sprintf("... (%d more) ...", len(members)-i-1)))})
i = len(members) - 2 // skip to last member after i++ at end of iteration
continue
}
// Handle alias/re-export symbols
if m.Flags&ast.SymbolFlagsAlias != 0 {
aliasDecl := b.ch.getDeclarationOfAliasSymbol(m)
target := b.ch.getMergedSymbol(b.ch.getTargetOfAliasDeclaration(aliasDecl))
if target != nil {
// If the alias target is a local symbol (not itself an export), emit its declaration first
if target.Flags&(ast.SymbolFlagsBlockScopedVariable|ast.SymbolFlagsFunctionScopedVariable|ast.SymbolFlagsProperty) != 0 {
if emittedLocals.AddIfAbsent(target) {
localType := b.ch.getWidenedType(b.ch.getTypeOfSymbol(target))
b.ctx.approximateLength += len(target.Name) + 5
localStmt := b.f.NewVariableStatement(nil,
b.f.NewVariableDeclarationList(b.f.NewNodeList([]*ast.Node{
b.f.NewVariableDeclaration(b.f.NewIdentifier(target.Name), nil, b.serializeTypeForDeclaration(nil, localType, target, true), nil),
}), ast.NodeFlagsLet))
bodyStmts = append(bodyStmts, hoverStatement{node: localStmt, isLocal: true})
}
}
targetName := target.Name
b.ctx.approximateLength += 16 + len(m.Name)
var propertyName *ast.Node
if m.Name != targetName {
propertyName = b.f.NewIdentifier(targetName)
}
stmt := b.f.NewExportDeclaration(
nil, false,
b.f.NewNamedExports(b.f.NewNodeList([]*ast.Node{
b.f.NewExportSpecifier(false, propertyName, b.f.NewIdentifier(m.Name)),
})),
nil, nil,
)
bodyStmts = append(bodyStmts, hoverStatement{node: stmt})
continue
}
}
resolved := b.ch.resolveSymbol(m)
// Handle functions as function declarations
if resolved.Flags&(ast.SymbolFlagsFunction|ast.SymbolFlagsMethod) != 0 {
t := b.ch.getTypeOfSymbol(resolved)
sigs := b.ch.getSignaturesOfType(t, SignatureKindCall)
for _, sig := range sigs {
b.ctx.approximateLength++
decl := b.signatureToSignatureDeclarationHelper(sig, ast.KindFunctionDeclaration, &SignatureToSignatureDeclarationOptions{
name: b.f.NewIdentifier(m.Name),
})
bodyStmts = append(bodyStmts, hoverStatement{node: decl})
}
// If the function also has namespace characteristics, emit an empty namespace.
merged := b.ch.getMergedSymbol(resolved)
hasModuleExports := merged.Flags&(ast.SymbolFlagsValueModule|ast.SymbolFlagsNamespaceModule) != 0 && merged.Exports != nil && len(merged.Exports) != 0
if !hasModuleExports {
bodyStmts = append(bodyStmts, hoverStatement{node: b.f.NewModuleDeclaration(nil, ast.KindNamespaceKeyword, b.f.NewIdentifier(m.Name), b.f.NewModuleBlock(b.f.NewNodeList(nil)))})
}
continue
}
// Handle remaining member kinds (type alias, enum, class, interface, namespace, variable)
if node := b.serializeNamespaceMember(resolved, m.Name); node != nil {
bodyStmts = append(bodyStmts, hoverStatement{node: node})
}
}
// Add export modifier to exported statements (skip local declarations and ExportDeclarations).
for i := range bodyStmts {
s := &bodyStmts[i]
if s.isLocal || ast.IsExportDeclaration(s.node) {
continue
}
if ast.CanHaveModifiers(s.node) {
mf := s.node.ModifierFlags() | ast.ModifierFlagsExport
s.node = ast.ReplaceModifiers(b.f, s.node, b.f.NewModifierList(ast.CreateModifiersFromModifierFlags(mf, b.f.NewModifier)))
}
}
// Collect nodes, stripping export if all statements are exported.
bodyStatements := make([]*ast.Node, len(bodyStmts))
for i := range bodyStmts {
bodyStatements[i] = bodyStmts[i].node
}
allExported := len(bodyStatements) > 0 && core.Every(bodyStatements, func(d *ast.Node) bool {
return ast.HasSyntacticModifier(d, ast.ModifierFlagsExport)
})
if allExported {
for i, stmt := range bodyStatements {
if ast.CanHaveModifiers(stmt) {
mf := stmt.ModifierFlags() &^ ast.ModifierFlagsExport
bodyStatements[i] = ast.ReplaceModifiers(b.f, stmt, b.f.NewModifierList(ast.CreateModifiersFromModifierFlags(mf, b.f.NewModifier)))
}
}
}
keyword := ast.KindNamespaceKeyword
if !ast.IsIdentifier(localName) {
keyword = ast.KindModuleKeyword
}
return b.f.NewModuleDeclaration(nil, keyword, localName, b.f.NewModuleBlock(b.f.NewNodeList(bodyStatements)))
}
// serializeTypeAliasForNamespace produces a TypeAliasDeclaration for a type alias inside a namespace body.
func (b *NodeBuilderImpl) serializeTypeAliasForNamespace(symbol *ast.Symbol, name string) *ast.Node {
aliasType := b.ch.getDeclaredTypeOfTypeAlias(symbol)
typeParams := b.ch.getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol)
typeParamDecls := core.Map(typeParams, func(p *Type) *ast.Node { return b.typeParameterToDeclaration(p) })
restoreFlags := b.saveRestoreFlags()
b.ctx.flags |= nodebuilder.FlagsInTypeAlias
typeNode := b.typeToTypeNode(aliasType)
restoreFlags()
b.ctx.approximateLength += 8 + len(name)
return b.f.NewTypeAliasDeclaration(nil, b.f.NewIdentifier(name), b.f.NewNodeList(typeParamDecls), typeNode)
}
// filterInheritedProperties removes properties already present in base types.
func (b *NodeBuilderImpl) filterInheritedProperties(t *Type, baseTypes []*Type, properties []*ast.Symbol) []*ast.Symbol {
if len(baseTypes) == 0 {
return properties
}
// Build a lookup from property name to symbol for parent-identity comparison.
propsByName := make(map[string]*ast.Symbol, len(properties))
for _, p := range properties {
propsByName[p.Name] = p
}
// Collect names of properties inherited unchanged from base types.
var inherited collections.Set[string]
for _, base := range baseTypes {
baseWithThis := b.ch.getTypeWithThisArgument(base, b.ch.getTargetType(t).AsInterfaceType().thisType, false)
for _, prop := range b.ch.getPropertiesOfType(baseWithThis) {
if existing, ok := propsByName[prop.Name]; ok && prop.Parent == existing.Parent {
inherited.Add(prop.Name)
}
}
}
if inherited.Len() == 0 {
return properties
}
return core.Filter(properties, func(p *ast.Symbol) bool {
return !inherited.Has(p.Name)
})
}
func (b *NodeBuilderImpl) isNamespaceMember(p *ast.Symbol) bool {
return p.Flags&(ast.SymbolFlagsType|ast.SymbolFlagsNamespace|ast.SymbolFlagsAlias) != 0 ||
!(p.Flags&ast.SymbolFlagsPrototype != 0 || p.Name == "prototype" || (p.ValueDeclaration != nil && ast.HasStaticModifier(p.ValueDeclaration) && ast.IsClassLike(p.ValueDeclaration.Parent)))
}
func isHashPrivate(s *ast.Symbol) bool {
return s.ValueDeclaration != nil && s.ValueDeclaration.Name() != nil && ast.IsPrivateIdentifier(s.ValueDeclaration.Name())
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,251 @@
package checker
import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/debug"
"github.com/microsoft/typescript-go/internal/nodebuilder"
)
func cloneNodeBuilderContext(context *NodeBuilderContext) func() {
// Make type parameters created within this context not consume the name outside this context
// The symbol serializer ends up creating many sibling scopes that all need "separate" contexts when
// it comes to naming things - within a normal `typeToTypeNode` call, the node builder only ever descends
// through the type tree, so the only cases where we could have used distinct sibling scopes was when there
// were multiple generic overloads with similar generated type parameter names
// The effect:
// When we write out
// export const x: <T>(x: T) => T
// export const y: <T>(x: T) => T
// we write it out like that, rather than as
// export const x: <T>(x: T) => T
// export const y: <T_1>(x: T_1) => T_1
restoreNames := context.typeParameterNames.EnterScope()
restoreNamesByText := context.typeParameterNamesByText.EnterScope()
restoreNamesByTextNextNameCount := context.typeParameterNamesByTextNextNameCount.EnterScope()
restoreSymbolList := context.typeParameterSymbolList.EnterScope()
return func() {
restoreNames()
restoreNamesByText()
restoreNamesByTextNextNameCount()
restoreSymbolList()
}
}
type localsRecord struct {
name string
oldSymbol *ast.Symbol
}
func (b *NodeBuilderImpl) addSymbolTypeToContext(symbol *ast.Symbol, t *Type) func() {
id := ast.GetSymbolId(symbol)
oldType, oldTypeExists := b.ctx.enclosingSymbolTypes[id]
b.ctx.enclosingSymbolTypes[id] = t
return func() {
if oldTypeExists {
b.ctx.enclosingSymbolTypes[id] = oldType
} else {
delete(b.ctx.enclosingSymbolTypes, id)
}
}
}
func (b *NodeBuilderImpl) enterSignatureScope(signature *Signature) (expandedParams []*ast.Symbol, cleanup func()) {
expandedParams = b.ch.getExpandedParameters(signature, true /*skipUnionExpanding*/)[0]
cleanup = b.enterNewScope(signature.declaration, expandedParams, signature.typeParameters, signature.parameters, signature.mapper)
return expandedParams, cleanup
}
func (b *NodeBuilderImpl) enterNewScope(declaration *ast.Node, expandedParams []*ast.Symbol, typeParameters []*Type, originalParameters []*ast.Symbol, mapper *TypeMapper) func() {
cleanupContext := cloneNodeBuilderContext(b.ctx)
// For regular function/method declarations, the enclosing declaration will already be signature.declaration,
// so this is a no-op, but for arrow functions and function expressions, the enclosing declaration will be
// the declaration that the arrow function / function expression is assigned to.
//
// If the parameters or return type include "typeof globalThis.paramName", using the wrong scope will lead
// us to believe that we can emit "typeof paramName" instead, even though that would refer to the parameter,
// not the global. Make sure we are in the right scope by changing the enclosingDeclaration to the function.
//
// We can't use the declaration directly; it may be in another file and so we may lose access to symbols
// accessible to the current enclosing declaration, or gain access to symbols not accessible to the current
// enclosing declaration. To keep this chain accurate, insert a fake scope into the chain which makes the
// function's parameters visible.
var cleanupParams func()
var cleanupTypeParams func()
oldEnclosingDecl := b.ctx.enclosingDeclaration
oldMapper := b.ctx.mapper
if mapper != nil {
b.ctx.mapper = mapper
}
if b.ctx.enclosingDeclaration != nil && declaration != nil {
// As a performance optimization, reuse the same fake scope within this chain.
// This is especially needed when we are working on an excessively deep type;
// if we don't do this, then we spend all of our time adding more and more
// scopes that need to be searched in isSymbolAccessible later. Since all we
// really want to do is to mark certain names as unavailable, we can just keep
// all of the names we're introducing in one large table and push/pop from it as
// needed; isSymbolAccessible will walk upward and find the closest "fake" scope,
// which will conveniently report on any and all faked scopes in the chain.
//
// It'd likely be better to store this somewhere else for isSymbolAccessible, but
// since that API _only_ uses the enclosing declaration (and its parents), this is
// seems like the best way to inject names into that search process.
//
// Note that we only check the most immediate enclosingDeclaration; the only place we
// could potentially add another fake scope into the chain is right here, so we don't
// traverse all ancestors.
pushFakeScope := func(kind string, addAll func(addSymbol func(name string, symbol *ast.Symbol))) func() {
// We only ever need to look two declarations upward.
debug.Assert(b.ctx.enclosingDeclaration != nil)
var existingFakeScope *ast.Node
if b.links.Has(b.ctx.enclosingDeclaration) {
links := b.links.Get(b.ctx.enclosingDeclaration)
if links.fakeScopeForSignatureDeclaration != nil && *links.fakeScopeForSignatureDeclaration == kind {
existingFakeScope = b.ctx.enclosingDeclaration
}
}
if existingFakeScope == nil && b.ctx.enclosingDeclaration.Parent != nil {
if b.links.Has(b.ctx.enclosingDeclaration.Parent) {
links := b.links.Get(b.ctx.enclosingDeclaration.Parent)
if links.fakeScopeForSignatureDeclaration != nil && *links.fakeScopeForSignatureDeclaration == kind {
existingFakeScope = b.ctx.enclosingDeclaration.Parent
}
}
}
debug.Assert(existingFakeScope == nil || ast.IsBlock(existingFakeScope))
var locals ast.SymbolTable
if existingFakeScope != nil {
locals = existingFakeScope.Locals()
}
if locals == nil {
locals = make(ast.SymbolTable)
}
newLocals := []string{}
oldLocals := []localsRecord{}
addAll(func(name string, symbol *ast.Symbol) {
// Add cleanup information only if we don't own the fake scope
if existingFakeScope != nil {
oldSymbol, ok := locals[name]
if !ok || oldSymbol == nil {
newLocals = append(newLocals, name)
} else {
oldLocals = append(oldLocals, localsRecord{name, oldSymbol})
}
}
locals[name] = symbol
})
if existingFakeScope == nil {
// Use a Block for this; the type of the node doesn't matter so long as it
// has locals, and this is cheaper/easier than using a function-ish Node.
fakeScope := b.f.NewBlock(b.f.NewNodeList([]*ast.Node{}), false)
b.links.Get(fakeScope).fakeScopeForSignatureDeclaration = &kind
data := fakeScope.LocalsContainerData()
data.Locals = locals
fakeScope.Parent = b.ctx.enclosingDeclaration
b.ctx.enclosingDeclaration = fakeScope
return nil
} else {
// We did not create the current scope, so we have to clean it up
undo := func() {
for _, s := range newLocals {
delete(locals, s)
}
for _, s := range oldLocals {
locals[s.name] = s.oldSymbol
}
}
return undo
}
}
if expandedParams == nil || !core.Some(expandedParams, func(p *ast.Symbol) bool { return p != nil }) {
cleanupParams = nil
} else {
cleanupParams = pushFakeScope("params", func(add func(name string, symbol *ast.Symbol)) {
if expandedParams == nil {
return
}
for pIndex, param := range expandedParams {
var originalParam *ast.Symbol
if pIndex < len(originalParameters) {
originalParam = originalParameters[pIndex]
}
if originalParameters != nil && originalParam != param {
// Can't reference the expanded parameter name, just the original, unless we've expanded the param list for some reason
if originalParam != nil {
add(originalParam.Name, originalParam)
}
} else if !core.Some(param.Declarations, func(d *ast.Node) bool {
var bindElement func(e *ast.BindingElement)
var bindPattern func(e *ast.BindingPattern)
bindPatternWorker := func(p *ast.BindingPattern) {
for _, e := range p.Elements.Nodes {
switch e.Kind {
case ast.KindOmittedExpression:
return
case ast.KindBindingElement:
bindElement(e.AsBindingElement())
return
default:
panic("Unhandled binding element kind")
}
}
}
bindElementWorker := func(e *ast.BindingElement) {
if e.Name() != nil && ast.IsBindingPattern(e.Name()) {
bindPattern(e.Name().AsBindingPattern())
return
}
symbol := b.ch.getSymbolOfDeclaration(e.AsNode())
if symbol != nil { // omitted expressions are now parsed as nameless binding patterns and also have no symbol
add(symbol.Name, symbol)
}
}
bindElement = bindElementWorker
bindPattern = bindPatternWorker
if ast.IsParameterDeclaration(d) && d.Name() != nil && ast.IsBindingPattern(d.Name()) {
bindPattern(d.Name().AsBindingPattern())
return true
}
return false
}) {
add(param.Name, param)
}
}
})
}
if b.ctx.flags&nodebuilder.FlagsGenerateNamesForShadowedTypeParams != 0 && typeParameters != nil && core.Some(typeParameters, func(p *Type) bool { return p != nil }) {
cleanupTypeParams = pushFakeScope("typeParams", func(add func(name string, symbol *ast.Symbol)) {
if typeParameters == nil {
return
}
for _, typeParam := range typeParameters {
if typeParam == nil {
continue
}
typeParamName := b.typeParameterToName(typeParam).Text
add(typeParamName, typeParam.symbol)
}
})
}
}
return func() {
if cleanupParams != nil {
cleanupParams()
}
if cleanupTypeParams != nil {
cleanupTypeParams()
}
cleanupContext()
b.ctx.enclosingDeclaration = oldEnclosingDecl
b.ctx.mapper = oldMapper
}
}

View File

@@ -0,0 +1,900 @@
package checker
import (
"strings"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/nodebuilder"
"github.com/microsoft/typescript-go/internal/printer"
)
func (b *NodeBuilderImpl) reuseNode(node *ast.Node) *ast.Node {
if node == nil {
return node
}
return b.tryReuseExistingNodeHelper(node)
}
func (b *NodeBuilderImpl) tryJSTypeNodeToTypeNode(node *ast.Node) *ast.Node {
return b.reuseNode(node)
}
func (b *NodeBuilderImpl) reuseName(node *ast.Node, isMethod bool) *ast.Node {
res := b.reuseNode(node)
if res == nil {
return res
}
text, ok := ast.TryGetTextOfPropertyName(res)
if !ok {
return res
}
kind := classifyPropertyName(text, ast.IsStringLiteral(res), isMethod)
if ast.IsIdentifier(res) && kind == propertyNameNodeKindIdentifier {
return res
}
if ast.IsStringLiteral(res) && kind == propertyNameNodeKindStringLiteral {
return res
}
var renamed *ast.Node
switch kind {
case propertyNameNodeKindIdentifier:
renamed = b.newIdentifier(text, nil)
case propertyNameNodeKindStringLiteral:
renamed = b.f.NewStringLiteral(text, ast.TokenFlagsNone)
default:
return res
}
b.e.SetOriginal(renamed, res)
return b.setTextRange(renamed, res)
}
func (b *NodeBuilderImpl) reuseTypeNode(node *ast.Node) *ast.Node {
if node == nil {
return node
}
r := b.reuseNode(node)
if r != nil {
// After successful reuse during hover, probe the reused AST for expandable
// type references so canIncreaseExpansionDepth is set even though
// typeToTypeNode (and shouldExpandType) were never called.
if b.ctx.maxExpansionDepth >= 0 && !b.ctx.canIncreaseExpansionDepth {
b.walkNodeForExpandability(node)
}
return r
}
b.ctx.tracker.ReportInferenceFallback(node)
t := b.getTypeFromTypeNode(node, false)
return b.typeToTypeNode(t)
}
// walkNodeForExpandability walks a reused AST node tree, calling checkTypeExpandability
// on each type reference, type predicate, or import type node.
// Short-circuits once canIncreaseExpansionDepth is set.
func (b *NodeBuilderImpl) walkNodeForExpandability(node *ast.Node) {
if b.ctx.canIncreaseExpansionDepth || node == nil {
return
}
// Check these explicitly so we look into type arguments wehther or not they are in the tree or not.
if ast.IsTypeReferenceNode(node) || ast.IsExpressionWithTypeArguments(node) || ast.IsTypePredicateNode(node) || ast.IsImportTypeNode(node) {
t := b.getTypeFromTypeNode(node, false)
if t != nil {
b.checkTypeExpandability(t)
if b.ctx.canIncreaseExpansionDepth {
return
}
}
}
node.ForEachChild(func(child *ast.Node) bool {
b.walkNodeForExpandability(child)
return b.ctx.canIncreaseExpansionDepth
})
}
type recoveryBoundary struct {
ctx *NodeBuilderContext
hadError bool
deferredReports []func()
oldTracker nodebuilder.SymbolTracker
oldTrackedSymbols []*TrackedSymbolArgs
trackedSymbols []*TrackedSymbolArgs
oldEncounteredError bool
oldApproximateLength int
}
func (b *recoveryBoundary) markError(f func()) {
b.hadError = true
if f != nil {
b.deferredReports = append(b.deferredReports, f)
}
}
type originalRecoveryScopeState struct {
trackedSymbolsTop int
unreportedErrorsTop int
hadError bool
}
func (b *recoveryBoundary) startRecoveryScope() originalRecoveryScopeState {
trackedSymbolsTop := len(b.ctx.trackedSymbols)
unreportedErrorsTop := len(b.deferredReports)
return originalRecoveryScopeState{trackedSymbolsTop: trackedSymbolsTop, unreportedErrorsTop: unreportedErrorsTop, hadError: b.hadError}
}
func (b *recoveryBoundary) endRecoveryScope(state originalRecoveryScopeState) {
b.hadError = state.hadError
b.ctx.trackedSymbols = b.ctx.trackedSymbols[0:state.trackedSymbolsTop]
b.deferredReports = b.deferredReports[0:state.unreportedErrorsTop]
}
type wrappingTracker struct {
wrapped nodebuilder.SymbolTracker
bound *recoveryBoundary
}
func (w *wrappingTracker) PopErrorFallbackNode() {
w.wrapped.PopErrorFallbackNode()
}
func (w *wrappingTracker) PushErrorFallbackNode(node *ast.Node) {
w.wrapped.PushErrorFallbackNode(node)
}
func (w *wrappingTracker) ReportCyclicStructureError() {
w.bound.markError(w.wrapped.ReportCyclicStructureError)
}
func (w *wrappingTracker) ReportInaccessibleThisError() {
w.bound.markError(w.wrapped.ReportInaccessibleThisError)
}
func (w *wrappingTracker) ReportInaccessibleUniqueSymbolError() {
w.bound.markError(w.wrapped.ReportInaccessibleUniqueSymbolError)
}
func (w *wrappingTracker) ReportInferenceFallback(node *ast.Node) {
w.wrapped.ReportInferenceFallback(node) // Should this also be deferred?
}
func (w *wrappingTracker) ReportLikelyUnsafeImportRequiredError(specifier string, symbolName string) {
w.bound.markError(func() { w.wrapped.ReportLikelyUnsafeImportRequiredError(specifier, symbolName) })
}
func (w *wrappingTracker) ReportNonSerializableProperty(propertyName string) {
w.bound.markError(func() { w.wrapped.ReportNonSerializableProperty(propertyName) })
}
func (w *wrappingTracker) ReportNonlocalAugmentation(containingFile *ast.SourceFile, parentSymbol *ast.Symbol, augmentingSymbol *ast.Symbol) {
w.wrapped.ReportNonlocalAugmentation(containingFile, parentSymbol, augmentingSymbol) // Should this also be deferred?
}
func (w *wrappingTracker) ReportPrivateInBaseOfClassExpression(propertyName string) {
w.bound.markError(func() { w.wrapped.ReportPrivateInBaseOfClassExpression(propertyName) })
}
func (w *wrappingTracker) ReportTruncationError() {
w.wrapped.ReportTruncationError() // Should this also be deferred?
}
func (w *wrappingTracker) TrackSymbol(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags) bool {
w.bound.trackedSymbols = append(w.bound.trackedSymbols, &TrackedSymbolArgs{symbol, enclosingDeclaration, meaning})
return false
}
func newWrappingTracker(inner nodebuilder.SymbolTracker, bound *recoveryBoundary) *wrappingTracker {
return &wrappingTracker{
wrapped: inner,
bound: bound,
}
}
func (b *NodeBuilderImpl) createRecoveryBoundary() *recoveryBoundary {
b.ch.checkNotCanceled()
bound := &recoveryBoundary{ctx: b.ctx, oldTracker: b.ctx.tracker, oldTrackedSymbols: b.ctx.trackedSymbols, oldEncounteredError: b.ctx.encounteredError, oldApproximateLength: b.ctx.approximateLength}
newTracker := NewSymbolTrackerImpl(b.ctx, newWrappingTracker(b.ctx.tracker, bound))
b.ctx.tracker = newTracker
b.ctx.trackedSymbols = nil
return bound
}
func (b *NodeBuilderImpl) finalizeBoundary(bound *recoveryBoundary) bool {
b.ctx.tracker = bound.oldTracker
b.ctx.trackedSymbols = bound.oldTrackedSymbols
b.ctx.encounteredError = bound.oldEncounteredError
b.ctx.approximateLength = bound.oldApproximateLength
for _, f := range bound.deferredReports {
f()
}
if bound.hadError {
return false
}
for _, a := range bound.trackedSymbols {
b.ctx.tracker.TrackSymbol(a.symbol, a.enclosingDeclaration, a.meaning)
}
return true
}
func (b *NodeBuilderImpl) tryReuseExistingNodeHelper(existing *ast.TypeNode) *ast.TypeNode {
bound := b.createRecoveryBoundary()
var transformed *ast.Node
v := getExistingNodeTreeVisitor(b, bound) // !!! TODO: Cache visitor and just reset bound+host builder? We try this for a *lot* of nodes.
transformed = v.VisitNode(existing)
if !b.finalizeBoundary(bound) {
return nil
}
b.ctx.approximateLength += existing.Loc.End() - existing.Loc.Pos()
return transformed
}
func (b *NodeBuilderImpl) getModuleSpecifierOverride(parent *ast.Node, lit *ast.Node) string {
if b.ctx.enclosingFile != ast.GetSourceFileOfNode(lit) {
mode := core.ResolutionModeNone
if parent.AsImportTypeNode().Attributes != nil {
mode = b.ch.getResolutionModeOverride(parent.AsImportTypeNode().Attributes.AsImportAttributes(), false)
}
name := lit.Text()
originalName := name
nodeSymbol := b.tryGetResolvedSymbolFromTypeNode(parent)
meaning := ast.SymbolFlagsType
if parent.AsImportTypeNode().IsTypeOf {
meaning = ast.SymbolFlagsValue
}
var parentSymbol *ast.Symbol
if nodeSymbol != nil && b.ch.IsSymbolAccessible(nodeSymbol, b.ctx.enclosingDeclaration, meaning, false).Accessibility == printer.SymbolAccessibilityAccessible {
parentSymbol = b.lookupSymbolChain(nodeSymbol, meaning, true)[0]
}
if parentSymbol != nil && IsExternalModuleSymbol(parentSymbol) {
name = b.getSpecifierForModuleSymbol(parentSymbol, mode)
} else {
targetFile := b.ch.getExternalModuleFileFromDeclaration(parent)
if targetFile != nil {
name = b.getSpecifierForModuleSymbol(targetFile.Symbol, mode)
}
}
if len(name) > 0 && strings.Contains(name, "/node_modules/") {
b.ctx.encounteredError = true
b.ctx.tracker.ReportLikelyUnsafeImportRequiredError(name, "")
}
if name != originalName {
return name
}
}
return ""
}
func (b *NodeBuilderImpl) rewriteModuleSpecifier(parent *ast.Node, lit *ast.Node) *ast.Node {
newName := b.getModuleSpecifierOverride(parent, lit)
if len(newName) == 0 {
return lit
}
res := b.f.NewStringLiteral(newName, ast.TokenFlagsNone)
b.e.SetOriginal(res, lit)
return res
}
func (b *NodeBuilderImpl) getEnclosingDeclarationIgnoringFakeScope() *ast.Node {
enc := b.ctx.enclosingDeclaration
for enc != nil && b.links.Get(enc).fakeScopeForSignatureDeclaration != nil {
enc = enc.Parent
}
return enc
}
func getExistingNodeTreeVisitor(b *NodeBuilderImpl, bound *recoveryBoundary) *ast.NodeVisitor {
// TODO: wrap all these closures into methods on an object so we can guarantee we reuse the same memory on each invocation by reusing/resetting the object
// instead of re-closing-over all of these each time we need a visitor. In theory the compiler could handle this, but in practice closure inlining hasn't been reliable
var visitor *ast.NodeVisitor
// note: also handles renaming type parameters renamed within the current context
attachSymbolToLeftmostIdentifier := func(leftmost *ast.Node, node *ast.Node, sym *ast.Symbol) *ast.Node {
var vis *ast.NodeVisitor
visitorFunc := func(node *ast.Node) *ast.Node {
if node == leftmost {
var type_ *Type
var name *ast.Node
if sym != nil {
type_ = b.ch.getDeclaredTypeOfSymbol(sym)
if sym.Flags&ast.SymbolFlagsTypeParameter != 0 {
name = b.typeParameterToName(type_).AsNode()
}
}
if name == nil {
name = b.newIdentifier(node.Text(), sym)
}
name = b.setTextRange(name, node)
b.e.AddEmitFlags(name, printer.EFNoAsciiEscaping)
return name
}
return b.setTextRange(node.VisitEachChild(vis), node)
}
vis = ast.NewNodeVisitor(visitorFunc, b.f, ast.NodeVisitorHooks{})
return visitorFunc(node)
}
trackExistingEntityName := func(node *ast.Node, overrideEnclosing *ast.Node) (bool, *ast.Node, *ast.Symbol) {
enclosingDeclaration := b.ctx.enclosingDeclaration
if overrideEnclosing != nil {
enclosingDeclaration = overrideEnclosing
}
introducesError := false
leftmost := ast.GetFirstIdentifier(node)
if ast.IsInJSFile(node) && (ast.IsExportsIdentifier(leftmost) || ast.IsModuleExportsAccessExpression(leftmost.Parent) || (ast.IsQualifiedName(leftmost.Parent) && ast.IsModuleIdentifier(leftmost.Parent.AsQualifiedName().Left) && ast.IsExportsIdentifier(leftmost.Parent.AsQualifiedName().Right))) {
introducesError = true
return introducesError, b.setTextRange(b.f.DeepCloneNode(node), node), nil
}
meaning := getMeaningOfEntityNameReference(node)
var sym *ast.Symbol
if ast.IsThisIdentifier(leftmost) {
// `this` isn't a bindable identifier - skip resolution, find a relevant `this` symbol directly and avoid exhaustive scope traversal
sym = b.ch.getSymbolOfDeclaration(b.ch.getThisContainer(leftmost, false, false))
if b.ch.IsSymbolAccessible(sym, leftmost, meaning, false).Accessibility != printer.SymbolAccessibilityAccessible {
introducesError = true
b.ctx.tracker.ReportInaccessibleThisError()
}
return introducesError, attachSymbolToLeftmostIdentifier(leftmost, node, sym), nil
}
sym = b.ch.resolveEntityName(leftmost, meaning, true, true, nil)
if b.ctx.enclosingDeclaration != nil && !(sym != nil && sym.Flags&ast.SymbolFlagsTypeParameter != 0) {
sym = b.ch.getExportSymbolOfValueSymbolIfExported(sym)
// Some declarations may be transplanted to a new location.
// When this happens we need to make sure that the name has the same meaning at both locations
// We also check for the unknownSymbol because when we create a fake scope some parameters may actually not be usable
// either because they are the expanded rest parameter,
// or because they are the newly added parameters from the tuple, which might have different meanings in the original context
symAtLocation := b.ch.resolveEntityName(leftmost, meaning, true, true, b.ctx.enclosingDeclaration)
if
// Check for unusable parameters symbols
symAtLocation == b.ch.unknownSymbol ||
// If the symbol is not found, but was not found in the original scope either we probably have an error, don't reuse the node
(symAtLocation == nil && sym != nil) ||
// If the symbol is found both in declaration scope and in current scope then it should point to the same reference
(symAtLocation != nil && sym != nil && b.ch.getSymbolIfSameReference(b.ch.getExportSymbolOfValueSymbolIfExported(symAtLocation), sym) == nil) {
// In isolated declaration we will not do rest parameter expansion so there is no need to report on these.
if symAtLocation != b.ch.unknownSymbol {
b.ctx.tracker.ReportInferenceFallback(node)
}
introducesError = true
return introducesError, b.setTextRange(b.f.DeepCloneNode(node), node), sym
} else {
sym = symAtLocation
}
}
if sym != nil {
// If a parameter is resolvable in the current context it is also visible, so no need to go to symbol accesibility
if sym.Flags&ast.SymbolFlagsFunctionScopedVariable != 0 && sym.ValueDeclaration != nil {
if ast.IsPartOfParameterDeclaration(sym.ValueDeclaration) || ast.IsJSDocParameterTag(sym.ValueDeclaration) {
return introducesError, attachSymbolToLeftmostIdentifier(leftmost, node, sym), nil
}
}
if sym.Flags&ast.SymbolFlagsTypeParameter == 0 /* Type parameters are visible in the current context if they are are resolvable */ && !ast.IsDeclarationName(node) &&
b.ch.IsSymbolAccessible(sym, enclosingDeclaration, meaning, false).Accessibility != printer.SymbolAccessibilityAccessible {
b.ctx.tracker.ReportInferenceFallback(node)
introducesError = true
} else {
b.ctx.tracker.TrackSymbol(sym, enclosingDeclaration, meaning)
}
return introducesError, attachSymbolToLeftmostIdentifier(leftmost, node, sym), nil
}
return introducesError, b.setTextRange(b.f.DeepCloneNode(node), node), nil
}
var tryVisitSimpleTypeNode func(node *ast.Node) *ast.Node
tryVisitIndexedAccess := func(node *ast.Node) *ast.Node {
resultObjectType := tryVisitSimpleTypeNode(node.AsIndexedAccessTypeNode().ObjectType)
if resultObjectType == nil {
return nil
}
return b.setTextRange(b.f.UpdateIndexedAccessTypeNode(node.AsIndexedAccessTypeNode(), resultObjectType, visitor.VisitNode(node.AsIndexedAccessTypeNode().IndexType)), node)
}
tryVisitKeyOf := func(node *ast.Node) *ast.Node {
to := node.AsTypeOperatorNode()
t := tryVisitSimpleTypeNode(to.Type)
if t == nil {
return nil
}
return b.setTextRange(b.f.UpdateTypeOperatorNode(to, to.Operator, t), node)
}
tryVisitTypeQuery := func(node *ast.Node) *ast.Node {
introducesError, exprName, _ := trackExistingEntityName(node.AsTypeQueryNode().ExprName, nil)
if !introducesError {
return b.setTextRange(b.f.UpdateTypeQueryNode(
node.AsTypeQueryNode(),
exprName,
visitor.VisitNodes(node.AsTypeQueryNode().TypeArguments),
), node)
}
serializedName := b.serializeTypeName(node.AsTypeQueryNode().ExprName, true, visitor.VisitNodes(node.AsTypeQueryNode().TypeArguments))
if serializedName != nil {
return b.setTextRange(serializedName, node.AsTypeQueryNode().ExprName)
}
return nil
}
tryVisitTypeReference := func(node *ast.Node) *ast.Node {
if ast.IsConstTypeReference(node) {
return nil
}
s := b.tryGetResolvedSymbolFromTypeNode(node)
if s == nil {
return nil // ???
}
if s.Flags&ast.SymbolFlagsTypeParameter != 0 {
declaredType := b.ch.getDeclaredTypeOfSymbol(s)
if b.ctx.mapper != nil && b.ctx.mapper.Map(declaredType) != declaredType {
return nil // refers to type parameter remapped by context (TODO improvement: just return the remapped param name?)
}
}
if !b.canReuseExistingJSTypeNode(node, b.getTypeFromTypeNode(node, false)) {
// fallback to serialization for jsdoc types that have insufficient or incomplete type args, or are remapped by the checker in only jsdoc contexts
// TODO: remappings like `promise` -> `Promise<any>` are static, we *could* statically remap the nodes, too. But that only matters for `isolatedDeclarations`
// in JS, should we enable that.
return nil
}
introducesError, newName, _ := trackExistingEntityName(node.AsTypeReferenceNode().TypeName, nil)
if !introducesError {
typeArguments := visitor.VisitNodes(node.AsTypeReferenceNode().TypeArguments)
return b.setTextRange(b.f.UpdateTypeReferenceNode(
node.AsTypeReferenceNode(),
newName,
typeArguments,
), node)
} else {
serializedName := b.serializeTypeName(node.AsTypeReferenceNode().TypeName, false, visitor.VisitNodes(node.AsTypeReferenceNode().TypeArguments))
if serializedName != nil {
return b.setTextRange(serializedName, node.AsTypeReferenceNode().TypeName)
}
return nil
}
}
tryVisitSimpleTypeNode = func(node *ast.Node) *ast.Node {
innerNode := ast.SkipParentheses(node)
switch innerNode.Kind {
case ast.KindTypeReference:
return tryVisitTypeReference(innerNode)
case ast.KindTypeQuery:
return tryVisitTypeQuery(innerNode)
case ast.KindIndexedAccessType:
return tryVisitIndexedAccess(innerNode)
case ast.KindTypeOperator:
if innerNode.AsTypeOperatorNode().Operator == ast.KindKeyOfKeyword {
return tryVisitKeyOf(innerNode)
}
}
return visitor.VisitNode(node)
}
visitExistingNodeTreeSymbolsWorker := func(node *ast.Node) *ast.Node {
factory := b.f
// !!! TODO: the reparser *should* make all the jsdoc remapping logic here redundant,
// assuming we only ever try to preserve reparsed nodes and never walk back to the jsdoc "originals"
// accidentally.
// Still, what can be ported of the logic is here, just in case.
// Begin JSDoc handling
if node.Kind == ast.KindJSDocTypeExpression {
// Unwrap JSDocTypeExpressions
return visitor.VisitNode(node.AsJSDocTypeExpression().Type)
}
// !!! TODO: We don't _actually_ support jsdoc namepath types, emit `any` instead; verify we handle as gracefully as strada
if node.Kind == ast.KindJSDocAllType /* || node.Kind == ast.JSDocNamepathType */ {
return factory.NewKeywordTypeNode(ast.KindAnyKeyword)
}
// !!! TODO: verify JSDocUnknwonType is hopefully just parsed into `unknown` upfront; the kind no longer exists
// if node.Kind == ast.KindJSDocUnknownType {
// return factory.NewKeywordTypeNode(ast.KindUnknownKeyword)
// }
if node.Kind == ast.KindJSDocNullableType {
unionMembers := []*ast.Node{
visitor.VisitNode(node.AsJSDocNullableType().Type),
factory.NewLiteralTypeNode(factory.NewKeywordExpression(ast.KindNullKeyword)),
}
return factory.NewUnionTypeNode(factory.NewNodeList(unionMembers))
}
if node.Kind == ast.KindJSDocOptionalType {
unionMembers := []*ast.Node{
visitor.VisitNode(node.AsJSDocOptionalType().Type),
factory.NewKeywordTypeNode(ast.KindUndefinedKeyword),
}
return factory.NewUnionTypeNode(factory.NewNodeList(unionMembers))
}
if node.Kind == ast.KindJSDocNonNullableType {
// Unwrap
return visitor.VisitNode(node.AsJSDocNonNullableType().Type)
}
if node.Kind == ast.KindJSDocVariadicType { // !!! TODO: verify this matches how jsdoc variadics are actually handled now?
return factory.NewArrayTypeNode(visitor.VisitNode(node.AsJSDocVariadicType().Type))
}
if node.Kind == ast.KindJSDocTypeLiteral {
var members []*ast.Node
for _, t := range node.AsJSDocTypeLiteral().JSDocPropertyTags {
if t.Kind != ast.KindJSDocPropertyTag && t.Kind != ast.KindJSDocParameterTag {
continue
}
n := t.Name()
var targetName *ast.Node
if ast.IsIdentifier(n) {
targetName = n
} else {
targetName = n.AsQualifiedName().Right // !!! TODO: without typesystem backup, doing this cast unguarded seems really suspect, even though it is what strada does
}
name := visitor.VisitNode(targetName)
shouldBeOptional := t.AsJSDocParameterOrPropertyTag().IsBracketed || (t.TypeExpression() != nil && t.TypeExpression().Kind == ast.KindJSDocOptionalType)
var question *ast.Node
if shouldBeOptional {
question = factory.NewToken(ast.KindQuestionToken)
}
ty := visitor.VisitNode(t.TypeExpression()) // !!! TODO: alternate lookup locations for the type? serialize on demand if it doesn't serialze? strada does something funky here.
members = append(members, factory.NewPropertySignatureDeclaration(nil, name, question, ty, nil))
}
return factory.NewTypeLiteralNode(factory.NewNodeList(members))
}
// if (ast.IsExpressionWithTypeArguments(node) || ast.IsTypeReferenceNode(node)) && ast.IsJSDocIndexSignature(node) { /// !!! TODO: JSDocIndexSignature handling hasn't been ported - readd if it's readded
// args := node.TypeArguments()
// if len(args) != 2 {
// return factory.NewKeywordTypeNode(ast.KindAnyKeyword) // shouldn't be flagged as a jsdoc index signature in the first place
// }
// return factory.NewTypeLiteralNode(factory.NewNodeList([]*ast.Node{
// factory.NewIndexSignatureDeclaration(nil, factory.NewNodeList([]*ast.Node{
// factory.NewParameterDeclaration(nil, nil, factory.NewIdentifier("x"), nil, visitor.VisitNode(args[0]), nil),
// }), visitor.VisitNode(args[1])),
// }))
// }
// if node.Kind == ast.KindJSDocFunctionType {} // !!! no longer exists
// End JSDoc handling
if ast.IsTypeReferenceNode(node) && ast.IsIdentifier(node.AsTypeReferenceNode().TypeName) && node.AsTypeReferenceNode().TypeName.AsIdentifier().Text == "" {
replacement := factory.NewKeywordTypeNode(ast.KindAnyKeyword)
b.e.SetOriginal(replacement, node)
return replacement
}
if ast.IsThisTypeNode(node) {
// TODO: strada never marks `this` type nodes as an error - it calls `canReuseTypeNode` on it, but that function always returns `true` for `this`
// type nodes, which in turn fails to verify that the `this` context is the same between the source and target locations. The conservative thing is to
// _never_ copy a `this`. We could improve this, but strada is *definitely* wrong and overbroad here. (note that we're inling uses of `canReuseTypeNode`
// in corsa because of the unfurled host structure meaning we don't need to defer to a host object for functionality it needs)
// bound.markError(nil) // conservative approach
return node
}
if ast.IsTypeParameterDeclaration(node) {
_, newName, _ := trackExistingEntityName(node.Name(), nil)
return factory.UpdateTypeParameterDeclaration(
node.AsTypeParameterDeclaration(),
visitor.VisitModifiers(node.Modifiers()),
newName,
visitor.VisitNode(node.AsTypeParameterDeclaration().Constraint),
visitor.VisitNode(node.AsTypeParameterDeclaration().Expression),
visitor.VisitNode(node.AsTypeParameterDeclaration().DefaultType),
)
}
if ast.IsIndexedAccessTypeNode(node) {
result := tryVisitIndexedAccess(node)
if result != nil {
return result
}
bound.markError(nil)
return node
}
if ast.IsTypeReferenceNode(node) {
result := tryVisitTypeReference(node)
if result != nil {
return result
}
bound.markError(nil)
return node
}
if ast.IsTypeQueryNode(node) {
result := tryVisitTypeQuery(node)
if result != nil {
return result
}
bound.markError(nil)
return node
}
if ast.IsTypeOperatorNode(node) {
if node.AsTypeOperatorNode().Operator == ast.KindUniqueKeyword && node.AsTypeOperatorNode().Type.Kind == ast.KindSymbolKeyword {
nonFakeEnclosing := b.getEnclosingDeclarationIgnoringFakeScope()
sameScope := ast.FindAncestor(node, func(a *ast.Node) bool {
return a == nonFakeEnclosing
})
if sameScope == nil {
bound.markError(nil)
return node
}
} else if node.AsTypeOperatorNode().Operator == ast.KindKeyOfKeyword {
result := tryVisitKeyOf(node)
if result != nil {
return result
}
bound.markError(nil)
return node
}
}
if ast.IsLiteralImportTypeNode(node) {
// assert keyword in imported attributes is deprecated, so we don't reuse types that contain it
// Ex: import("pkg", { assert: {} }
if node.AsImportTypeNode().Attributes != nil && node.AsImportTypeNode().Attributes.AsImportAttributes().Token == ast.KindAssertKeyword {
bound.markError(nil)
return node
}
t := b.getTypeFromTypeNode(node, true)
if t == nil {
bound.markError(nil)
return node
}
if ast.IsInJSFile(node) {
// !!! TODO: invalidate node reuse if js fallback logic used in type param list/typeof lookup (but isn't this logic gone?)
// s := b.ch.symbolNodeLinks.Get(node).resolvedSymbol
}
originalSpec := node.AsImportTypeNode().Argument.AsLiteralTypeNode().Literal
specifier := b.rewriteModuleSpecifier(node, originalSpec)
if originalSpec == specifier {
specifier = visitor.VisitNode(specifier) // visit node if not replaced
}
arg := node.AsImportTypeNode().Argument
if specifier != originalSpec {
arg = factory.NewLiteralTypeNode(specifier)
}
return factory.UpdateImportTypeNode(
node.AsImportTypeNode(),
node.AsImportTypeNode().IsTypeOf,
arg,
visitor.VisitNode(node.AsImportTypeNode().Attributes),
visitor.VisitNode(node.AsImportTypeNode().Qualifier),
visitor.VisitNodes(node.AsImportTypeNode().TypeArguments),
)
}
if node.Name() != nil && node.Name().Kind == ast.KindComputedPropertyName && !b.ch.hasLateBindableName(node) {
if !ast.HasDynamicName(node) {
// !!! TODO: This matches strada, but rather than recursing, this should probably fall down to later cases.
// Take a `["field"]` property declaration - it still needs a `: any` appended to it
return visitor.VisitEachChild(node)
}
// !!! TODO: this condition matches strada, but it just seems wrong? Or at the very least extraordinarily approximate, and doesn't flag a builder error...
shouldRemoveDeclaration := !((b.ctx.internalFlags&nodebuilder.InternalFlagsAllowUnresolvedNames != 0) && ast.IsEntityNameExpression(node.Name().AsComputedPropertyName().Expression) && (b.ch.checkComputedPropertyName(node.Name()).flags&TypeFlagsAny != 0))
if shouldRemoveDeclaration {
return nil
}
}
if (ast.IsFunctionLike(node) && node.Type() == nil) || (ast.IsPropertyDeclaration(node) && node.Type() == nil && node.Initializer() == nil) || (ast.IsPropertySignatureDeclaration(node) && node.Type() == nil && node.Initializer() == nil) || (ast.IsParameterDeclaration(node) && node.Type() == nil && node.Initializer() == nil) {
visited := visitor.VisitEachChild(node)
if visited == node {
visited = b.setTextRange(node.Clone(factory), node)
}
node = visited
newType := factory.NewKeywordTypeNode(ast.KindAnyKeyword)
switch node.Kind {
case ast.KindPropertyDeclaration:
return factory.UpdatePropertyDeclaration(
node.AsPropertyDeclaration(),
node.Modifiers(),
node.Name(),
node.PostfixToken(),
newType,
nil,
)
case ast.KindPropertySignature:
return factory.UpdatePropertySignatureDeclaration(
node.AsPropertySignatureDeclaration(),
node.Modifiers(),
node.Name(),
node.PostfixToken(),
newType,
nil,
)
case ast.KindParameter:
return factory.UpdateParameterDeclaration(
node.AsParameterDeclaration(),
nil,
node.AsParameterDeclaration().DotDotDotToken,
node.Name(),
node.AsParameterDeclaration().QuestionToken,
newType,
nil,
)
case ast.KindMethodSignature:
return factory.UpdateMethodSignatureDeclaration(
node.AsMethodSignatureDeclaration(),
node.Modifiers(),
node.Name(),
node.AsMethodSignatureDeclaration().PostfixToken,
node.AsMethodSignatureDeclaration().TypeParameters,
node.AsMethodSignatureDeclaration().Parameters,
newType,
)
case ast.KindCallSignature:
return factory.UpdateCallSignatureDeclaration(
node.AsCallSignatureDeclaration(),
node.AsCallSignatureDeclaration().TypeParameters,
node.AsCallSignatureDeclaration().Parameters,
newType,
)
case ast.KindJSDocSignature:
return factory.UpdateJSDocSignature(
node.AsJSDocSignature(),
node.AsJSDocSignature().TypeParameters,
node.AsJSDocSignature().Parameters,
newType,
)
case ast.KindConstructSignature:
return factory.UpdateConstructSignatureDeclaration(
node.AsConstructSignatureDeclaration(),
node.AsConstructSignatureDeclaration().TypeParameters,
node.AsConstructSignatureDeclaration().Parameters,
newType,
)
case ast.KindIndexSignature:
return factory.UpdateIndexSignatureDeclaration(
node.AsIndexSignatureDeclaration(),
node.Modifiers(),
node.AsIndexSignatureDeclaration().Parameters,
newType,
)
case ast.KindFunctionType:
return factory.UpdateFunctionTypeNode(
node.AsFunctionTypeNode(),
node.AsFunctionTypeNode().TypeParameters,
node.AsFunctionTypeNode().Parameters,
newType,
)
case ast.KindConstructorType:
return factory.UpdateConstructorTypeNode(
node.AsConstructorTypeNode(),
node.Modifiers(),
node.AsConstructorTypeNode().TypeParameters,
node.AsConstructorTypeNode().Parameters,
newType,
)
}
}
if ast.IsComputedPropertyName(node) && ast.IsEntityNameExpression(node.AsComputedPropertyName().Expression) {
introducesError, result, _ := trackExistingEntityName(node.AsComputedPropertyName().Expression, nil)
if !introducesError {
return factory.UpdateComputedPropertyName(node.AsComputedPropertyName(), result)
} else {
// !!! TODO: rewriting computed names based on evaluator/typecheck results?
// strada's behavior seems hard to justify vs marking an error and moving on
bound.markError(nil)
return visitor.VisitEachChild(node)
}
}
if ast.IsTypePredicateNode(node) {
var parameterName *ast.Node
if ast.IsIdentifier(node.AsTypePredicateNode().ParameterName) {
introducesError, result, _ := trackExistingEntityName(node.AsTypePredicateNode().ParameterName, nil)
// Should not usually happen the only case is when a type predicate comes from a JSDoc type annotation with it's own parameter symbol definition.
// /** @type {(v: unknown) => v is undefined} */
// const isUndef = v => v === undefined;
if introducesError {
bound.markError(nil)
}
parameterName = result
} else {
parameterName = node.AsTypePredicateNode().ParameterName.Clone(factory)
}
return factory.UpdateTypePredicateNode(
node.AsTypePredicateNode(),
visitor.VisitNode(node.AsTypePredicateNode().AssertsModifier),
parameterName,
visitor.VisitNode(node.AsTypePredicateNode().Type),
)
}
if ast.IsConditionalTypeNode(node) {
checkType := visitor.VisitNode(node.AsConditionalTypeNode().CheckType)
dispose := b.enterNewScope(node, nil, b.ch.getInferTypeParameters(node), nil, nil)
extendsType := visitor.VisitNode(node.AsConditionalTypeNode().ExtendsType)
trueType := visitor.VisitNode(node.AsConditionalTypeNode().TrueType)
dispose()
falseType := visitor.VisitNode(node.AsConditionalTypeNode().FalseType)
return factory.UpdateConditionalTypeNode(
node.AsConditionalTypeNode(),
checkType,
extendsType,
trueType,
falseType,
)
}
// style applications
if ast.IsTupleTypeNode(node) || (b.ctx.flags&nodebuilder.FlagsMultilineObjectLiterals == 0 && ast.IsTypeLiteralNode(node)) || ast.IsMappedTypeNode(node) {
// make tuples/types/mappedtypes single line
res := visitor.VisitEachChild(node)
if res == node {
res = res.Clone(factory)
res = b.setTextRange(res, node)
}
b.e.AddEmitFlags(res, printer.EFSingleLine)
return res
}
if ast.IsStringLiteralLike(node) {
// Preserve the original characters of the literal (e.g. emojis) in declaration emit
// rather than escaping them as ASCII Unicode escapes. Mirrors TypeScript's behavior
// for synthesized string literal types in the node builder (checker.ts:6853).
c := node.Clone(b.f)
if ast.IsStringLiteral(node) && b.ctx.flags&nodebuilder.FlagsUseSingleQuotesForStringLiteralType != 0 && node.AsStringLiteral().TokenFlags&ast.TokenFlagsSingleQuote == 0 {
// set single quote on string literals
c.AsStringLiteral().TokenFlags ^= ast.TokenFlagsSingleQuote
}
b.e.AddEmitFlags(c, printer.EFNoAsciiEscaping)
return c
}
return visitor.VisitEachChild(node)
}
nonLocalNode := true
visitor = ast.NewNodeVisitor(func(node *ast.Node) *ast.Node {
// If there was an error in a sibling node bail early, the result will be discarded anyway
if bound.hadError {
return node
}
recover_ := bound.startRecoveryScope()
introducesNewScope := ast.IsFunctionLike(node) || ast.IsMappedTypeNode(node)
var exit func()
if introducesNewScope {
var params []*ast.Symbol
var typeParams []*Type
if ast.IsFunctionLike(node) {
sig := b.ch.getSignatureFromDeclaration(node)
params = sig.parameters
typeParams = sig.typeParameters
} else if ast.IsConditionalTypeNode(node) { // !!! TODO: impossible in combination with the scope start check???
typeParams = b.ch.getInferTypeParameters(node)
} else if ast.IsMappedTypeNode(node) {
typeParams = []*Type{b.ch.getDeclaredTypeOfTypeParameter(b.ch.getSymbolOfDeclaration(node.AsMappedTypeNode().TypeParameter))}
}
exit = b.enterNewScope(node, params, typeParams, nil, nil)
}
result := visitExistingNodeTreeSymbolsWorker(node)
if exit != nil {
exit()
}
if result == node && !ast.NodeIsSynthesized(node) {
result = b.f.DeepCloneNode(node) // always clone a new node
}
// We want to clone the subtree, so when we mark it up with __pos and __end in quickfixes,
// we don't get odd behavior because of reused nodes. We also need to clone to _remove_
// the position information if the node comes from a different file than the one the node builder
// is set to build for (even though we are reusing the node structure, the position information
// would make the printer print invalid spans for literals and identifiers, and the formatter would
// choke on the mismatched positonal spans between a parent and an injected child from another file).
result = b.setTextRange(result, node)
if bound.hadError {
if ast.IsTypeNode(node) && !ast.IsTypePredicateNode(node) {
bound.endRecoveryScope(recover_)
// TODO: this fallback matches strada behavior, but it lacks any verification that the type from `node` actually matches
// the type we'd expect at this traversal position within the parent type.
t := b.getTypeFromTypeNode(node, false)
return b.typeToTypeNode(t)
}
return b.setTextRange(node.Clone(b.f), node)
}
return result
}, b.f, ast.NodeVisitorHooks{
VisitNodes: func(nodes *ast.NodeList, v *ast.NodeVisitor) *ast.NodeList {
res := v.VisitNodes(nodes)
if nonLocalNode && res != nil {
// Remove position data from node lists originating in other files
if res == nodes {
res = nodes.Clone(b.f)
}
res.Loc = core.NewTextRange(-1, -1)
}
return res
},
VisitNode: func(node *ast.Node, v *ast.NodeVisitor) *ast.Node {
// Capture if the current node is in the current file so node lists knoww if they can keep positions or not
oldNonLocalNode := nonLocalNode
nonLocalNode = b.ctx.enclosingFile == nil || b.ctx.enclosingFile != ast.GetSourceFileOfNode(b.e.MostOriginal(node))
res := v.VisitNode(node)
nonLocalNode = oldNonLocalNode
return res
},
})
return visitor
}

View File

@@ -0,0 +1,486 @@
package checker
import (
"strings"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/nodebuilder"
"github.com/microsoft/typescript-go/internal/printer"
)
// TODO: Memoize once per checker to retain threadsafety
func createPrinterWithDefaults(emitContext *printer.EmitContext) *printer.Printer {
return printer.NewPrinter(printer.PrinterOptions{}, printer.PrintHandlers{}, emitContext)
}
func createPrinterWithRemoveComments(emitContext *printer.EmitContext) *printer.Printer {
return printer.NewPrinter(printer.PrinterOptions{RemoveComments: true}, printer.PrintHandlers{}, emitContext)
}
func createPrinterWithRemoveCommentsOmitTrailingSemicolonNeverAsciiEscape(emitContext *printer.EmitContext) *printer.Printer {
// TODO: OmitTrailingSemicolon support
return printer.NewPrinter(printer.PrinterOptions{
RemoveComments: true,
NeverAsciiEscape: true,
}, printer.PrintHandlers{}, emitContext)
}
func createPrinterWithRemoveCommentsNeverAsciiEscape(emitContext *printer.EmitContext) *printer.Printer {
return printer.NewPrinter(printer.PrinterOptions{
RemoveComments: true,
NeverAsciiEscape: true,
}, printer.PrintHandlers{}, emitContext)
}
type semicolonRemoverWriter struct {
hasPendingSemicolon bool
inner printer.EmitTextWriter
}
func (s *semicolonRemoverWriter) commitSemicolon() {
if s.hasPendingSemicolon {
s.inner.WriteTrailingSemicolon(";")
s.hasPendingSemicolon = false
}
}
func (s *semicolonRemoverWriter) Clear() {
s.inner.Clear()
}
func (s *semicolonRemoverWriter) DecreaseIndent() {
s.commitSemicolon()
s.inner.DecreaseIndent()
}
func (s *semicolonRemoverWriter) GetColumn() core.UTF16Offset {
return s.inner.GetColumn()
}
func (s *semicolonRemoverWriter) GetIndent() int {
return s.inner.GetIndent()
}
func (s *semicolonRemoverWriter) GetLine() int {
return s.inner.GetLine()
}
func (s *semicolonRemoverWriter) GetTextPos() int {
return s.inner.GetTextPos()
}
func (s *semicolonRemoverWriter) HasTrailingComment() bool {
return s.inner.HasTrailingComment()
}
func (s *semicolonRemoverWriter) HasTrailingWhitespace() bool {
return s.inner.HasTrailingWhitespace()
}
func (s *semicolonRemoverWriter) IncreaseIndent() {
s.commitSemicolon()
s.inner.IncreaseIndent()
}
func (s *semicolonRemoverWriter) IsAtStartOfLine() bool {
return s.inner.IsAtStartOfLine()
}
func (s *semicolonRemoverWriter) RawWrite(s1 string) {
s.commitSemicolon()
s.inner.RawWrite(s1)
}
func (s *semicolonRemoverWriter) String() string {
s.commitSemicolon()
return s.inner.String()
}
func (s *semicolonRemoverWriter) Write(s1 string) {
s.commitSemicolon()
s.inner.Write(s1)
}
func (s *semicolonRemoverWriter) WriteComment(text string) {
s.commitSemicolon()
s.inner.WriteComment(text)
}
func (s *semicolonRemoverWriter) WriteKeyword(text string) {
s.commitSemicolon()
s.inner.WriteKeyword(text)
}
func (s *semicolonRemoverWriter) WriteLine() {
s.commitSemicolon()
s.inner.WriteLine()
}
func (s *semicolonRemoverWriter) WriteLineForce(force bool) {
s.commitSemicolon()
s.inner.WriteLineForce(force)
}
func (s *semicolonRemoverWriter) WriteLiteral(s1 string) {
s.commitSemicolon()
s.inner.WriteLiteral(s1)
}
func (s *semicolonRemoverWriter) WriteOperator(text string) {
s.commitSemicolon()
s.inner.WriteOperator(text)
}
func (s *semicolonRemoverWriter) WriteParameter(text string) {
s.commitSemicolon()
s.inner.WriteParameter(text)
}
func (s *semicolonRemoverWriter) WriteProperty(text string) {
s.commitSemicolon()
s.inner.WriteProperty(text)
}
func (s *semicolonRemoverWriter) WritePunctuation(text string) {
s.commitSemicolon()
s.inner.WritePunctuation(text)
}
func (s *semicolonRemoverWriter) WriteSpace(text string) {
s.commitSemicolon()
s.inner.WriteSpace(text)
}
func (s *semicolonRemoverWriter) WriteStringLiteral(text string) {
s.commitSemicolon()
s.inner.WriteStringLiteral(text)
}
func (s *semicolonRemoverWriter) WriteSymbol(text string, symbol *ast.Symbol) {
s.commitSemicolon()
s.inner.WriteSymbol(text, symbol)
}
func (s *semicolonRemoverWriter) WriteTrailingSemicolon(text string) {
s.hasPendingSemicolon = true
}
func getTrailingSemicolonDeferringWriter(writer printer.EmitTextWriter) printer.EmitTextWriter {
return &semicolonRemoverWriter{false, writer}
}
func (c *Checker) TypeToString(t *Type) string {
return c.typeToString(t, nil)
}
func (c *Checker) typeToString(t *Type, enclosingDeclaration *ast.Node) string {
return c.typeToStringEx(t, enclosingDeclaration, TypeFormatFlagsAllowUniqueESSymbolType|TypeFormatFlagsUseAliasDefinedOutsideCurrentScope, nil)
}
func toNodeBuilderFlags(flags TypeFormatFlags) nodebuilder.Flags {
return nodebuilder.Flags(flags & TypeFormatFlagsNodeBuilderFlagsMask)
}
func (c *Checker) TypeToStringEx(t *Type, enclosingDeclaration *ast.Node, flags TypeFormatFlags, vc *VerbosityContext) string {
return c.typeToStringEx(t, enclosingDeclaration, flags, vc)
}
func (c *Checker) typeToStringEx(t *Type, enclosingDeclaration *ast.Node, flags TypeFormatFlags, vc *VerbosityContext) string {
// Serialization of types can lead to (lazy) resolution of members, which can cause diagnostics that again require
// serialization of types. This can potentially result in infinite recursion and stack overflows. To prevent that,
// after a certain number of recursive invocations the function simply returns "?".
if c.serializationLevel >= maxSerializationLevel {
return "?"
}
newLine := ""
if flags&TypeFormatFlagsMultilineObjectLiterals != 0 {
newLine = "\n"
}
writer := printer.NewTextWriter(newLine, 0)
noTruncation := ((vc == nil || vc.MaxTruncationLength == 0) && c.compilerOptions.NoErrorTruncation == core.TSTrue) || (flags&TypeFormatFlagsNoTruncation != 0)
combinedFlags := toNodeBuilderFlags(flags) | nodebuilder.FlagsIgnoreErrors
if noTruncation {
combinedFlags = combinedFlags | nodebuilder.FlagsNoTruncation
}
nodeBuilder, release := c.getNodeBuilder()
defer release()
oldVerbosity := nodeBuilder.verbosity
nodeBuilder.verbosity = vc
defer func() {
nodeBuilder.verbosity = oldVerbosity
}()
c.serializationLevel++
typeNode := nodeBuilder.TypeToTypeNode(t, enclosingDeclaration, combinedFlags, nodebuilder.InternalFlagsNone, nil)
c.serializationLevel--
if typeNode == nil {
panic("should always get typenode")
}
// The unresolved type gets a synthesized comment on `any` to hint to users that it's not a plain `any`.
// Otherwise, we always strip comments out.
var p *printer.Printer
if t == c.unresolvedType {
p = createPrinterWithDefaults(nodeBuilder.EmitContext())
} else {
p = createPrinterWithRemoveComments(nodeBuilder.EmitContext())
}
var sourceFile *ast.SourceFile
if enclosingDeclaration != nil {
sourceFile = ast.GetSourceFileOfNode(enclosingDeclaration)
}
p.Write(typeNode, sourceFile, writer, nil)
result := writer.String()
maxLength := defaultMaximumTruncationLength * 2
if vc != nil && vc.MaxTruncationLength > 0 {
maxLength = vc.MaxTruncationLength * 10 // hard cutoff matching Strada's absoluteMaximumLength
}
if noTruncation {
maxLength = noTruncationMaximumTruncationLength * 2
}
if maxLength > 0 && result != "" && len(result) >= maxLength {
if vc != nil {
vc.Truncated = true
}
return result[0:maxLength-len("...")] + "..."
}
return result
}
func (c *Checker) SymbolToString(s *ast.Symbol) string {
return c.symbolToString(s)
}
func (c *Checker) symbolToString(symbol *ast.Symbol) string {
return c.symbolToStringEx(symbol, nil, ast.SymbolFlagsAll, SymbolFormatFlagsAllowAnyNodeKind)
}
func (c *Checker) SymbolToStringEx(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags, flags SymbolFormatFlags) string {
return c.symbolToStringEx(symbol, enclosingDeclaration, meaning, flags)
}
func (c *Checker) symbolToStringEx(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags, flags SymbolFormatFlags) string {
writer, putWriter := printer.GetSingleLineStringWriter()
defer putWriter()
nodeFlags := nodebuilder.FlagsIgnoreErrors
internalNodeFlags := nodebuilder.InternalFlagsNone
if flags&SymbolFormatFlagsUseOnlyExternalAliasing != 0 {
nodeFlags |= nodebuilder.FlagsUseOnlyExternalAliasing
}
if flags&SymbolFormatFlagsWriteTypeParametersOrArguments != 0 {
nodeFlags |= nodebuilder.FlagsWriteTypeParametersInQualifiedName
}
if flags&SymbolFormatFlagsUseAliasDefinedOutsideCurrentScope != 0 {
nodeFlags |= nodebuilder.FlagsUseAliasDefinedOutsideCurrentScope
}
if flags&SymbolFormatFlagsDoNotIncludeSymbolChain != 0 {
internalNodeFlags |= nodebuilder.InternalFlagsDoNotIncludeSymbolChain
}
if flags&SymbolFormatFlagsWriteComputedProps != 0 {
internalNodeFlags |= nodebuilder.InternalFlagsWriteComputedProps
}
nodeBuilder, release := c.getNodeBuilder()
defer release()
var sourceFile *ast.SourceFile
if enclosingDeclaration != nil {
sourceFile = ast.GetSourceFileOfNode(enclosingDeclaration)
}
var printer_ *printer.Printer
// add neverAsciiEscape for GH#39027
if enclosingDeclaration != nil && enclosingDeclaration.Kind == ast.KindSourceFile {
printer_ = createPrinterWithRemoveCommentsNeverAsciiEscape(nodeBuilder.EmitContext())
} else {
printer_ = createPrinterWithRemoveComments(nodeBuilder.EmitContext())
}
var builder func(symbol *ast.Symbol, meaning ast.SymbolFlags, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node
if flags&SymbolFormatFlagsAllowAnyNodeKind != 0 {
builder = nodeBuilder.SymbolToNode
} else {
builder = nodeBuilder.SymbolToEntityName
}
entity := builder(symbol, meaning, enclosingDeclaration, nodeFlags, internalNodeFlags, nil) // TODO: GH#18217
printer_.Write(entity /*sourceFile*/, sourceFile, getTrailingSemicolonDeferringWriter(writer), nil) // TODO: GH#18217
return writer.String()
}
func (c *Checker) signatureToString(signature *Signature) string {
return c.signatureToStringEx(signature, nil, TypeFormatFlagsNone, nil)
}
func (c *Checker) SignatureToStringEx(signature *Signature, enclosingDeclaration *ast.Node, flags TypeFormatFlags, vc *VerbosityContext) string {
return c.signatureToStringEx(signature, enclosingDeclaration, flags, vc)
}
func (c *Checker) signatureToStringEx(signature *Signature, enclosingDeclaration *ast.Node, flags TypeFormatFlags, vc *VerbosityContext) string {
isConstructor := signature.flags&SignatureFlagsConstruct != 0 && flags&TypeFormatFlagsWriteCallStyleSignature == 0
var sigOutput ast.Kind
if flags&TypeFormatFlagsWriteArrowStyleSignature != 0 {
if isConstructor {
sigOutput = ast.KindConstructorType
} else {
sigOutput = ast.KindFunctionType
}
} else {
if isConstructor {
sigOutput = ast.KindConstructSignature
} else {
sigOutput = ast.KindCallSignature
}
}
nodeBuilder, release := c.getNodeBuilder()
defer release()
oldVerbosity := nodeBuilder.verbosity
nodeBuilder.verbosity = vc
defer func() {
nodeBuilder.verbosity = oldVerbosity
}()
combinedFlags := toNodeBuilderFlags(flags) | nodebuilder.FlagsIgnoreErrors | nodebuilder.FlagsWriteTypeParametersInQualifiedName
sig := nodeBuilder.SignatureToSignatureDeclaration(signature, sigOutput, enclosingDeclaration, combinedFlags, nodebuilder.InternalFlagsNone, nil)
p := createPrinterWithRemoveCommentsOmitTrailingSemicolonNeverAsciiEscape(nodeBuilder.EmitContext())
var sourceFile *ast.SourceFile
if enclosingDeclaration != nil {
sourceFile = ast.GetSourceFileOfNode(enclosingDeclaration)
}
if flags&TypeFormatFlagsMultilineObjectLiterals != 0 {
writer := printer.NewTextWriter("\n", 0)
p.Write(sig, sourceFile, getTrailingSemicolonDeferringWriter(writer), nil)
return writer.String()
}
writer, putWriter := printer.GetSingleLineStringWriter()
defer putWriter()
p.Write(sig, sourceFile, getTrailingSemicolonDeferringWriter(writer), nil)
return writer.String()
}
func (c *Checker) typePredicateToString(typePredicate *TypePredicate) string {
return c.typePredicateToStringEx(typePredicate, nil, TypeFormatFlagsUseAliasDefinedOutsideCurrentScope)
}
func (c *Checker) typePredicateToStringEx(typePredicate *TypePredicate, enclosingDeclaration *ast.Node, flags TypeFormatFlags) string {
writer, putWriter := printer.GetSingleLineStringWriter()
defer putWriter()
nodeBuilder, release := c.getNodeBuilder()
defer release()
combinedFlags := toNodeBuilderFlags(flags) | nodebuilder.FlagsIgnoreErrors | nodebuilder.FlagsWriteTypeParametersInQualifiedName
predicate := nodeBuilder.TypePredicateToTypePredicateNode(typePredicate, enclosingDeclaration, combinedFlags, nodebuilder.InternalFlagsNone, nil) // TODO: GH#18217
printer_ := createPrinterWithRemoveComments(nodeBuilder.EmitContext())
var sourceFile *ast.SourceFile
if enclosingDeclaration != nil {
sourceFile = ast.GetSourceFileOfNode(enclosingDeclaration)
}
printer_.Write(predicate /*sourceFile*/, sourceFile, writer, nil)
return writer.String()
}
func (c *Checker) valueToString(value any) string {
return ValueToString(value)
}
func (c *Checker) formatUnionTypes(types []*Type, expandingEnum bool) []*Type {
var result []*Type
var flags TypeFlags
for i := 0; i < len(types); i++ {
t := types[i]
flags |= t.flags
if t.flags&TypeFlagsNullable == 0 {
if t.flags&TypeFlagsBooleanLiteral != 0 || (!expandingEnum && t.flags&TypeFlagsEnumLike != 0) {
var baseType *Type
if t.flags&TypeFlagsBooleanLiteral != 0 {
baseType = c.booleanType
} else {
baseType = c.getBaseTypeOfEnumLikeType(t)
}
if baseType.flags&TypeFlagsUnion != 0 {
count := len(baseType.AsUnionType().types)
if i+count <= len(types) && c.getRegularTypeOfLiteralType(types[i+count-1]) == c.getRegularTypeOfLiteralType(baseType.AsUnionType().types[count-1]) {
result = append(result, baseType)
i += count - 1
continue
}
}
}
result = append(result, t)
}
}
if flags&TypeFlagsNull != 0 {
result = append(result, c.nullType)
}
if flags&TypeFlagsUndefined != 0 {
result = append(result, c.undefinedType)
}
return result
}
func (c *Checker) TypeToTypeNode(t *Type, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *ast.TypeNode {
nodeBuilder := c.getNodeBuilderEx(idToSymbol)
return nodeBuilder.TypeToTypeNode(t, enclosingDeclaration, flags, nodebuilder.InternalFlagsNone, nil)
}
func (c *Checker) SignatureToSignatureDeclaration(signature *Signature, kind ast.Kind, enclosingDeclaration *ast.Node, flags nodebuilder.Flags) *ast.Node {
nodeBuilder, release := c.getNodeBuilder()
defer release()
return nodeBuilder.SignatureToSignatureDeclaration(signature, kind, enclosingDeclaration, flags, nodebuilder.InternalFlagsNone, nil)
}
// ExpandSymbolForHover produces declaration strings for a symbol with verbosity support for expandable hover.
func (c *Checker) ExpandSymbolForHover(symbol *ast.Symbol, meaning ast.SymbolFlags, vc *VerbosityContext) string {
nodeBuilder, release := c.getNodeBuilder()
defer release()
oldVerbosity := nodeBuilder.verbosity
nodeBuilder.verbosity = vc
defer func() {
nodeBuilder.verbosity = oldVerbosity
}()
nodes := nodeBuilder.ExpandSymbolForHover(symbol, meaning)
if len(nodes) == 0 {
return ""
}
p := createPrinterWithRemoveComments(nodeBuilder.EmitContext())
var sourceFile *ast.SourceFile
if symbol.ValueDeclaration != nil {
sourceFile = ast.GetSourceFileOfNode(symbol.ValueDeclaration)
}
var b strings.Builder
for i, node := range nodes {
if i > 0 {
b.WriteString("\n")
}
b.WriteString(p.Emit(node, sourceFile))
}
return b.String()
}
// TypeParameterToStringEx renders a type parameter declaration (e.g. "T extends Foo") with optional verbosity support.
func (c *Checker) TypeParameterToStringEx(t *Type, enclosingDeclaration *ast.Node, vc *VerbosityContext) string {
nodeBuilder, release := c.getNodeBuilder()
defer release()
oldVerbosity := nodeBuilder.verbosity
nodeBuilder.verbosity = vc
defer func() {
nodeBuilder.verbosity = oldVerbosity
}()
typeParamNode := nodeBuilder.TypeParameterToDeclaration(t, enclosingDeclaration, nodebuilder.FlagsIgnoreErrors, nodebuilder.InternalFlagsNone, nil)
if typeParamNode == nil {
return c.TypeToString(t)
}
p := createPrinterWithRemoveComments(nodeBuilder.EmitContext())
var sourceFile *ast.SourceFile
if enclosingDeclaration != nil {
sourceFile = ast.GetSourceFileOfNode(enclosingDeclaration)
}
return p.Emit(typeParamNode, sourceFile)
}
func (c *Checker) TypeToTypeNodeEx(t *Type, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *ast.TypeNode {
nodeBuilder := c.getNodeBuilderEx(idToSymbol)
return nodeBuilder.TypeToTypeNode(t, enclosingDeclaration, flags, internalFlags, nil)
}
func (c *Checker) TypePredicateToTypePredicateNode(t *TypePredicate, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *ast.TypePredicateNodeNode {
nodeBuilder := c.getNodeBuilderEx(idToSymbol)
return nodeBuilder.TypePredicateToTypePredicateNode(t, enclosingDeclaration, flags, nodebuilder.InternalFlagsNone, nil)
}

View File

@@ -0,0 +1,765 @@
package checker
import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/debug"
"github.com/microsoft/typescript-go/internal/nodebuilder"
"github.com/microsoft/typescript-go/internal/printer"
"github.com/microsoft/typescript-go/internal/pseudochecker"
)
// pseudoTypeToNodeWithCheckerFallback is like pseudoTypeToNode but when the top-level pseudo type
// is PseudoTypeInferred, it reports any error nodes and then serializes from the checker's type.
// This avoids incorrect type output when PseudoTypeInferred would derive the type from the
// original declaration expression in an instantiated context.
func (b *NodeBuilderImpl) pseudoTypeToNodeWithCheckerFallback(t *pseudochecker.PseudoType, checkerType *Type) *ast.Node {
if t.Kind == pseudochecker.PseudoTypeKindInferred {
if !b.ctx.suppressReportInferenceFallback {
if errorNodes := t.AsPseudoTypeInferred().ErrorNodes; len(errorNodes) > 0 {
for _, n := range errorNodes {
b.ctx.tracker.ReportInferenceFallback(n)
}
} else {
b.ctx.tracker.ReportInferenceFallback(t.AsPseudoTypeInferred().Expression)
}
}
oldSuppress := b.ctx.suppressReportInferenceFallback
b.ctx.suppressReportInferenceFallback = true
result := b.typeToTypeNode(checkerType)
b.ctx.suppressReportInferenceFallback = oldSuppress
return result
} else if t.Kind == pseudochecker.PseudoTypeKindDirect {
existing := t.AsPseudoTypeDirect().TypeNode
if !b.canReuseExistingJSTypeNode(existing, checkerType) {
if !b.ctx.suppressReportInferenceFallback {
b.ctx.tracker.ReportInferenceFallback(existing)
}
oldSuppress := b.ctx.suppressReportInferenceFallback
b.ctx.suppressReportInferenceFallback = true
result := b.typeToTypeNode(checkerType)
b.ctx.suppressReportInferenceFallback = oldSuppress
return result
}
}
return b.pseudoTypeToNode(t)
}
// Maps a pseudochecker's pseudotypes into ast nodes and reports any inference fallback errors the pseudotype structure implies
func (b *NodeBuilderImpl) pseudoTypeToNode(t *pseudochecker.PseudoType) *ast.Node {
debug.Assert(t != nil, "Attempted to serialize nil pseudotype")
switch t.Kind {
case pseudochecker.PseudoTypeKindDirect:
return b.reuseTypeNode(t.AsPseudoTypeDirect().TypeNode)
case pseudochecker.PseudoTypeKindInferred:
inferred := t.AsPseudoTypeInferred()
node := inferred.Expression
if errorNodes := inferred.ErrorNodes; len(errorNodes) > 0 {
for _, n := range errorNodes {
b.ctx.tracker.ReportInferenceFallback(n)
}
} else if ast.IsEntityNameExpression(node) && ast.IsDeclaration(node.Parent) {
b.ctx.tracker.ReportInferenceFallback(node.Parent)
} else {
b.ctx.tracker.ReportInferenceFallback(node)
}
if inferred.IsSignatureReturn {
return b.serializeReturnTypeForSignature(b.ch.getSignatureFromDeclaration(node), false)
}
// use symbol type from parent declaration to automatically handle expression type widening without duplicating logic
if ast.IsReturnStatement(node.Parent) {
enclosing := ast.GetContainingFunction(node)
if ast.IsAccessor(enclosing) {
return b.serializeTypeForDeclaration(enclosing, nil, nil, false)
}
return b.serializeReturnTypeForSignature(b.ch.getSignatureFromDeclaration(enclosing), false)
}
if ast.IsArrowFunction(node.Parent) && node.Parent.AsArrowFunction().Body == node {
return b.serializeReturnTypeForSignature(b.ch.getSignatureFromDeclaration(node.Parent), false)
}
if ast.IsDeclaration(node.Parent) {
return b.serializeTypeForDeclaration(node.Parent, nil, nil, false)
}
// This might be effectively unreachable. If it's not, it may need more widening rules to mirror checker behavior for whatever expressions are serialized here
ty := b.ch.getTypeOfExpression(node)
return b.typeToTypeNode(ty)
case pseudochecker.PseudoTypeKindNoResult:
node := t.AsPseudoTypeNoResult().Declaration
b.ctx.tracker.ReportInferenceFallback(node)
if ast.IsFunctionLike(node) && !ast.IsAccessor(node) {
return b.serializeReturnTypeForSignature(b.ch.getSignatureFromDeclaration(node), false)
}
return b.serializeTypeForDeclaration(node, nil, nil, false)
case pseudochecker.PseudoTypeKindMaybeConstLocation:
d := t.AsPseudoTypeMaybeConstLocation()
// see checkExpressionWithContextualType for general literal widening rules which need to be emulated here, plus
// checkTemplateLiteralExpression for template literal widening rules if the pseudochecker ever supports literalized templates
isInConstContext := b.ch.isConstContext(d.Node)
if !isInConstContext && pseudochecker.IsInConstContext(d.Node) {
// Only consult the contextual type if the pseudochecker's syntactic check also puts us in a const context.
// getContextualType returns post-inference results at node-printing time which may not have existed
// during initial checking (e.g. when the contextual type depends on inference), causing incorrect
// literal type preservation.
contextualType := b.ch.getContextualType(d.Node, ContextFlagsNone)
t := b.pseudoTypeToType(d.ConstType)
if t != nil && b.ch.isLiteralOfContextualType(t, b.ch.instantiateContextualType(contextualType, d.Node, ContextFlagsNone)) {
isInConstContext = true
}
}
if isInConstContext {
return b.pseudoTypeToNode(d.ConstType)
} else {
return b.pseudoTypeToNode(d.RegularType)
}
case pseudochecker.PseudoTypeKindUnion:
var res []*ast.Node
var hasElidedType bool
var hasUndefined bool
members := t.AsPseudoTypeUnion().Types
var appendTypeNode func(node *ast.Node)
appendTypeNode = func(node *ast.Node) {
if ast.IsUnionTypeNode(node) {
for _, node := range node.AsUnionTypeNode().Types.Nodes {
appendTypeNode(node)
}
return
}
if node.Kind == ast.KindUndefinedKeyword {
if hasUndefined {
return
}
hasUndefined = true
}
res = append(res, node)
}
for _, m := range members {
if !b.ch.strictNullChecks {
if m.Kind == pseudochecker.PseudoTypeKindUndefined || m.Kind == pseudochecker.PseudoTypeKindNull {
hasElidedType = true
continue
}
}
appendTypeNode(b.pseudoTypeToNode(m))
}
if len(res) == 1 {
return res[0]
}
if len(res) == 0 {
if hasElidedType {
return b.f.NewKeywordTypeNode(ast.KindAnyKeyword)
}
return b.f.NewKeywordTypeNode(ast.KindNeverKeyword)
}
return b.f.NewUnionTypeNode(b.f.NewNodeList(res))
case pseudochecker.PseudoTypeKindUndefined:
if !b.ch.strictNullChecks {
return b.f.NewKeywordTypeNode(ast.KindAnyKeyword)
}
return b.f.NewKeywordTypeNode(ast.KindUndefinedKeyword)
case pseudochecker.PseudoTypeKindNull:
if !b.ch.strictNullChecks {
return b.f.NewKeywordTypeNode(ast.KindAnyKeyword)
}
return b.f.NewLiteralTypeNode(b.f.NewKeywordExpression(ast.KindNullKeyword))
case pseudochecker.PseudoTypeKindAny:
return b.f.NewKeywordTypeNode(ast.KindAnyKeyword)
case pseudochecker.PseudoTypeKindString:
return b.f.NewKeywordTypeNode(ast.KindStringKeyword)
case pseudochecker.PseudoTypeKindNumber:
return b.f.NewKeywordTypeNode(ast.KindNumberKeyword)
case pseudochecker.PseudoTypeKindBigInt:
return b.f.NewKeywordTypeNode(ast.KindBigIntKeyword)
case pseudochecker.PseudoTypeKindBoolean:
return b.f.NewKeywordTypeNode(ast.KindBooleanKeyword)
case pseudochecker.PseudoTypeKindFalse:
return b.f.NewLiteralTypeNode(b.f.NewKeywordExpression(ast.KindFalseKeyword))
case pseudochecker.PseudoTypeKindTrue:
return b.f.NewLiteralTypeNode(b.f.NewKeywordExpression(ast.KindTrueKeyword))
case pseudochecker.PseudoTypeKindSingleCallSignature:
d := t.AsPseudoTypeSingleCallSignature()
signature := b.ch.getSignatureFromDeclaration(d.Signature)
expandedParams := b.ch.getExpandedParameters(signature, true /*skipUnionExpanding*/)[0]
cleanup := b.enterNewScope(d.Signature, expandedParams, signature.typeParameters, signature.parameters, signature.mapper)
defer cleanup()
var typeParams *ast.NodeList
if len(d.TypeParameters) > 0 {
res := make([]*ast.Node, 0, len(d.TypeParameters))
for _, tp := range d.TypeParameters {
res = append(res, b.reuseNode(tp.AsNode()))
}
typeParams = b.f.NewNodeList(res)
}
params := b.pseudoParametersToNodeList(d.Parameters)
returnType := b.pseudoTypeToNode(d.ReturnType)
return b.f.NewFunctionTypeNode(typeParams, params, returnType)
case pseudochecker.PseudoTypeKindTuple:
var res []*ast.Node
elements := t.AsPseudoTypeTuple().Elements
for _, e := range elements {
res = append(res, b.pseudoTypeToNode(e))
}
// pseudo-tuples are implicitly `readonly` since they originate from `as const` contexts
// but strada *sometimes* fails to add the `readonly` modifier to the generated node.
result := b.f.NewTupleTypeNode(b.f.NewNodeList(res))
b.e.AddEmitFlags(result, printer.EFSingleLine)
return b.f.NewTypeOperatorNode(ast.KindReadonlyKeyword, result)
case pseudochecker.PseudoTypeKindObjectLiteral:
elements := t.AsPseudoTypeObjectLiteral().Elements
if len(elements) == 0 {
result := b.f.NewTypeLiteralNode(b.f.NewNodeList(nil))
b.e.AddEmitFlags(result, printer.EFSingleLine)
return result
}
// NOTE: using the checker's `isConstContext` instead of the pseudochecker's `isInConstContext`
// results in different results here. The checker one is more "correct" but means we'll mark
// objects in parameter positions contextually typed by const type parameters as readonly -
// something a true syntactic ID emitter couldn't possibly know (since the signature could
// be from across files). This can't *really* happen in any cases ID doesn't already error on, though.
// Just something to keep in mind if the ID checker keeps growing.
isConst := b.ch.isConstContext(elements[0].Name.Parent.Parent)
newElements := make([]*ast.Node, 0, len(elements))
// Member types are serialized within an object type literal, so set the
// corresponding flag to mirror createTypeNodeFromObjectType. This ensures
// inaccessible `this` references inside the members are reported (TS2527).
restoreObjectLiteralFlags := b.saveRestoreFlags()
b.ctx.flags |= nodebuilder.FlagsInObjectTypeLiteral
for _, e := range elements {
var modifiers *ast.ModifierList
if isConst || (e.Kind == pseudochecker.PseudoObjectElementKindPropertyAssignment && e.AsPseudoPropertyAssignment().Readonly) {
modifiers = b.f.NewModifierList([]*ast.Node{b.f.NewModifier(ast.KindReadonlyKeyword)})
}
var cleanup func()
if e.Kind != pseudochecker.PseudoObjectElementKindPropertyAssignment {
signature := b.ch.getSignatureFromDeclaration(e.Signature())
expandedParams := b.ch.getExpandedParameters(signature, true /*skipUnionExpanding*/)[0]
cleanup = b.enterNewScope(e.Signature(), expandedParams, signature.typeParameters, signature.parameters, signature.mapper)
}
var newProp *ast.Node
switch e.Kind {
case pseudochecker.PseudoObjectElementKindMethod:
d := e.AsPseudoObjectMethod()
var typeParams *ast.NodeList
if len(d.TypeParameters) > 0 {
res := make([]*ast.Node, 0, len(d.TypeParameters))
for _, tp := range d.TypeParameters {
res = append(res, b.reuseNode(tp.AsNode()))
}
typeParams = b.f.NewNodeList(res)
}
if isConst {
newProp = b.f.NewPropertySignatureDeclaration(
modifiers,
b.reuseName(e.Name, false /*isMethod*/),
nil,
b.f.NewFunctionTypeNode(
typeParams,
b.pseudoParametersToNodeList(d.Parameters),
b.pseudoTypeToNode(d.ReturnType),
),
nil,
)
break
}
newProp = b.f.NewMethodSignatureDeclaration(
modifiers,
b.reuseName(e.Name, true /*isMethod*/),
nil,
typeParams,
b.pseudoParametersToNodeList(d.Parameters),
b.pseudoTypeToNode(d.ReturnType),
)
case pseudochecker.PseudoObjectElementKindPropertyAssignment:
d := e.AsPseudoPropertyAssignment()
newProp = b.f.NewPropertySignatureDeclaration(
modifiers,
b.reuseName(e.Name, false /*isMethod*/),
nil,
b.pseudoTypeToNode(d.Type),
nil,
)
case pseudochecker.PseudoObjectElementKindSetAccessor:
d := e.AsPseudoSetAccessor()
newProp = b.f.NewSetAccessorDeclaration(
nil,
b.reuseName(e.Name, false /*isMethod*/),
nil,
b.f.NewNodeList([]*ast.Node{b.pseudoParameterToNode(d.Parameter)}),
nil,
nil,
nil,
)
case pseudochecker.PseudoObjectElementKindGetAccessor:
d := e.AsPseudoGetAccessor()
newProp = b.f.NewGetAccessorDeclaration(
nil,
b.reuseName(e.Name, false /*isMethod*/),
nil,
nil,
b.pseudoTypeToNode(d.Type),
nil,
nil,
)
}
if b.ctx.enclosingFile == ast.GetSourceFileOfNode(e.Name) {
b.e.SetCommentRange(newProp, e.Name.Parent.Loc)
}
newElements = append(newElements, newProp)
if cleanup != nil {
cleanup()
}
}
restoreObjectLiteralFlags()
result := b.f.NewTypeLiteralNode(b.f.NewNodeList(newElements))
if b.ctx.flags&nodebuilder.FlagsMultilineObjectLiterals == 0 {
b.e.AddEmitFlags(result, printer.EFSingleLine)
}
return result
case pseudochecker.PseudoTypeKindStringLiteral, pseudochecker.PseudoTypeKindNumericLiteral, pseudochecker.PseudoTypeKindBigIntLiteral:
source := t.AsPseudoTypeLiteral().Node
return b.f.NewLiteralTypeNode(b.reuseNode(source))
default:
debug.AssertNever(t.Kind, "Unhandled pseudotype kind in pseudotype node construction")
return nil
}
}
func (b *NodeBuilderImpl) pseudoParametersToNodeList(params []*pseudochecker.PseudoParameter) *ast.NodeList {
res := make([]*ast.Node, 0, len(params))
for _, p := range params {
res = append(res, b.pseudoParameterToNode(p))
}
return b.f.NewNodeList(res)
}
func (b *NodeBuilderImpl) pseudoParameterToNode(p *pseudochecker.PseudoParameter) *ast.Node {
var dotDotDot *ast.Node
var questionMark *ast.Node
if p.Rest {
dotDotDot = b.f.NewToken(ast.KindDotDotDotToken)
}
if p.Optional {
questionMark = b.f.NewToken(ast.KindQuestionToken)
}
parameter := b.f.NewParameterDeclaration(
nil,
dotDotDot,
// matches strada behavior of always reserializing param names from scratch
b.parameterToParameterDeclarationName(p.Name.Parent.Symbol(), p.Name.Parent),
questionMark,
b.pseudoTypeToNode(p.Type),
nil,
)
if original := p.Name.Parent; ast.IsParameterDeclaration(original) {
b.setCommentRange(parameter, original)
}
return parameter
}
// see `typeNodeIsEquivalentToType` in strada, but applied more broadly here, so is setup to handle more equivalences - strada only used it via
// the `canReuseTypeNodeAnnotation` host hook and not the `canReuseTypeNode` hook, which meant locations using the later were reliant on
// over-invalidation by the ID inference engine to not emit incorrect types.
func (b *NodeBuilderImpl) pseudoTypeEquivalentToType(t *pseudochecker.PseudoType, type_ *Type, isOptionalAnnotated bool, reportErrors bool) bool {
// if type_ resolves to an error, we charitably assume equality, since we might be in a single-file checking mode
if type_ != nil && b.ch.isErrorType(type_) {
return true
}
// If we can easily operate on just types, we should
typeFromPseudo := b.pseudoTypeToType(t) // note: cannot convert complex types like objects, which must be validated separately
if typeFromPseudo == type_ {
return true
}
undefinedStripped := type_
if isOptionalAnnotated {
undefinedStripped = b.ch.getTypeWithFacts(type_, TypeFactsNEUndefined)
}
if typeFromPseudo != nil && type_ != nil {
if isOptionalAnnotated {
if undefinedStripped == typeFromPseudo {
return true
}
if typeFromPseudo.flags&TypeFlagsUnion != 0 && undefinedStripped.flags&TypeFlagsUnion != 0 {
// does union comparison in general, since the unions may not be `==` identical due to aliasing and the like
if b.ch.compareTypesIdentical(typeFromPseudo, undefinedStripped) == TernaryTrue {
return true
}
}
}
// handles freshness mismatches (e.g., fresh true vs regular true in as const)
if b.ch.getRegularTypeOfLiteralType(typeFromPseudo) == b.ch.getRegularTypeOfLiteralType(type_) {
return true
}
if typeFromPseudo.flags&TypeFlagsUnion != 0 && type_.flags&TypeFlagsUnion != 0 {
// handles union comparison in general, since unions may not be `==` identical due to aliasing
if b.ch.compareTypesIdentical(typeFromPseudo, type_) == TernaryTrue {
return true
}
}
}
// otherwise, fallback to actual pseudo/type cross-comparisons
switch t.Kind {
case pseudochecker.PseudoTypeKindInferred:
// PseudoTypeInferred with error nodes identifies specific problematic children.
// Report fine-grained errors on them, then return false so the parent falls back
// to checker-based serialization (avoiding issues like reusing raw JSON string
// literal property names from the pseudochecker's AST).
if errorNodes := t.AsPseudoTypeInferred().ErrorNodes; len(errorNodes) > 0 {
if reportErrors {
for _, n := range errorNodes {
b.ctx.tracker.ReportInferenceFallback(n)
}
}
return false
}
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(t.AsPseudoTypeInferred().Expression)
}
return false
case pseudochecker.PseudoTypeKindObjectLiteral:
pt := t.AsPseudoTypeObjectLiteral()
if type_ == nil {
return false
}
targetProps := b.ch.getPropertiesOfType(undefinedStripped)
// Count total declarations across all target prop symbols to handle getter/setter pairs,
// which are two elements in pt.Elements but only one symbol in targetProps.
targetDeclCount := 0
for _, prop := range targetProps {
targetDeclCount += len(prop.Declarations)
}
if len(pt.Elements) != targetDeclCount {
return false
}
for _, e := range pt.Elements {
var targetProp *ast.Symbol
elemSymbol := e.Name.Parent.Symbol()
if elemSymbol != nil {
targetProp = b.ch.getPropertyOfType(undefinedStripped, elemSymbol.Name)
}
if targetProp == nil {
// Name lookup failed or returned no result; search target properties
// for one whose declaration name node matches the one we have
for _, prop := range targetProps {
if prop.ValueDeclaration != nil && prop.ValueDeclaration.Name() == e.Name {
targetProp = prop
break
}
}
if targetProp == nil {
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(e.Name.Parent)
}
return false
}
}
targetIsOptional := targetProp.Flags&ast.SymbolFlagsOptional != 0
if e.Optional != targetIsOptional {
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(e.Name.Parent)
}
return false
}
propType := b.ch.getTypeOfSymbol(targetProp)
propType = b.ch.removeMissingType(propType, targetIsOptional)
switch e.Kind {
case pseudochecker.PseudoObjectElementKindPropertyAssignment:
d := e.AsPseudoPropertyAssignment()
if !b.pseudoTypeEquivalentToType(d.Type, propType, e.Optional, false) {
if reportErrors {
if d.Type.Kind == pseudochecker.PseudoTypeKindInferred && len(d.Type.AsPseudoTypeInferred().ErrorNodes) > 0 {
// Re-report the fine-grained error nodes; the recursive call used reportErrors=false
for _, n := range d.Type.AsPseudoTypeInferred().ErrorNodes {
b.ctx.tracker.ReportInferenceFallback(n)
}
} else if !isStructuralPseudoType(d.Type) {
b.ctx.tracker.ReportInferenceFallback(e.Name.Parent)
}
}
return false
}
case pseudochecker.PseudoObjectElementKindMethod:
d := e.AsPseudoObjectMethod()
targetSig := b.ch.getSingleCallSignature(propType)
if targetSig == nil {
// Target property type doesn't have a single call signature; can't validate
continue
}
paramEq := b.pseudoParametersEquivalentToParameters(d.Parameters, targetSig, reportErrors, e.Name.Parent)
if !paramEq {
return false
}
targetPredicate := b.ch.getTypePredicateOfSignature(targetSig)
if targetPredicate != nil {
if !b.pseudoReturnTypeMatchesPredicate(d.ReturnType, targetPredicate) {
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(e.Name.Parent)
}
return false
}
} else if !b.pseudoTypeEquivalentToType(d.ReturnType, b.ch.getReturnTypeOfSignature(targetSig), false, false) {
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(e.Name.Parent)
}
return false
}
case pseudochecker.PseudoObjectElementKindGetAccessor:
d := e.AsPseudoGetAccessor()
if !b.pseudoTypeEquivalentToType(d.Type, propType, false, false) {
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(e.Name.Parent)
}
return false
}
case pseudochecker.PseudoObjectElementKindSetAccessor:
d := e.AsPseudoSetAccessor()
writeType := b.ch.getWriteTypeOfSymbol(targetProp)
if !b.pseudoTypeEquivalentToType(d.Parameter.Type, writeType, false, false) {
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(e.Name.Parent)
}
return false
}
}
}
return true
case pseudochecker.PseudoTypeKindTuple:
pt := t.AsPseudoTypeTuple()
if undefinedStripped == nil || !isTupleType(undefinedStripped) {
return false
}
tupleTarget := undefinedStripped.TargetTupleType()
// Pseudo-tuples come from `as const` array literals, so they only ever have required elements.
// If the target tuple has optional, rest, or variadic elements, the structures can't match.
if tupleTarget.combinedFlags&ElementFlagsNonRequired != 0 {
return false
}
elementTypes := b.ch.getTypeArguments(undefinedStripped)
if len(pt.Elements) != len(elementTypes) {
return false
}
for i, elem := range pt.Elements {
if !b.pseudoTypeEquivalentToType(elem, elementTypes[i], false, reportErrors) {
return false
}
}
return true
case pseudochecker.PseudoTypeKindSingleCallSignature:
targetSig := b.ch.getSingleCallSignature(undefinedStripped)
if targetSig == nil {
return false
}
pt := t.AsPseudoTypeSingleCallSignature()
if len(targetSig.typeParameters) != len(pt.TypeParameters) {
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(pt.Signature)
}
return false
}
paramEq := b.pseudoParametersEquivalentToParameters(pt.Parameters, targetSig, reportErrors, pt.Signature)
if !paramEq {
return false
}
targetPredicate := b.ch.getTypePredicateOfSignature(targetSig)
if targetPredicate != nil {
if !b.pseudoReturnTypeMatchesPredicate(pt.ReturnType, targetPredicate) {
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(pt.Signature)
}
return false
}
} else if !b.pseudoTypeEquivalentToType(pt.ReturnType, b.ch.getReturnTypeOfSignature(targetSig), false, reportErrors) {
// error reported within the return type
return false
}
return true
case pseudochecker.PseudoTypeKindNoResult:
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(t.AsPseudoTypeNoResult().Declaration)
}
return false
default:
return false
}
}
func (b *NodeBuilderImpl) pseudoParametersEquivalentToParameters(params []*pseudochecker.PseudoParameter, targetSig *Signature, reportErrors bool, nonParamErrorLocation *ast.Node) bool {
if targetSig.thisParameter != nil && len(params) == 0 {
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(nonParamErrorLocation) // missing `this` param
}
return false
} else if targetSig.thisParameter != nil && ast.IsThisIdentifier(params[0].Name) {
targetParam := targetSig.thisParameter
paramType := b.ch.getTypeOfParameter(targetParam)
if !b.pseudoTypeEquivalentToType(params[0].Type, paramType, params[0].Optional, false) {
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(params[0].Name.Parent)
}
return false
}
params = params[1:]
} else if targetSig.thisParameter != nil {
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(nonParamErrorLocation)
}
return false
}
if len(targetSig.parameters) != len(params) {
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(nonParamErrorLocation)
}
return false // TODO: spread tuple params may mess with this check
}
for i, p := range params {
targetParam := targetSig.parameters[i]
if p.Optional != b.ch.isOptionalParameter(targetParam.ValueDeclaration) {
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(p.Name.Parent)
}
return false
}
paramType := b.ch.getTypeOfParameter(targetParam)
if !b.pseudoTypeEquivalentToType(p.Type, paramType, p.Optional, false) {
if reportErrors {
b.ctx.tracker.ReportInferenceFallback(p.Name.Parent)
}
return false
}
}
return true
}
func isStructuralPseudoType(t *pseudochecker.PseudoType) bool {
switch t.Kind {
case pseudochecker.PseudoTypeKindObjectLiteral, pseudochecker.PseudoTypeKindTuple, pseudochecker.PseudoTypeKindSingleCallSignature:
return true
case pseudochecker.PseudoTypeKindMaybeConstLocation:
d := t.AsPseudoTypeMaybeConstLocation()
return isStructuralPseudoType(d.ConstType) || isStructuralPseudoType(d.RegularType)
}
return false
}
// pseudoReturnTypeMatchesPredicate checks if a pseudo return type (which should be a Direct type
// wrapping a TypePredicate) matches the given type predicate from the checker.
func (b *NodeBuilderImpl) pseudoReturnTypeMatchesPredicate(rt *pseudochecker.PseudoType, predicate *TypePredicate) bool {
if rt.Kind != pseudochecker.PseudoTypeKindDirect {
return false
}
node := rt.AsPseudoTypeDirect().TypeNode
if !ast.IsTypePredicateNode(node) {
return false
}
tp := node.AsTypePredicateNode()
// Check asserts modifier matches
isAsserts := tp.AssertsModifier != nil
predicateIsAsserts := predicate.kind == TypePredicateKindAssertsThis || predicate.kind == TypePredicateKindAssertsIdentifier
if isAsserts != predicateIsAsserts {
return false
}
// Check this vs identifier matches
isThis := ast.IsThisTypeNode(tp.ParameterName)
predicateIsThis := predicate.kind == TypePredicateKindThis || predicate.kind == TypePredicateKindAssertsThis
if isThis != predicateIsThis {
return false
}
// For identifier predicates, check parameter name matches
if !isThis {
if tp.ParameterName.Text() != predicate.parameterName {
return false
}
}
// Check the narrowed type, if any
if predicate.t != nil {
if tp.Type == nil {
return false
}
predicateTypeFromNode := b.ch.getTypeFromTypeNode(tp.Type)
if predicateTypeFromNode != predicate.t {
if b.ch.compareTypesIdentical(predicateTypeFromNode, predicate.t) != TernaryTrue {
return false
}
}
} else if tp.Type != nil {
return false
}
return true
}
func (b *NodeBuilderImpl) pseudoTypeToType(t *pseudochecker.PseudoType) *Type {
// !!! TODO: only literal types currently mapped because this is only used to determine if literal contextual typing need apply to the pseudotype
// If this is used more broadly, the implementation needs to be filled out more to handle the structural pseudotypes - signatures, objects, tuples, etc
debug.Assert(t != nil, "Attempted to realize nil pseudotype")
switch t.Kind {
case pseudochecker.PseudoTypeKindDirect:
return b.ch.getTypeFromTypeNode(t.AsPseudoTypeDirect().TypeNode)
case pseudochecker.PseudoTypeKindInferred:
node := t.AsPseudoTypeInferred().Expression
if t.AsPseudoTypeInferred().IsSignatureReturn {
return b.ch.getReturnTypeOfSignature(b.ch.getSignatureFromDeclaration(node))
}
ty := b.ch.getWidenedType(b.ch.getRegularTypeOfExpression(node))
return ty
case pseudochecker.PseudoTypeKindNoResult:
return nil // TODO: extract type selection logic from `serializeTypeForDeclaration`, not needed for current usecases but needed if completeness becomes required
case pseudochecker.PseudoTypeKindMaybeConstLocation:
d := t.AsPseudoTypeMaybeConstLocation()
if b.ch.isConstContext(d.Node) {
return b.pseudoTypeToType(d.ConstType)
}
return b.pseudoTypeToType(d.RegularType)
case pseudochecker.PseudoTypeKindUnion:
var res []*Type
var hasElidedType bool
members := t.AsPseudoTypeUnion().Types
for _, m := range members {
if !b.ch.strictNullChecks {
if m.Kind == pseudochecker.PseudoTypeKindUndefined || m.Kind == pseudochecker.PseudoTypeKindNull {
hasElidedType = true
continue
}
}
t := b.pseudoTypeToType(m)
if t == nil {
return nil // propagate failure
}
res = append(res, t)
}
if len(res) == 1 {
return res[0]
}
if len(res) == 0 {
if hasElidedType {
return b.ch.anyType
}
return b.ch.neverType
}
return b.ch.getUnionType(res)
case pseudochecker.PseudoTypeKindUndefined:
return b.ch.undefinedWideningType
case pseudochecker.PseudoTypeKindNull:
return b.ch.nullWideningType
case pseudochecker.PseudoTypeKindAny:
return b.ch.anyType
case pseudochecker.PseudoTypeKindString:
return b.ch.stringType
case pseudochecker.PseudoTypeKindNumber:
return b.ch.numberType
case pseudochecker.PseudoTypeKindBigInt:
return b.ch.bigintType
case pseudochecker.PseudoTypeKindBoolean:
return b.ch.booleanType
case pseudochecker.PseudoTypeKindFalse:
return b.ch.falseType
case pseudochecker.PseudoTypeKindTrue:
return b.ch.trueType
case pseudochecker.PseudoTypeKindStringLiteral, pseudochecker.PseudoTypeKindNumericLiteral, pseudochecker.PseudoTypeKindBigIntLiteral:
source := t.AsPseudoTypeLiteral().Node
return b.ch.getRegularTypeOfExpression(source) // big shortcut, uses cached expression types where possible
case pseudochecker.PseudoTypeKindObjectLiteral, pseudochecker.PseudoTypeKindSingleCallSignature, pseudochecker.PseudoTypeKindTuple:
return nil // no simple mapping to a type, since these are structural types
default:
debug.Fail("Unhandled pseudochecker.PseudoTypeKind in pseudoTypeToType")
return nil
}
}

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,25 @@
// Code generated by "stringer -type=SignatureKind -output=stringer_generated.go"; DO NOT EDIT.
package checker
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[SignatureKindCall-0]
_ = x[SignatureKindConstruct-1]
}
const _SignatureKind_name = "SignatureKindCallSignatureKindConstruct"
var _SignatureKind_index = [...]uint8{0, 17, 39}
func (i SignatureKind) String() string {
idx := int(i) - 0
if i < 0 || idx >= len(_SignatureKind_index)-1 {
return "SignatureKind(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _SignatureKind_name[_SignatureKind_index[idx]:_SignatureKind_index[idx+1]]
}

View File

@@ -0,0 +1,876 @@
package checker
import (
"slices"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/core"
"github.com/microsoft/typescript-go/internal/printer"
)
func (c *Checker) IsTypeSymbolAccessible(typeSymbol *ast.Symbol, enclosingDeclaration *ast.Node) bool {
access := c.isSymbolAccessibleWorker(typeSymbol, enclosingDeclaration, ast.SymbolFlagsType /*shouldComputeAliasesToMakeVisible*/, false /*allowModules*/, true)
return access.Accessibility == printer.SymbolAccessibilityAccessible
}
func (c *Checker) IsValueSymbolAccessible(symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool {
access := c.isSymbolAccessibleWorker(symbol, enclosingDeclaration, ast.SymbolFlagsValue /*shouldComputeAliasesToMakeVisible*/, false /*allowModules*/, true)
return access.Accessibility == printer.SymbolAccessibilityAccessible
}
func (c *Checker) IsSymbolAccessibleByFlags(symbol *ast.Symbol, enclosingDeclaration *ast.Node, flags ast.SymbolFlags) bool {
access := c.isSymbolAccessibleWorker(symbol, enclosingDeclaration, flags /*shouldComputeAliasesToMakeVisible*/, false /*allowModules*/, false) // TODO: Strada bug? Why is this allowModules: false?
return access.Accessibility == printer.SymbolAccessibilityAccessible
}
func (c *Checker) IsAnySymbolAccessible(symbols []*ast.Symbol, enclosingDeclaration *ast.Node, initialSymbol *ast.Symbol, meaning ast.SymbolFlags, shouldComputeAliasesToMakeVisible bool, allowModules bool) *printer.SymbolAccessibilityResult {
if len(symbols) == 0 {
return nil
}
var hadAccessibleChain *ast.Symbol
earlyModuleBail := false
for _, symbol := range symbols {
// Symbol is accessible if it by itself is accessible
accessibleSymbolChain := c.getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning /*useOnlyExternalAliasing*/, false)
if len(accessibleSymbolChain) > 0 {
hadAccessibleChain = symbol
// TODO: going through emit resolver here is weird. Relayer these APIs.
hasAccessibleDeclarations := c.GetEmitResolver().hasVisibleDeclarations(accessibleSymbolChain[0], shouldComputeAliasesToMakeVisible)
if hasAccessibleDeclarations != nil {
return hasAccessibleDeclarations
}
}
if allowModules {
if core.Some(symbol.Declarations, hasNonGlobalAugmentationExternalModuleSymbol) {
if shouldComputeAliasesToMakeVisible {
earlyModuleBail = true
// Generally speaking, we want to use the aliases that already exist to refer to a module, if present
// In order to do so, we need to find those aliases in order to retain them in declaration emit; so
// if we are in declaration emit, we cannot use the fast path for module visibility until we've exhausted
// all other visibility options (in order to capture the possible aliases used to reference the module)
continue
}
// Any meaning of a module symbol is always accessible via an `import` type
return &printer.SymbolAccessibilityResult{
Accessibility: printer.SymbolAccessibilityAccessible,
}
}
}
// If we haven't got the accessible symbol, it doesn't mean the symbol is actually inaccessible.
// It could be a qualified symbol and hence verify the path
// e.g.:
// module m {
// export class c {
// }
// }
// const x: typeof m.c
// In the above example when we start with checking if typeof m.c symbol is accessible,
// we are going to see if c can be accessed in scope directly.
// But it can't, hence the accessible is going to be undefined, but that doesn't mean m.c is inaccessible
// It is accessible if the parent m is accessible because then m.c can be accessed through qualification
containers := c.getContainersOfSymbol(symbol, enclosingDeclaration, meaning)
nextMeaning := meaning
if initialSymbol == symbol {
nextMeaning = getQualifiedLeftMeaning(meaning)
}
parentResult := c.IsAnySymbolAccessible(containers, enclosingDeclaration, initialSymbol, nextMeaning, shouldComputeAliasesToMakeVisible, allowModules)
if parentResult != nil {
return parentResult
}
}
if earlyModuleBail {
return &printer.SymbolAccessibilityResult{
Accessibility: printer.SymbolAccessibilityAccessible,
}
}
if hadAccessibleChain != nil {
var moduleName string
if hadAccessibleChain != initialSymbol {
moduleName = c.symbolToStringEx(hadAccessibleChain, enclosingDeclaration, ast.SymbolFlagsNamespace, SymbolFormatFlagsAllowAnyNodeKind)
}
return &printer.SymbolAccessibilityResult{
Accessibility: printer.SymbolAccessibilityNotAccessible,
ErrorSymbolName: c.symbolToStringEx(initialSymbol, enclosingDeclaration, meaning, SymbolFormatFlagsAllowAnyNodeKind),
ErrorModuleName: moduleName,
}
}
return nil
}
func hasNonGlobalAugmentationExternalModuleSymbol(declaration *ast.Node) bool {
return ast.IsModuleWithStringLiteralName(declaration) || (declaration.Kind == ast.KindSourceFile && ast.IsExternalOrCommonJSModule(declaration.AsSourceFile()))
}
func getQualifiedLeftMeaning(rightMeaning ast.SymbolFlags) ast.SymbolFlags {
// If we are looking in value space, the parent meaning is value, other wise it is namespace
if rightMeaning == ast.SymbolFlagsValue {
return ast.SymbolFlagsValue
}
return ast.SymbolFlagsNamespace
}
func (c *Checker) getWithAlternativeContainers(container *ast.Symbol, symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags) []*ast.Symbol {
additionalContainers := core.MapNonNil(container.Declarations, func(d *ast.Node) *ast.Symbol {
return c.getFileSymbolIfFileSymbolExportEqualsContainer(d, container)
})
var reexportContainers []*ast.Symbol
if enclosingDeclaration != nil {
reexportContainers = c.getAlternativeContainingModules(symbol, enclosingDeclaration)
}
objectLiteralContainer := c.getVariableDeclarationOfObjectLiteral(container, meaning)
leftMeaning := getQualifiedLeftMeaning(meaning)
if enclosingDeclaration != nil &&
container.Flags&leftMeaning != 0 &&
len(c.getAccessibleSymbolChain(container, enclosingDeclaration, ast.SymbolFlagsNamespace /*useOnlyExternalAliasing*/, false)) > 0 {
// This order expresses a preference for the real container if it is in scope
res := append(append([]*ast.Symbol{container}, additionalContainers...), reexportContainers...)
if objectLiteralContainer != nil {
res = append(res, objectLiteralContainer)
}
return res
}
// we potentially have a symbol which is a member of the instance side of something - look for a variable in scope with the container's type
// which may be acting like a namespace (eg, `Symbol` acts like a namespace when looking up `Symbol.toStringTag`)
var variableMatches []*ast.Symbol
if (meaning == ast.SymbolFlagsValue &&
container.Flags&leftMeaning == 0) &&
container.Flags&ast.SymbolFlagsType != 0 &&
c.getDeclaredTypeOfSymbol(container).flags&TypeFlagsObject != 0 {
c.someSymbolTableInScope(enclosingDeclaration, func(t ast.SymbolTable, _ symbolTableID, _ bool, _ bool, _ *ast.Node) bool {
found := false
for _, s := range t {
if s.Flags&leftMeaning != 0 && c.getTypeOfSymbol(s) == c.getDeclaredTypeOfSymbol(container) {
variableMatches = append(variableMatches, s)
found = true
}
}
return found
})
c.sortSymbols(variableMatches)
}
var res []*ast.Symbol
res = append(res, variableMatches...)
res = append(res, additionalContainers...)
res = append(res, container)
if objectLiteralContainer != nil {
res = append(res, objectLiteralContainer)
}
res = append(res, reexportContainers...)
return res
}
func (c *Checker) getAlternativeContainingModules(symbol *ast.Symbol, enclosingDeclaration *ast.Node) []*ast.Symbol {
if enclosingDeclaration == nil {
return nil
}
containingFile := ast.GetSourceFileOfNode(enclosingDeclaration)
id := ast.GetNodeId(containingFile.AsNode())
links := c.symbolContainerLinks.Get(symbol)
if links.extendedContainersByFile == nil {
links.extendedContainersByFile = make(map[ast.NodeId][]*ast.Symbol)
}
existing, ok := links.extendedContainersByFile[id]
if ok && existing != nil {
return existing
}
var results []*ast.Symbol
if len(containingFile.Imports()) > 0 {
// Try to make an import using an import already in the enclosing file, if possible
for _, importRef := range containingFile.Imports() {
if ast.NodeIsSynthesized(importRef) {
// Synthetic names can't be resolved by `resolveExternalModuleName` - they'll cause a debug assert if they error
continue
}
resolvedModule := c.resolveExternalModuleName(enclosingDeclaration, importRef /*ignoreErrors*/, true)
if resolvedModule == nil {
continue
}
ref := c.getAliasForSymbolInContainer(resolvedModule, symbol)
if ref == nil {
continue
}
results = append(results, resolvedModule)
}
if len(results) > 0 {
links.extendedContainersByFile[id] = results
return results
}
}
if links.extendedContainers != nil {
return *links.extendedContainers
}
// No results from files already being imported by this file - expand search (expensive, but not location-specific, so cached)
otherFiles := c.program.SourceFiles()
for _, file := range otherFiles {
if !ast.IsExternalModule(file) {
continue
}
sym := c.getSymbolOfDeclaration(file.AsNode())
ref := c.getAliasForSymbolInContainer(sym, symbol)
if ref == nil {
continue
}
results = append(results, sym)
}
links.extendedContainers = &results
return results
}
func (c *Checker) getVariableDeclarationOfObjectLiteral(symbol *ast.Symbol, meaning ast.SymbolFlags) *ast.Symbol {
// If we're trying to reference some object literal in, eg `var a = { x: 1 }`, the symbol for the literal, `__object`, is distinct
// from the symbol of the declaration it is being assigned to. Since we can use the declaration to refer to the literal, however,
// we'd like to make that connection here - potentially causing us to paint the declaration's visibility, and therefore the literal.
if meaning&ast.SymbolFlagsValue == 0 {
return nil
}
if len(symbol.Declarations) == 0 {
return nil
}
firstDecl := symbol.Declarations[0]
if firstDecl.Parent == nil {
return nil
}
if !ast.IsVariableDeclaration(firstDecl.Parent) {
return nil
}
if ast.IsObjectLiteralExpression(firstDecl) && firstDecl == firstDecl.Parent.Initializer() || ast.IsTypeLiteralNode(firstDecl) && firstDecl == firstDecl.Parent.Type() {
return c.getSymbolOfDeclaration(firstDecl.Parent)
}
return nil
}
func hasExternalModuleSymbol(declaration *ast.Node) bool {
return ast.IsAmbientModule(declaration) || (declaration.Kind == ast.KindSourceFile && ast.IsExternalOrCommonJSModule(declaration.AsSourceFile()))
}
func (c *Checker) getExternalModuleContainer(declaration *ast.Node) *ast.Symbol {
node := ast.FindAncestor(declaration, hasExternalModuleSymbol)
if node == nil {
return nil
}
return c.getSymbolOfDeclaration(node)
}
func (c *Checker) getFileSymbolIfFileSymbolExportEqualsContainer(d *ast.Node, container *ast.Symbol) *ast.Symbol {
fileSymbol := c.getExternalModuleContainer(d)
if fileSymbol == nil || fileSymbol.Exports == nil {
return nil
}
exported, ok := fileSymbol.Exports[ast.InternalSymbolNameExportEquals]
if !ok || exported == nil {
return nil
}
if c.getSymbolIfSameReference(exported, container) != nil {
return fileSymbol
}
return nil
}
/**
* Attempts to find the symbol corresponding to the container a symbol is in - usually this
* is just its' `.parent`, but for locals, this value is `undefined`
*/
func (c *Checker) getContainersOfSymbol(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags) []*ast.Symbol {
container := c.getParentOfSymbol(symbol)
// Type parameters end up in the `members` lists but are not externally visible
if container != nil && (symbol.Flags&ast.SymbolFlagsTypeParameter == 0) {
return c.getWithAlternativeContainers(container, symbol, enclosingDeclaration, meaning)
}
var candidates []*ast.Symbol
for _, d := range symbol.Declarations {
if !ast.IsAmbientModule(d) && d.Parent != nil {
// direct children of a module
if hasNonGlobalAugmentationExternalModuleSymbol(d.Parent) {
sym := c.getSymbolOfDeclaration(d.Parent)
if sym != nil && !slices.Contains(candidates, sym) {
candidates = append(candidates, sym)
}
continue
}
// export ='d member of an ambient module
if ast.IsModuleBlock(d.Parent) && d.Parent.Parent != nil && c.resolveExternalModuleSymbol(c.getSymbolOfDeclaration(d.Parent.Parent), false) == symbol {
sym := c.getSymbolOfDeclaration(d.Parent.Parent)
if sym != nil && !slices.Contains(candidates, sym) {
candidates = append(candidates, sym)
}
continue
}
}
if ast.IsClassExpression(d) && ast.IsBinaryExpression(d.Parent) && d.Parent.AsBinaryExpression().OperatorToken.Kind == ast.KindEqualsToken && ast.IsAccessExpression(d.Parent.AsBinaryExpression().Left) && ast.IsEntityNameExpression(d.Parent.AsBinaryExpression().Left.Expression()) {
if ast.IsModuleExportsAccessExpression(d.Parent.AsBinaryExpression().Left) || ast.IsExportsIdentifier(d.Parent.AsBinaryExpression().Left.Expression()) {
sym := c.getSymbolOfDeclaration(ast.GetSourceFileOfNode(d).AsNode())
if sym != nil && !slices.Contains(candidates, sym) {
candidates = append(candidates, sym)
}
continue
}
c.checkExpressionCached(d.Parent.AsBinaryExpression().Left.Expression())
sym := c.symbolNodeLinks.Get(d.Parent.AsBinaryExpression().Left.Expression()).resolvedSymbol
if sym != nil && !slices.Contains(candidates, sym) {
candidates = append(candidates, sym)
}
continue
}
}
if len(candidates) == 0 {
return nil
}
var bestContainers []*ast.Symbol
var alternativeContainers []*ast.Symbol
for _, container := range candidates {
if c.getAliasForSymbolInContainer(container, symbol) == nil {
continue
}
allAlts := c.getWithAlternativeContainers(container, symbol, enclosingDeclaration, meaning)
if len(allAlts) == 0 {
continue
}
bestContainers = append(bestContainers, allAlts[0])
alternativeContainers = append(alternativeContainers, allAlts[1:]...)
}
return append(bestContainers, alternativeContainers...)
}
func (c *Checker) getAliasForSymbolInContainer(container *ast.Symbol, symbol *ast.Symbol) *ast.Symbol {
if container == c.getParentOfSymbol(symbol) {
// fast path, `symbol` is either already the alias or isn't aliased
return symbol
}
// Check if container is a thing with an `export=` which points directly at `symbol`, and if so, return
// the container itself as the alias for the symbol
if container.Exports != nil {
exportEquals, ok := container.Exports[ast.InternalSymbolNameExportEquals]
if ok && exportEquals != nil && c.getSymbolIfSameReference(exportEquals, symbol) != nil {
return container
}
}
exports := c.getExportsOfSymbol(container)
quick, ok := exports[symbol.Name]
if ok && quick != nil && c.getSymbolIfSameReference(quick, symbol) != nil {
return quick
}
var candidates []*ast.Symbol
for _, exported := range exports {
if c.getSymbolIfSameReference(exported, symbol) != nil {
candidates = append(candidates, exported)
}
}
if len(candidates) > 0 {
c.sortSymbols(candidates) // _must_ sort exports for stable results - symbol table is randomly iterated
return candidates[0]
}
return nil
}
func (c *Checker) getAccessibleSymbolChain(
symbol *ast.Symbol,
enclosingDeclaration *ast.Node,
meaning ast.SymbolFlags,
useOnlyExternalAliasing bool,
) []*ast.Symbol {
return c.getAccessibleSymbolChainEx(accessibleSymbolChainContext{symbol, enclosingDeclaration, meaning, useOnlyExternalAliasing, make(map[ast.SymbolId]map[symbolTableID]struct{})})
}
func (c *Checker) GetAccessibleSymbolChain(
symbol *ast.Symbol,
enclosingDeclaration *ast.Node,
meaning ast.SymbolFlags,
useOnlyExternalAliasing bool,
) []*ast.Symbol {
return c.getAccessibleSymbolChain(symbol, enclosingDeclaration, meaning, useOnlyExternalAliasing)
}
type accessibleSymbolChainContext struct {
symbol *ast.Symbol
enclosingDeclaration *ast.Node
meaning ast.SymbolFlags
useOnlyExternalAliasing bool
visitedSymbolTablesMap map[ast.SymbolId]map[symbolTableID]struct{}
}
// symbolTableID uniquely identifies a symbol table by encoding its source.
// The high 3 bits encode the kind, and the remaining bits encode the
// NodeId or SymbolId of the source.
type symbolTableID uint64
const stKindShift = 61
const (
stKindLocals symbolTableID = iota << stKindShift
stKindExports
stKindMembers
stKindGlobals
stKindResolvedExports // resolved/derived exports from getExportsOfSymbol, distinct from raw sym.Exports
// stKindMask extracts the kind bits from a symbolTableID.
stKindMask symbolTableID = (iota - 1) << stKindShift
)
func symbolTableIDFromLocals(node *ast.Node) symbolTableID {
return stKindLocals | symbolTableID(ast.GetNodeId(node))
}
func symbolTableIDFromExports(sym *ast.Symbol) symbolTableID {
return stKindExports | symbolTableID(ast.GetSymbolId(sym))
}
// symbolTableIDFromResolvedExports returns an ID for resolved/derived export tables
// (e.g. from getExportsOfSymbol/getExportsOfModule which may include export * resolution
// and late-bound members). This is distinct from symbolTableIDFromExports to prevent
// cache collisions with raw sym.Exports tables passed by someSymbolTableInScope.
func symbolTableIDFromResolvedExports(sym *ast.Symbol) symbolTableID {
return stKindResolvedExports | symbolTableID(ast.GetSymbolId(sym))
}
func symbolTableIDFromMembers(sym *ast.Symbol) symbolTableID {
return stKindMembers | symbolTableID(ast.GetSymbolId(sym))
}
func symbolTableIDFromGlobals() symbolTableID {
return stKindGlobals
}
func (c *Checker) getAccessibleSymbolChainEx(ctx accessibleSymbolChainContext) []*ast.Symbol {
if ctx.symbol == nil {
return nil
}
if isPropertyOrMethodDeclarationSymbol(ctx.symbol) {
return nil
}
// Go from enclosingDeclaration to the first scope we check, so the cache is keyed off the scope and thus shared more
var firstRelevantLocation *ast.Node
c.someSymbolTableInScope(ctx.enclosingDeclaration, func(_ ast.SymbolTable, _ symbolTableID, _ bool, _ bool, node *ast.Node) bool {
firstRelevantLocation = node
return true
})
links := c.symbolContainerLinks.Get(ctx.symbol)
linkKey := accessibleChainCacheKey{ctx.useOnlyExternalAliasing, firstRelevantLocation, ctx.meaning}
if links.accessibleChainCache == nil {
links.accessibleChainCache = make(map[accessibleChainCacheKey][]*ast.Symbol)
}
existing, ok := links.accessibleChainCache[linkKey]
if ok {
return existing
}
var result []*ast.Symbol
c.someSymbolTableInScope(ctx.enclosingDeclaration, func(t ast.SymbolTable, tableId symbolTableID, ignoreQualification bool, isLocalNameLookup bool, _ *ast.Node) bool {
res := c.getAccessibleSymbolChainFromSymbolTable(ctx, t, tableId, ignoreQualification, isLocalNameLookup)
if len(res) > 0 {
result = res
return true
}
return false
})
links.accessibleChainCache[linkKey] = result
return result
}
/**
* @param {ignoreQualification} boolean Set when a symbol is being looked for through the exports of another symbol (meaning we have a route to qualify it already)
*/
func (c *Checker) getAccessibleSymbolChainFromSymbolTable(ctx accessibleSymbolChainContext, t ast.SymbolTable, tableId symbolTableID, ignoreQualification bool, isLocalNameLookup bool) []*ast.Symbol {
symId := ast.GetSymbolId(ctx.symbol)
visitedSymbolTables, ok := ctx.visitedSymbolTablesMap[symId]
if !ok {
visitedSymbolTables = make(map[symbolTableID]struct{})
ctx.visitedSymbolTablesMap[symId] = visitedSymbolTables
}
_, present := visitedSymbolTables[tableId]
if present {
return nil
}
visitedSymbolTables[tableId] = struct{}{}
res := c.trySymbolTable(ctx, t, tableId, ignoreQualification, isLocalNameLookup)
delete(visitedSymbolTables, tableId)
return res
}
// getSymbolTableAliases returns only the alias symbols from a symbol table,
// caching the result by tableId to avoid repeated iteration over large tables.
// Members tables are skipped entirely since someSymbolTableInScope filters them
// to SymbolFlagsType & ^SymbolFlagsAssignment, which never includes aliases.
func (c *Checker) getSymbolTableAliases(symbols ast.SymbolTable, tableId symbolTableID) []*ast.Symbol {
kind := tableId & stKindMask
// Members tables never contain alias symbols; skip entirely.
if kind == stKindMembers {
return nil
}
// Cache globals and exports tables (which are large and revisited often).
// Locals tables are small and per-scope, so they are filtered but not cached.
if kind == stKindGlobals || kind == stKindExports || kind == stKindResolvedExports {
if c.symbolTableAliasCache != nil {
if aliases, ok := c.symbolTableAliasCache[tableId]; ok {
return aliases
}
}
}
var aliases []*ast.Symbol
for _, sym := range symbols {
if sym.Flags&ast.SymbolFlagsAlias != 0 {
aliases = append(aliases, sym)
}
}
if kind == stKindGlobals || kind == stKindExports || kind == stKindResolvedExports {
if c.symbolTableAliasCache == nil {
c.symbolTableAliasCache = make(map[symbolTableID][]*ast.Symbol)
}
c.symbolTableAliasCache[tableId] = aliases
}
return aliases
}
func (c *Checker) trySymbolTable(
ctx accessibleSymbolChainContext,
symbols ast.SymbolTable,
tableId symbolTableID,
ignoreQualification bool,
isLocalNameLookup bool,
) []*ast.Symbol {
isGlobals := tableId == stKindGlobals
// If symbol is directly available by its name in the symbol table
res, ok := symbols[ctx.symbol.Name]
if ok && res != nil && c.isAccessible(ctx, res /*resolvedAliasSymbol*/, nil, ignoreQualification) {
return []*ast.Symbol{ctx.symbol}
}
var candidateChains [][]*ast.Symbol
// Check for ExportSymbol by direct name lookup rather than discovering it during
// the alias iteration below (where it would never match, since only alias-flagged
// symbols are iterated).
if ok && res != nil && res.ExportSymbol != nil {
if c.isAccessible(ctx, c.getMergedSymbol(res.ExportSymbol) /*resolvedAliasSymbol*/, nil, ignoreQualification) {
candidateChains = append(candidateChains, []*ast.Symbol{ctx.symbol})
}
}
// Iterate only alias symbols from the table (cached per tableId).
// This avoids iterating thousands of non-alias symbols in large tables like globals.
for _, symbolFromSymbolTable := range c.getSymbolTableAliases(symbols, tableId) {
// for every non-default, non-export= alias symbol in scope, check if it refers to or can chain to the target symbol
if symbolFromSymbolTable.Name != ast.InternalSymbolNameExportEquals &&
symbolFromSymbolTable.Name != ast.InternalSymbolNameDefault &&
!(isUMDExportSymbol(symbolFromSymbolTable) && ctx.enclosingDeclaration != nil && ast.IsExternalModule(ast.GetSourceFileOfNode(ctx.enclosingDeclaration))) &&
// If `!useOnlyExternalAliasing`, we can use any type of alias to get the name
(!ctx.useOnlyExternalAliasing || core.Some(symbolFromSymbolTable.Declarations, ast.IsExternalModuleImportEqualsDeclaration)) &&
// If we're looking up a local name to reference directly, omit namespace reexports, otherwise when we're trawling through an export list to make a dotted name, we can keep it
(isLocalNameLookup && !core.Some(symbolFromSymbolTable.Declarations, isNamespaceReexportDeclaration) || !isLocalNameLookup) &&
// While exports are generally considered to be in scope, export-specifier declared symbols are _not_
// See similar comment in `resolveName` for details
(ignoreQualification || len(getDeclarationsOfKind(symbolFromSymbolTable, ast.KindExportSpecifier)) == 0) {
resolvedImportedSymbol := c.resolveAlias(symbolFromSymbolTable)
candidate := c.getCandidateListForSymbol(ctx, symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification)
if len(candidate) > 0 {
candidateChains = append(candidateChains, candidate)
}
}
}
if len(candidateChains) > 0 {
// pick first, shortest
slices.SortStableFunc(candidateChains, c.compareSymbolChains)
return candidateChains[0]
}
// If there's no result and we're looking at the global symbol table, treat `globalThis` like an alias and try to lookup thru that
if isGlobals {
return c.getCandidateListForSymbol(ctx, c.globalThisSymbol, c.globalThisSymbol, ignoreQualification)
}
return nil
}
func (c *Checker) compareSymbolChainsWorker(a []*ast.Symbol, b []*ast.Symbol) int {
chainLen := len(a) - len(b)
if chainLen != 0 {
return chainLen
}
idx := 0
for idx < len(a) {
comparison := c.compareSymbols(a[idx], b[idx])
if comparison != 0 {
return comparison
}
idx++
}
return 0
}
func isUMDExportSymbol(symbol *ast.Symbol) bool {
return symbol != nil && len(symbol.Declarations) > 0 && symbol.Declarations[0] != nil && ast.IsNamespaceExportDeclaration(symbol.Declarations[0])
}
func isNamespaceReexportDeclaration(node *ast.Node) bool {
return ast.IsNamespaceExport(node) && node.Parent.ModuleSpecifier() != nil
}
func (c *Checker) getCandidateListForSymbol(
ctx accessibleSymbolChainContext,
symbolFromSymbolTable *ast.Symbol,
resolvedImportedSymbol *ast.Symbol,
ignoreQualification bool,
) []*ast.Symbol {
if c.isAccessible(ctx, symbolFromSymbolTable, resolvedImportedSymbol, ignoreQualification) {
return []*ast.Symbol{symbolFromSymbolTable}
}
// Look in the exported members, if we can find accessibleSymbolChain, symbol is accessible using this chain
// but only if the symbolFromSymbolTable can be qualified
candidateTable := c.getExportsOfSymbol(resolvedImportedSymbol)
if candidateTable == nil {
return nil
}
candidateTableId := symbolTableIDFromResolvedExports(resolvedImportedSymbol)
accessibleSymbolsFromExports := c.getAccessibleSymbolChainFromSymbolTable(ctx, candidateTable, candidateTableId /*ignoreQualification*/, true, false)
if len(accessibleSymbolsFromExports) == 0 {
return nil
}
if !c.canQualifySymbol(ctx, symbolFromSymbolTable, getQualifiedLeftMeaning(ctx.meaning)) {
return nil
}
return append([]*ast.Symbol{symbolFromSymbolTable}, accessibleSymbolsFromExports...)
}
func (c *Checker) isAccessible(
ctx accessibleSymbolChainContext,
symbolFromSymbolTable *ast.Symbol,
resolvedAliasSymbol *ast.Symbol,
ignoreQualification bool,
) bool {
likeSymbols := false
if ctx.symbol == resolvedAliasSymbol {
likeSymbols = true
}
if ctx.symbol == symbolFromSymbolTable {
likeSymbols = true
}
symbol := c.getMergedSymbol(ctx.symbol)
if symbol == c.getMergedSymbol(resolvedAliasSymbol) {
likeSymbols = true
}
if symbol == c.getMergedSymbol(symbolFromSymbolTable) {
likeSymbols = true
}
if !likeSymbols {
return false
}
// if the symbolFromSymbolTable is not external module (it could be if it was determined as ambient external module and would be in globals table)
// and if symbolFromSymbolTable or alias resolution matches the symbol,
// check the symbol can be qualified, it is only then this symbol is accessible
return !core.Some(symbolFromSymbolTable.Declarations, hasNonGlobalAugmentationExternalModuleSymbol) &&
(ignoreQualification || c.canQualifySymbol(ctx, c.getMergedSymbol(symbolFromSymbolTable), ctx.meaning))
}
func (c *Checker) canQualifySymbol(
ctx accessibleSymbolChainContext,
symbolFromSymbolTable *ast.Symbol,
meaning ast.SymbolFlags,
) bool {
// If the symbol is equivalent and doesn't need further qualification, this symbol is accessible
return !c.needsQualification(symbolFromSymbolTable, ctx.enclosingDeclaration, meaning) ||
// If symbol needs qualification, make sure that parent is accessible, if it is then this symbol is accessible too
len(c.getAccessibleSymbolChainEx(accessibleSymbolChainContext{symbolFromSymbolTable.Parent, ctx.enclosingDeclaration, getQualifiedLeftMeaning(meaning), ctx.useOnlyExternalAliasing, ctx.visitedSymbolTablesMap})) > 0
}
func (c *Checker) needsQualification(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags) bool {
qualify := false
c.someSymbolTableInScope(enclosingDeclaration, func(symbolTable ast.SymbolTable, _ symbolTableID, _ bool, _ bool, _ *ast.Node) bool {
// If symbol of this name is not available in the symbol table we are ok
res, ok := symbolTable[symbol.Name]
if !ok || res == nil {
return false
}
symbolFromSymbolTable := c.getMergedSymbol(res)
if symbolFromSymbolTable == nil {
// Continue to the next symbol table
return false
}
// If the symbol with this name is present it should refer to the symbol
if symbolFromSymbolTable == symbol {
// No need to qualify
return true
}
// Qualify if the symbol from symbol table has same meaning as expected
shouldResolveAlias := symbolFromSymbolTable.Flags&ast.SymbolFlagsAlias != 0 && ast.GetDeclarationOfKind(symbolFromSymbolTable, ast.KindExportSpecifier) == nil
if shouldResolveAlias {
symbolFromSymbolTable = c.resolveAlias(symbolFromSymbolTable)
}
flags := symbolFromSymbolTable.Flags
if shouldResolveAlias {
flags = c.getSymbolFlags(symbolFromSymbolTable)
}
if flags&meaning != 0 {
qualify = true
return true
}
// Continue to the next symbol table
return false
})
return qualify
}
func isPropertyOrMethodDeclarationSymbol(symbol *ast.Symbol) bool {
if len(symbol.Declarations) > 0 {
for _, declaration := range symbol.Declarations {
switch declaration.Kind {
case ast.KindPropertyDeclaration,
ast.KindMethodDeclaration,
ast.KindGetAccessor,
ast.KindSetAccessor:
continue
default:
return false
}
}
return true
}
return false
}
func (c *Checker) someSymbolTableInScope(
enclosingDeclaration *ast.Node,
callback func(symbolTable ast.SymbolTable, tableId symbolTableID, ignoreQualification bool, isLocalNameLookup bool, scopeNode *ast.Node) bool,
) bool {
for location := enclosingDeclaration; location != nil; location = location.Parent {
// Locals of a source file are not in scope (because they get merged into the global symbol table)
if canHaveLocals(location) && location.Locals() != nil && !ast.IsGlobalSourceFile(location) {
if callback(location.Locals(), symbolTableIDFromLocals(location.AsNode()), false, true, location) {
return true
}
}
switch location.Kind {
case ast.KindSourceFile, ast.KindModuleDeclaration:
if ast.IsSourceFile(location) && !ast.IsExternalOrCommonJSModule(location.AsSourceFile()) {
break
}
sym := c.getSymbolOfDeclaration(ast.GetReparsedNodeForNode(location))
if callback(sym.Exports, symbolTableIDFromExports(sym), false, true, location) {
return true
}
case ast.KindClassDeclaration, ast.KindClassExpression, ast.KindInterfaceDeclaration:
// Type parameters are bound into `members` lists so they can merge across declarations
// This is troublesome, since in all other respects, they behave like locals :cries:
// TODO: the below is shared with similar code in `resolveName` - in fact, rephrasing all this symbol
// lookup logic in terms of `resolveName` would be nice
// The below is used to lookup type parameters within a class or interface, as they are added to the class/interface locals
// These can never be latebound, so the symbol's raw members are sufficient. `getMembersOfNode` cannot be used, as it would
// trigger resolving late-bound names, which we may already be in the process of doing while we're here!
var table ast.SymbolTable
sym := c.getSymbolOfDeclaration(location)
// TODO: Should this filtered table be cached in some way?
for key, memberSymbol := range sym.Members {
if memberSymbol.Flags&(ast.SymbolFlagsType & ^ast.SymbolFlagsAssignment) != 0 {
if table == nil {
table = make(ast.SymbolTable)
}
table[key] = memberSymbol
}
}
if table != nil && callback(table, symbolTableIDFromMembers(sym), false, false, location) {
return true
}
// Class expression names (e.g., `B` in `class B {}`) are not stored in any
// scope table — the binder uses bindAnonymousDeclaration. Expose the name
// binding here so getAccessibleSymbolChain can resolve self-references.
// This mirrors the special casing of class expression names in
// (*NameResolver).Resolve; if class names are ever bound differently
// (e.g., via class-local type aliases), both sites should be updated.
if ast.IsClassExpression(location) && location.AsClassExpression().Name() != nil {
nameTable := c.getClassExpressionNameTable(location)
if nameTable != nil && callback(nameTable, symbolTableIDFromLocals(location.AsNode()), false, true, location) {
return true
}
}
}
}
return callback(c.globals, symbolTableIDFromGlobals(), false, true, nil)
}
// getClassExpressionNameTable returns a cached symbol table containing the class
// expression's name binding. Class expression names are bound via
// bindAnonymousDeclaration and aren't stored in any container's locals, so this
// synthesized table lets someSymbolTableInScope expose them during accessibility checks.
func (c *Checker) getClassExpressionNameTable(location *ast.Node) ast.SymbolTable {
nodeId := ast.GetNodeId(location)
if c.classExpressionNameTables != nil {
if table, ok := c.classExpressionNameTables[nodeId]; ok {
return table
}
}
classSymbol := c.getSymbolOfDeclaration(location)
nameText := location.AsClassExpression().Name().Text()
if len(nameText) == 0 || classSymbol == nil {
return nil
}
table := ast.SymbolTable{nameText: classSymbol}
if c.classExpressionNameTables == nil {
c.classExpressionNameTables = make(map[ast.NodeId]ast.SymbolTable)
}
c.classExpressionNameTables[nodeId] = table
return table
}
/**
* Check if the given symbol in given enclosing declaration is accessible and mark all associated alias to be visible if requested
*
* @param symbol a Symbol to check if accessible
* @param enclosingDeclaration a Node containing reference to the symbol
* @param meaning a SymbolFlags to check if such meaning of the symbol is accessible
* @param shouldComputeAliasToMakeVisible a boolean value to indicate whether to return aliases to be mark visible in case the symbol is accessible
*/
func (c *Checker) IsSymbolAccessible(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags, shouldComputeAliasesToMakeVisible bool) printer.SymbolAccessibilityResult {
return c.isSymbolAccessibleWorker(symbol, enclosingDeclaration, meaning, shouldComputeAliasesToMakeVisible, true /*allowModules*/)
}
func (c *Checker) isSymbolAccessibleWorker(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags, shouldComputeAliasesToMakeVisible bool, allowModules bool) printer.SymbolAccessibilityResult {
if symbol != nil && enclosingDeclaration != nil {
result := c.IsAnySymbolAccessible([]*ast.Symbol{symbol}, enclosingDeclaration, symbol, meaning, shouldComputeAliasesToMakeVisible, allowModules)
if result != nil {
return *result
}
// This could be a symbol that is not exported in the external module
// or it could be a symbol from different external module that is not aliased and hence cannot be named
symbolExternalModule := core.FirstNonNil(symbol.Declarations, c.getExternalModuleContainer)
if symbolExternalModule != nil {
enclosingExternalModule := c.getExternalModuleContainer(enclosingDeclaration)
if symbolExternalModule != enclosingExternalModule {
// name from different external module that is not visible
return printer.SymbolAccessibilityResult{
Accessibility: printer.SymbolAccessibilityCannotBeNamed,
ErrorSymbolName: c.symbolToStringEx(symbol, enclosingDeclaration, meaning, SymbolFormatFlagsAllowAnyNodeKind),
ErrorModuleName: c.symbolToString(symbolExternalModule),
ErrorNode: core.IfElse(ast.IsInJSFile(enclosingDeclaration), enclosingDeclaration, nil),
}
}
}
// Just a local name that is not accessible
return printer.SymbolAccessibilityResult{
Accessibility: printer.SymbolAccessibilityNotAccessible,
ErrorSymbolName: c.symbolToStringEx(symbol, enclosingDeclaration, meaning, SymbolFormatFlagsAllowAnyNodeKind),
}
}
return printer.SymbolAccessibilityResult{
Accessibility: printer.SymbolAccessibilityAccessible,
}
}

View File

@@ -0,0 +1,129 @@
package checker
import (
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/nodebuilder"
)
type SymbolTrackerImpl struct {
context *NodeBuilderContext
inner nodebuilder.SymbolTracker
DisableTrackSymbol bool
}
func NewSymbolTrackerImpl(context *NodeBuilderContext, tracker nodebuilder.SymbolTracker) *SymbolTrackerImpl {
if tracker != nil {
for {
t, ok := tracker.(*SymbolTrackerImpl)
if !ok {
break
}
tracker = t.inner
}
}
return &SymbolTrackerImpl{context, tracker, false}
}
func (this *SymbolTrackerImpl) TrackSymbol(symbol *ast.Symbol, enclosingDeclaration *ast.Node, meaning ast.SymbolFlags) bool {
if !this.DisableTrackSymbol {
if this.inner != nil && this.inner.TrackSymbol(symbol, enclosingDeclaration, meaning) {
this.onDiagnosticReported()
return true
}
// Skip recording type parameters as they dont contribute to late painted statements
if symbol.Flags&ast.SymbolFlagsTypeParameter == 0 {
this.context.trackedSymbols = append(this.context.trackedSymbols, &TrackedSymbolArgs{symbol, enclosingDeclaration, meaning})
}
}
return false
}
func (this *SymbolTrackerImpl) ReportInaccessibleThisError() {
this.onDiagnosticReported()
if this.inner == nil {
return
}
this.inner.ReportInaccessibleThisError()
}
func (this *SymbolTrackerImpl) ReportPrivateInBaseOfClassExpression(propertyName string) {
this.onDiagnosticReported()
if this.inner == nil {
return
}
this.inner.ReportPrivateInBaseOfClassExpression(propertyName)
}
func (this *SymbolTrackerImpl) ReportInaccessibleUniqueSymbolError() {
this.onDiagnosticReported()
if this.inner == nil {
return
}
this.inner.ReportInaccessibleUniqueSymbolError()
}
func (this *SymbolTrackerImpl) ReportCyclicStructureError() {
this.onDiagnosticReported()
if this.inner == nil {
return
}
this.inner.ReportCyclicStructureError()
}
func (this *SymbolTrackerImpl) ReportLikelyUnsafeImportRequiredError(specifier string, symbolName string) {
this.onDiagnosticReported()
if this.inner == nil {
return
}
this.inner.ReportLikelyUnsafeImportRequiredError(specifier, symbolName)
}
func (this *SymbolTrackerImpl) ReportTruncationError() {
this.onDiagnosticReported()
if this.inner == nil {
return
}
this.inner.ReportTruncationError()
}
func (this *SymbolTrackerImpl) ReportNonlocalAugmentation(containingFile *ast.SourceFile, parentSymbol *ast.Symbol, augmentingSymbol *ast.Symbol) {
this.onDiagnosticReported()
if this.inner == nil {
return
}
this.inner.ReportNonlocalAugmentation(containingFile, parentSymbol, augmentingSymbol)
}
func (this *SymbolTrackerImpl) ReportNonSerializableProperty(propertyName string) {
this.onDiagnosticReported()
if this.inner == nil {
return
}
this.inner.ReportNonSerializableProperty(propertyName)
}
func (this *SymbolTrackerImpl) onDiagnosticReported() {
this.context.reportedDiagnostic = true
}
func (this *SymbolTrackerImpl) ReportInferenceFallback(node *ast.Node) {
if this.inner == nil {
return
}
this.inner.ReportInferenceFallback(node)
}
func (this *SymbolTrackerImpl) PushErrorFallbackNode(node *ast.Node) {
if this.inner == nil {
return
}
this.inner.PushErrorFallbackNode(node)
}
func (this *SymbolTrackerImpl) PopErrorFallbackNode() {
if this.inner == nil {
return
}
this.inner.PopErrorFallbackNode()
}

View File

@@ -0,0 +1,366 @@
package checker
import (
"maps"
"github.com/microsoft/typescript-go/internal/ast"
"github.com/microsoft/typescript-go/internal/tracing"
)
// Tracer records types and trace events during type checking. A nil *Tracer
// is a valid no-op, so call sites can use `if tr := c.tracer; tr != nil` to
// gate work that only matters under --generateTrace.
type Tracer struct {
tracing *tracing.Tracing
recorder tracing.Tracer
checkerIndex int
}
// NewTracer creates a Tracer for the given checker index that records both
// type-creation events and trace events through the provided tracing session.
func NewTracer(tr *tracing.Tracing, checkerIndex int) *Tracer {
return &Tracer{tracing: tr, recorder: tr.NewTypeTracer(checkerIndex), checkerIndex: checkerIndex}
}
func (t *Tracer) RecordType(typ *Type) {
t.recorder.RecordType(wrapType(typ))
}
func (t *Tracer) Push(phase tracing.Phase, name string, args map[string]any, separateBeginAndEnd bool) func() {
if !separateBeginAndEnd {
return t.tracing.Push(phase, name, t.copyWithCheckerIndex(args), separateBeginAndEnd)
}
args, restore := t.temporarilyAddCheckerIndex(args)
pop := t.tracing.Push(phase, name, args, separateBeginAndEnd)
restore()
return func() {
_, restoreEndArgs := t.temporarilyAddCheckerIndex(args)
defer restoreEndArgs()
pop()
}
}
func (t *Tracer) Instant(phase tracing.Phase, name string, args map[string]any) {
t.tracing.Instant(phase, name, t.copyWithCheckerIndex(args))
}
func (t *Tracer) copyWithCheckerIndex(args map[string]any) map[string]any {
withCheckerIndex := make(map[string]any, len(args)+1)
maps.Copy(withCheckerIndex, args)
withCheckerIndex["checkerId"] = t.checkerIndex
return withCheckerIndex
}
func (t *Tracer) temporarilyAddCheckerIndex(args map[string]any) (map[string]any, func()) {
if args == nil {
args = map[string]any{}
}
previous, hadPrevious := args["checkerId"]
args["checkerId"] = t.checkerIndex
return args, func() {
if hadPrevious {
args["checkerId"] = previous
} else {
delete(args, "checkerId")
}
}
}
// tracedTypeAdapter adapts a Type to the tracing.TracedType interface
type tracedTypeAdapter struct {
t *Type
checker *Checker
}
var _ tracing.TracedType = (*tracedTypeAdapter)(nil)
func (a *tracedTypeAdapter) Id() uint32 {
return uint32(a.t.id)
}
func (a *tracedTypeAdapter) FormatFlags() []string {
return FormatTypeFlags(a.t.flags)
}
func (a *tracedTypeAdapter) IsConditional() bool {
return a.t.flags&TypeFlagsConditional != 0
}
func (a *tracedTypeAdapter) Symbol() *ast.Symbol {
return a.t.symbol
}
func (a *tracedTypeAdapter) AliasSymbol() *ast.Symbol {
if a.t.alias == nil {
return nil
}
return a.t.alias.Symbol()
}
func (a *tracedTypeAdapter) AliasTypeArguments() []tracing.TracedType {
if a.t.alias == nil {
return nil
}
return wrapTypes(a.t.alias.TypeArguments())
}
func (a *tracedTypeAdapter) IntrinsicName() string {
if a.t.flags&TypeFlagsIntrinsic == 0 {
return ""
}
data, ok := a.t.data.(*IntrinsicType)
if !ok {
return ""
}
return data.intrinsicName
}
func (a *tracedTypeAdapter) UnionTypes() []tracing.TracedType {
if a.t.flags&TypeFlagsUnion == 0 {
return nil
}
return wrapTypes(a.t.AsUnionType().types)
}
func (a *tracedTypeAdapter) IntersectionTypes() []tracing.TracedType {
if a.t.flags&TypeFlagsIntersection == 0 {
return nil
}
return wrapTypes(a.t.AsIntersectionType().types)
}
func (a *tracedTypeAdapter) IndexType() tracing.TracedType {
if a.t.flags&TypeFlagsIndex == 0 {
return nil
}
t := a.t.AsIndexType().target
if t == nil {
return nil
}
return wrapType(t)
}
func (a *tracedTypeAdapter) IndexedAccessObjectType() tracing.TracedType {
if a.t.flags&TypeFlagsIndexedAccess == 0 {
return nil
}
t := a.t.AsIndexedAccessType().objectType
if t == nil {
return nil
}
return wrapType(t)
}
func (a *tracedTypeAdapter) IndexedAccessIndexType() tracing.TracedType {
if a.t.flags&TypeFlagsIndexedAccess == 0 {
return nil
}
t := a.t.AsIndexedAccessType().indexType
if t == nil {
return nil
}
return wrapType(t)
}
func (a *tracedTypeAdapter) ConditionalCheckType() tracing.TracedType {
if a.t.flags&TypeFlagsConditional == 0 {
return nil
}
t := a.t.AsConditionalType().checkType
if t == nil {
return nil
}
return wrapType(t)
}
func (a *tracedTypeAdapter) ConditionalExtendsType() tracing.TracedType {
if a.t.flags&TypeFlagsConditional == 0 {
return nil
}
t := a.t.AsConditionalType().extendsType
if t == nil {
return nil
}
return wrapType(t)
}
func (a *tracedTypeAdapter) ConditionalTrueType() tracing.TracedType {
if a.t.flags&TypeFlagsConditional == 0 {
return nil
}
t := a.t.AsConditionalType().resolvedTrueType
if t == nil {
return nil
}
return wrapType(t)
}
func (a *tracedTypeAdapter) ConditionalFalseType() tracing.TracedType {
if a.t.flags&TypeFlagsConditional == 0 {
return nil
}
t := a.t.AsConditionalType().resolvedFalseType
if t == nil {
return nil
}
return wrapType(t)
}
func (a *tracedTypeAdapter) SubstitutionBaseType() tracing.TracedType {
if a.t.flags&TypeFlagsSubstitution == 0 {
return nil
}
t := a.t.AsSubstitutionType().baseType
if t == nil {
return nil
}
return wrapType(t)
}
func (a *tracedTypeAdapter) SubstitutionConstraintType() tracing.TracedType {
if a.t.flags&TypeFlagsSubstitution == 0 {
return nil
}
t := a.t.AsSubstitutionType().constraint
if t == nil {
return nil
}
return wrapType(t)
}
func (a *tracedTypeAdapter) ReferenceTarget() tracing.TracedType {
if a.t.flags&TypeFlagsObject == 0 || a.t.objectFlags&ObjectFlagsReference == 0 {
return nil
}
t := a.t.AsTypeReference().target
if t == nil {
return nil
}
return wrapType(t)
}
func (a *tracedTypeAdapter) ReferenceTypeArguments() []tracing.TracedType {
if a.t.flags&TypeFlagsObject == 0 || a.t.objectFlags&ObjectFlagsReference == 0 {
return nil
}
return wrapTypes(a.t.AsTypeReference().resolvedTypeArguments)
}
func (a *tracedTypeAdapter) ReferenceNode() *ast.Node {
if a.t.flags&TypeFlagsObject == 0 || a.t.objectFlags&ObjectFlagsReference == 0 {
return nil
}
return a.t.AsTypeReference().node
}
func (a *tracedTypeAdapter) ReverseMappedSourceType() tracing.TracedType {
if a.t.flags&TypeFlagsObject == 0 || a.t.objectFlags&ObjectFlagsReverseMapped == 0 {
return nil
}
t := a.t.AsReverseMappedType().source
if t == nil {
return nil
}
return wrapType(t)
}
func (a *tracedTypeAdapter) ReverseMappedMappedType() tracing.TracedType {
if a.t.flags&TypeFlagsObject == 0 || a.t.objectFlags&ObjectFlagsReverseMapped == 0 {
return nil
}
t := a.t.AsReverseMappedType().mappedType
if t == nil {
return nil
}
return wrapType(t)
}
func (a *tracedTypeAdapter) ReverseMappedConstraintType() tracing.TracedType {
if a.t.flags&TypeFlagsObject == 0 || a.t.objectFlags&ObjectFlagsReverseMapped == 0 {
return nil
}
t := a.t.AsReverseMappedType().constraintType
if t == nil {
return nil
}
return wrapType(t)
}
func (a *tracedTypeAdapter) EvolvingArrayElementType() tracing.TracedType {
if a.t.flags&TypeFlagsObject == 0 || a.t.objectFlags&ObjectFlagsEvolvingArray == 0 {
return nil
}
t := a.t.AsEvolvingArrayType().elementType
if t == nil {
return nil
}
return wrapType(t)
}
func (a *tracedTypeAdapter) EvolvingArrayFinalType() tracing.TracedType {
if a.t.flags&TypeFlagsObject == 0 || a.t.objectFlags&ObjectFlagsEvolvingArray == 0 {
return nil
}
t := a.t.AsEvolvingArrayType().finalArrayType
if t == nil {
return nil
}
return wrapType(t)
}
func (a *tracedTypeAdapter) IsTuple() bool {
return a.t.objectFlags&ObjectFlagsTuple != 0
}
func (a *tracedTypeAdapter) Pattern() *ast.Node {
if a.checker == nil {
return nil
}
return a.checker.patternForType[a.t]
}
func (a *tracedTypeAdapter) RecursionIdentity() any {
return getRecursionIdentity(a.t).value
}
func (a *tracedTypeAdapter) Display() string {
// Compute display text for types where it's valuable for trace analysis.
// TypeScript only does this for Anonymous|Literal types, but we extend to
// unions, intersections, and template literals since they often lack
// firstDeclaration and the display text helps identify them.
// Incomplete types during tracing can cause panics, which we intentionally
// suppress (returning ""), matching TypeScript's try/catch around typeToString.
if a.checker == nil {
return ""
}
if a.t.objectFlags&ObjectFlagsAnonymous != 0 ||
a.t.flags&(TypeFlagsLiteral|TypeFlagsTemplateLiteral|TypeFlagsUnion|TypeFlagsIntersection) != 0 {
defer func() {
_ = recover()
}()
return a.checker.TypeToString(a.t)
}
return ""
}
func wrapType(t *Type) tracing.TracedType {
if t == nil {
return nil
}
return &tracedTypeAdapter{t: t, checker: t.checker}
}
func wrapTypes(types []*Type) []tracing.TracedType {
if len(types) == 0 {
return nil
}
result := make([]tracing.TracedType, len(types))
for i, t := range types {
result[i] = wrapType(t)
}
return result
}

View File

@@ -0,0 +1,69 @@
package checker
import (
"io/fs"
"testing"
"testing/fstest"
"github.com/microsoft/typescript-go/internal/json"
"github.com/microsoft/typescript-go/internal/tracing"
"github.com/microsoft/typescript-go/internal/vfs/vfstest"
"gotest.tools/v3/assert"
)
func TestTracerPushPreservesEndArgMutations(t *testing.T) {
t.Parallel()
fsys := vfstest.FromMap(fstest.MapFS{
"/trace": &fstest.MapFile{Mode: fs.ModeDir},
}, true)
tr, err := tracing.StartTracing(fsys, "/trace", "", true /*deterministic*/)
assert.NilError(t, err)
args := map[string]any{"id": 1}
tracer := NewTracer(tr, 7)
pop := tracer.Push(tracing.PhaseCheckTypes, "getVariancesWorker", args, true)
_, hasCheckerID := args["checkerId"]
assert.Assert(t, !hasCheckerID)
args["variances"] = []string{"out"}
pop()
_, hasCheckerID = args["checkerId"]
assert.Assert(t, !hasCheckerID)
assert.NilError(t, tr.StopTracing())
traceText, ok := fsys.ReadFile("/trace/trace.json")
assert.Assert(t, ok)
var events []testTraceEvent
assert.NilError(t, json.Unmarshal([]byte(traceText), &events))
beginEvent := findTestTraceEvent(t, events, "B", "getVariancesWorker")
assert.Equal(t, beginEvent.Args["checkerId"], float64(7))
assert.Equal(t, beginEvent.Args["variances"], nil)
endEvent := findTestTraceEvent(t, events, "E", "getVariancesWorker")
assert.Equal(t, endEvent.Args["checkerId"], float64(7))
variances, ok := endEvent.Args["variances"].([]any)
assert.Assert(t, ok)
assert.DeepEqual(t, variances, []any{"out"})
}
type testTraceEvent struct {
PH string `json:"ph"`
Name string `json:"name"`
Args map[string]any `json:"args"`
}
func findTestTraceEvent(t *testing.T, events []testTraceEvent, phase string, name string) testTraceEvent {
t.Helper()
for _, event := range events {
if event.PH == phase && event.Name == name {
return event
}
}
t.Fatalf("failed to find %s event %q", phase, name)
return testTraceEvent{}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff