vendor tsgo
This commit is contained in:
31926
tools/tsgo/internal/checker/checker.go
Normal file
31926
tools/tsgo/internal/checker/checker.go
Normal file
File diff suppressed because it is too large
Load Diff
84
tools/tsgo/internal/checker/checker_test.go
Normal file
84
tools/tsgo/internal/checker/checker_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
1322
tools/tsgo/internal/checker/emitresolver.go
Normal file
1322
tools/tsgo/internal/checker/emitresolver.go
Normal file
File diff suppressed because it is too large
Load Diff
359
tools/tsgo/internal/checker/exports.go
Normal file
359
tools/tsgo/internal/checker/exports.go
Normal 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)
|
||||
}
|
||||
2734
tools/tsgo/internal/checker/flow.go
Normal file
2734
tools/tsgo/internal/checker/flow.go
Normal file
File diff suppressed because it is too large
Load Diff
2202
tools/tsgo/internal/checker/grammarchecks.go
Normal file
2202
tools/tsgo/internal/checker/grammarchecks.go
Normal file
File diff suppressed because it is too large
Load Diff
1651
tools/tsgo/internal/checker/inference.go
Normal file
1651
tools/tsgo/internal/checker/inference.go
Normal file
File diff suppressed because it is too large
Load Diff
100
tools/tsgo/internal/checker/jsdoc.go
Normal file
100
tools/tsgo/internal/checker/jsdoc.go
Normal 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
|
||||
}
|
||||
1482
tools/tsgo/internal/checker/jsx.go
Normal file
1482
tools/tsgo/internal/checker/jsx.go
Normal file
File diff suppressed because it is too large
Load Diff
315
tools/tsgo/internal/checker/mapper.go
Normal file
315
tools/tsgo/internal/checker/mapper.go
Normal 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
|
||||
}
|
||||
302
tools/tsgo/internal/checker/nodebuilder.go
Normal file
302
tools/tsgo/internal/checker/nodebuilder.go
Normal 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
|
||||
}
|
||||
597
tools/tsgo/internal/checker/nodebuilder_hover.go
Normal file
597
tools/tsgo/internal/checker/nodebuilder_hover.go
Normal 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())
|
||||
}
|
||||
3585
tools/tsgo/internal/checker/nodebuilderimpl.go
Normal file
3585
tools/tsgo/internal/checker/nodebuilderimpl.go
Normal file
File diff suppressed because it is too large
Load Diff
251
tools/tsgo/internal/checker/nodebuilderscopes.go
Normal file
251
tools/tsgo/internal/checker/nodebuilderscopes.go
Normal 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
|
||||
}
|
||||
}
|
||||
900
tools/tsgo/internal/checker/nodecopy.go
Normal file
900
tools/tsgo/internal/checker/nodecopy.go
Normal 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
|
||||
}
|
||||
486
tools/tsgo/internal/checker/printer.go
Normal file
486
tools/tsgo/internal/checker/printer.go
Normal 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)
|
||||
}
|
||||
765
tools/tsgo/internal/checker/pseudotypenodebuilder.go
Normal file
765
tools/tsgo/internal/checker/pseudotypenodebuilder.go
Normal 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
|
||||
}
|
||||
}
|
||||
5006
tools/tsgo/internal/checker/relater.go
Normal file
5006
tools/tsgo/internal/checker/relater.go
Normal file
File diff suppressed because it is too large
Load Diff
1140
tools/tsgo/internal/checker/services.go
Normal file
1140
tools/tsgo/internal/checker/services.go
Normal file
File diff suppressed because it is too large
Load Diff
25
tools/tsgo/internal/checker/stringer_generated.go
Normal file
25
tools/tsgo/internal/checker/stringer_generated.go
Normal 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]]
|
||||
}
|
||||
876
tools/tsgo/internal/checker/symbolaccessibility.go
Normal file
876
tools/tsgo/internal/checker/symbolaccessibility.go
Normal 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,
|
||||
}
|
||||
}
|
||||
129
tools/tsgo/internal/checker/symboltracker.go
Normal file
129
tools/tsgo/internal/checker/symboltracker.go
Normal 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()
|
||||
}
|
||||
366
tools/tsgo/internal/checker/tracer.go
Normal file
366
tools/tsgo/internal/checker/tracer.go
Normal 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
|
||||
}
|
||||
69
tools/tsgo/internal/checker/tracer_test.go
Normal file
69
tools/tsgo/internal/checker/tracer_test.go
Normal 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{}
|
||||
}
|
||||
1459
tools/tsgo/internal/checker/types.go
Normal file
1459
tools/tsgo/internal/checker/types.go
Normal file
File diff suppressed because it is too large
Load Diff
1844
tools/tsgo/internal/checker/utilities.go
Normal file
1844
tools/tsgo/internal/checker/utilities.go
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user