diff --git a/microsoft/typescript-go b/microsoft/typescript-go index 0a7c6b47e..879968116 160000 --- a/microsoft/typescript-go +++ b/microsoft/typescript-go @@ -1 +1 @@ -Subproject commit 0a7c6b47e6163892880e0e5814e519c435445f11 +Subproject commit 879968116c1dc9110249dd7e74ba47558e68621b diff --git a/pkg/checker/checker.go b/pkg/checker/checker.go index a50ea0f08..7c4e02155 100644 --- a/pkg/checker/checker.go +++ b/pkg/checker/checker.go @@ -645,6 +645,7 @@ type Checker struct { errorTypes map[CacheHashKey]*Type moduleSymbols map[*ast.Node]*ast.Symbol globalThisSymbol *ast.Symbol + symbolTableAliasCache map[symbolTableID][]*ast.Symbol resolveName func(location *ast.Node, name string, meaning ast.SymbolFlags, nameNotFoundMessage *diagnostics.Message, isUse bool, excludeGlobals bool) *ast.Symbol resolveNameForSymbolSuggestion func(location *ast.Node, name string, meaning ast.SymbolFlags, nameNotFoundMessage *diagnostics.Message, isUse bool, excludeGlobals bool) *ast.Symbol tupleTypes map[CacheHashKey]*Type @@ -17984,7 +17985,7 @@ func (c *Checker) getAssignmentDeclarationInitializerType(node *ast.Node) *Type default: t = c.checkExpressionForMutableLocation(node.AsBinaryExpression().Right, CheckModeNormal) } - if c.isEmptyArrayLiteralType(t) { + if c.isEmptyArrayLiteralType(t) && !c.hasParentWithTypeAnnotation(node.Symbol()) { c.reportImplicitAny(node, c.anyArrayType, WideningKindNormal) return c.anyArrayType } @@ -17996,6 +17997,20 @@ func (c *Checker) getAssignmentDeclarationInitializerType(node *ast.Node) *Type return nil } +// Return true if the parent symbol of the given assignment declaration symbol has declaration with a type +// annotation. For example, returns true for the symbol associated with `f.a` below: +// +// const f: { (): void, a: string[] } = () => {}; +// f.a = []; +func (c *Checker) hasParentWithTypeAnnotation(symbol *ast.Symbol) bool { + if symbol.Parent != nil && symbol.Parent.ValueDeclaration != nil && ast.IsFunctionExpressionOrArrowFunction(symbol.Parent.ValueDeclaration) { + if possiblyAnnotatedSymbol := c.getSymbolOfNode(symbol.Parent.ValueDeclaration.Parent); possiblyAnnotatedSymbol != nil && possiblyAnnotatedSymbol.ValueDeclaration != nil { + return possiblyAnnotatedSymbol.ValueDeclaration.Type() != nil + } + } + return false +} + func (c *Checker) containsSameNamedThisProperty(thisProperty *ast.Node, expression *ast.Node) bool { var visit func(node *ast.Node) bool visit = func(node *ast.Node) bool { diff --git a/pkg/checker/emitresolver.go b/pkg/checker/emitresolver.go index 9ca8e0285..82dd15ab1 100644 --- a/pkg/checker/emitresolver.go +++ b/pkg/checker/emitresolver.go @@ -984,6 +984,15 @@ func (r *EmitResolver) CreateLiteralConstValue(emitContext *printer.EmitContext, case string: return emitContext.Factory.NewStringLiteral(value, ast.TokenFlagsNone) case jsnum.Number: + if value.IsInf() { + if value > 0 { + return emitContext.Factory.NewIdentifier("Infinity") + } + return emitContext.Factory.NewPrefixUnaryExpression(ast.KindMinusToken, emitContext.Factory.NewIdentifier("Infinity")) + } + if value.IsNaN() { + return emitContext.Factory.NewIdentifier("NaN") + } if value.Abs() != value { // negative return emitContext.Factory.NewPrefixUnaryExpression( diff --git a/pkg/checker/nodebuilderimpl.go b/pkg/checker/nodebuilderimpl.go index ca4c41833..a0b79d066 100644 --- a/pkg/checker/nodebuilderimpl.go +++ b/pkg/checker/nodebuilderimpl.go @@ -2693,7 +2693,17 @@ func (b *NodeBuilderImpl) createAnonymousTypeNodeEx(t *Type, forceClassExpansion // The problem is each constituent of the intersection will be associated with typeof Err // And when extracting a type for typeof ErrImpl from typeof Err does not make sense. if ast.IsTypeQueryNode(existing) && b.getTypeFromTypeNode(existing, false) == t { + // Guard against unbounded recursion when the existing typeof node fails to be reused + // (e.g. its entity name isn't accessible from this scope) and the recovery boundary's + // fallback re-enters typeToTypeNode with the very same instantiation type, which would + // in turn try to reuse the same node again. Mark the type as visited around the reuse + // attempt so the inner recursion bottoms out via the visitedTypes guard below. + if b.ctx.visitedTypes.Has(typeId) { + return b.createElidedInformationPlaceholder() + } + b.ctx.visitedTypes.Add(typeId) typeNode := b.tryReuseExistingNonParameterTypeNode(existing, t, nil, nil) + b.ctx.visitedTypes.Delete(typeId) if typeNode != nil { return typeNode } diff --git a/pkg/checker/pseudotypenodebuilder.go b/pkg/checker/pseudotypenodebuilder.go index b932a18df..3a267ed4c 100644 --- a/pkg/checker/pseudotypenodebuilder.go +++ b/pkg/checker/pseudotypenodebuilder.go @@ -195,7 +195,7 @@ func (b *NodeBuilderImpl) pseudoTypeToNode(t *pseudochecker.PseudoType) *ast.Nod // 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) + isConst := b.ch.isConstContext(elements[0].Name.Parent.Parent) newElements := make([]*ast.Node, 0, len(elements)) for _, e := range elements { @@ -203,11 +203,11 @@ func (b *NodeBuilderImpl) pseudoTypeToNode(t *pseudochecker.PseudoType) *ast.Nod 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) - defer cleanup() + cleanup = b.enterNewScope(e.Signature(), expandedParams, signature.typeParameters, signature.parameters, signature.mapper) } var newProp *ast.Node switch e.Kind { @@ -279,6 +279,9 @@ func (b *NodeBuilderImpl) pseudoTypeToNode(t *pseudochecker.PseudoType) *ast.Nod b.e.SetCommentRange(newProp, e.Name.Parent.Loc) } newElements = append(newElements, newProp) + if cleanup != nil { + cleanup() + } } result := b.f.NewTypeLiteralNode(b.f.NewNodeList(newElements)) if b.ctx.flags&nodebuilder.FlagsMultilineObjectLiterals == 0 { diff --git a/pkg/checker/symbolaccessibility.go b/pkg/checker/symbolaccessibility.go index 49edd0ebb..75b7c2ed5 100644 --- a/pkg/checker/symbolaccessibility.go +++ b/pkg/checker/symbolaccessibility.go @@ -397,15 +397,21 @@ type accessibleSymbolChainContext struct { } // symbolTableID uniquely identifies a symbol table by encoding its source. -// The high 2 bits encode the kind (locals, exports, members, globals), -// and the remaining bits encode the NodeId or SymbolId of the 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 << 62 + 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 { @@ -416,6 +422,14 @@ 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)) } @@ -478,19 +492,54 @@ func (c *Checker) getAccessibleSymbolChainFromSymbolTable(ctx accessibleSymbolCh } visitedSymbolTables[tableId] = struct{}{} - res := c.trySymbolTable(ctx, t, tableId == stKindGlobals, ignoreQualification, isLocalNameLookup) + 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, - isGlobals bool, + 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) { @@ -498,11 +547,21 @@ func (c *Checker) trySymbolTable( } var candidateChains [][]*ast.Symbol - // collect all possible chains to sort them and return the shortest/best - for _, symbolFromSymbolTable := range symbols { + + // 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.Flags&ast.SymbolFlagsAlias != 0 && - symbolFromSymbolTable.Name != ast.InternalSymbolNameExportEquals && + 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 @@ -518,11 +577,6 @@ func (c *Checker) trySymbolTable( candidateChains = append(candidateChains, candidate) } } - if symbolFromSymbolTable.Name == ctx.symbol.Name && symbolFromSymbolTable.ExportSymbol != nil { - if c.isAccessible(ctx, c.getMergedSymbol(symbolFromSymbolTable.ExportSymbol) /*resolvedAliasSymbol*/, nil, ignoreQualification) { - candidateChains = append(candidateChains, []*ast.Symbol{ctx.symbol}) - } - } } if len(candidateChains) > 0 { @@ -579,7 +633,7 @@ func (c *Checker) getCandidateListForSymbol( if candidateTable == nil { return nil } - candidateTableId := symbolTableIDFromExports(resolvedImportedSymbol) + candidateTableId := symbolTableIDFromResolvedExports(resolvedImportedSymbol) accessibleSymbolsFromExports := c.getAccessibleSymbolChainFromSymbolTable(ctx, candidateTable, candidateTableId /*ignoreQualification*/, true, false) if len(accessibleSymbolsFromExports) == 0 { return nil diff --git a/pkg/fourslash/tests/autoImportDefaultPascalCase_test.go b/pkg/fourslash/tests/autoImportDefaultPascalCase_test.go new file mode 100644 index 000000000..5150a87a3 --- /dev/null +++ b/pkg/fourslash/tests/autoImportDefaultPascalCase_test.go @@ -0,0 +1,235 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/core" + "github.com/buke/typescript-go-internal/pkg/fourslash" + . "github.com/buke/typescript-go-internal/pkg/fourslash/tests/util" + "github.com/buke/typescript-go-internal/pkg/ls/lsutil" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestAutoImportDefaultPascalCase(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `// @jsx: react +// @module: esnext +// @moduleResolution: bundler + +// @Filename: /src/components/ChargerHeader.tsx +export default function ChargerHeader() { + return null; +} + +// @Filename: /src/screens/SomeScreen.tsx +export function SomeScreen() { + return {}; +// @Filename: /project/src/app/index.ts +helper/**/` + + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + f.Configure(t, lsutil.UserPreferences{ + IncludeCompletionsForModuleExports: core.TSTrue, + IncludeCompletionsForImportStatements: core.TSTrue, + ImportModuleSpecifierPreference: modulespecifiers.ImportModuleSpecifierPreferenceProjectRelative, + }) + f.VerifyCompletions(t, "", &fourslash.CompletionsExpectedList{ + ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{ + CommitCharacters: &DefaultCommitCharacters, + EditRange: Ignored, + }, + Items: &fourslash.CompletionsExpectedItems{ + Includes: []fourslash.CompletionsExpectedItem{"helperFunc"}, + }, + }) + f.BaselineAutoImportsCompletions(t, []string{""}) +} + func TestImportModuleSpecifierPreferenceNonRelative(t *testing.T) { t.Parallel() defer testutil.RecoverAndFail(t, "Panic on fourslash test") diff --git a/pkg/ls/autoimport/extract.go b/pkg/ls/autoimport/extract.go index ba27b8b60..e7271e8f3 100644 --- a/pkg/ls/autoimport/extract.go +++ b/pkg/ls/autoimport/extract.go @@ -331,13 +331,14 @@ func (e *symbolExtractor) createExport(symbol *ast.Symbol, moduleID ModuleID, mo namedSymbol = s } export.localName = getDefaultLikeExportNameFromDeclaration(namedSymbol) - if isUnusableName(export.localName) { - export.localName = lsutil.ModuleSpecifierToValidIdentifier(string(export.Target.ModuleID), false) - } - } else { - export.localName = lsutil.ModuleSpecifierToValidIdentifier(string(moduleID), false) } } + if isUnusableName(export.localName) { + // Last resort: derive identifier from the file name. Use FileName() (original + // casing) rather than ModuleID/Path() which is lowercased on case-insensitive + // file systems, losing PascalCase. + export.localName = lsutil.ModuleSpecifierToValidIdentifier(fileNameForDefaultExportName(targetSymbol, moduleFileName, moduleID), false) + } } if isUnusableName(export.Name()) { @@ -441,3 +442,20 @@ func isUnusableName(name string) bool { name == ast.InternalSymbolNameDefault || name == ast.InternalSymbolNameExportEquals } + +// fileNameForDefaultExportName returns the best file name to use when deriving +// a fallback identifier for a default-like export. It prefers the target symbol's +// source file (closest to the export origin), falls back to the module's original +// file name, and uses the lowercased moduleID only for ambient modules where no +// original file name is available. +func fileNameForDefaultExportName(targetSymbol *ast.Symbol, moduleFileName string, moduleID ModuleID) string { + if targetSymbol != nil && len(targetSymbol.Declarations) > 0 { + if fn := ast.GetSourceFileOfNode(targetSymbol.Declarations[0]).FileName(); fn != "" { + return fn + } + } + if moduleFileName != "" { + return moduleFileName + } + return string(moduleID) +} diff --git a/pkg/modulespecifiers/specifiers.go b/pkg/modulespecifiers/specifiers.go index 6384db66a..a7cd14665 100644 --- a/pkg/modulespecifiers/specifiers.go +++ b/pkg/modulespecifiers/specifiers.go @@ -582,15 +582,15 @@ func getLocalModuleSpecifier( if preferences.relativePreference == RelativePreferenceExternalNonRelative && !tspath.PathIsRelative(maybeNonRelative) { var projectDirectory tspath.Path if len(compilerOptions.ConfigFilePath) > 0 { - projectDirectory = tspath.ToPath(compilerOptions.ConfigFilePath, host.GetCurrentDirectory(), host.UseCaseSensitiveFileNames()) + projectDirectory = tspath.ToPath(tspath.GetDirectoryPath(compilerOptions.ConfigFilePath), host.GetCurrentDirectory(), host.UseCaseSensitiveFileNames()) } else { projectDirectory = tspath.ToPath(host.GetCurrentDirectory(), host.GetCurrentDirectory(), host.UseCaseSensitiveFileNames()) } canonicalSourceDirectory := tspath.ToPath(sourceDirectory, host.GetCurrentDirectory(), host.UseCaseSensitiveFileNames()) modulePath := tspath.ToPath(moduleFileName, string(projectDirectory), host.UseCaseSensitiveFileNames()) - sourceIsInternal := strings.HasPrefix(string(canonicalSourceDirectory), string(projectDirectory)) - targetIsInternal := strings.HasPrefix(string(modulePath), string(projectDirectory)) + sourceIsInternal := projectDirectory.ContainsPath(canonicalSourceDirectory) + targetIsInternal := projectDirectory.ContainsPath(modulePath) if sourceIsInternal && !targetIsInternal || !sourceIsInternal && targetIsInternal { // 1. The import path crosses the boundary of the tsconfig.json-containing directory. // @@ -621,9 +621,8 @@ func getLocalModuleSpecifier( // return maybeNonRelative } - if len(fromPackageJsonImports) > 0 { - return relativePath - } + + return relativePath } // Prefer a relative import over a baseUrl import if it has fewer components. diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go index c5e656694..83d9d7e21 100644 --- a/pkg/printer/printer.go +++ b/pkg/printer/printer.go @@ -5671,10 +5671,7 @@ func (p *Printer) emitDetachedComments(textRange core.TextRange) (result detache } } - if p.shouldWriteComment(comment) { - detachedComments = append(detachedComments, comment) - } - + detachedComments = append(detachedComments, comment) lastComment = comment } @@ -5687,11 +5684,21 @@ func (p *Printer) emitDetachedComments(textRange core.TextRange) (result detache if nodeLine >= lastCommentLine+2 { // Valid detachedComments - if len(leadingComments) > 0 && p.shouldEmitNewLineBeforeLeadingCommentOfPosition(textRange.Pos(), leadingComments[0].Pos()) { - p.writeLine() + // Filter to only comments that should be written (e.g., JSDoc-style in declaration emit) + var commentsToEmit []ast.CommentRange + for _, comment := range detachedComments { + if p.shouldWriteComment(comment) { + commentsToEmit = append(commentsToEmit, comment) + } } - p.emitComments(detachedComments, commentSeparatorAfter) + if len(commentsToEmit) > 0 { + if p.shouldEmitNewLineBeforeLeadingCommentOfPosition(textRange.Pos(), commentsToEmit[0].Pos()) { + p.writeLine() + } + + p.emitComments(commentsToEmit, commentSeparatorAfter) + } result = detachedCommentsInfo{nodePos: textRange.Pos(), detachedCommentEndPos: core.LastOrNil(detachedComments).End()} hasResult = true } diff --git a/pkg/printer/utilities.go b/pkg/printer/utilities.go index cde061c0a..2774b95ad 100644 --- a/pkg/printer/utilities.go +++ b/pkg/printer/utilities.go @@ -849,7 +849,7 @@ func IsRecognizedTripleSlashComment(text string, commentRange ast.CommentRange) func isJSDocLikeText(text string, comment ast.CommentRange) bool { return comment.Kind == ast.KindMultiLineCommentTrivia && - comment.Len() > 5 && + comment.Len() >= 5 && text[comment.Pos()+2] == '*' && text[comment.Pos()+3] != '/' } diff --git a/pkg/transformers/declarations/transform.go b/pkg/transformers/declarations/transform.go index bff2d3578..61bda0c2d 100644 --- a/pkg/transformers/declarations/transform.go +++ b/pkg/transformers/declarations/transform.go @@ -1834,7 +1834,15 @@ func (tx *DeclarationTransformer) transformEnumDeclaration(input *ast.EnumDeclar var newInitializer *ast.Node switch value := enumValue.Value.(type) { case jsnum.Number: - if value >= 0 { + if value.IsInf() { + if value > 0 { + newInitializer = tx.Factory().NewIdentifier("Infinity") + } else { + newInitializer = tx.Factory().NewPrefixUnaryExpression(ast.KindMinusToken, tx.Factory().NewIdentifier("Infinity")) + } + } else if value.IsNaN() { + newInitializer = tx.Factory().NewIdentifier("NaN") + } else if value >= 0 { newInitializer = tx.Factory().NewNumericLiteral(value.String(), ast.TokenFlagsNone) } else { newInitializer = tx.Factory().NewPrefixUnaryExpression( diff --git a/pkg/transformers/moduletransforms/commonjsmodule.go b/pkg/transformers/moduletransforms/commonjsmodule.go index e3a7cbf7d..a5a1770ba 100644 --- a/pkg/transformers/moduletransforms/commonjsmodule.go +++ b/pkg/transformers/moduletransforms/commonjsmodule.go @@ -1109,13 +1109,14 @@ func (tx *CommonJSModuleTransformer) transformInitializedVariable(node *ast.Vari } name := node.Name() if ast.IsBindingPattern(name) { - return transformers.FlattenDestructuringAssignment( - &tx.Transformer, - tx.Visitor().VisitNode(node.AsNode()), - false, /*needsValue*/ - transformers.FlattenLevelAll, - tx.createAllExportExpressions, - ) + // Convert the binding pattern into an equivalent assignment expression and visit it + // as a destructuring assignment. This preserves native destructuring (and therefore + // iterator semantics for array patterns) whenever each leaf identifier can be + // substituted to an export reference. Only when the destructuring would assign to + // re-aliased or multi-exported names (where native destructuring cannot update all + // targets) does `visitDestructuringAssignment` fall back to flattening. + assignment := transformers.ConvertVariableDeclarationToAssignmentExpression(tx.EmitContext(), node) + return tx.visitDestructuringAssignment(assignment.AsBinaryExpression(), true /*valueIsDiscarded*/) } propertyAccess := tx.Factory().NewPropertyAccessExpression( tx.Factory().NewIdentifier("exports"), @@ -1452,11 +1453,22 @@ func (tx *CommonJSModuleTransformer) destructuringNeedsFlattening(node *ast.Node } } else if ast.IsIdentifier(node) { exportedNames := tx.getExports(node) - threshold := 0 if transformers.IsExportName(tx.EmitContext(), node) { - threshold = 1 + // The identifier is already wrapped to be an export reference; tolerate up to one + // matching export. + return len(exportedNames) > 1 + } + if len(exportedNames) == 0 { + return false } - return len(exportedNames) > threshold + // A single direct export whose export name matches the identifier text can be handled + // natively: substitution will rewrite the identifier to `exports.X`, so no flattening + // is needed. Re-aliased exports (where the export name differs from the local name) or + // multi-exported names cannot be expressed natively in a destructuring assignment. + if len(exportedNames) == 1 && tx.isDirectExport(node) && exportedNames[0].Text() == node.Text() { + return false + } + return true } return false } diff --git a/testdata/baselines/reference/compiler/declarationEmitConstObjectLiteralGenericMethod1.js b/testdata/baselines/reference/compiler/declarationEmitConstObjectLiteralGenericMethod1.js index 5c4eb4225..fbb6d45b5 100644 --- a/testdata/baselines/reference/compiler/declarationEmitConstObjectLiteralGenericMethod1.js +++ b/testdata/baselines/reference/compiler/declarationEmitConstObjectLiteralGenericMethod1.js @@ -24,6 +24,6 @@ export const obj1 = { //// [declarationEmitConstObjectLiteralGenericMethod1.d.ts] export declare const obj1: { - id(value: T): T; - pair(left: T_1, right: T_1): T_1[]; + readonly id: (value: T) => T; + readonly pair: (left: T, right: T) => T[]; }; diff --git a/testdata/baselines/reference/compiler/declarationEmitEnumNaN.js b/testdata/baselines/reference/compiler/declarationEmitEnumNaN.js new file mode 100644 index 000000000..428686320 --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitEnumNaN.js @@ -0,0 +1,20 @@ +//// [tests/cases/compiler/declarationEmitEnumNaN.ts] //// + +//// [declarationEmitEnumNaN.ts] +export declare enum E { + A = -NaN, + B = NaN, + C = Infinity, + D = -Infinity, +} + + + + +//// [declarationEmitEnumNaN.d.ts] +export declare enum E { + A = NaN, + B = NaN, + C = Infinity, + D = -Infinity +} diff --git a/testdata/baselines/reference/compiler/declarationEmitEnumNaN.symbols b/testdata/baselines/reference/compiler/declarationEmitEnumNaN.symbols new file mode 100644 index 000000000..bc0cf90c4 --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitEnumNaN.symbols @@ -0,0 +1,23 @@ +//// [tests/cases/compiler/declarationEmitEnumNaN.ts] //// + +=== declarationEmitEnumNaN.ts === +export declare enum E { +>E : Symbol(E, Decl(declarationEmitEnumNaN.ts, 0, 0)) + + A = -NaN, +>A : Symbol(E.A, Decl(declarationEmitEnumNaN.ts, 0, 23)) +>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --)) + + B = NaN, +>B : Symbol(E.B, Decl(declarationEmitEnumNaN.ts, 1, 13)) +>NaN : Symbol(NaN, Decl(lib.es5.d.ts, --, --)) + + C = Infinity, +>C : Symbol(E.C, Decl(declarationEmitEnumNaN.ts, 2, 12)) +>Infinity : Symbol(Infinity, Decl(lib.es5.d.ts, --, --)) + + D = -Infinity, +>D : Symbol(E.D, Decl(declarationEmitEnumNaN.ts, 3, 17)) +>Infinity : Symbol(Infinity, Decl(lib.es5.d.ts, --, --)) +} + diff --git a/testdata/baselines/reference/compiler/declarationEmitEnumNaN.types b/testdata/baselines/reference/compiler/declarationEmitEnumNaN.types new file mode 100644 index 000000000..cf0e5db31 --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitEnumNaN.types @@ -0,0 +1,25 @@ +//// [tests/cases/compiler/declarationEmitEnumNaN.ts] //// + +=== declarationEmitEnumNaN.ts === +export declare enum E { +>E : E + + A = -NaN, +>A : E.A +>-NaN : number +>NaN : number + + B = NaN, +>B : E.A +>NaN : number + + C = Infinity, +>C : E.C +>Infinity : number + + D = -Infinity, +>D : E.D +>-Infinity : number +>Infinity : number +} + diff --git a/testdata/baselines/reference/compiler/declarationEmitObjectLiteralMethodGenericNoSuffix.js b/testdata/baselines/reference/compiler/declarationEmitObjectLiteralMethodGenericNoSuffix.js new file mode 100644 index 000000000..64c510f27 --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitObjectLiteralMethodGenericNoSuffix.js @@ -0,0 +1,37 @@ +//// [tests/cases/compiler/declarationEmitObjectLiteralMethodGenericNoSuffix.ts] //// + +//// [declarationEmitObjectLiteralMethodGenericNoSuffix.ts] +export const o = { + foo(): void { }, + bar(): void { }, +}; + +export const o2 = { + foo(value: T): T { return value; }, + bar(value: T): T { return value; }, + baz(a: T, b: U): [T, U] { return [a, b]; }, +}; + + +//// [declarationEmitObjectLiteralMethodGenericNoSuffix.js] +export const o = { + foo() { }, + bar() { }, +}; +export const o2 = { + foo(value) { return value; }, + bar(value) { return value; }, + baz(a, b) { return [a, b]; }, +}; + + +//// [declarationEmitObjectLiteralMethodGenericNoSuffix.d.ts] +export declare const o: { + foo(): void; + bar(): void; +}; +export declare const o2: { + foo(value: T): T; + bar(value: T): T; + baz(a: T, b: U): [T, U]; +}; diff --git a/testdata/baselines/reference/compiler/declarationEmitObjectLiteralMethodGenericNoSuffix.symbols b/testdata/baselines/reference/compiler/declarationEmitObjectLiteralMethodGenericNoSuffix.symbols new file mode 100644 index 000000000..a0688c687 --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitObjectLiteralMethodGenericNoSuffix.symbols @@ -0,0 +1,50 @@ +//// [tests/cases/compiler/declarationEmitObjectLiteralMethodGenericNoSuffix.ts] //// + +=== declarationEmitObjectLiteralMethodGenericNoSuffix.ts === +export const o = { +>o : Symbol(o, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 0, 12)) + + foo(): void { }, +>foo : Symbol(foo, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 0, 18)) +>M : Symbol(M, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 1, 8)) + + bar(): void { }, +>bar : Symbol(bar, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 1, 38)) +>M : Symbol(M, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 2, 8)) + +}; + +export const o2 = { +>o2 : Symbol(o2, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 5, 12)) + + foo(value: T): T { return value; }, +>foo : Symbol(foo, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 5, 19)) +>T : Symbol(T, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 6, 8)) +>value : Symbol(value, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 6, 11)) +>T : Symbol(T, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 6, 8)) +>T : Symbol(T, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 6, 8)) +>value : Symbol(value, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 6, 11)) + + bar(value: T): T { return value; }, +>bar : Symbol(bar, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 6, 42)) +>T : Symbol(T, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 7, 8)) +>value : Symbol(value, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 7, 11)) +>T : Symbol(T, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 7, 8)) +>T : Symbol(T, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 7, 8)) +>value : Symbol(value, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 7, 11)) + + baz(a: T, b: U): [T, U] { return [a, b]; }, +>baz : Symbol(baz, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 7, 42)) +>T : Symbol(T, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 8, 8)) +>U : Symbol(U, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 8, 10)) +>a : Symbol(a, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 8, 14)) +>T : Symbol(T, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 8, 8)) +>b : Symbol(b, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 8, 19)) +>U : Symbol(U, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 8, 10)) +>T : Symbol(T, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 8, 8)) +>U : Symbol(U, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 8, 10)) +>a : Symbol(a, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 8, 14)) +>b : Symbol(b, Decl(declarationEmitObjectLiteralMethodGenericNoSuffix.ts, 8, 19)) + +}; + diff --git a/testdata/baselines/reference/compiler/declarationEmitObjectLiteralMethodGenericNoSuffix.types b/testdata/baselines/reference/compiler/declarationEmitObjectLiteralMethodGenericNoSuffix.types new file mode 100644 index 000000000..6c7e6c814 --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitObjectLiteralMethodGenericNoSuffix.types @@ -0,0 +1,39 @@ +//// [tests/cases/compiler/declarationEmitObjectLiteralMethodGenericNoSuffix.ts] //// + +=== declarationEmitObjectLiteralMethodGenericNoSuffix.ts === +export const o = { +>o : { foo(): void; bar(): void; } +>{ foo(): void { }, bar(): void { },} : { foo(): void; bar(): void; } + + foo(): void { }, +>foo : () => void + + bar(): void { }, +>bar : () => void + +}; + +export const o2 = { +>o2 : { foo(value: T): T; bar(value: T): T; baz(a: T, b: U): [T, U]; } +>{ foo(value: T): T { return value; }, bar(value: T): T { return value; }, baz(a: T, b: U): [T, U] { return [a, b]; },} : { foo(value: T): T; bar(value: T): T; baz(a: T, b: U): [T, U]; } + + foo(value: T): T { return value; }, +>foo : (value: T) => T +>value : T +>value : T + + bar(value: T): T { return value; }, +>bar : (value: T) => T +>value : T +>value : T + + baz(a: T, b: U): [T, U] { return [a, b]; }, +>baz : (a: T, b: U) => [T, U] +>a : T +>b : U +>[a, b] : [T, U] +>a : T +>b : U + +}; + diff --git a/testdata/baselines/reference/compiler/declarationEmitReadonlyAsConst.js b/testdata/baselines/reference/compiler/declarationEmitReadonlyAsConst.js new file mode 100644 index 000000000..1b6b25352 --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitReadonlyAsConst.js @@ -0,0 +1,31 @@ +//// [tests/cases/compiler/declarationEmitReadonlyAsConst.ts] //// + +//// [declarationEmitReadonlyAsConst.ts] +export const value = { + method(): string { + return "a"; + }, + prop: { + nested: 1, + }, +} as const; + + +//// [declarationEmitReadonlyAsConst.js] +export const value = { + method() { + return "a"; + }, + prop: { + nested: 1, + }, +}; + + +//// [declarationEmitReadonlyAsConst.d.ts] +export declare const value: { + readonly method: () => string; + readonly prop: { + readonly nested: 1; + }; +}; diff --git a/testdata/baselines/reference/compiler/declarationEmitReadonlyAsConst.symbols b/testdata/baselines/reference/compiler/declarationEmitReadonlyAsConst.symbols new file mode 100644 index 000000000..ff3e36844 --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitReadonlyAsConst.symbols @@ -0,0 +1,21 @@ +//// [tests/cases/compiler/declarationEmitReadonlyAsConst.ts] //// + +=== declarationEmitReadonlyAsConst.ts === +export const value = { +>value : Symbol(value, Decl(declarationEmitReadonlyAsConst.ts, 0, 12)) + + method(): string { +>method : Symbol(method, Decl(declarationEmitReadonlyAsConst.ts, 0, 22)) + + return "a"; + }, + prop: { +>prop : Symbol(prop, Decl(declarationEmitReadonlyAsConst.ts, 3, 4)) + + nested: 1, +>nested : Symbol(nested, Decl(declarationEmitReadonlyAsConst.ts, 4, 9)) + + }, +} as const; +>const : Symbol(const) + diff --git a/testdata/baselines/reference/compiler/declarationEmitReadonlyAsConst.types b/testdata/baselines/reference/compiler/declarationEmitReadonlyAsConst.types new file mode 100644 index 000000000..286decdf9 --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitReadonlyAsConst.types @@ -0,0 +1,26 @@ +//// [tests/cases/compiler/declarationEmitReadonlyAsConst.ts] //// + +=== declarationEmitReadonlyAsConst.ts === +export const value = { +>value : { readonly method: () => string; readonly prop: { readonly nested: 1; }; } +>{ method(): string { return "a"; }, prop: { nested: 1, },} as const : { readonly method: () => string; readonly prop: { readonly nested: 1; }; } +>{ method(): string { return "a"; }, prop: { nested: 1, },} : { readonly method: () => string; readonly prop: { readonly nested: 1; }; } + + method(): string { +>method : () => string + + return "a"; +>"a" : "a" + + }, + prop: { +>prop : { readonly nested: 1; } +>{ nested: 1, } : { readonly nested: 1; } + + nested: 1, +>nested : 1 +>1 : 1 + + }, +} as const; + diff --git a/testdata/baselines/reference/compiler/emptyJSDocComment.js b/testdata/baselines/reference/compiler/emptyJSDocComment.js new file mode 100644 index 000000000..3893436b2 --- /dev/null +++ b/testdata/baselines/reference/compiler/emptyJSDocComment.js @@ -0,0 +1,15 @@ +//// [tests/cases/compiler/emptyJSDocComment.ts] //// + +//// [emptyJSDocComment.ts] +/***/ +export const foo = 1; + + +//// [emptyJSDocComment.js] +/***/ +export const foo = 1; + + +//// [emptyJSDocComment.d.ts] +/***/ +export declare const foo = 1; diff --git a/testdata/baselines/reference/compiler/emptyJSDocComment.symbols b/testdata/baselines/reference/compiler/emptyJSDocComment.symbols new file mode 100644 index 000000000..f95120cd1 --- /dev/null +++ b/testdata/baselines/reference/compiler/emptyJSDocComment.symbols @@ -0,0 +1,7 @@ +//// [tests/cases/compiler/emptyJSDocComment.ts] //// + +=== emptyJSDocComment.ts === +/***/ +export const foo = 1; +>foo : Symbol(foo, Decl(emptyJSDocComment.ts, 1, 12)) + diff --git a/testdata/baselines/reference/compiler/emptyJSDocComment.types b/testdata/baselines/reference/compiler/emptyJSDocComment.types new file mode 100644 index 000000000..e3d8c5d27 --- /dev/null +++ b/testdata/baselines/reference/compiler/emptyJSDocComment.types @@ -0,0 +1,8 @@ +//// [tests/cases/compiler/emptyJSDocComment.ts] //// + +=== emptyJSDocComment.ts === +/***/ +export const foo = 1; +>foo : 1 +>1 : 1 + diff --git a/testdata/baselines/reference/compiler/expandoPropertyEmptyArrayWidening.errors.txt b/testdata/baselines/reference/compiler/expandoPropertyEmptyArrayWidening.errors.txt new file mode 100644 index 000000000..d158611b7 --- /dev/null +++ b/testdata/baselines/reference/compiler/expandoPropertyEmptyArrayWidening.errors.txt @@ -0,0 +1,35 @@ +expandoPropertyEmptyArrayWidening.ts(10,1): error TS7008: Member 'a' implicitly has an 'any[]' type. +expandoPropertyEmptyArrayWidening.ts(13,1): error TS7008: Member 'a' implicitly has an 'any[]' type. +expandoPropertyEmptyArrayWidening.ts(16,1): error TS7008: Member 'a' implicitly has an 'any[]' type. + + +==== expandoPropertyEmptyArrayWidening.ts (3 errors) ==== + // https://github.com/microsoft/typescript-go/issues/3976 + + const example: { + (): void; + items?: string[]; + } = () => undefined; + example.items = []; + + function f1() {} + f1.a = []; // Implicit any error + ~~~~~~~~~ +!!! error TS7008: Member 'a' implicitly has an 'any[]' type. + + const f2 = function() {}; + f2.a = []; // Implicit any error + ~~~~~~~~~ +!!! error TS7008: Member 'a' implicitly has an 'any[]' type. + + const f3 = () => {}; + f3.a = []; // Implicit any error + ~~~~~~~~~ +!!! error TS7008: Member 'a' implicitly has an 'any[]' type. + + const f4: { (): void, a: string[] } = () => {}; + f4.a = []; + + const f5: { (): void, a: string[] } = function() {}; + f5.a = []; + \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/expandoPropertyEmptyArrayWidening.symbols b/testdata/baselines/reference/compiler/expandoPropertyEmptyArrayWidening.symbols new file mode 100644 index 000000000..25a1c7d0c --- /dev/null +++ b/testdata/baselines/reference/compiler/expandoPropertyEmptyArrayWidening.symbols @@ -0,0 +1,62 @@ +//// [tests/cases/compiler/expandoPropertyEmptyArrayWidening.ts] //// + +=== expandoPropertyEmptyArrayWidening.ts === +// https://github.com/microsoft/typescript-go/issues/3976 + +const example: { +>example : Symbol(example, Decl(expandoPropertyEmptyArrayWidening.ts, 2, 5)) + + (): void; + items?: string[]; +>items : Symbol(items, Decl(expandoPropertyEmptyArrayWidening.ts, 3, 11)) + +} = () => undefined; +>undefined : Symbol(undefined) + +example.items = []; +>example.items : Symbol(items, Decl(expandoPropertyEmptyArrayWidening.ts, 3, 11)) +>example : Symbol(example, Decl(expandoPropertyEmptyArrayWidening.ts, 2, 5)) +>items : Symbol(items, Decl(expandoPropertyEmptyArrayWidening.ts, 3, 11)) + +function f1() {} +>f1 : Symbol(f1, Decl(expandoPropertyEmptyArrayWidening.ts, 6, 19)) + +f1.a = []; // Implicit any error +>f1.a : Symbol(f1.a, Decl(expandoPropertyEmptyArrayWidening.ts, 8, 16)) +>f1 : Symbol(f1, Decl(expandoPropertyEmptyArrayWidening.ts, 6, 19)) +>a : Symbol(f1.a, Decl(expandoPropertyEmptyArrayWidening.ts, 8, 16)) + +const f2 = function() {}; +>f2 : Symbol(f2, Decl(expandoPropertyEmptyArrayWidening.ts, 11, 5)) + +f2.a = []; // Implicit any error +>f2.a : Symbol(f2.a, Decl(expandoPropertyEmptyArrayWidening.ts, 11, 25)) +>f2 : Symbol(f2, Decl(expandoPropertyEmptyArrayWidening.ts, 11, 5)) +>a : Symbol(f2.a, Decl(expandoPropertyEmptyArrayWidening.ts, 11, 25)) + +const f3 = () => {}; +>f3 : Symbol(f3, Decl(expandoPropertyEmptyArrayWidening.ts, 14, 5)) + +f3.a = []; // Implicit any error +>f3.a : Symbol(f3.a, Decl(expandoPropertyEmptyArrayWidening.ts, 14, 20)) +>f3 : Symbol(f3, Decl(expandoPropertyEmptyArrayWidening.ts, 14, 5)) +>a : Symbol(f3.a, Decl(expandoPropertyEmptyArrayWidening.ts, 14, 20)) + +const f4: { (): void, a: string[] } = () => {}; +>f4 : Symbol(f4, Decl(expandoPropertyEmptyArrayWidening.ts, 17, 5)) +>a : Symbol(a, Decl(expandoPropertyEmptyArrayWidening.ts, 17, 21)) + +f4.a = []; +>f4.a : Symbol(a, Decl(expandoPropertyEmptyArrayWidening.ts, 17, 21)) +>f4 : Symbol(f4, Decl(expandoPropertyEmptyArrayWidening.ts, 17, 5)) +>a : Symbol(a, Decl(expandoPropertyEmptyArrayWidening.ts, 17, 21)) + +const f5: { (): void, a: string[] } = function() {}; +>f5 : Symbol(f5, Decl(expandoPropertyEmptyArrayWidening.ts, 20, 5)) +>a : Symbol(a, Decl(expandoPropertyEmptyArrayWidening.ts, 20, 21)) + +f5.a = []; +>f5.a : Symbol(a, Decl(expandoPropertyEmptyArrayWidening.ts, 20, 21)) +>f5 : Symbol(f5, Decl(expandoPropertyEmptyArrayWidening.ts, 20, 5)) +>a : Symbol(a, Decl(expandoPropertyEmptyArrayWidening.ts, 20, 21)) + diff --git a/testdata/baselines/reference/compiler/expandoPropertyEmptyArrayWidening.types b/testdata/baselines/reference/compiler/expandoPropertyEmptyArrayWidening.types new file mode 100644 index 000000000..7ca8f4fcc --- /dev/null +++ b/testdata/baselines/reference/compiler/expandoPropertyEmptyArrayWidening.types @@ -0,0 +1,79 @@ +//// [tests/cases/compiler/expandoPropertyEmptyArrayWidening.ts] //// + +=== expandoPropertyEmptyArrayWidening.ts === +// https://github.com/microsoft/typescript-go/issues/3976 + +const example: { +>example : { (): void; items?: string[]; } + + (): void; + items?: string[]; +>items : string[] | undefined + +} = () => undefined; +>() => undefined : { (): undefined; items: never[]; } +>undefined : undefined + +example.items = []; +>example.items = [] : never[] +>example.items : string[] | undefined +>example : { (): void; items?: string[]; } +>items : string[] | undefined +>[] : never[] + +function f1() {} +>f1 : { (): void; a: any[]; } + +f1.a = []; // Implicit any error +>f1.a = [] : never[] +>f1.a : any[] +>f1 : { (): void; a: any[]; } +>a : any[] +>[] : never[] + +const f2 = function() {}; +>f2 : { (): void; a: any[]; } +>function() {} : { (): void; a: any[]; } + +f2.a = []; // Implicit any error +>f2.a = [] : never[] +>f2.a : any[] +>f2 : { (): void; a: any[]; } +>a : any[] +>[] : never[] + +const f3 = () => {}; +>f3 : { (): void; a: any[]; } +>() => {} : { (): void; a: any[]; } + +f3.a = []; // Implicit any error +>f3.a = [] : never[] +>f3.a : any[] +>f3 : { (): void; a: any[]; } +>a : any[] +>[] : never[] + +const f4: { (): void, a: string[] } = () => {}; +>f4 : { (): void; a: string[]; } +>a : string[] +>() => {} : { (): void; a: never[]; } + +f4.a = []; +>f4.a = [] : never[] +>f4.a : string[] +>f4 : { (): void; a: string[]; } +>a : string[] +>[] : never[] + +const f5: { (): void, a: string[] } = function() {}; +>f5 : { (): void; a: string[]; } +>a : string[] +>function() {} : { (): void; a: never[]; } + +f5.a = []; +>f5.a = [] : never[] +>f5.a : string[] +>f5 : { (): void; a: string[]; } +>a : string[] +>[] : never[] + diff --git a/testdata/baselines/reference/compiler/exportDestructuring.js b/testdata/baselines/reference/compiler/exportDestructuring.js index 3462a6419..33d6858ab 100644 --- a/testdata/baselines/reference/compiler/exportDestructuring.js +++ b/testdata/baselines/reference/compiler/exportDestructuring.js @@ -10,4 +10,4 @@ export const [a, b] = arr; Object.defineProperty(exports, "__esModule", { value: true }); exports.b = exports.a = void 0; const arr = [1, 2]; -exports.a = arr[0], exports.b = arr[1]; +[exports.a, exports.b] = arr; diff --git a/testdata/baselines/reference/compiler/exportDestructuringIterator.js b/testdata/baselines/reference/compiler/exportDestructuringIterator.js new file mode 100644 index 000000000..f8fcfaee2 --- /dev/null +++ b/testdata/baselines/reference/compiler/exportDestructuringIterator.js @@ -0,0 +1,24 @@ +//// [tests/cases/compiler/exportDestructuringIterator.ts] //// + +//// [exportDestructuringIterator.ts] +declare function foo(): any; +export const [A, V] = foo(); +export const { x, y } = foo(); +export const [a = 1, b = 2] = foo(); +export const [c, ...d] = foo(); +export const [, e, , f] = foo(); +export const [[g, h], { i, j: k }] = foo(); +export const { m: [n, o], p: { q } } = foo(); + + +//// [exportDestructuringIterator.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.q = exports.o = exports.n = exports.k = exports.i = exports.h = exports.g = exports.f = exports.e = exports.d = exports.c = exports.b = exports.a = exports.y = exports.x = exports.V = exports.A = void 0; +[exports.A, exports.V] = foo(); +({ x: exports.x, y: exports.y } = foo()); +[exports.a = 1, exports.b = 2] = foo(); +[exports.c, ...exports.d] = foo(); +[, exports.e, , exports.f] = foo(); +[[exports.g, exports.h], { i: exports.i, j: exports.k }] = foo(); +({ m: [exports.n, exports.o], p: { q: exports.q } } = foo()); diff --git a/testdata/baselines/reference/compiler/exportDestructuringIterator.symbols b/testdata/baselines/reference/compiler/exportDestructuringIterator.symbols new file mode 100644 index 000000000..e69cbd17b --- /dev/null +++ b/testdata/baselines/reference/compiler/exportDestructuringIterator.symbols @@ -0,0 +1,44 @@ +//// [tests/cases/compiler/exportDestructuringIterator.ts] //// + +=== exportDestructuringIterator.ts === +declare function foo(): any; +>foo : Symbol(foo, Decl(exportDestructuringIterator.ts, 0, 0)) + +export const [A, V] = foo(); +>A : Symbol(A, Decl(exportDestructuringIterator.ts, 1, 14)) +>V : Symbol(V, Decl(exportDestructuringIterator.ts, 1, 16)) +>foo : Symbol(foo, Decl(exportDestructuringIterator.ts, 0, 0)) + +export const { x, y } = foo(); +>x : Symbol(x, Decl(exportDestructuringIterator.ts, 2, 14)) +>y : Symbol(y, Decl(exportDestructuringIterator.ts, 2, 17)) +>foo : Symbol(foo, Decl(exportDestructuringIterator.ts, 0, 0)) + +export const [a = 1, b = 2] = foo(); +>a : Symbol(a, Decl(exportDestructuringIterator.ts, 3, 14)) +>b : Symbol(b, Decl(exportDestructuringIterator.ts, 3, 20)) +>foo : Symbol(foo, Decl(exportDestructuringIterator.ts, 0, 0)) + +export const [c, ...d] = foo(); +>c : Symbol(c, Decl(exportDestructuringIterator.ts, 4, 14)) +>d : Symbol(d, Decl(exportDestructuringIterator.ts, 4, 16)) +>foo : Symbol(foo, Decl(exportDestructuringIterator.ts, 0, 0)) + +export const [, e, , f] = foo(); +>e : Symbol(e, Decl(exportDestructuringIterator.ts, 5, 15)) +>f : Symbol(f, Decl(exportDestructuringIterator.ts, 5, 20)) +>foo : Symbol(foo, Decl(exportDestructuringIterator.ts, 0, 0)) + +export const [[g, h], { i, j: k }] = foo(); +>g : Symbol(g, Decl(exportDestructuringIterator.ts, 6, 15)) +>h : Symbol(h, Decl(exportDestructuringIterator.ts, 6, 17)) +>i : Symbol(i, Decl(exportDestructuringIterator.ts, 6, 23)) +>k : Symbol(k, Decl(exportDestructuringIterator.ts, 6, 26)) +>foo : Symbol(foo, Decl(exportDestructuringIterator.ts, 0, 0)) + +export const { m: [n, o], p: { q } } = foo(); +>n : Symbol(n, Decl(exportDestructuringIterator.ts, 7, 19)) +>o : Symbol(o, Decl(exportDestructuringIterator.ts, 7, 21)) +>q : Symbol(q, Decl(exportDestructuringIterator.ts, 7, 30)) +>foo : Symbol(foo, Decl(exportDestructuringIterator.ts, 0, 0)) + diff --git a/testdata/baselines/reference/compiler/exportDestructuringIterator.types b/testdata/baselines/reference/compiler/exportDestructuringIterator.types new file mode 100644 index 000000000..3e6634b1a --- /dev/null +++ b/testdata/baselines/reference/compiler/exportDestructuringIterator.types @@ -0,0 +1,56 @@ +//// [tests/cases/compiler/exportDestructuringIterator.ts] //// + +=== exportDestructuringIterator.ts === +declare function foo(): any; +>foo : () => any + +export const [A, V] = foo(); +>A : any +>V : any +>foo() : any +>foo : () => any + +export const { x, y } = foo(); +>x : any +>y : any +>foo() : any +>foo : () => any + +export const [a = 1, b = 2] = foo(); +>a : any +>1 : 1 +>b : any +>2 : 2 +>foo() : any +>foo : () => any + +export const [c, ...d] = foo(); +>c : any +>d : any +>foo() : any +>foo : () => any + +export const [, e, , f] = foo(); +>e : any +>f : any +>foo() : any +>foo : () => any + +export const [[g, h], { i, j: k }] = foo(); +>g : any +>h : any +>i : any +>j : any +>k : any +>foo() : any +>foo : () => any + +export const { m: [n, o], p: { q } } = foo(); +>m : any +>n : any +>o : any +>p : any +>q : any +>foo() : any +>foo : () => any + diff --git a/testdata/baselines/reference/compiler/jsDocCommentOfElidedImportPreserved.js b/testdata/baselines/reference/compiler/jsDocCommentOfElidedImportPreserved.js new file mode 100644 index 000000000..7c1ef6635 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsDocCommentOfElidedImportPreserved.js @@ -0,0 +1,54 @@ +//// [tests/cases/compiler/jsDocCommentOfElidedImportPreserved.ts] //// + +//// [index.ts] +export interface Foo {} + +//// [main.ts] +/** + * Some random docs not related to foo + */ +/* trigger */ +import * as x from './index.js'; +export const foo = 1; + +//// [detachedCopyright.ts] +/** + * Copyright header + */ + +import * as x from './index.js'; +export const bar = 2; + +//// [detachedCopyrightNonJSDoc.ts] +/* Non-JSDoc copyright header */ + +import * as x from './index.js'; +export const baz = 3; + + +//// [index.js] +export {}; +//// [main.js] +export const foo = 1; +//// [detachedCopyright.js] +/** + * Copyright header + */ +export const bar = 2; +//// [detachedCopyrightNonJSDoc.js] +/* Non-JSDoc copyright header */ +export const baz = 3; + + +//// [index.d.ts] +export interface Foo { +} +//// [main.d.ts] +export declare const foo = 1; +//// [detachedCopyright.d.ts] +/** + * Copyright header + */ +export declare const bar = 2; +//// [detachedCopyrightNonJSDoc.d.ts] +export declare const baz = 3; diff --git a/testdata/baselines/reference/compiler/jsDocCommentOfElidedImportPreserved.symbols b/testdata/baselines/reference/compiler/jsDocCommentOfElidedImportPreserved.symbols new file mode 100644 index 000000000..bdd14fc73 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsDocCommentOfElidedImportPreserved.symbols @@ -0,0 +1,37 @@ +//// [tests/cases/compiler/jsDocCommentOfElidedImportPreserved.ts] //// + +=== index.ts === +export interface Foo {} +>Foo : Symbol(Foo, Decl(index.ts, 0, 0)) + +=== main.ts === +/** + * Some random docs not related to foo + */ +/* trigger */ +import * as x from './index.js'; +>x : Symbol(x, Decl(main.ts, 4, 6)) + +export const foo = 1; +>foo : Symbol(foo, Decl(main.ts, 5, 12)) + +=== detachedCopyright.ts === +/** + * Copyright header + */ + +import * as x from './index.js'; +>x : Symbol(x, Decl(detachedCopyright.ts, 4, 6)) + +export const bar = 2; +>bar : Symbol(bar, Decl(detachedCopyright.ts, 5, 12)) + +=== detachedCopyrightNonJSDoc.ts === +/* Non-JSDoc copyright header */ + +import * as x from './index.js'; +>x : Symbol(x, Decl(detachedCopyrightNonJSDoc.ts, 2, 6)) + +export const baz = 3; +>baz : Symbol(baz, Decl(detachedCopyrightNonJSDoc.ts, 3, 12)) + diff --git a/testdata/baselines/reference/compiler/jsDocCommentOfElidedImportPreserved.types b/testdata/baselines/reference/compiler/jsDocCommentOfElidedImportPreserved.types new file mode 100644 index 000000000..78c49e117 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsDocCommentOfElidedImportPreserved.types @@ -0,0 +1,40 @@ +//// [tests/cases/compiler/jsDocCommentOfElidedImportPreserved.ts] //// + +=== index.ts === + +export interface Foo {} + +=== main.ts === +/** + * Some random docs not related to foo + */ +/* trigger */ +import * as x from './index.js'; +>x : typeof x + +export const foo = 1; +>foo : 1 +>1 : 1 + +=== detachedCopyright.ts === +/** + * Copyright header + */ + +import * as x from './index.js'; +>x : typeof x + +export const bar = 2; +>bar : 2 +>2 : 2 + +=== detachedCopyrightNonJSDoc.ts === +/* Non-JSDoc copyright header */ + +import * as x from './index.js'; +>x : typeof x + +export const baz = 3; +>baz : 3 +>3 : 3 + diff --git a/testdata/baselines/reference/compiler/symbolToNodeBoundaryNoStackOverflow.errors.txt b/testdata/baselines/reference/compiler/symbolToNodeBoundaryNoStackOverflow.errors.txt new file mode 100644 index 000000000..baddeff68 --- /dev/null +++ b/testdata/baselines/reference/compiler/symbolToNodeBoundaryNoStackOverflow.errors.txt @@ -0,0 +1,36 @@ +symbolToNodeBoundaryNoStackOverflow.ts(22,10): error TS2345: Argument of type '() => () => CustomNode' is not assignable to parameter of type '() => CustomNode'. + Type '() => CustomNode>' is not assignable to type 'CustomNode'. + + +==== symbolToNodeBoundaryNoStackOverflow.ts (1 errors) ==== + // Regression test for https://github.com/microsoft/TypeScript/issues/63441 + // In createAnonymousTypeNodeEx for an InstantiationExpressionType, when the + // existing typeof node failed to be reused (e.g. its entity name isn't + // accessible from the current scope), the recovery boundary's fallback path + // re-entered typeToTypeNode with the same instantiation type, which would + // try to reuse the same node again, recursing without bound. + // The fix marks the type as visited around the reuse attempt so the inner + // recursion bottoms out via the existing visitedTypes guard. + + export interface CustomNode

{ + getNextNode: () => CustomNode

; + } + + export declare const createNode: () => { + getNextNode: () => CustomNode; + }; + + function wrapNode(getNode: () => CustomNode) { + return getNode; + } + + wrapNode(() => { + ~~~~~~~ +!!! error TS2345: Argument of type '() => () => CustomNode' is not assignable to parameter of type '() => CustomNode'. +!!! error TS2345: Type '() => CustomNode>' is not assignable to type 'CustomNode'. +!!! related TS6212 symbolToNodeBoundaryNoStackOverflow.ts:22:10: Did you mean to call this expression? + const node = createNode(); + + return wrapNode>(node.getNextNode); + }); + \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/symbolToNodeBoundaryNoStackOverflow.symbols b/testdata/baselines/reference/compiler/symbolToNodeBoundaryNoStackOverflow.symbols new file mode 100644 index 000000000..728a2a890 --- /dev/null +++ b/testdata/baselines/reference/compiler/symbolToNodeBoundaryNoStackOverflow.symbols @@ -0,0 +1,62 @@ +//// [tests/cases/compiler/symbolToNodeBoundaryNoStackOverflow.ts] //// + +=== symbolToNodeBoundaryNoStackOverflow.ts === +// Regression test for https://github.com/microsoft/TypeScript/issues/63441 +// In createAnonymousTypeNodeEx for an InstantiationExpressionType, when the +// existing typeof node failed to be reused (e.g. its entity name isn't +// accessible from the current scope), the recovery boundary's fallback path +// re-entered typeToTypeNode with the same instantiation type, which would +// try to reuse the same node again, recursing without bound. +// The fix marks the type as visited around the reuse attempt so the inner +// recursion bottoms out via the existing visitedTypes guard. + +export interface CustomNode

{ +>CustomNode : Symbol(CustomNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 0, 0)) +>P : Symbol(P, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 9, 28)) + + getNextNode: () => CustomNode

; +>getNextNode : Symbol(CustomNode.getNextNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 9, 32)) +>CustomNode : Symbol(CustomNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 0, 0)) +>P : Symbol(P, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 9, 28)) +} + +export declare const createNode: () => { +>createNode : Symbol(createNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 13, 20)) + + getNextNode: () => CustomNode; +>getNextNode : Symbol(getNextNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 13, 40)) +>T : Symbol(T, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 14, 18)) +>CustomNode : Symbol(CustomNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 0, 0)) +>T : Symbol(T, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 14, 18)) + +}; + +function wrapNode(getNode: () => CustomNode) { +>wrapNode : Symbol(wrapNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 15, 2)) +>T : Symbol(T, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 17, 18)) +>getNode : Symbol(getNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 17, 21)) +>CustomNode : Symbol(CustomNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 0, 0)) +>T : Symbol(T, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 17, 18)) + + return getNode; +>getNode : Symbol(getNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 17, 21)) +} + +wrapNode(() => { +>wrapNode : Symbol(wrapNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 15, 2)) + + const node = createNode(); +>node : Symbol(node, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 22, 9)) +>createNode : Symbol(createNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 13, 20)) + + return wrapNode>(node.getNextNode); +>wrapNode : Symbol(wrapNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 15, 2)) +>node.getNextNode : Symbol(getNextNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 13, 40)) +>node : Symbol(node, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 22, 9)) +>getNextNode : Symbol(getNextNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 13, 40)) +>node.getNextNode : Symbol(getNextNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 13, 40)) +>node : Symbol(node, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 22, 9)) +>getNextNode : Symbol(getNextNode, Decl(symbolToNodeBoundaryNoStackOverflow.ts, 13, 40)) + +}); + diff --git a/testdata/baselines/reference/compiler/symbolToNodeBoundaryNoStackOverflow.types b/testdata/baselines/reference/compiler/symbolToNodeBoundaryNoStackOverflow.types new file mode 100644 index 000000000..453f720f5 --- /dev/null +++ b/testdata/baselines/reference/compiler/symbolToNodeBoundaryNoStackOverflow.types @@ -0,0 +1,55 @@ +//// [tests/cases/compiler/symbolToNodeBoundaryNoStackOverflow.ts] //// + +=== symbolToNodeBoundaryNoStackOverflow.ts === +// Regression test for https://github.com/microsoft/TypeScript/issues/63441 +// In createAnonymousTypeNodeEx for an InstantiationExpressionType, when the +// existing typeof node failed to be reused (e.g. its entity name isn't +// accessible from the current scope), the recovery boundary's fallback path +// re-entered typeToTypeNode with the same instantiation type, which would +// try to reuse the same node again, recursing without bound. +// The fix marks the type as visited around the reuse attempt so the inner +// recursion bottoms out via the existing visitedTypes guard. + +export interface CustomNode

{ + getNextNode: () => CustomNode

; +>getNextNode : () => CustomNode

+} + +export declare const createNode: () => { +>createNode : () => { getNextNode: () => CustomNode; } + + getNextNode: () => CustomNode; +>getNextNode : () => CustomNode + +}; + +function wrapNode(getNode: () => CustomNode) { +>wrapNode : (getNode: () => CustomNode) => () => CustomNode +>getNode : () => CustomNode + + return getNode; +>getNode : () => CustomNode +} + +wrapNode(() => { +>wrapNode(() => { const node = createNode(); return wrapNode>(node.getNextNode);}) : () => CustomNode +>wrapNode : (getNode: () => CustomNode) => () => CustomNode +>() => { const node = createNode(); return wrapNode>(node.getNextNode);} : () => () => CustomNode + + const node = createNode(); +>node : { getNextNode: () => CustomNode; } +>createNode() : { getNextNode: () => CustomNode; } +>createNode : () => { getNextNode: () => CustomNode; } + + return wrapNode>(node.getNextNode); +>wrapNode>(node.getNextNode) : () => CustomNode +>wrapNode : (getNode: () => CustomNode) => () => CustomNode +>node.getNextNode : () => CustomNode +>node : { getNextNode: () => CustomNode; } +>getNextNode : () => CustomNode +>node.getNextNode : () => CustomNode +>node : { getNextNode: () => CustomNode; } +>getNextNode : () => CustomNode + +}); + diff --git a/testdata/baselines/reference/fourslash/autoImports/autoImportDefaultPascalCase.baseline.md b/testdata/baselines/reference/fourslash/autoImports/autoImportDefaultPascalCase.baseline.md new file mode 100644 index 000000000..8fa8c1e36 --- /dev/null +++ b/testdata/baselines/reference/fourslash/autoImports/autoImportDefaultPascalCase.baseline.md @@ -0,0 +1,16 @@ +// === Auto Imports === +```tsx +// @FileName: /src/screens/SomeScreen.tsx +export function SomeScreen() { + return (): void { }, + bar(): void { }, +}; + +export const o2 = { + foo(value: T): T { return value; }, + bar(value: T): T { return value; }, + baz(a: T, b: U): [T, U] { return [a, b]; }, +}; diff --git a/testdata/tests/cases/compiler/declarationEmitReadonlyAsConst.ts b/testdata/tests/cases/compiler/declarationEmitReadonlyAsConst.ts new file mode 100644 index 000000000..e7703234b --- /dev/null +++ b/testdata/tests/cases/compiler/declarationEmitReadonlyAsConst.ts @@ -0,0 +1,11 @@ +// @strict: true +// @declaration: true + +export const value = { + method(): string { + return "a"; + }, + prop: { + nested: 1, + }, +} as const; diff --git a/testdata/tests/cases/compiler/emptyJSDocComment.ts b/testdata/tests/cases/compiler/emptyJSDocComment.ts new file mode 100644 index 000000000..5c46514e5 --- /dev/null +++ b/testdata/tests/cases/compiler/emptyJSDocComment.ts @@ -0,0 +1,4 @@ +// @declaration: true + +/***/ +export const foo = 1; diff --git a/testdata/tests/cases/compiler/expandoPropertyEmptyArrayWidening.ts b/testdata/tests/cases/compiler/expandoPropertyEmptyArrayWidening.ts new file mode 100644 index 000000000..0f4051cb5 --- /dev/null +++ b/testdata/tests/cases/compiler/expandoPropertyEmptyArrayWidening.ts @@ -0,0 +1,24 @@ +// @noEmit: true + +// https://github.com/microsoft/typescript-go/issues/3976 + +const example: { + (): void; + items?: string[]; +} = () => undefined; +example.items = []; + +function f1() {} +f1.a = []; // Implicit any error + +const f2 = function() {}; +f2.a = []; // Implicit any error + +const f3 = () => {}; +f3.a = []; // Implicit any error + +const f4: { (): void, a: string[] } = () => {}; +f4.a = []; + +const f5: { (): void, a: string[] } = function() {}; +f5.a = []; diff --git a/testdata/tests/cases/compiler/exportDestructuringIterator.ts b/testdata/tests/cases/compiler/exportDestructuringIterator.ts new file mode 100644 index 000000000..4c21141e7 --- /dev/null +++ b/testdata/tests/cases/compiler/exportDestructuringIterator.ts @@ -0,0 +1,12 @@ +// @target: esnext +// @module: commonjs +// @strict: true + +declare function foo(): any; +export const [A, V] = foo(); +export const { x, y } = foo(); +export const [a = 1, b = 2] = foo(); +export const [c, ...d] = foo(); +export const [, e, , f] = foo(); +export const [[g, h], { i, j: k }] = foo(); +export const { m: [n, o], p: { q } } = foo(); diff --git a/testdata/tests/cases/compiler/jsDocCommentOfElidedImportPreserved.ts b/testdata/tests/cases/compiler/jsDocCommentOfElidedImportPreserved.ts new file mode 100644 index 000000000..9926abd53 --- /dev/null +++ b/testdata/tests/cases/compiler/jsDocCommentOfElidedImportPreserved.ts @@ -0,0 +1,26 @@ +// @declaration: true + +// @filename: index.ts +export interface Foo {} + +// @filename: main.ts +/** + * Some random docs not related to foo + */ +/* trigger */ +import * as x from './index.js'; +export const foo = 1; + +// @filename: detachedCopyright.ts +/** + * Copyright header + */ + +import * as x from './index.js'; +export const bar = 2; + +// @filename: detachedCopyrightNonJSDoc.ts +/* Non-JSDoc copyright header */ + +import * as x from './index.js'; +export const baz = 3; diff --git a/testdata/tests/cases/compiler/symbolToNodeBoundaryNoStackOverflow.ts b/testdata/tests/cases/compiler/symbolToNodeBoundaryNoStackOverflow.ts new file mode 100644 index 000000000..45701b462 --- /dev/null +++ b/testdata/tests/cases/compiler/symbolToNodeBoundaryNoStackOverflow.ts @@ -0,0 +1,29 @@ +// @noEmit: true +// @strict: true + +// Regression test for https://github.com/microsoft/TypeScript/issues/63441 +// In createAnonymousTypeNodeEx for an InstantiationExpressionType, when the +// existing typeof node failed to be reused (e.g. its entity name isn't +// accessible from the current scope), the recovery boundary's fallback path +// re-entered typeToTypeNode with the same instantiation type, which would +// try to reuse the same node again, recursing without bound. +// The fix marks the type as visited around the reuse attempt so the inner +// recursion bottoms out via the existing visitedTypes guard. + +export interface CustomNode

{ + getNextNode: () => CustomNode

; +} + +export declare const createNode: () => { + getNextNode: () => CustomNode; +}; + +function wrapNode(getNode: () => CustomNode) { + return getNode; +} + +wrapNode(() => { + const node = createNode(); + + return wrapNode>(node.getNextNode); +});