From bb2703f3abae6fa5feb1874ff7dae34a0c653066 Mon Sep 17 00:00:00 2001 From: buke <1013738+buke@users.noreply.github.com> Date: Fri, 14 Nov 2025 01:39:47 +0000 Subject: [PATCH] chore(sync): mirror internal packages into pkg/ (auto) --- pkg/checker/checker.go | 2 +- pkg/checker/nodebuilderimpl.go | 3 + pkg/compiler/checkerpool.go | 21 ++- pkg/compiler/program.go | 98 +++++----- pkg/fourslash/_scripts/failingTests.txt | 3 - .../gen/quickInfoFromContextualType_test.go | 2 +- .../gen/quickInfoMappedTypeMethods_test.go | 2 +- .../gen/quickInfoUnion_discriminated_test.go | 2 +- pkg/fourslash/tests/quickInfoFunction_test.go | 17 ++ pkg/ls/hover.go | 14 +- pkg/project/checkerpool.go | 18 +- pkg/testrunner/compiler_runner.go | 6 +- pkg/vfs/internal/internal.go | 14 +- pkg/vfs/osvfs/helpers_test.go | 27 +++ pkg/vfs/osvfs/os.go | 4 +- pkg/vfs/osvfs/realpath_test.go | 18 -- pkg/vfs/osvfs/reparsepoint_other.go | 6 + pkg/vfs/osvfs/reparsepoint_windows.go | 29 +++ pkg/vfs/osvfs/reparsepoint_windows_test.go | 169 ++++++++++++++++++ .../documentHighlights_40082.baseline.jsonc | 2 +- ...portDefaultClassConstructor.baseline.jsonc | 2 +- ...ndAllRefsForDefaultExport04.baseline.jsonc | 14 +- ...sForDefaultExport_anonymous.baseline.jsonc | 5 +- ...RefsForFunctionExpression01.baseline.jsonc | 4 +- ...WithLeadingUnderscoreNames8.baseline.jsonc | 2 +- ...WithLeadingUnderscoreNames9.baseline.jsonc | 2 +- .../referencesForModifiers.baseline.jsonc | 2 +- .../goToDefinitionSwitchCase5.baseline.jsonc | 2 +- .../goToDefinitionSwitchCase7.baseline.jsonc | 2 +- .../quickInfo/jsDocAliasQuickInfo.baseline | 24 ++- ...DisplayPartsClassDefaultAnonymous.baseline | 48 ++++- ...InfoDisplayPartsClassDefaultNamed.baseline | 48 ++++- ...ckInfoDisplayPartsClassIncomplete.baseline | 24 ++- ...nfoDisplayPartsFunctionIncomplete.baseline | 50 +++++- ...nfoNestedExportEqualExportDefault.baseline | 24 ++- 35 files changed, 571 insertions(+), 139 deletions(-) create mode 100644 pkg/fourslash/tests/quickInfoFunction_test.go create mode 100644 pkg/vfs/osvfs/helpers_test.go create mode 100644 pkg/vfs/osvfs/reparsepoint_other.go create mode 100644 pkg/vfs/osvfs/reparsepoint_windows.go create mode 100644 pkg/vfs/osvfs/reparsepoint_windows_test.go diff --git a/pkg/checker/checker.go b/pkg/checker/checker.go index ea9a1f059..f164c5edc 100644 --- a/pkg/checker/checker.go +++ b/pkg/checker/checker.go @@ -30518,7 +30518,7 @@ func (c *Checker) getSymbolAtLocation(node *ast.Node, ignoreErrors bool) *ast.Sy } return nil case ast.KindDefaultKeyword, ast.KindFunctionKeyword, ast.KindEqualsGreaterThanToken, ast.KindClassKeyword: - return c.getSymbolOfNode(node) + return c.getSymbolOfNode(node.Parent) case ast.KindImportType: if ast.IsLiteralImportTypeNode(node) { return c.getSymbolAtLocation(node.AsImportTypeNode().Argument.AsLiteralTypeNode().Literal, ignoreErrors) diff --git a/pkg/checker/nodebuilderimpl.go b/pkg/checker/nodebuilderimpl.go index ef9e76427..7e8259a1f 100644 --- a/pkg/checker/nodebuilderimpl.go +++ b/pkg/checker/nodebuilderimpl.go @@ -886,6 +886,9 @@ func (b *NodeBuilderImpl) getNameOfSymbolAsWritten(symbol *ast.Symbol) string { if len(name) > 0 { return name } + if symbol.Name == ast.InternalSymbolNameMissing { + return "__missing" + } return symbol.Name } diff --git a/pkg/compiler/checkerpool.go b/pkg/compiler/checkerpool.go index f73f9fbd5..e666ac832 100644 --- a/pkg/compiler/checkerpool.go +++ b/pkg/compiler/checkerpool.go @@ -12,10 +12,11 @@ import ( ) type CheckerPool interface { + Count() int GetChecker(ctx context.Context) (*checker.Checker, func()) GetCheckerForFile(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) GetCheckerForFileExclusive(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) - GetAllCheckers(ctx context.Context) ([]*checker.Checker, func()) + ForEachCheckerParallel(ctx context.Context, cb func(idx int, c *checker.Checker)) Files(checker *checker.Checker) iter.Seq[*ast.SourceFile] } @@ -42,6 +43,10 @@ func newCheckerPool(checkerCount int, program *Program) *checkerPool { return pool } +func (p *checkerPool) Count() int { + return p.checkerCount +} + func (p *checkerPool) GetCheckerForFile(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) { p.createCheckers() checker := p.fileAssociations[file] @@ -82,9 +87,19 @@ func (p *checkerPool) createCheckers() { }) } -func (p *checkerPool) GetAllCheckers(ctx context.Context) ([]*checker.Checker, func()) { +// Runs `cb` for each checker in the pool concurrently, locking and unlocking checker mutexes as it goes, +// making it safe to call `ForEachCheckerParallel` from many threads simultaneously. +func (p *checkerPool) ForEachCheckerParallel(ctx context.Context, cb func(idx int, c *checker.Checker)) { p.createCheckers() - return p.checkers, noop + wg := core.NewWorkGroup(p.program.SingleThreaded()) + for idx, checker := range p.checkers { + wg.Queue(func() { + p.locks[idx].Lock() + defer p.locks[idx].Unlock() + cb(idx, checker) + }) + } + wg.RunAndWait() } func (p *checkerPool) Files(checker *checker.Checker) iter.Seq[*ast.SourceFile] { diff --git a/pkg/compiler/program.go b/pkg/compiler/program.go index b90af92a6..45897d0be 100644 --- a/pkg/compiler/program.go +++ b/pkg/compiler/program.go @@ -8,6 +8,7 @@ import ( "slices" "strings" "sync" + "sync/atomic" "github.com/go-json-experiment/json" "github.com/buke/typescript-go-internal/pkg/ast" @@ -351,19 +352,13 @@ func (p *Program) BindSourceFiles() { } func (p *Program) CheckSourceFiles(ctx context.Context, files []*ast.SourceFile) { - wg := core.NewWorkGroup(p.SingleThreaded()) - checkers, done := p.checkerPool.GetAllCheckers(ctx) - defer done() - for _, checker := range checkers { - wg.Queue(func() { - for file := range p.checkerPool.Files(checker) { - if files == nil || slices.Contains(files, file) { - checker.CheckSourceFile(ctx, file) - } + p.checkerPool.ForEachCheckerParallel(ctx, func(_ int, checker *checker.Checker) { + for file := range p.checkerPool.Files(checker) { + if files == nil || slices.Contains(files, file) { + checker.CheckSourceFile(ctx, file) } - }) - } - wg.RunAndWait() + } + }) } // Return the type checker associated with the program. @@ -371,8 +366,8 @@ func (p *Program) GetTypeChecker(ctx context.Context) (*checker.Checker, func()) return p.checkerPool.GetChecker(ctx) } -func (p *Program) GetTypeCheckers(ctx context.Context) ([]*checker.Checker, func()) { - return p.checkerPool.GetAllCheckers(ctx) +func (p *Program) ForEachCheckerParallel(ctx context.Context, cb func(idx int, c *checker.Checker)) { + p.checkerPool.ForEachCheckerParallel(ctx, cb) } // Return a checker for the given file. We may have multiple checkers in concurrent scenarios and this @@ -965,14 +960,12 @@ func (p *Program) GetGlobalDiagnostics(ctx context.Context) []*ast.Diagnostic { return nil } - var globalDiagnostics []*ast.Diagnostic - checkers, done := p.checkerPool.GetAllCheckers(ctx) - defer done() - for _, checker := range checkers { - globalDiagnostics = append(globalDiagnostics, checker.GetGlobalDiagnostics()...) - } + globalDiagnostics := make([][]*ast.Diagnostic, p.checkerPool.Count()) + p.checkerPool.ForEachCheckerParallel(ctx, func(idx int, checker *checker.Checker) { + globalDiagnostics[idx] = checker.GetGlobalDiagnostics() + }) - return SortAndDeduplicateDiagnostics(globalDiagnostics) + return SortAndDeduplicateDiagnostics(slices.Concat(globalDiagnostics...)) } func (p *Program) GetDeclarationDiagnostics(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { @@ -1033,22 +1026,23 @@ func (p *Program) getSemanticDiagnosticsForFileNotFilter(ctx context.Context, so defer done() } diags := slices.Clip(sourceFile.BindDiagnostics()) - checkers, closeCheckers := p.checkerPool.GetAllCheckers(ctx) - defer closeCheckers() // Ask for diags from all checkers; checking one file may add diagnostics to other files. // These are deduplicated later. - for _, checker := range checkers { + checkerDiags := make([][]*ast.Diagnostic, p.checkerPool.Count()) + p.checkerPool.ForEachCheckerParallel(ctx, func(idx int, checker *checker.Checker) { if sourceFile == nil || checker == fileChecker { - diags = append(diags, checker.GetDiagnostics(ctx, sourceFile)...) + checkerDiags[idx] = checker.GetDiagnostics(ctx, sourceFile) } else { - diags = append(diags, checker.GetDiagnosticsWithoutCheck(sourceFile)...) + checkerDiags[idx] = checker.GetDiagnosticsWithoutCheck(sourceFile) } - } + }) if ctx.Err() != nil { return nil } + diags = append(diags, slices.Concat(checkerDiags...)...) + // !!! This should be rewritten to work like getBindAndCheckDiagnosticsForFileNoCache. isPlainJS := ast.IsPlainJSFile(sourceFile, compilerOptions.CheckJs) @@ -1140,22 +1134,20 @@ func (p *Program) getSuggestionDiagnosticsForFile(ctx context.Context, sourceFil diags := slices.Clip(sourceFile.BindSuggestionDiagnostics) - checkers, closeCheckers := p.checkerPool.GetAllCheckers(ctx) - defer closeCheckers() - - // Ask for diags from all checkers; checking one file may add diagnostics to other files. - // These are deduplicated later. - for _, checker := range checkers { + checkerDiags := make([][]*ast.Diagnostic, p.checkerPool.Count()) + p.checkerPool.ForEachCheckerParallel(ctx, func(idx int, checker *checker.Checker) { if sourceFile == nil || checker == fileChecker { - diags = append(diags, checker.GetSuggestionDiagnostics(ctx, sourceFile)...) + checkerDiags[idx] = checker.GetSuggestionDiagnostics(ctx, sourceFile) } else { // !!! is there any case where suggestion diagnostics are produced in other checkers? } - } + }) if ctx.Err() != nil { return nil } + diags = append(diags, slices.Concat(checkerDiags...)...) + return diags } @@ -1251,32 +1243,28 @@ func (p *Program) SymbolCount() int { for _, file := range p.files { count += file.SymbolCount } - checkers, done := p.checkerPool.GetAllCheckers(context.Background()) - defer done() - for _, checker := range checkers { - count += int(checker.SymbolCount) - } - return count + var val atomic.Uint32 + val.Store(uint32(count)) + p.checkerPool.ForEachCheckerParallel(context.Background(), func(idx int, c *checker.Checker) { + val.Add(c.SymbolCount) + }) + return int(val.Load()) } func (p *Program) TypeCount() int { - var count int - checkers, done := p.checkerPool.GetAllCheckers(context.Background()) - defer done() - for _, checker := range checkers { - count += int(checker.TypeCount) - } - return count + var val atomic.Uint32 + p.checkerPool.ForEachCheckerParallel(context.Background(), func(idx int, c *checker.Checker) { + val.Add(c.TypeCount) + }) + return int(val.Load()) } func (p *Program) InstantiationCount() int { - var count int - checkers, done := p.checkerPool.GetAllCheckers(context.Background()) - defer done() - for _, checker := range checkers { - count += int(checker.TotalInstantiationCount) - } - return count + var val atomic.Uint32 + p.checkerPool.ForEachCheckerParallel(context.Background(), func(idx int, c *checker.Checker) { + val.Add(c.TotalInstantiationCount) + }) + return int(val.Load()) } func (p *Program) Program() *Program { diff --git a/pkg/fourslash/_scripts/failingTests.txt b/pkg/fourslash/_scripts/failingTests.txt index 7e671cbed..a54667b7b 100644 --- a/pkg/fourslash/_scripts/failingTests.txt +++ b/pkg/fourslash/_scripts/failingTests.txt @@ -454,7 +454,6 @@ TestQuickInfoForTypeParameterInTypeAlias1 TestQuickInfoForTypeParameterInTypeAlias2 TestQuickInfoForTypeofParameter TestQuickInfoForUMDModuleAlias -TestQuickInfoFromContextualType TestQuickInfoFunctionKeyword TestQuickInfoGenerics TestQuickInfoGetterSetter @@ -471,7 +470,6 @@ TestQuickInfoJsDocNonDiscriminatedUnionSharedProp TestQuickInfoJsdocTypedefMissingType TestQuickInfoMappedSpreadTypes TestQuickInfoMappedType -TestQuickInfoMappedTypeMethods TestQuickInfoMappedTypeRecursiveInference TestQuickInfoModuleVariables TestQuickInfoNarrowedTypeOfAliasSymbol @@ -526,7 +524,6 @@ TestQuickInfoTypeError TestQuickInfoTypeOfThisInStatics TestQuickInfoTypeOnlyNamespaceAndClass TestQuickInfoUnionOfNamespaces -TestQuickInfoUnion_discriminated TestQuickInfoWidenedTypes TestQuickInfo_notInsideComment TestQuickInforForSucessiveInferencesIsNotAny diff --git a/pkg/fourslash/tests/gen/quickInfoFromContextualType_test.go b/pkg/fourslash/tests/gen/quickInfoFromContextualType_test.go index 00e552179..c103504d3 100644 --- a/pkg/fourslash/tests/gen/quickInfoFromContextualType_test.go +++ b/pkg/fourslash/tests/gen/quickInfoFromContextualType_test.go @@ -9,7 +9,7 @@ import ( func TestQuickInfoFromContextualType(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @Filename: quickInfoExportAssignmentOfGenericInterface_0.ts interface I { diff --git a/pkg/fourslash/tests/gen/quickInfoMappedTypeMethods_test.go b/pkg/fourslash/tests/gen/quickInfoMappedTypeMethods_test.go index 93b13c87b..a9924be6d 100644 --- a/pkg/fourslash/tests/gen/quickInfoMappedTypeMethods_test.go +++ b/pkg/fourslash/tests/gen/quickInfoMappedTypeMethods_test.go @@ -9,7 +9,7 @@ import ( func TestQuickInfoMappedTypeMethods(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `type M = { [K in 'one']: any }; const x: M = { diff --git a/pkg/fourslash/tests/gen/quickInfoUnion_discriminated_test.go b/pkg/fourslash/tests/gen/quickInfoUnion_discriminated_test.go index 8e405d076..54b3975d0 100644 --- a/pkg/fourslash/tests/gen/quickInfoUnion_discriminated_test.go +++ b/pkg/fourslash/tests/gen/quickInfoUnion_discriminated_test.go @@ -9,7 +9,7 @@ import ( func TestQuickInfoUnion_discriminated(t *testing.T) { t.Parallel() - t.Skip() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") const content = `// @Filename: quickInfoJsDocTags.ts type U = A | B; diff --git a/pkg/fourslash/tests/quickInfoFunction_test.go b/pkg/fourslash/tests/quickInfoFunction_test.go new file mode 100644 index 000000000..c3669f4b0 --- /dev/null +++ b/pkg/fourslash/tests/quickInfoFunction_test.go @@ -0,0 +1,17 @@ +package fourslash_test + +import ( + "testing" + + "github.com/buke/typescript-go-internal/pkg/fourslash" + "github.com/buke/typescript-go-internal/pkg/testutil" +) + +func TestQuickInfoFunction(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = `/**/function foo() { return "hi"; }` + + f := fourslash.NewFourslash(t, nil /*capabilities*/, content) + f.VerifyQuickInfoAt(t, "", "function foo(): string", "") +} diff --git a/pkg/ls/hover.go b/pkg/ls/hover.go index 8d249ab6e..b2416399f 100644 --- a/pkg/ls/hover.go +++ b/pkg/ls/hover.go @@ -28,7 +28,8 @@ func (l *LanguageService) ProvideHover(ctx context.Context, documentURI lsproto. c, done := program.GetTypeCheckerForFile(ctx, file) defer done() rangeNode := getNodeForQuickInfo(node) - quickInfo, documentation := l.getQuickInfoAndDocumentationForSymbol(c, c.GetSymbolAtLocation(node), rangeNode, contentFormat) + symbol := getSymbolAtLocationForQuickInfo(c, node) + quickInfo, documentation := l.getQuickInfoAndDocumentationForSymbol(c, symbol, rangeNode, contentFormat) if quickInfo == "" { return lsproto.HoverOrNull{}, nil } @@ -282,6 +283,17 @@ func getNodeForQuickInfo(node *ast.Node) *ast.Node { return node } +func getSymbolAtLocationForQuickInfo(c *checker.Checker, node *ast.Node) *ast.Symbol { + if objectElement := getContainingObjectLiteralElement(node); objectElement != nil { + if contextualType := c.GetContextualType(objectElement.Parent, checker.ContextFlagsNone); contextualType != nil { + if properties := c.GetPropertySymbolsFromContextualType(objectElement, contextualType, false /*unionSymbolOk*/); len(properties) == 1 { + return properties[0] + } + } + } + return c.GetSymbolAtLocation(node) +} + func inConstructorContext(node *ast.Node) bool { if node.Kind == ast.KindConstructorKeyword { return true diff --git a/pkg/project/checkerpool.go b/pkg/project/checkerpool.go index 94e0c40fa..c0d05b612 100644 --- a/pkg/project/checkerpool.go +++ b/pkg/project/checkerpool.go @@ -94,22 +94,32 @@ func (p *CheckerPool) Files(checker *checker.Checker) iter.Seq[*ast.SourceFile] panic("unimplemented") } -func (p *CheckerPool) GetAllCheckers(ctx context.Context) ([]*checker.Checker, func()) { +func (p *CheckerPool) Count() int { + return p.maxCheckers +} + +func (p *CheckerPool) ForEachCheckerParallel(ctx context.Context, cb func(idx int, c *checker.Checker)) { p.mu.Lock() defer p.mu.Unlock() requestID := core.GetRequestID(ctx) if requestID == "" { - panic("cannot call GetAllCheckers on a project.checkerPool without a request ID") + panic("cannot call ForEachCheckerParallel on a project.checkerPool without a request ID") } // A request can only access one checker if c, release := p.getRequestCheckerLocked(requestID); c != nil { - return []*checker.Checker{c}, release + defer release() + cb(0, c) + return } + // TODO: Does this ever work without deadlocking? `p.GetChecker` also tries to lock this mutex. + // Should this just be a panic? c, release := p.GetChecker(ctx) - return []*checker.Checker{c}, release + defer release() + cb(0, c) + return } func (p *CheckerPool) getCheckerLocked(requestID string) (*checker.Checker, int) { diff --git a/pkg/testrunner/compiler_runner.go b/pkg/testrunner/compiler_runner.go index 91895ac6a..4601a4fa3 100644 --- a/pkg/testrunner/compiler_runner.go +++ b/pkg/testrunner/compiler_runner.go @@ -529,9 +529,7 @@ func createHarnessTestFile(unit *testUnit, currentDirectory string) *harnessutil func (c *compilerTest) verifyUnionOrdering(t *testing.T) { t.Run("union ordering", func(t *testing.T) { p := c.result.Program.Program() - checkers, done := p.GetTypeCheckers(t.Context()) - defer done() - for _, c := range checkers { + p.ForEachCheckerParallel(t.Context(), func(_ int, c *checker.Checker) { for union := range c.UnionTypes() { types := union.Types() @@ -549,7 +547,7 @@ func (c *compilerTest) verifyUnionOrdering(t *testing.T) { assert.Assert(t, slices.Equal(shuffled, types), "compareTypes does not sort union types consistently") } } - } + }) }) } diff --git a/pkg/vfs/internal/internal.go b/pkg/vfs/internal/internal.go index 090d73516..7a29ae99d 100644 --- a/pkg/vfs/internal/internal.go +++ b/pkg/vfs/internal/internal.go @@ -13,8 +13,8 @@ import ( ) type Common struct { - RootFor func(root string) fs.FS - Realpath func(path string) string + RootFor func(root string) fs.FS + IsReparsePoint func(path string) bool } func RootLength(p string) int { @@ -93,12 +93,12 @@ func (vfs *Common) GetAccessibleEntries(path string) (result vfs.Entries) { continue } - if entryType&fs.ModeIrregular != 0 && vfs.Realpath != nil { - // Could be a Windows junction. Try Realpath. - // TODO(jakebailey): use syscall.Win32FileAttributeData instead + if entryType&fs.ModeIrregular != 0 && vfs.IsReparsePoint != nil { + // Could be a Windows junction or other reparse point. + // Check using the OS-specific helper. fullPath := path + "/" + entry.Name() - if realpath := vfs.Realpath(fullPath); fullPath != realpath { - if stat := vfs.Stat(realpath); stat != nil { + if vfs.IsReparsePoint(fullPath) { + if stat := vfs.Stat(fullPath); stat != nil { addToResult(entry.Name(), stat.Mode()) } } diff --git a/pkg/vfs/osvfs/helpers_test.go b/pkg/vfs/osvfs/helpers_test.go new file mode 100644 index 000000000..c1d4126f7 --- /dev/null +++ b/pkg/vfs/osvfs/helpers_test.go @@ -0,0 +1,27 @@ +package osvfs + +import ( + "os" + "os/exec" + "runtime" + "strings" + "testing" + + "gotest.tools/v3/assert" +) + +func mklink(tb testing.TB, target, link string, isDir bool) { + tb.Helper() + + if runtime.GOOS == "windows" && isDir { + // Don't use os.Symlink on Windows, as it creates a "real" symlink, not a junction. + assert.NilError(tb, exec.Command("cmd", "/c", "mklink", "/J", link, target).Run()) + } else { + err := os.Symlink(target, link) + if err != nil && !isDir && runtime.GOOS == "windows" && strings.Contains(err.Error(), "A required privilege is not held by the client") { + tb.Log(err) + tb.Skip("file symlink support is not enabled without elevation or developer mode") + } + assert.NilError(tb, err) + } +} diff --git a/pkg/vfs/osvfs/os.go b/pkg/vfs/osvfs/os.go index 98e2fb049..b25ba6229 100644 --- a/pkg/vfs/osvfs/os.go +++ b/pkg/vfs/osvfs/os.go @@ -21,8 +21,8 @@ func FS() vfs.FS { var osVFS vfs.FS = &osFS{ common: internal.Common{ - RootFor: os.DirFS, - Realpath: osFSRealpath, + RootFor: os.DirFS, + IsReparsePoint: isReparsePoint, }, } diff --git a/pkg/vfs/osvfs/realpath_test.go b/pkg/vfs/osvfs/realpath_test.go index b8d4a571d..5a0ffcda0 100644 --- a/pkg/vfs/osvfs/realpath_test.go +++ b/pkg/vfs/osvfs/realpath_test.go @@ -4,8 +4,6 @@ import ( "os" "os/exec" "path/filepath" - "runtime" - "strings" "testing" "github.com/buke/typescript-go-internal/pkg/tspath" @@ -54,22 +52,6 @@ func setupSymlinks(tb testing.TB) (targetFile, linkFile string) { return targetFile, linkFile } -func mklink(tb testing.TB, target, link string, isDir bool) { - tb.Helper() - - if runtime.GOOS == "windows" && isDir { - // Don't use os.Symlink on Windows, as it creates a "real" symlink, not a junction. - assert.NilError(tb, exec.Command("cmd", "/c", "mklink", "/J", link, target).Run()) - } else { - err := os.Symlink(target, link) - if err != nil && !isDir && runtime.GOOS == "windows" && strings.Contains(err.Error(), "A required privilege is not held by the client") { - tb.Log(err) - tb.Skip("file symlink support is not enabled without elevation or developer mode") - } - assert.NilError(tb, err) - } -} - func BenchmarkRealpath(b *testing.B) { targetFile, linkFile := setupSymlinks(b) diff --git a/pkg/vfs/osvfs/reparsepoint_other.go b/pkg/vfs/osvfs/reparsepoint_other.go new file mode 100644 index 000000000..1206b6bc3 --- /dev/null +++ b/pkg/vfs/osvfs/reparsepoint_other.go @@ -0,0 +1,6 @@ +//go:build !windows + +package osvfs + +// Only Windows has reparse points; leave this nil for other OSes. +var isReparsePoint func(path string) bool diff --git a/pkg/vfs/osvfs/reparsepoint_windows.go b/pkg/vfs/osvfs/reparsepoint_windows.go new file mode 100644 index 000000000..12a1e59cb --- /dev/null +++ b/pkg/vfs/osvfs/reparsepoint_windows.go @@ -0,0 +1,29 @@ +package osvfs + +import ( + "syscall" + "unsafe" +) + +func isReparsePoint(path string) bool { + if len(path) >= 248 { + path = `\\?\` + path + } + + pathUTF16, err := syscall.UTF16PtrFromString(path) + if err != nil { + return false + } + + var data syscall.Win32FileAttributeData + err = syscall.GetFileAttributesEx( + pathUTF16, + syscall.GetFileExInfoStandard, + (*byte)(unsafe.Pointer(&data)), + ) + if err != nil { + return false + } + + return data.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 +} diff --git a/pkg/vfs/osvfs/reparsepoint_windows_test.go b/pkg/vfs/osvfs/reparsepoint_windows_test.go new file mode 100644 index 000000000..c1dba9ecd --- /dev/null +++ b/pkg/vfs/osvfs/reparsepoint_windows_test.go @@ -0,0 +1,169 @@ +package osvfs + +import ( + "os" + "os/exec" + "path/filepath" + "testing" + + "gotest.tools/v3/assert" +) + +func TestIsReparsePoint(t *testing.T) { + t.Parallel() + + tmp := t.TempDir() + + t.Run("regular file", func(t *testing.T) { + t.Parallel() + file := filepath.Join(tmp, "regular.txt") + assert.NilError(t, os.WriteFile(file, []byte("hello"), 0o666)) + assert.Equal(t, isReparsePoint(file), false) + }) + + t.Run("regular directory", func(t *testing.T) { + t.Parallel() + dir := filepath.Join(tmp, "regular-dir") + assert.NilError(t, os.MkdirAll(dir, 0o777)) + assert.Equal(t, isReparsePoint(dir), false) + }) + + t.Run("junction point", func(t *testing.T) { + t.Parallel() + target := filepath.Join(tmp, "junction-target") + link := filepath.Join(tmp, "junction-link") + assert.NilError(t, os.MkdirAll(target, 0o777)) + mklink(t, target, link, true) + assert.Equal(t, isReparsePoint(link), true) + }) + + t.Run("file symlink", func(t *testing.T) { + t.Parallel() + target := filepath.Join(tmp, "symlink-target.txt") + link := filepath.Join(tmp, "symlink-link.txt") + assert.NilError(t, os.WriteFile(target, []byte("hello"), 0o666)) + mklink(t, target, link, false) + assert.Equal(t, isReparsePoint(link), true) + }) + + t.Run("directory symlink", func(t *testing.T) { + t.Parallel() + target := filepath.Join(tmp, "dir-symlink-target") + link := filepath.Join(tmp, "dir-symlink-link") + assert.NilError(t, os.MkdirAll(target, 0o777)) + mklink(t, target, link, false) + assert.Equal(t, isReparsePoint(link), true) + }) + + t.Run("nonexistent path", func(t *testing.T) { + t.Parallel() + nonexistent := filepath.Join(tmp, "does-not-exist") + assert.Equal(t, isReparsePoint(nonexistent), false) + }) + + t.Run("empty path", func(t *testing.T) { + t.Parallel() + assert.Equal(t, isReparsePoint(""), false) + }) + + t.Run("invalid path with null byte", func(t *testing.T) { + t.Parallel() + assert.Equal(t, isReparsePoint("invalid\x00path"), false) + }) +} + +func TestIsReparsePointLongPath(t *testing.T) { + t.Parallel() + + tmp := t.TempDir() + + // Create a deeply nested path that exceeds 248 characters + longPathBase := tmp + pathComponent := "very_long_directory_name_to_exceed_max_path_limit_abcdefghijklmnopqrstuvwxyz" + + for len(longPathBase) < 250 { + longPathBase = filepath.Join(longPathBase, pathComponent) + } + + target := filepath.Join(longPathBase, "target") + link := filepath.Join(longPathBase, "link") + + // Use \\?\ prefix to enable long path support for mklink + longTarget := `\\?\` + target + longLink := `\\?\` + link + + assert.NilError(t, os.MkdirAll(longTarget, 0o777)) + assert.NilError(t, exec.Command("cmd", "/c", "mklink", "/J", longLink, longTarget).Run()) + + // With long path support enabled, this should work even for paths >= 248 chars + assert.Equal(t, isReparsePoint(link), true) +} + +func TestIsReparsePointNestedInSymlink(t *testing.T) { + t.Parallel() + + tmp := t.TempDir() + + // Create a structure: target/inner-target, link -> target, then check link/inner-link + target := filepath.Join(tmp, "target") + innerTarget := filepath.Join(target, "inner-target") + assert.NilError(t, os.MkdirAll(innerTarget, 0o777)) + + link := filepath.Join(tmp, "link") + mklink(t, target, link, true) + + // Create a junction inside the target + innerLink := filepath.Join(target, "inner-link") + mklink(t, innerTarget, innerLink, true) + + // Check the junction through the symlink path + nestedPath := filepath.Join(link, "inner-link") + assert.Equal(t, isReparsePoint(nestedPath), true) +} + +func TestIsReparsePointRelativePath(t *testing.T) { //nolint:paralleltest // Cannot use t.Parallel() with t.Chdir() + tmp := t.TempDir() + t.Chdir(tmp) + + target := "target-rel" + link := "link-rel" + assert.NilError(t, os.MkdirAll(target, 0o777)) + mklink(t, target, link, true) + + assert.Equal(t, isReparsePoint(link), true) + assert.Equal(t, isReparsePoint(target), false) +} + +func BenchmarkIsSymlinkOrJunction(b *testing.B) { + tmp := b.TempDir() + + regularFile := filepath.Join(tmp, "regular.txt") + assert.NilError(b, os.WriteFile(regularFile, []byte("hello"), 0o666)) + + target := filepath.Join(tmp, "target") + link := filepath.Join(tmp, "link") + assert.NilError(b, os.MkdirAll(target, 0o777)) + assert.NilError(b, exec.Command("cmd", "/c", "mklink", "/J", link, target).Run()) + + b.Run("regular file", func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + isReparsePoint(regularFile) + } + }) + + b.Run("junction", func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + isReparsePoint(link) + } + }) + + b.Run("nonexistent", func(b *testing.B) { + b.ReportAllocs() + nonexistent := filepath.Join(tmp, "does-not-exist") + for b.Loop() { + isReparsePoint(nonexistent) + } + }) +} diff --git a/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_40082.baseline.jsonc b/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_40082.baseline.jsonc index 9a74597f6..5d0cc5cbf 100644 --- a/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_40082.baseline.jsonc +++ b/testdata/baselines/reference/fourslash/documentHighlights/documentHighlights_40082.baseline.jsonc @@ -1,6 +1,6 @@ // === documentHighlights === // === /documentHighlights_40082.ts === // export = (state, messages) => { -// export /*HIGHLIGHTS*/default { +// export /*HIGHLIGHTS*/[|default|] { // } // } \ No newline at end of file diff --git a/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsExportDefaultClassConstructor.baseline.jsonc b/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsExportDefaultClassConstructor.baseline.jsonc index af95e7e1a..4bedb5b16 100644 --- a/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsExportDefaultClassConstructor.baseline.jsonc +++ b/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsExportDefaultClassConstructor.baseline.jsonc @@ -1,5 +1,5 @@ // === findAllReferences === // === /findAllRefsExportDefaultClassConstructor.ts === // export default class { -// /*FIND ALL REFS*/constructor() {} +// /*FIND ALL REFS*/[|constructor|]() {} // } \ No newline at end of file diff --git a/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsForDefaultExport04.baseline.jsonc b/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsForDefaultExport04.baseline.jsonc index a5fd072ce..d1fe7e866 100644 --- a/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsForDefaultExport04.baseline.jsonc +++ b/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsForDefaultExport04.baseline.jsonc @@ -23,11 +23,19 @@ // === findAllReferences === // === /a.ts === // const a = 0; -// export /*FIND ALL REFS*/default a; +// export /*FIND ALL REFS*/[|default|] a; + +// === /b.ts === +// import [|a|] from "./a"; +// [|a|]; // === findAllReferences === +// === /a.ts === +// const a = 0; +// export [|default|] a; + // === /b.ts === // import /*FIND ALL REFS*/[|a|] from "./a"; // [|a|]; @@ -35,6 +43,10 @@ // === findAllReferences === +// === /a.ts === +// const a = 0; +// export [|default|] a; + // === /b.ts === // import [|a|] from "./a"; // /*FIND ALL REFS*/[|a|]; \ No newline at end of file diff --git a/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsForDefaultExport_anonymous.baseline.jsonc b/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsForDefaultExport_anonymous.baseline.jsonc index 5f0ee6337..8fe9cad9a 100644 --- a/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsForDefaultExport_anonymous.baseline.jsonc +++ b/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsForDefaultExport_anonymous.baseline.jsonc @@ -1,3 +1,6 @@ // === findAllReferences === // === /a.ts === -// export /*FIND ALL REFS*/default 1; \ No newline at end of file +// export /*FIND ALL REFS*/[|default|] 1; + +// === /b.ts === +// import [|a|] from "./a"; \ No newline at end of file diff --git a/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsForFunctionExpression01.baseline.jsonc b/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsForFunctionExpression01.baseline.jsonc index f02db6604..ae707a851 100644 --- a/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsForFunctionExpression01.baseline.jsonc +++ b/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsForFunctionExpression01.baseline.jsonc @@ -1,7 +1,7 @@ // === findAllReferences === // === /file1.ts === -// var foo = /*FIND ALL REFS*/function foo(a = foo(), b = () => foo) { -// foo(foo, foo); +// var foo = /*FIND ALL REFS*/function [|foo|](a = [|foo|](), b = () => [|foo|]) { +// [|foo|]([|foo|], [|foo|]); // } diff --git a/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsWithLeadingUnderscoreNames8.baseline.jsonc b/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsWithLeadingUnderscoreNames8.baseline.jsonc index 16510e0f5..4e30c5fd6 100644 --- a/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsWithLeadingUnderscoreNames8.baseline.jsonc +++ b/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsWithLeadingUnderscoreNames8.baseline.jsonc @@ -1,7 +1,7 @@ // === findAllReferences === // === /findAllRefsWithLeadingUnderscoreNames8.ts === // (/*FIND ALL REFS*/function __foo() { -// __foo(); +// [|__foo|](); // }) diff --git a/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsWithLeadingUnderscoreNames9.baseline.jsonc b/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsWithLeadingUnderscoreNames9.baseline.jsonc index 099cdfee5..c13998007 100644 --- a/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsWithLeadingUnderscoreNames9.baseline.jsonc +++ b/testdata/baselines/reference/fourslash/findAllReferences/findAllRefsWithLeadingUnderscoreNames9.baseline.jsonc @@ -1,7 +1,7 @@ // === findAllReferences === // === /findAllRefsWithLeadingUnderscoreNames9.ts === // (/*FIND ALL REFS*/function ___foo() { -// ___foo(); +// [|___foo|](); // }) diff --git a/testdata/baselines/reference/fourslash/findAllReferences/referencesForModifiers.baseline.jsonc b/testdata/baselines/reference/fourslash/findAllReferences/referencesForModifiers.baseline.jsonc index 5edfd5392..564c05add 100644 --- a/testdata/baselines/reference/fourslash/findAllReferences/referencesForModifiers.baseline.jsonc +++ b/testdata/baselines/reference/fourslash/findAllReferences/referencesForModifiers.baseline.jsonc @@ -124,4 +124,4 @@ // const enum E { // } // async function fn() {} -// export /*FIND ALL REFS*/default class C2 {} \ No newline at end of file +// export /*FIND ALL REFS*/[|default|] class C2 {} \ No newline at end of file diff --git a/testdata/baselines/reference/fourslash/goToDefinition/goToDefinitionSwitchCase5.baseline.jsonc b/testdata/baselines/reference/fourslash/goToDefinition/goToDefinitionSwitchCase5.baseline.jsonc index 656f1d3f2..236b9c597 100644 --- a/testdata/baselines/reference/fourslash/goToDefinition/goToDefinitionSwitchCase5.baseline.jsonc +++ b/testdata/baselines/reference/fourslash/goToDefinition/goToDefinitionSwitchCase5.baseline.jsonc @@ -1,3 +1,3 @@ // === goToDefinition === // === /goToDefinitionSwitchCase5.ts === -// export /*GOTO DEF*/default {} \ No newline at end of file +// [|export /*GOTO DEF*/default {}|] \ No newline at end of file diff --git a/testdata/baselines/reference/fourslash/goToDefinition/goToDefinitionSwitchCase7.baseline.jsonc b/testdata/baselines/reference/fourslash/goToDefinition/goToDefinitionSwitchCase7.baseline.jsonc index 14f21d476..0d74ed5ba 100644 --- a/testdata/baselines/reference/fourslash/goToDefinition/goToDefinitionSwitchCase7.baseline.jsonc +++ b/testdata/baselines/reference/fourslash/goToDefinition/goToDefinitionSwitchCase7.baseline.jsonc @@ -2,4 +2,4 @@ // === /goToDefinitionSwitchCase7.ts === // switch (null) { // case null: -// export /*GOTO DEF*/default 123; \ No newline at end of file +// [|export /*GOTO DEF*/default 123;|] \ No newline at end of file diff --git a/testdata/baselines/reference/fourslash/quickInfo/jsDocAliasQuickInfo.baseline b/testdata/baselines/reference/fourslash/quickInfo/jsDocAliasQuickInfo.baseline index 9ab690fbe..52ea31202 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/jsDocAliasQuickInfo.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/jsDocAliasQuickInfo.baseline @@ -5,9 +5,12 @@ // * @type {number} // */ // export default 10; -// ^ +// ^^^^^^^ // | ---------------------------------------------------------------------- -// | No quickinfo at /*1*/. +// | ```tsx +// | (property) default: 10 +// | ``` +// | Comment // | ---------------------------------------------------------------------- === /test.ts === // export { default as test } from "./jsDocAliasQuickInfo"; @@ -32,7 +35,22 @@ "Name": "1", "Data": {} }, - "item": null + "item": { + "contents": { + "kind": "markdown", + "value": "```tsx\n(property) default: 10\n```\nComment" + }, + "range": { + "start": { + "line": 4, + "character": 7 + }, + "end": { + "line": 4, + "character": 14 + } + } + } }, { "marker": { diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsClassDefaultAnonymous.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsClassDefaultAnonymous.baseline index 748eae06e..f683142ed 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsClassDefaultAnonymous.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsClassDefaultAnonymous.baseline @@ -5,13 +5,19 @@ // | ---------------------------------------------------------------------- // | No quickinfo at /*1*/. // | ---------------------------------------------------------------------- -// ^ +// ^^^^^^^ // | ---------------------------------------------------------------------- -// | No quickinfo at /*2*/. +// | ```tsx +// | class default +// | ``` +// | // | ---------------------------------------------------------------------- -// ^ +// ^^^^^ // | ---------------------------------------------------------------------- -// | No quickinfo at /*3*/. +// | ```tsx +// | class default +// | ``` +// | // | ---------------------------------------------------------------------- // ^ // | ---------------------------------------------------------------------- @@ -41,7 +47,22 @@ "Name": "2", "Data": {} }, - "item": null + "item": { + "contents": { + "kind": "markdown", + "value": "```tsx\nclass default\n```\n" + }, + "range": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 14 + } + } + } }, { "marker": { @@ -53,7 +74,22 @@ "Name": "3", "Data": {} }, - "item": null + "item": { + "contents": { + "kind": "markdown", + "value": "```tsx\nclass default\n```\n" + }, + "range": { + "start": { + "line": 0, + "character": 15 + }, + "end": { + "line": 0, + "character": 20 + } + } + } }, { "marker": { diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsClassDefaultNamed.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsClassDefaultNamed.baseline index 2c560a24b..25d722530 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsClassDefaultNamed.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsClassDefaultNamed.baseline @@ -5,13 +5,19 @@ // | ---------------------------------------------------------------------- // | No quickinfo at /*1*/. // | ---------------------------------------------------------------------- -// ^ +// ^^^^^^^ // | ---------------------------------------------------------------------- -// | No quickinfo at /*2*/. +// | ```tsx +// | class C +// | ``` +// | // | ---------------------------------------------------------------------- -// ^ +// ^^^^^ // | ---------------------------------------------------------------------- -// | No quickinfo at /*3*/. +// | ```tsx +// | class C +// | ``` +// | // | ---------------------------------------------------------------------- // ^ // | ---------------------------------------------------------------------- @@ -48,7 +54,22 @@ "Name": "2", "Data": {} }, - "item": null + "item": { + "contents": { + "kind": "markdown", + "value": "```tsx\nclass C\n```\n" + }, + "range": { + "start": { + "line": 0, + "character": 7 + }, + "end": { + "line": 0, + "character": 14 + } + } + } }, { "marker": { @@ -60,7 +81,22 @@ "Name": "3", "Data": {} }, - "item": null + "item": { + "contents": { + "kind": "markdown", + "value": "```tsx\nclass C\n```\n" + }, + "range": { + "start": { + "line": 0, + "character": 15 + }, + "end": { + "line": 0, + "character": 20 + } + } + } }, { "marker": { diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsClassIncomplete.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsClassIncomplete.baseline index c864ef9b5..7ff456ccd 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsClassIncomplete.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsClassIncomplete.baseline @@ -1,9 +1,12 @@ // === QuickInfo === === /quickInfoDisplayPartsClassIncomplete.ts === // class { -// ^ +// ^^^^^ // | ---------------------------------------------------------------------- -// | No quickinfo at /*1*/. +// | ```tsx +// | class __missing +// | ``` +// | // | ---------------------------------------------------------------------- // ^ // | ---------------------------------------------------------------------- @@ -21,7 +24,22 @@ "Name": "1", "Data": {} }, - "item": null + "item": { + "contents": { + "kind": "markdown", + "value": "```tsx\nclass __missing\n```\n" + }, + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 5 + } + } + } }, { "marker": { diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsFunctionIncomplete.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsFunctionIncomplete.baseline index 4fdac11c6..65e84951f 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsFunctionIncomplete.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoDisplayPartsFunctionIncomplete.baseline @@ -1,9 +1,13 @@ // === QuickInfo === === /quickInfoDisplayPartsFunctionIncomplete.ts === // function (param: string) { -// ^ +// ^^^^^^^^ // | ---------------------------------------------------------------------- -// | No quickinfo at /*1*/. +// | ```tsx +// | function (Missing)(param: string): void +// | function (Missing)(): void +// | ``` +// | // | ---------------------------------------------------------------------- // ^ // | ---------------------------------------------------------------------- @@ -11,9 +15,13 @@ // | ---------------------------------------------------------------------- // }\ // function { -// ^ +// ^^^^^^^^ // | ---------------------------------------------------------------------- -// | No quickinfo at /*3*/. +// | ```tsx +// | function (Missing)(param: string): void +// | function (Missing)(): void +// | ``` +// | // | ---------------------------------------------------------------------- // ^ // | ---------------------------------------------------------------------- @@ -31,7 +39,22 @@ "Name": "1", "Data": {} }, - "item": null + "item": { + "contents": { + "kind": "markdown", + "value": "```tsx\nfunction (Missing)(param: string): void\nfunction (Missing)(): void\n```\n" + }, + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 8 + } + } + } }, { "marker": { @@ -55,7 +78,22 @@ "Name": "3", "Data": {} }, - "item": null + "item": { + "contents": { + "kind": "markdown", + "value": "```tsx\nfunction (Missing)(param: string): void\nfunction (Missing)(): void\n```\n" + }, + "range": { + "start": { + "line": 2, + "character": 0 + }, + "end": { + "line": 2, + "character": 8 + } + } + } }, { "marker": { diff --git a/testdata/baselines/reference/fourslash/quickInfo/quickInfoNestedExportEqualExportDefault.baseline b/testdata/baselines/reference/fourslash/quickInfo/quickInfoNestedExportEqualExportDefault.baseline index 9be707f09..ae5f4f86d 100644 --- a/testdata/baselines/reference/fourslash/quickInfo/quickInfoNestedExportEqualExportDefault.baseline +++ b/testdata/baselines/reference/fourslash/quickInfo/quickInfoNestedExportEqualExportDefault.baseline @@ -9,9 +9,12 @@ // | ``` // | // | ---------------------------------------------------------------------- -// ^ +// ^^^^^^^ // | ---------------------------------------------------------------------- -// | No quickinfo at /*2*/. +// | ```tsx +// | (property) (Anonymous function).default: {} +// | ``` +// | // | ---------------------------------------------------------------------- // } // } @@ -53,6 +56,21 @@ "Name": "2", "Data": {} }, - "item": null + "item": { + "contents": { + "kind": "markdown", + "value": "```tsx\n(property) (Anonymous function).default: {}\n```\n" + }, + "range": { + "start": { + "line": 1, + "character": 10 + }, + "end": { + "line": 1, + "character": 17 + } + } + } } ] \ No newline at end of file