ttsc 0.31.4 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/cmd/ttsc/compiler_argument_cwd_test.go +38 -38
  2. package/cmd/ttscserver/main.go +2 -12
  3. package/cmd/ttscserver/resolve_launch_uses_native_flag_authority_test.go +7 -7
  4. package/driver/lsp.go +4 -43
  5. package/driver/rewrite.go +1 -1
  6. package/internal/e2etrace/completion_hints.go +3 -3
  7. package/internal/e2etrace/completion_hints_test.go +26 -12
  8. package/internal/graph/build.go +5 -6
  9. package/internal/graph/graph.go +3 -3
  10. package/internal/graph/line_model.go +2 -35
  11. package/internal/lspserver/lsp_clean_didopen_refreshes_compiler_state_test.go +13 -36
  12. package/internal/lspserver/lsp_hints_refresh_ignores_a_superseded_generation_test.go +9 -3
  13. package/internal/lspserver/lsp_plugin.go +19 -0
  14. package/internal/lspserver/lsp_proxy.go +9 -129
  15. package/internal/lspserver/lsp_server.go +0 -8
  16. package/internal/lspserver/lsp_watched_file_changes_reach_compiler_caches_test.go +6 -36
  17. package/internal/sourceprocess/windows_target_owns_suspended_admission_test.go +5 -5
  18. package/lib/compiler/internal/build/BuildExecution.js +11 -3
  19. package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
  20. package/lib/compiler/internal/build/TsgoArguments.js.map +1 -1
  21. package/lib/compiler/internal/project/CompilerProjectSelection.d.ts +9 -8
  22. package/lib/compiler/internal/project/CompilerProjectSelection.js +11 -9
  23. package/lib/compiler/internal/project/CompilerProjectSelection.js.map +1 -1
  24. package/lib/compiler/internal/project/resolveOwningProjectConfig.d.ts +20 -8
  25. package/lib/compiler/internal/project/resolveOwningProjectConfig.js +21 -12
  26. package/lib/compiler/internal/project/resolveOwningProjectConfig.js.map +1 -1
  27. package/lib/compiler/internal/runSingleFileEmit.d.ts +5 -4
  28. package/lib/compiler/internal/runSingleFileEmit.js +5 -4
  29. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  30. package/lib/flags/ParseOptions.d.ts +2 -2
  31. package/lib/internal/E2ETrace.d.ts +1 -0
  32. package/lib/internal/E2ETrace.js.map +1 -1
  33. package/lib/internal/OwnedNativeProcess.d.ts +2 -2
  34. package/lib/internal/OwnedNativeProcess.js +2 -2
  35. package/lib/internal/SourceNativeRetirement.d.ts +8 -7
  36. package/lib/internal/SourceNativeRetirement.js +19 -10
  37. package/lib/internal/SourceNativeRetirement.js.map +1 -1
  38. package/lib/internal/TtsxNodeOptions.d.ts +79 -0
  39. package/lib/internal/TtsxNodeOptions.js +121 -0
  40. package/lib/internal/TtsxNodeOptions.js.map +1 -0
  41. package/lib/internal/javascriptRuntimeCapabilities.js +4 -1
  42. package/lib/internal/javascriptRuntimeCapabilities.js.map +1 -1
  43. package/lib/internal/parseWindowsDirectoryCaseSensitivity.d.ts +5 -5
  44. package/lib/internal/parseWindowsDirectoryCaseSensitivity.js +5 -5
  45. package/lib/internal/pathIdentity/createCaseSensitivityObservations.d.ts +35 -0
  46. package/lib/internal/pathIdentity/createCaseSensitivityObservations.js +209 -0
  47. package/lib/internal/pathIdentity/createCaseSensitivityObservations.js.map +1 -0
  48. package/lib/internal/pathIdentity/createFilesystemPathIdentityContext.js +2 -143
  49. package/lib/internal/pathIdentity/createFilesystemPathIdentityContext.js.map +1 -1
  50. package/lib/internal/probeLocalProcess.d.ts +5 -1
  51. package/lib/internal/probeLocalProcess.js +9 -7
  52. package/lib/internal/probeLocalProcess.js.map +1 -1
  53. package/lib/internal/projectInputPathIdentity.d.ts +1 -0
  54. package/lib/internal/projectInputPathIdentity.js +1 -0
  55. package/lib/internal/projectInputPathIdentity.js.map +1 -1
  56. package/lib/launcher/internal/parseTtsxCLI.d.ts +5 -4
  57. package/lib/launcher/internal/parseTtsxCLI.js +5 -4
  58. package/lib/launcher/internal/parseTtsxCLI.js.map +1 -1
  59. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  60. package/lib/launcher/internal/resolveSingleFileOutput.d.ts +8 -9
  61. package/lib/launcher/internal/resolveSingleFileOutput.js +8 -9
  62. package/lib/launcher/internal/resolveSingleFileOutput.js.map +1 -1
  63. package/lib/launcher/internal/runTtsc.js +5 -5
  64. package/lib/launcher/internal/runTtsx.js +5 -2
  65. package/lib/launcher/internal/runTtsx.js.map +1 -1
  66. package/lib/launcher/internal/runtime/ProcessOwnedDirectory.d.ts +2 -0
  67. package/lib/launcher/internal/runtime/ProcessOwnedDirectory.js +2 -1
  68. package/lib/launcher/internal/runtime/ProcessOwnedDirectory.js.map +1 -1
  69. package/lib/launcher/internal/runtime/RuntimeEntrySources.d.ts +40 -0
  70. package/lib/launcher/internal/runtime/RuntimeEntrySources.js +57 -0
  71. package/lib/launcher/internal/runtime/RuntimeEntrySources.js.map +1 -0
  72. package/lib/launcher/internal/runtime/RuntimeProjectOwnership.d.ts +5 -4
  73. package/lib/launcher/internal/runtime/RuntimeProjectOwnership.js +8 -6
  74. package/lib/launcher/internal/runtime/RuntimeProjectOwnership.js.map +1 -1
  75. package/lib/launcher/internal/runtime/commonJsExportNames.d.ts +2 -1
  76. package/lib/launcher/internal/runtime/commonJsExportNames.js +2 -1
  77. package/lib/launcher/internal/runtime/commonJsExportNames.js.map +1 -1
  78. package/lib/launcher/internal/runtime/installRuntimeHooks.js +35 -9
  79. package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
  80. package/lib/launcher/internal/runtime/isLocalProcessGone.d.ts +1 -1
  81. package/lib/launcher/internal/runtime/isLocalProcessGone.js +4 -4
  82. package/lib/launcher/internal/runtime/isLocalProcessGone.js.map +1 -1
  83. package/lib/launcher/internal/runtime/resolveRuntimeCleanTargets.js +3 -1
  84. package/lib/launcher/internal/runtime/resolveRuntimeCleanTargets.js.map +1 -1
  85. package/lib/launcher/internal/watch/FseventsStreams.d.ts +2 -0
  86. package/lib/launcher/internal/watch/FseventsStreams.js +19 -0
  87. package/lib/launcher/internal/watch/FseventsStreams.js.map +1 -1
  88. package/lib/launcher/internal/watch/WatchTopology.d.ts +74 -12
  89. package/lib/launcher/internal/watch/WatchTopology.js +301 -92
  90. package/lib/launcher/internal/watch/WatchTopology.js.map +1 -1
  91. package/lib/launcher/internal/watch/projectInputReloadEventShouldNotify.d.ts +4 -1
  92. package/lib/launcher/internal/watch/projectInputReloadEventShouldNotify.js +3 -2
  93. package/lib/launcher/internal/watch/projectInputReloadEventShouldNotify.js.map +1 -1
  94. package/lib/plugin/internal/load/loadProjectPlugins.js +17 -9
  95. package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
  96. package/lib/plugin/internal/load/resolveNativeSource.js +2 -3
  97. package/lib/plugin/internal/load/resolveNativeSource.js.map +1 -1
  98. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.d.ts +4 -1
  99. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js +9 -2
  100. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js.map +1 -1
  101. package/lib/structures/internal/TtscCommonOptions.d.ts +2 -2
  102. package/package.json +9 -9
  103. package/src/compiler/internal/build/BuildExecution.ts +15 -6
  104. package/src/compiler/internal/build/TsgoArguments.ts +4 -1
  105. package/src/compiler/internal/project/CompilerProjectSelection.ts +14 -14
  106. package/src/compiler/internal/project/resolveOwningProjectConfig.ts +29 -14
  107. package/src/compiler/internal/runSingleFileEmit.ts +11 -7
  108. package/src/flags/ParseOptions.ts +2 -2
  109. package/src/internal/E2ETrace.ts +5 -1
  110. package/src/internal/OwnedNativeProcess.ts +2 -2
  111. package/src/internal/SourceNativeRetirement.ts +42 -19
  112. package/src/internal/TtsxNodeOptions.ts +124 -0
  113. package/src/internal/javascriptRuntimeCapabilities.ts +6 -1
  114. package/src/internal/parseWindowsDirectoryCaseSensitivity.ts +5 -5
  115. package/src/internal/pathIdentity/createCaseSensitivityObservations.ts +242 -0
  116. package/src/internal/pathIdentity/createFilesystemPathIdentityContext.ts +2 -169
  117. package/src/internal/probeLocalProcess.ts +13 -8
  118. package/src/internal/projectInputPathIdentity.ts +1 -0
  119. package/src/launcher/internal/parseTtsxCLI.ts +5 -4
  120. package/src/launcher/internal/prepareExecution.ts +4 -1
  121. package/src/launcher/internal/resolveSingleFileOutput.ts +8 -9
  122. package/src/launcher/internal/runTtsc.ts +5 -5
  123. package/src/launcher/internal/runTtsx.ts +17 -8
  124. package/src/launcher/internal/runtime/ProcessOwnedDirectory.ts +5 -1
  125. package/src/launcher/internal/runtime/RuntimeEntrySources.ts +57 -0
  126. package/src/launcher/internal/runtime/RuntimeProjectOwnership.ts +23 -10
  127. package/src/launcher/internal/runtime/commonJsExportNames.ts +11 -4
  128. package/src/launcher/internal/runtime/installRuntimeHooks.ts +55 -13
  129. package/src/launcher/internal/runtime/isLocalProcessGone.ts +5 -3
  130. package/src/launcher/internal/runtime/resolveRuntimeCleanTargets.ts +17 -5
  131. package/src/launcher/internal/watch/FseventsStreams.ts +22 -0
  132. package/src/launcher/internal/watch/WatchTopology.ts +387 -99
  133. package/src/launcher/internal/watch/projectInputReloadEventShouldNotify.ts +14 -9
  134. package/src/plugin/internal/load/loadProjectPlugins.ts +17 -3
  135. package/src/plugin/internal/load/resolveNativeSource.ts +10 -4
  136. package/src/plugin/internal/source/pluginModuleReplaceDirectories.ts +10 -1
  137. package/src/structures/internal/TtscCommonOptions.ts +2 -2
  138. package/internal/graphsymbols/position_helpers_follow_ecma_line_terminators_test.go +0 -78
  139. package/internal/graphsymbols/provider.go +0 -650
  140. package/internal/lspserver/lsp_symbols.go +0 -212
@@ -1,650 +0,0 @@
1
- // Package graphsymbols answers the two graph-oriented LSP methods
2
- // (textDocument/documentSymbol and textDocument/references) from ttsc's
3
- // compiler-backed code graph. It is the lspserver.SymbolProvider implementation
4
- // ttscserver wires in so a raw-LSP graph consumer such as @samchon/graph can get
5
- // declarations (graph nodes) and usages (graph edges) computed from that graph.
6
- //
7
- // tsgo's own LSP implements both methods with its compiler-exact language
8
- // service, so the proxy forwards to tsgo by default and falls back to this
9
- // provider only when tsgo does not advertise the capability, or when a consumer
10
- // explicitly opts into graph-derived answers (see lspserver.Proxy).
11
- //
12
- // It lives in its own package rather than in internal/lspserver because it
13
- // imports internal/graph and driver, and driver already imports lspserver;
14
- // putting the compiler-facing logic here keeps lspserver free of that
15
- // dependency cycle. Only cmd/ttscserver imports this package.
16
- package graphsymbols
17
-
18
- import (
19
- "fmt"
20
- "net/url"
21
- "os"
22
- "path/filepath"
23
- "sort"
24
- "strconv"
25
- "strings"
26
- "sync"
27
- "unicode/utf16"
28
- "unicode/utf8"
29
-
30
- shimtspath "github.com/microsoft/typescript-go/shim/tspath"
31
-
32
- "github.com/samchon/ttsc/packages/ttsc/driver"
33
- "github.com/samchon/ttsc/packages/ttsc/internal/graph"
34
- "github.com/samchon/ttsc/packages/ttsc/internal/lspserver"
35
- )
36
-
37
- // Provider computes documentSymbol/references from a code graph built for one
38
- // project (tsconfig). The graph is loaded lazily on the first request and
39
- // cached until Invalidate is called. Queries use on-disk sources, not unsaved
40
- // editor buffers; the proxy invalidates on source and project-state changes.
41
- // It is safe for concurrent use. A request already holding an immutable
42
- // snapshot may finish on it while the next request loads a replacement.
43
- //
44
- // @evidence contracts/common.md#principled-implementation The provider combines a compiler-backed graph, its source texts and the compiler filesystem's case policy in one snapshot; UTF-16 LSP positions are converted against those exact bytes.
45
- // @evidence contracts/common.md#clear-and-simple-design The project anchor and mutex own snapshot publication. Private indexes answer graph queries without coupling the LSP wire types to mutable compiler objects.
46
- // @evidence contracts/common.md#prohibited-implementation-shortcuts The graph comes from the supported driver and checker rather than source-pattern inference or replaced language-server methods. This fallback explicitly answers from saved files, not an invented live-buffer snapshot.
47
- // @evidence contracts/common.md#meaningful-documentation Native paragraphs explain lazy loading, saved-source semantics, invalidation and concurrent snapshot ownership under the documentation skill's state and limitation guidance.
48
- // @evidence contracts/portability.md#os-neutral-implementation Native project anchors remain separate from file URIs. Source identity uses the loaded compiler's case policy, not unconditional case folding or the host OS name.
49
- // @evidenceExclude contracts/performance.md#efficient-algorithms The representation groups snapshot ownership; its load and query operations choose the processing strategy.
50
- // @evidenceExclude contracts/performance.md#reuse-equivalent-work The load operation authorizes sharing and invalidation; this type defines the state it protects.
51
- // @evidenceExclude contracts/performance.md#bound-retention-and-release-resources Acquisition and snapshot replacement belong to load and Invalidate rather than this representation independently.
52
- type Provider struct {
53
- // cwd anchors the compiler project independently of request URI spelling.
54
- cwd string
55
-
56
- // tsconfig selects the project rebuilt after invalidation.
57
- tsconfig string
58
-
59
- // mu protects short publication and invalidation transitions.
60
- mu sync.Mutex
61
-
62
- // buildMu permits one native compilation without blocking invalidation.
63
- buildMu sync.Mutex
64
-
65
- // loading identifies the flight authorized to publish the current generation.
66
- loading *providerLoad
67
-
68
- // loaded records either a published snapshot or a cached load failure.
69
- loaded bool
70
-
71
- // snapshot is the current immutable saved-project generation.
72
- snapshot *providerSnapshot
73
-
74
- // loadErr is retried after the same invalidation that revokes a snapshot.
75
- loadErr error
76
- }
77
-
78
- // providerLoad shares one generation's result. Closing done publishes its
79
- // immutable result to waiters even when invalidation revoked global retention.
80
- type providerLoad struct {
81
- done chan struct{}
82
-
83
- snapshot *providerSnapshot
84
-
85
- err error
86
- }
87
-
88
- // providerSnapshot keeps the compiler's source identities with the graph that
89
- // resolved them. An in-flight request may finish on its snapshot after a later
90
- // invalidation without observing half of the replacement state.
91
- type providerSnapshot struct {
92
- graph *graph.Graph
93
-
94
- sources map[string]string
95
-
96
- caseSensitive bool
97
-
98
- files map[string]string
99
-
100
- nodesByFile map[string][]*graph.Node
101
-
102
- edgesByFile map[string][]*graph.Edge
103
-
104
- incoming map[string][]*graph.Edge
105
- }
106
-
107
- func (s *providerSnapshot) fileKey(path string) string {
108
- return shimtspath.GetCanonicalFileName(shimtspath.NormalizePath(path), s.caseSensitive)
109
- }
110
-
111
- func (s *providerSnapshot) fileFromURI(uri string) (string, bool) {
112
- path, ok := filePathFromURI(uri)
113
- if !ok {
114
- return "", false
115
- }
116
- file, ok := s.files[s.fileKey(shimtspath.ResolvePath(path))]
117
- return file, ok
118
- }
119
-
120
- // NewProvider returns a Provider that builds the graph for the project rooted at
121
- // cwd using tsconfig (defaulting to "tsconfig.json").
122
- // It acquires no compiler or file handle; the first query loads saved sources.
123
- // The caller keeps the provider for one project and invalidates it when that
124
- // project's source or configuration state changes.
125
- //
126
- // @evidence contracts/common.md#principled-implementation The constructor preserves the project anchor and selects the documented default only for a blank config selector; loading is deferred until a query needs compiler facts.
127
- // @evidence contracts/common.md#clear-and-simple-design Construction initializes identity only. The shared load path owns compilation, errors and snapshot indexes for both query methods.
128
- // @evidence contracts/common.md#prohibited-implementation-shortcuts The default config name is the CLI contract; no consumer identity or expected graph affects it, and no foreign server is patched.
129
- // @evidence contracts/common.md#meaningful-documentation Native prose states lazy acquisition, saved-source semantics and the caller's invalidation responsibility under the documentation skill's ownership guidance.
130
- // @evidence contracts/portability.md#os-neutral-implementation cwd and tsconfig retain native path spelling for the driver to resolve; URI decoding and actual compiler case policy belong to the loaded snapshot.
131
- // @evidence contracts/performance.md#efficient-algorithms Construction trims only the config selector and allocates one provider; it does not eagerly scan sources or build a graph no request needs.
132
- // @evidenceExclude contracts/performance.md#reuse-equivalent-work The constructor creates one project owner; the common load operation coordinates its later requests.
133
- // @evidence contracts/performance.md#bound-retention-and-release-resources The caller receives ownership of one provider with no open handle. Lazy snapshots live until invalidation or the last provider/request reference is dropped; no global history retains past projects.
134
- func NewProvider(cwd, tsconfig string) *Provider {
135
- tsconfig = strings.TrimSpace(tsconfig)
136
- if tsconfig == "" {
137
- tsconfig = "tsconfig.json"
138
- }
139
- return &Provider{cwd: cwd, tsconfig: tsconfig}
140
- }
141
-
142
- // load shares one flight per invalidation generation. Native compilation runs
143
- // outside the publication mutex so editor notifications can revoke a flight
144
- // without waiting for parsing or graph construction. Revoked flights may answer
145
- // their existing requests but cannot republish into the current generation.
146
- func (pr *Provider) load() (*providerSnapshot, error) {
147
- pr.mu.Lock()
148
- if pr.loaded {
149
- snapshot, err := pr.snapshot, pr.loadErr
150
- pr.mu.Unlock()
151
- return snapshot, err
152
- }
153
- if pr.loading != nil {
154
- flight := pr.loading
155
- pr.mu.Unlock()
156
- <-flight.done
157
- return flight.snapshot, flight.err
158
- }
159
- flight := &providerLoad{done: make(chan struct{})}
160
- pr.loading = flight
161
- pr.mu.Unlock()
162
-
163
- snapshot, err := pr.buildSnapshot()
164
- pr.mu.Lock()
165
- flight.snapshot, flight.err = snapshot, err
166
- if pr.loading == flight {
167
- pr.snapshot, pr.loadErr = snapshot, err
168
- pr.loaded = true
169
- pr.loading = nil
170
- }
171
- close(flight.done)
172
- pr.mu.Unlock()
173
- return snapshot, err
174
- }
175
-
176
- // buildSnapshot serializes native acquisition separately from publication.
177
- // Repeated invalidations can leave old callers waiting, but only one Program
178
- // is built at a time and every acquired Program closes before this returns.
179
- func (pr *Provider) buildSnapshot() (*providerSnapshot, error) {
180
- pr.buildMu.Lock()
181
- defer pr.buildMu.Unlock()
182
-
183
- cwd := pr.cwd
184
- if abs, err := filepath.Abs(cwd); err == nil {
185
- cwd = abs
186
- }
187
- cwd = shimtspath.ResolvePath(cwd)
188
-
189
- prog, _, err := driver.LoadProgram(cwd, pr.tsconfig, driver.LoadProgramOptions{})
190
- if err != nil {
191
- return nil, err
192
- }
193
- if prog == nil {
194
- return nil, fmt.Errorf("graphsymbols: could not load %s/%s", cwd, pr.tsconfig)
195
- }
196
- defer func() { _ = prog.Close() }()
197
-
198
- snapshot := &providerSnapshot{
199
- graph: graph.Build(prog),
200
- sources: graph.SourceTexts(prog),
201
- caseSensitive: prog.TSProgram.UseCaseSensitiveFileNames(),
202
- files: map[string]string{},
203
- nodesByFile: map[string][]*graph.Node{},
204
- edgesByFile: map[string][]*graph.Edge{},
205
- incoming: map[string][]*graph.Edge{},
206
- }
207
- for file := range snapshot.sources {
208
- snapshot.files[snapshot.fileKey(file)] = file
209
- }
210
- for _, node := range snapshot.graph.Nodes {
211
- if surfaceableNode(node) {
212
- key := snapshot.fileKey(node.File)
213
- snapshot.nodesByFile[key] = append(snapshot.nodesByFile[key], node)
214
- }
215
- }
216
- for _, edge := range snapshot.graph.Edges {
217
- file := edgeSourceFile(edge)
218
- if file != "" {
219
- key := snapshot.fileKey(file)
220
- snapshot.edgesByFile[key] = append(snapshot.edgesByFile[key], edge)
221
- }
222
- if !isStructuralEdge(edge.Kind) {
223
- snapshot.incoming[edge.To] = append(snapshot.incoming[edge.To], edge)
224
- }
225
- }
226
- return snapshot, nil
227
- }
228
-
229
- // Invalidate discards the cached graph so the next DocumentSymbols/References
230
- // call rebuilds it against the current on-disk sources. The proxy calls this on
231
- // didChange/didSave; without it the first request's snapshot would answer every
232
- // later request for the process lifetime.
233
- // Already-running requests retain their immutable snapshot until they return.
234
- // This transition never waits for native compilation; a revoked flight may
235
- // finish its existing requests but cannot publish over the replacement state.
236
- //
237
- // @evidence contracts/common.md#principled-implementation The publication mutex clears the snapshot and cached error so future queries acquire a new saved-source view. Flight publication is coherent, but acquisition is not an atomic filesystem snapshot.
238
- // @evidence contracts/common.md#clear-and-simple-design One transition resets loaded state and snapshot ownership; each query uses a single captured snapshot rather than separately mutable graph and source fields.
239
- // @evidence contracts/common.md#prohibited-implementation-shortcuts Invalidation responds to the caller's actual project-state notifications; it does not fabricate fresh facts from a quiet watcher or suppress a retained load failure.
240
- // @evidence contracts/common.md#meaningful-documentation Native prose distinguishes future requests from in-flight readers and names the saved-source rebuild boundary under documentation-skill guidance.
241
- // @evidenceExclude contracts/portability.md#os-neutral-implementation This state transition performs no filesystem, path comparison or process operation; load retains the native project boundary.
242
- // @evidence contracts/performance.md#efficient-algorithms Resetting the owner clears a constant number of references without walking graph entries or copying retained source bytes.
243
- // @evidence contracts/performance.md#reuse-equivalent-work Clearing loaded state, errors and the authorized flight revokes reuse for subsequent queries. Publication checks exact flight identity under the mutex, so a revoked load cannot overwrite newer state; its existing callers can still finish on its own result.
244
- // @evidence contracts/performance.md#bound-retention-and-release-resources Owner snapshot/source/index/error references are dropped together, but invalidation does not cancel running or queued native builds. Repeated invalidations can retain uncapped old flights and request-held snapshots until their work and references finish; no deadline or cancellation policy is provided here.
245
- func (pr *Provider) Invalidate() {
246
- pr.mu.Lock()
247
- defer pr.mu.Unlock()
248
- pr.loaded = false
249
- pr.loading = nil
250
- pr.snapshot = nil
251
- pr.loadErr = nil
252
- }
253
-
254
- // DocumentSymbols returns the declarations in uri as a hierarchy: top-level
255
- // declarations at the root, class/interface members nested under their owner.
256
- // Unknown files return an empty list; project-load failures return an error.
257
- // Declaration ranges also serve as selection ranges because the graph does
258
- // not separately record identifier spans. The result uses saved UTF-8 source
259
- // converted to fixed UTF-16 coordinates, matching the proxy's session policy.
260
- //
261
- // @evidence contracts/common.md#principled-implementation Compiler graph nodes are grouped by qualified owner, excluding file-module and external leaves. Strict owner prefixes terminate nesting; saved bytes supply valid UTF-16 ranges, with declaration spans used for selection as documented.
262
- // @evidence contracts/common.md#clear-and-simple-design The method selects a snapshot and file, then delegates hierarchy and range construction to focused helpers. It does not reproduce compiler declaration discovery.
263
- // @evidence contracts/common.md#prohibited-implementation-shortcuts Actual graph declarations determine the outline; source strings are not searched for expected names and foreign LSP methods remain unchanged.
264
- // @evidence contracts/common.md#meaningful-documentation Native prose specifies empty-file handling, load failure, saved-source coordinates and the selection-range limitation under the documentation skill's units and absence guidance.
265
- // @evidence contracts/portability.md#os-neutral-implementation File URIs are decoded to native paths and looked up using the snapshot's compiler case policy. The provider preserves the compiler's identity decisions rather than claiming to measure every directory's native case capability independently.
266
- // @evidence contracts/performance.md#efficient-algorithms A cold query first loads and indexes the native project. A cached query selects file-local nodes, hashes qualified-name bytes, groups owners and sorts siblings in at most O(N log N) comparisons, including tied-name comparisons. One source-byte line index is shared, but each range still skips leading trivia, binary-searches line starts and decodes its line prefixes to UTF-16; deeply nested owners also consume recursive stack depth.
267
- // @evidence contracts/performance.md#reuse-equivalent-work load shares one flight, graph, source set and indexes per invalidation generation, including a load error. Native builds are serialized separately from short publication/invalidation transitions; exact flight identity prevents revoked loads from republishing. Actual caller invalidation remains required on changed project state.
268
- // @evidence contracts/performance.md#bound-retention-and-release-resources Graph extraction defers Program.Close but ignores its returned error. Snapshot/index/source bytes, revoked in-flight generations, hierarchy depth and returned symbols have no cap here; invalidation does not cancel queued builds. Query-local maps and line indexes are not retained as history, while results can share name strings with the captured graph. Native loading supplies no deadline in this method.
269
- func (pr *Provider) DocumentSymbols(uri string) ([]lspserver.LSPDocumentSymbol, error) {
270
- snapshot, err := pr.load()
271
- if err != nil {
272
- return nil, err
273
- }
274
- file, ok := snapshot.fileFromURI(uri)
275
- if !ok {
276
- return []lspserver.LSPDocumentSymbol{}, nil
277
- }
278
- text := snapshot.sources[file]
279
-
280
- fileNodes := snapshot.nodesByFile[snapshot.fileKey(file)]
281
- return buildDocumentSymbols(fileNodes, text), nil
282
- }
283
-
284
- // References returns the usage locations of the symbol at pos in uri. When
285
- // includeDeclaration is set the symbol's own declaration is added to the result.
286
- // A covering usage expression takes precedence over its enclosing declaration
287
- // body. Non-addressable files or positions return an empty list, and load
288
- // failures return an error. Each edge retains its explicit source-file provenance
289
- // when an assigned implementation lives outside the owning declaration's file.
290
- //
291
- // @evidence contracts/common.md#principled-implementation UTF-16 positions select the narrowest covering usage expression before a declaration. Incoming non-export edges provide uses, explicit edge files preserve assigned-implementation provenance, and exact URI/byte-span keys remove duplicate locations.
292
- // @evidence contracts/common.md#clear-and-simple-design Snapshot indexes select the file, candidate symbol and incoming edges. One location builder owns source lookup, byte-to-UTF-16 conversion and deduplication for both uses and the optional declaration.
293
- // @evidence contracts/common.md#prohibited-implementation-shortcuts Recorded graph endpoints supply target identity; the graph can also contain artifact relations, so an endpoint is not by itself a checker-binding certificate. Covering uses take precedence over declaration fallback without textual name guesses. Structural exports are excluded because they are publication facts without use spans.
294
- // @evidence contracts/common.md#meaningful-documentation Native paragraphs describe cursor precedence, declaration inclusion, empty/error outcomes and cross-file edge provenance under documentation-skill guidance.
295
- // @evidence contracts/portability.md#os-neutral-implementation URI resolution uses compiler case-aware snapshot keys. Locations preserve the actual recorded file and encode URI spelling separately from native filesystem identity.
296
- // @evidence contracts/performance.md#efficient-algorithms A cold query includes native project loading and indexing. Cached target selection scans file-local edges and, when needed, nodes; converting the input position also builds its source line index. Incoming edges avoid an all-project scan. Output line indexes are shared per file, but URI/span key construction and two UTF-16 prefix decodings happen for each candidate before duplicate suppression, with source-byte, key-byte and line-prefix costs in addition to entry counts.
297
- // @evidence contracts/performance.md#reuse-equivalent-work Both query methods share the mutex-published graph and its source/index generation until invalidation. Within a response, immutable source text authorizes sharing each file's line index across all its locations.
298
- // @evidence contracts/performance.md#bound-retention-and-release-resources Snapshot nodes, edges, names, keys, source bytes and indexes are uncapped, as are request-local candidate keys, line indexes and returned locations. Invalidation drops current owner references without cancelling old flights or requests. Extraction defers Program.Close with errors ignored; this method adds no load deadline or cancellation. Local lookup state becomes reclaimable after return, and returned URI strings remain caller-owned.
299
- func (pr *Provider) References(uri string, pos lspserver.LSPPosition, includeDeclaration bool) ([]lspserver.LSPLocation, error) {
300
- snapshot, err := pr.load()
301
- if err != nil {
302
- return nil, err
303
- }
304
- file, ok := snapshot.fileFromURI(uri)
305
- if !ok {
306
- return []lspserver.LSPLocation{}, nil
307
- }
308
- text := snapshot.sources[file]
309
- offset, ok := lspPositionToOffset(text, pos)
310
- if !ok {
311
- return []lspserver.LSPLocation{}, nil
312
- }
313
- target := targetNodeAt(snapshot, file, offset)
314
- if target == nil {
315
- return []lspserver.LSPLocation{}, nil
316
- }
317
-
318
- locations := []lspserver.LSPLocation{}
319
- seen := map[string]struct{}{}
320
- lineStarts := map[string][]int{}
321
- add := func(f string, start, end int) {
322
- t, ok := snapshot.sources[f]
323
- if !ok {
324
- return
325
- }
326
- if end < start {
327
- end = start
328
- }
329
- starts := lineStarts[f]
330
- if starts == nil {
331
- starts = graph.ECMALineStarts(t)
332
- lineStarts[f] = starts
333
- }
334
- loc := lspserver.LSPLocation{
335
- URI: uriFromFile(f),
336
- Range: lspserver.LSPRange{
337
- Start: offsetToPositionAt(t, starts, start),
338
- End: offsetToPositionAt(t, starts, end),
339
- },
340
- }
341
- key := fmt.Sprintf("%s:%d:%d", loc.URI, start, end)
342
- if _, dup := seen[key]; dup {
343
- return
344
- }
345
- seen[key] = struct{}{}
346
- locations = append(locations, loc)
347
- }
348
-
349
- // Edges point from a using declaration to the referenced symbol. Assigned
350
- // implementations may carry an explicit File different from that declaration.
351
- //
352
- // A structural edge is not a usage. A module's `exports` edge says the symbol
353
- // stands on that module's public surface — a fact about the declaration, not a
354
- // place it is used — and it carries no span, so counting it as a usage puts a
355
- // phantom reference at the top of the file.
356
- for _, e := range snapshot.incoming[target.ID] {
357
- f := edgeSourceFile(e)
358
- if f == "" {
359
- continue
360
- }
361
- add(f, e.Pos, e.End)
362
- }
363
- if includeDeclaration {
364
- declText := snapshot.sources[target.File]
365
- add(target.File, graph.FirstCodeOffset(declText, target.Pos), target.End)
366
- }
367
- return locations, nil
368
- }
369
-
370
- // buildDocumentSymbols turns a file's graph nodes into a DocumentSymbol forest.
371
- // A node whose owner (its qualified name minus its simple name) is another node
372
- // in the file nests under that owner; everything else is a root.
373
- func buildDocumentSymbols(nodes []*graph.Node, text string) []lspserver.LSPDocumentSymbol {
374
- names := make(map[string]bool, len(nodes))
375
- for _, n := range nodes {
376
- names[n.Name] = true
377
- }
378
- childrenOf := map[string][]*graph.Node{}
379
- var roots []*graph.Node
380
- for _, n := range nodes {
381
- owner := ownerName(n)
382
- if owner != "" && names[owner] {
383
- childrenOf[owner] = append(childrenOf[owner], n)
384
- } else {
385
- roots = append(roots, n)
386
- }
387
- }
388
- sortNodes(roots)
389
- starts := graph.ECMALineStarts(text)
390
- out := make([]lspserver.LSPDocumentSymbol, 0, len(roots))
391
- for _, r := range roots {
392
- out = append(out, buildSymbol(r, childrenOf, text, starts))
393
- }
394
- return out
395
- }
396
-
397
- // buildSymbol materializes one node (and, recursively, the nodes owned by it)
398
- // as an LSPDocumentSymbol. The owner relation is a strict name-prefix, so the
399
- // recursion terminates (no cycles).
400
- func buildSymbol(n *graph.Node, childrenOf map[string][]*graph.Node, text string, starts []int) lspserver.LSPDocumentSymbol {
401
- sym := nodeToSymbol(n, text, starts)
402
- kids := childrenOf[n.Name]
403
- sortNodes(kids)
404
- for _, k := range kids {
405
- sym.Children = append(sym.Children, buildSymbol(k, childrenOf, text, starts))
406
- }
407
- return sym
408
- }
409
-
410
- func nodeToSymbol(n *graph.Node, text string, starts []int) lspserver.LSPDocumentSymbol {
411
- start := graph.FirstCodeOffset(text, n.Pos)
412
- end := n.End
413
- if end < start {
414
- end = start
415
- }
416
- rng := lspserver.LSPRange{
417
- Start: offsetToPositionAt(text, starts, start),
418
- End: offsetToPositionAt(text, starts, end),
419
- }
420
- // A quoted member can have an empty or whitespace-only semantic name. Keep
421
- // that graph identity, but quote its display label because LSP symbol names
422
- // must contain a visible character.
423
- name := n.Simple
424
- if strings.TrimSpace(name) == "" {
425
- name = strconv.Quote(name)
426
- }
427
- return lspserver.LSPDocumentSymbol{
428
- Name: name,
429
- Kind: symbolKind(n.Kind),
430
- Range: rng,
431
- // The graph does not record the identifier's own span separately, so the
432
- // selection range reuses the declaration range. LSP only requires it to be
433
- // contained in Range, which this trivially satisfies.
434
- SelectionRange: rng,
435
- }
436
- }
437
-
438
- // ownerName is the qualified name of a node's owner (a class/interface/namespace),
439
- // or "" for a top-level declaration. It strips the trailing ".<simple>" using the
440
- // node's recorded simple name so a quoted member whose name contains a dot splits
441
- // exactly.
442
- func ownerName(n *graph.Node) string {
443
- if (n.Simple == "" && !n.HasSimple) || n.Name == n.Simple {
444
- return ""
445
- }
446
- suffix := "." + n.Simple
447
- if strings.HasSuffix(n.Name, suffix) {
448
- return n.Name[:len(n.Name)-len(suffix)]
449
- }
450
- return ""
451
- }
452
-
453
- // symbolKind maps a graph node kind onto the closest LSP SymbolKind. LSP has no
454
- // dedicated kind for a type alias; Struct keeps it in the named-type bucket
455
- // without reporting it as a runtime class.
456
- func symbolKind(k graph.NodeKind) lspserver.LSPSymbolKind {
457
- switch k {
458
- case graph.NodeFunction:
459
- return lspserver.LSPSymbolKindFunction
460
- case graph.NodeClass:
461
- return lspserver.LSPSymbolKindClass
462
- case graph.NodeInterface:
463
- return lspserver.LSPSymbolKindInterface
464
- case graph.NodeEnum:
465
- return lspserver.LSPSymbolKindEnum
466
- case graph.NodeMethod:
467
- return lspserver.LSPSymbolKindMethod
468
- case graph.NodeTypeAlias:
469
- return lspserver.LSPSymbolKindStruct
470
- case graph.NodeVariable:
471
- return lspserver.LSPSymbolKindVariable
472
- default:
473
- return lspserver.LSPSymbolKindVariable
474
- }
475
- }
476
-
477
- // targetNodeAt prefers the narrowest usage expression at the cursor, then the
478
- // narrowest declaration. Declaration spans include bodies, so selecting them
479
- // first would resolve every call in a function body to the containing function.
480
- func targetNodeAt(snapshot *providerSnapshot, file string, offset int) *graph.Node {
481
- key := snapshot.fileKey(file)
482
- var usage *graph.Edge
483
- for _, edge := range snapshot.edgesByFile[key] {
484
- if isStructuralEdge(edge.Kind) || offset < edge.Pos || offset >= edge.End {
485
- continue
486
- }
487
- if snapshot.graph.Nodes[edge.To] == nil {
488
- continue
489
- }
490
- if usage == nil || edge.End-edge.Pos < usage.End-usage.Pos {
491
- usage = edge
492
- }
493
- }
494
- if usage != nil {
495
- return snapshot.graph.Nodes[usage.To]
496
- }
497
- var best *graph.Node
498
- for _, n := range snapshot.nodesByFile[key] {
499
- if offset >= n.Pos && offset < n.End {
500
- if best == nil || (n.End-n.Pos) < (best.End-best.Pos) {
501
- best = n
502
- }
503
- }
504
- }
505
- return best
506
- }
507
-
508
- // edgeSourceFile respects the graph's assigned-implementation provenance.
509
- func edgeSourceFile(edge *graph.Edge) string {
510
- if edge.File != "" {
511
- return edge.File
512
- }
513
- return graph.NodeFile(edge.From)
514
- }
515
-
516
- // isStructuralEdge reports an edge that records where a symbol stands rather than
517
- // a place it is used.
518
- func isStructuralEdge(kind graph.EdgeKind) bool {
519
- return kind == graph.EdgeExports
520
- }
521
-
522
- // surfaceableNode reports whether a graph node should appear as a user-facing
523
- // LSP outline entry or declaration fallback. A per-file module node (NodeModule) carries
524
- // the source file path as its name and spans the whole file, so surfacing it
525
- // would put the absolute path in the outline and swallow whole-file reference
526
- // queries. External boundary leaves and nodes without a recorded member name
527
- // are likewise omitted from declaration candidates. A recorded usage edge can
528
- // still identify an external reference target. HasSimple preserves valid empty
529
- // string names rather than treating them as absence.
530
- func surfaceableNode(n *graph.Node) bool {
531
- return !n.External && n.Kind != graph.NodeModule && (n.Simple != "" || n.HasSimple)
532
- }
533
-
534
- func sortNodes(nodes []*graph.Node) {
535
- sort.Slice(nodes, func(i, j int) bool {
536
- if nodes[i].Pos != nodes[j].Pos {
537
- return nodes[i].Pos < nodes[j].Pos
538
- }
539
- return nodes[i].Name < nodes[j].Name
540
- })
541
- }
542
-
543
- // filePathFromURI decodes a file:// uri to an absolute OS path. It mirrors the
544
- // proxy's own unexported converter so this package stays self-contained.
545
- func filePathFromURI(raw string) (string, bool) {
546
- parsed, err := url.Parse(raw)
547
- if err != nil || parsed.Scheme != "file" {
548
- return "", false
549
- }
550
- path := parsed.Path
551
- // RFC 8089 gives localhost the same local meaning as an absent authority.
552
- if parsed.Host != "" && !strings.EqualFold(parsed.Host, "localhost") {
553
- path = "//" + parsed.Host + path
554
- }
555
- if path == "" {
556
- return "", false
557
- }
558
- if os.PathSeparator == '\\' && strings.HasPrefix(path, "/") && len(path) >= 3 && path[2] == ':' {
559
- path = path[1:]
560
- }
561
- abs, err := filepath.Abs(path)
562
- if err != nil {
563
- return "", false
564
- }
565
- return abs, true
566
- }
567
-
568
- // uriFromFile encodes an OS path as a file:// uri.
569
- func uriFromFile(path string) string {
570
- slashed := filepath.ToSlash(path)
571
- if !strings.HasPrefix(slashed, "/") {
572
- slashed = "/" + slashed
573
- }
574
- return (&url.URL{Scheme: "file", Path: slashed}).String()
575
- }
576
-
577
- // offsetToPosition converts a byte offset into an LSP Position (0-based line,
578
- // UTF-16 code-unit column). The column unit is the session's negotiated
579
- // PositionEncodingKind, which the proxy's constrainInitializePositionEncoding
580
- // pins to UTF-16 for every ttscserver session.
581
- func offsetToPosition(text string, offset int) lspserver.LSPPosition {
582
- return offsetToPositionAt(text, graph.ECMALineStarts(text), offset)
583
- }
584
-
585
- // offsetToPositionAt shares a source's line index across all ranges in a
586
- // response. The index and text must come from the same immutable snapshot.
587
- func offsetToPositionAt(text string, starts []int, offset int) lspserver.LSPPosition {
588
- if offset < 0 {
589
- offset = 0
590
- }
591
- if offset > len(text) {
592
- offset = len(text)
593
- }
594
- line := sort.Search(len(starts), func(i int) bool { return starts[i] > offset }) - 1
595
- if line < 0 {
596
- line = 0
597
- }
598
- lineStart := starts[line]
599
- character := 0
600
- for i := lineStart; i < offset; {
601
- r, size := utf8.DecodeRuneInString(text[i:])
602
- if size == 0 {
603
- break
604
- }
605
- if n := utf16.RuneLen(r); n > 0 {
606
- character += n
607
- } else {
608
- character++
609
- }
610
- i += size
611
- }
612
- return lspserver.LSPPosition{Line: line, Character: character}
613
- }
614
-
615
- // lspPositionToOffset converts an LSP Position (0-based line, UTF-16 column —
616
- // the encoding the proxy's constrainInitializePositionEncoding pins for every
617
- // ttscserver session) into a byte offset. It returns (offset, false) when the
618
- // position points past the end of the text so the caller can treat it as "no
619
- // symbol here".
620
- func lspPositionToOffset(text string, pos lspserver.LSPPosition) (int, bool) {
621
- if pos.Line < 0 || pos.Character < 0 {
622
- return 0, false
623
- }
624
- starts := graph.ECMALineStarts(text)
625
- if pos.Line >= len(starts) {
626
- return len(text), false
627
- }
628
- i := starts[pos.Line]
629
- lineEnd := graph.LineEnd(text, starts, pos.Line)
630
- units := 0
631
- for units < pos.Character {
632
- if i >= lineEnd {
633
- return len(text), false
634
- }
635
- r, size := utf8.DecodeRuneInString(text[i:])
636
- if size == 0 {
637
- return len(text), false
638
- }
639
- n := utf16.RuneLen(r)
640
- if n <= 0 {
641
- n = 1
642
- }
643
- if units+n > pos.Character {
644
- return i, false
645
- }
646
- units += n
647
- i += size
648
- }
649
- return i, true
650
- }