ttsc 0.18.3 → 0.19.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.
- package/README.md +5 -6
- package/cmd/graphbench/main.go +1 -1
- package/cmd/platform/main.go +1 -48
- package/cmd/ttsc/build_branches_test.go +5 -1
- package/cmd/ttsc/main.go +0 -50
- package/cmd/ttsc/main_alias_branches_test.go +6 -12
- package/cmd/ttscgraph/dump.go +13 -3
- package/cmd/ttscgraph/main.go +18 -12
- package/cmd/ttscgraph/run_help_version_and_bad_flags_exit_codes_test.go +23 -5
- package/cmd/ttscgraph/serve.go +863 -0
- package/cmd/ttscgraph/serve_protocol_rejects_invalid_request_line_test.go +43 -0
- package/cmd/ttscgraph/serve_protocol_retries_session_creation_test.go +83 -0
- package/cmd/ttscgraph/serve_protocol_reuses_session_test.go +35 -0
- package/cmd/ttscgraph/serve_session_applies_source_edit_incrementally_test.go +55 -0
- package/cmd/ttscgraph/serve_session_fails_closed_on_deleted_config_test.go +68 -0
- package/cmd/ttscgraph/serve_session_fails_closed_on_invalid_config_test.go +50 -0
- package/cmd/ttscgraph/serve_session_keeps_missing_literal_root_unchanged_test.go +46 -0
- package/cmd/ttscgraph/serve_session_rebuilds_import_graph_change_test.go +60 -0
- package/cmd/ttscgraph/serve_session_reloads_created_literal_root_test.go +44 -0
- package/cmd/ttscgraph/serve_session_reloads_extended_config_change_test.go +45 -0
- package/cmd/ttscgraph/serve_session_reloads_newly_resolved_conditional_export_test.go +60 -0
- package/cmd/ttscgraph/serve_session_reloads_newly_resolved_config_types_test.go +44 -0
- package/cmd/ttscgraph/serve_session_reloads_newly_resolved_excluded_import_test.go +62 -0
- package/cmd/ttscgraph/serve_session_reloads_newly_resolved_package_export_test.go +46 -0
- package/cmd/ttscgraph/serve_session_reloads_newly_resolved_package_import_test.go +46 -0
- package/cmd/ttscgraph/serve_session_reloads_newly_resolved_path_alias_test.go +47 -0
- package/cmd/ttscgraph/serve_session_reloads_newly_resolved_reference_path_test.go +42 -0
- package/cmd/ttscgraph/serve_session_reloads_newly_resolved_root_dirs_target_test.go +46 -0
- package/cmd/ttscgraph/serve_session_reloads_newly_resolved_type_reference_test.go +42 -0
- package/cmd/ttscgraph/serve_session_reloads_root_file_set_test.go +52 -0
- package/cmd/ttscgraph/serve_session_reloads_source_deleted_during_load_test.go +60 -0
- package/cmd/ttscgraph/serve_session_reloads_transitive_project_reference_test.go +55 -0
- package/cmd/ttscgraph/serve_session_reloads_when_program_plugin_linked_test.go +53 -0
- package/cmd/ttscgraph/serve_session_reuses_unchanged_preamble_source_test.go +45 -0
- package/cmd/ttscgraph/serve_session_reuses_unchanged_snapshot_test.go +52 -0
- package/cmd/ttscgraph/serve_session_revisits_source_edited_during_load_test.go +61 -0
- package/cmd/ttscgraph/serve_session_tolerates_os_invalid_resolution_candidate_test.go +56 -0
- package/cmd/ttscgraph/serve_session_tracks_all_module_specifier_forms_test.go +41 -0
- package/cmd/ttscgraph/serve_session_tracks_diamond_project_references_once_test.go +61 -0
- package/cmd/ttscserver/main.go +5 -3
- package/driver/lsp.go +13 -1
- package/driver/plugins.go +56 -5
- package/driver/program.go +25 -10
- package/driver/rewrite.go +381 -21
- package/driver/session.go +11 -3
- package/go.mod +1 -0
- package/internal/graph/build.go +297 -3
- package/internal/graph/build_records_a_node_per_top_level_declaration_test.go +16 -2
- package/internal/graph/diagnostics.go +5 -4
- package/internal/graph/dump.go +60 -4
- package/internal/graph/dump_is_a_function_of_the_source_test.go +71 -0
- package/internal/graph/dump_serializes_the_full_graph_test.go +16 -4
- package/internal/graph/edges.go +122 -11
- package/internal/graph/exports.go +74 -17
- package/internal/graph/graph.go +33 -1
- package/internal/graph/node_identity_is_position_invariant_test.go +4 -2
- package/internal/graph/resolve.go +24 -0
- package/internal/graphsymbols/provider.go +50 -14
- package/internal/lspserver/lsp_native_plugin_source.go +77 -23
- package/internal/lspserver/lsp_plugin.go +19 -2
- package/internal/lspserver/lsp_proxy.go +244 -52
- package/internal/lspserver/lsp_server.go +35 -27
- package/internal/lspserver/lsp_symbols.go +25 -16
- package/lib/TtscCompiler.d.ts +13 -2
- package/lib/TtscCompiler.js +30 -20
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/TtscService.js +9 -7
- package/lib/TtscService.js.map +1 -1
- package/lib/compiler/internal/compileProjectInMemory.js +15 -1
- package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
- package/lib/compiler/internal/project/createNativeProjectContextArgs.d.ts +3 -0
- package/lib/compiler/internal/project/createNativeProjectContextArgs.js +14 -0
- package/lib/compiler/internal/project/createNativeProjectContextArgs.js.map +1 -0
- package/lib/compiler/internal/project/readProjectConfig.js +53 -4
- package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
- package/lib/compiler/internal/project/resolveProjectConfig.d.ts +9 -2
- package/lib/compiler/internal/project/resolveProjectConfig.js +45 -16
- package/lib/compiler/internal/project/resolveProjectConfig.js.map +1 -1
- package/lib/compiler/internal/residentTransformProcess.d.ts +16 -5
- package/lib/compiler/internal/residentTransformProcess.js +68 -20
- package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +18 -0
- package/lib/compiler/internal/runBuild.js +220 -24
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/runSingleFileEmit.js +7 -4
- package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
- package/lib/compiler/internal/sharedHostHelpers.d.ts +16 -0
- package/lib/compiler/internal/sharedHostHelpers.js +23 -0
- package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.js +18 -1
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.js +34 -8
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/flags/parser.d.ts +17 -0
- package/lib/flags/parser.js +11 -1
- package/lib/flags/parser.js.map +1 -1
- package/lib/flags/schema.js +7 -0
- package/lib/flags/schema.js.map +1 -1
- package/lib/launcher/internal/prepareExecution.js +21 -2
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/runTtsc.js +13 -53
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.js +11 -0
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +27 -11
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +81 -0
- package/lib/launcher/internal/runtimeHooks.js +632 -43
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/launcher/internal/servedSourceMap.d.ts +25 -0
- package/lib/launcher/internal/servedSourceMap.js +164 -0
- package/lib/launcher/internal/servedSourceMap.js.map +1 -0
- package/lib/plugin/internal/buildSourcePlugin.d.ts +146 -2
- package/lib/plugin/internal/buildSourcePlugin.js +651 -113
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.d.ts +7 -0
- package/lib/plugin/internal/loadProjectPlugins.js +33 -10
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/structures/ITtscCompilerContext.d.ts +16 -2
- package/lib/structures/ITtscCompilerResult.d.ts +8 -5
- package/lib/structures/ITtscPluginCapabilities.d.ts +8 -0
- package/lib/structures/ITtscPluginFactoryContext.d.ts +14 -0
- package/lib/structures/internal/ITtscParsedProjectConfig.d.ts +3 -0
- package/lib/structures/internal/ITtscProjectIdentity.d.ts +17 -0
- package/lib/structures/internal/ITtscProjectIdentity.js +3 -0
- package/lib/structures/internal/ITtscProjectIdentity.js.map +1 -0
- package/lib/structures/internal/TtscCommonOptions.d.ts +8 -0
- package/package.json +11 -8
- package/shim/ast/lint.go +10 -0
- package/shim/ast/shim.go +44 -0
- package/shim/checker/enums_gen.go +19 -0
- package/shim/checker/shim.go +130 -0
- package/shim/diagnosticwriter/lint.go +30 -3
- package/shim/printer/shim.go +22 -4
- package/shim/scanner/regexp.go +27 -0
- package/src/TtscCompiler.ts +29 -22
- package/src/TtscService.ts +15 -7
- package/src/compiler/internal/compileProjectInMemory.ts +18 -1
- package/src/compiler/internal/project/createNativeProjectContextArgs.ts +15 -0
- package/src/compiler/internal/project/readProjectConfig.ts +61 -5
- package/src/compiler/internal/project/resolveProjectConfig.ts +51 -21
- package/src/compiler/internal/residentTransformProcess.ts +95 -21
- package/src/compiler/internal/runBuild.ts +313 -32
- package/src/compiler/internal/runSingleFileEmit.ts +7 -4
- package/src/compiler/internal/sharedHostHelpers.ts +24 -0
- package/src/compiler/internal/startResidentTransform.ts +21 -1
- package/src/compiler/internal/transformProjectInMemory.ts +49 -6
- package/src/flags/parser.ts +28 -1
- package/src/flags/schema.ts +8 -0
- package/src/launcher/internal/prepareExecution.ts +22 -2
- package/src/launcher/internal/runTtsc.ts +13 -61
- package/src/launcher/internal/runTtscserver.ts +14 -0
- package/src/launcher/internal/runTtsx.ts +28 -13
- package/src/launcher/internal/runtimeHooks.ts +771 -53
- package/src/launcher/internal/servedSourceMap.ts +183 -0
- package/src/plugin/internal/buildSourcePlugin.ts +859 -122
- package/src/plugin/internal/loadProjectPlugins.ts +36 -6
- package/src/structures/ITtscCompilerContext.ts +17 -2
- package/src/structures/ITtscCompilerResult.ts +8 -5
- package/src/structures/ITtscPluginCapabilities.ts +9 -0
- package/src/structures/ITtscPluginFactoryContext.ts +15 -0
- package/src/structures/internal/ITtscParsedProjectConfig.ts +3 -0
- package/src/structures/internal/ITtscProjectIdentity.ts +17 -0
- package/src/structures/internal/TtscCommonOptions.ts +8 -0
- package/tools/shim_audit/baseline.json +16 -7
- package/tools/shim_audit/main.go +777 -71
- package/tools/shim_audit/main_test.go +28 -1
- package/tools/shim_audit/producer_exemptions_require_rationale_test.go +42 -0
- package/tools/shim_audit/producer_surface_includes_exposed_methods_test.go +154 -0
- package/tools/shim_audit/producer_surface_rejects_unproducible_reference_test.go +142 -0
- package/utility/host.go +46 -20
- package/utility/serve.go +1 -1
- package/shim/checker/signature_api_completeness.go +0 -58
|
@@ -21,8 +21,9 @@ const nativePluginCommandStderrLimit = 1024 * 1024
|
|
|
21
21
|
// passes through TTSC_LSP_PLUGINS_JSON after running normal project plugin
|
|
22
22
|
// discovery and source-plugin builds.
|
|
23
23
|
type NativePluginManifest struct {
|
|
24
|
-
Plugins
|
|
25
|
-
LSPPlugins
|
|
24
|
+
Plugins []NativePluginConfigEntry `json:"plugins"`
|
|
25
|
+
LSPPlugins []NativeLSPPluginEntry `json:"lspPlugins"`
|
|
26
|
+
ProjectContext json.RawMessage `json:"projectContext,omitempty"`
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
// NativePluginConfigEntry mirrors the compact sidecar protocol used by
|
|
@@ -36,9 +37,10 @@ type NativePluginConfigEntry struct {
|
|
|
36
37
|
// NativeLSPPluginEntry names one built sidecar that opted into the LSP
|
|
37
38
|
// protocol through its JavaScript descriptor capabilities.
|
|
38
39
|
type NativeLSPPluginEntry struct {
|
|
39
|
-
Binary
|
|
40
|
-
Name
|
|
41
|
-
|
|
40
|
+
Binary string `json:"binary"`
|
|
41
|
+
Name string `json:"name,omitempty"`
|
|
42
|
+
ProjectContextArgs bool `json:"projectContextArgs,omitempty"`
|
|
43
|
+
Stage string `json:"stage,omitempty"`
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
// NativePluginSourceOptions configures a sidecar-backed PluginSource.
|
|
@@ -52,11 +54,12 @@ type NativePluginSourceOptions struct {
|
|
|
52
54
|
// NativePluginSource implements PluginSource by delegating to native sidecars
|
|
53
55
|
// that explicitly support ttsc's LSP subcommands.
|
|
54
56
|
type NativePluginSource struct {
|
|
55
|
-
cwd
|
|
56
|
-
err
|
|
57
|
-
plugins
|
|
58
|
-
pluginsJSON
|
|
59
|
-
|
|
57
|
+
cwd string
|
|
58
|
+
err io.Writer
|
|
59
|
+
plugins []NativeLSPPluginEntry
|
|
60
|
+
pluginsJSON string
|
|
61
|
+
projectContextJSON string
|
|
62
|
+
tsconfig string
|
|
60
63
|
|
|
61
64
|
commandIDs []string
|
|
62
65
|
codeActionKinds []string
|
|
@@ -110,40 +113,88 @@ func NewNativePluginSource(opts NativePluginSourceOptions) (*NativePluginSource,
|
|
|
110
113
|
if err != nil {
|
|
111
114
|
return nil, fmt.Errorf("ttscserver: encode plugin manifest: %w", err)
|
|
112
115
|
}
|
|
116
|
+
sidecarCwd := opts.Cwd
|
|
117
|
+
sidecarTsconfig := opts.Tsconfig
|
|
118
|
+
if len(manifest.ProjectContext) > 0 {
|
|
119
|
+
var identity struct {
|
|
120
|
+
PhysicalConfigPath string `json:"physicalConfigPath"`
|
|
121
|
+
PhysicalProjectRoot string `json:"physicalProjectRoot"`
|
|
122
|
+
}
|
|
123
|
+
if err := json.Unmarshal(manifest.ProjectContext, &identity); err != nil {
|
|
124
|
+
return nil, fmt.Errorf("ttscserver: decode project context: %w", err)
|
|
125
|
+
}
|
|
126
|
+
if strings.TrimSpace(identity.PhysicalProjectRoot) != "" {
|
|
127
|
+
sidecarCwd = identity.PhysicalProjectRoot
|
|
128
|
+
}
|
|
129
|
+
if strings.TrimSpace(identity.PhysicalConfigPath) != "" {
|
|
130
|
+
sidecarTsconfig = identity.PhysicalConfigPath
|
|
131
|
+
}
|
|
132
|
+
}
|
|
113
133
|
source := &NativePluginSource{
|
|
114
|
-
cwd:
|
|
115
|
-
err:
|
|
116
|
-
plugins:
|
|
117
|
-
pluginsJSON:
|
|
118
|
-
|
|
119
|
-
|
|
134
|
+
cwd: sidecarCwd,
|
|
135
|
+
err: opts.Err,
|
|
136
|
+
plugins: manifest.LSPPlugins,
|
|
137
|
+
pluginsJSON: string(pluginsJSON),
|
|
138
|
+
projectContextJSON: string(manifest.ProjectContext),
|
|
139
|
+
tsconfig: sidecarTsconfig,
|
|
140
|
+
owners: map[string]NativeLSPPluginEntry{},
|
|
120
141
|
}
|
|
121
142
|
source.discoverCommandIDs()
|
|
122
143
|
return source, nil
|
|
123
144
|
}
|
|
124
145
|
|
|
125
|
-
// Diagnostics asks every LSP-capable sidecar for diagnostics
|
|
126
|
-
|
|
146
|
+
// Diagnostics asks every LSP-capable sidecar for document diagnostics and its
|
|
147
|
+
// separate project publication.
|
|
148
|
+
func (s *NativePluginSource) Diagnostics(doc LSPDocumentVersion) LSPDiagnosticsResult {
|
|
127
149
|
if s == nil || doc.URI == "" {
|
|
128
|
-
return
|
|
150
|
+
return LSPDiagnosticsResult{}
|
|
129
151
|
}
|
|
130
|
-
|
|
152
|
+
out := LSPDiagnosticsResult{}
|
|
131
153
|
for _, plugin := range s.plugins {
|
|
132
154
|
body, err := s.run(plugin, "lsp-diagnostics", "--uri="+doc.URI)
|
|
133
155
|
if err != nil {
|
|
134
156
|
s.log("%v", err)
|
|
135
157
|
continue
|
|
136
158
|
}
|
|
137
|
-
|
|
138
|
-
if err
|
|
159
|
+
result, err := decodeNativeDiagnostics(body)
|
|
160
|
+
if err != nil {
|
|
139
161
|
s.log("ttscserver: %s lsp-diagnostics returned invalid JSON: %v", pluginLabel(plugin), err)
|
|
140
162
|
continue
|
|
141
163
|
}
|
|
142
|
-
out = append(out,
|
|
164
|
+
out.Document = append(out.Document, result.Document...)
|
|
165
|
+
if result.Project == nil {
|
|
166
|
+
continue
|
|
167
|
+
}
|
|
168
|
+
if out.Project == nil {
|
|
169
|
+
copied := *result.Project
|
|
170
|
+
copied.Diagnostics = append([]LSPDiagnostic(nil), result.Project.Diagnostics...)
|
|
171
|
+
out.Project = &copied
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
if out.Project.URI != result.Project.URI {
|
|
175
|
+
s.log("ttscserver: %s returned project diagnostics for %s after %s; replacing the prior project publication", pluginLabel(plugin), result.Project.URI, out.Project.URI)
|
|
176
|
+
copied := *result.Project
|
|
177
|
+
copied.Diagnostics = append([]LSPDiagnostic(nil), result.Project.Diagnostics...)
|
|
178
|
+
out.Project = &copied
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
out.Project.Diagnostics = append(out.Project.Diagnostics, result.Project.Diagnostics...)
|
|
143
182
|
}
|
|
144
183
|
return out
|
|
145
184
|
}
|
|
146
185
|
|
|
186
|
+
func decodeNativeDiagnostics(body []byte) (LSPDiagnosticsResult, error) {
|
|
187
|
+
var result LSPDiagnosticsResult
|
|
188
|
+
if err := json.Unmarshal(body, &result); err == nil {
|
|
189
|
+
return result, nil
|
|
190
|
+
}
|
|
191
|
+
var legacy []LSPDiagnostic
|
|
192
|
+
if err := json.Unmarshal(body, &legacy); err != nil {
|
|
193
|
+
return LSPDiagnosticsResult{}, err
|
|
194
|
+
}
|
|
195
|
+
return LSPDiagnosticsResult{Document: legacy}, nil
|
|
196
|
+
}
|
|
197
|
+
|
|
147
198
|
// CodeActions asks every LSP-capable sidecar for actions matching the request.
|
|
148
199
|
func (s *NativePluginSource) CodeActions(uri string, rng LSPRange, ctx LSPCodeActionContext) []LSPCodeAction {
|
|
149
200
|
if s == nil || uri == "" {
|
|
@@ -353,6 +404,9 @@ func (s *NativePluginSource) runWithStdin(plugin NativeLSPPluginEntry, command s
|
|
|
353
404
|
"--tsconfig=" + s.tsconfig,
|
|
354
405
|
"--plugins-json=" + s.pluginsJSON,
|
|
355
406
|
}
|
|
407
|
+
if plugin.ProjectContextArgs && strings.TrimSpace(s.projectContextJSON) != "" {
|
|
408
|
+
allArgs = append(allArgs, "--project-context-json="+s.projectContextJSON)
|
|
409
|
+
}
|
|
356
410
|
allArgs = append(allArgs, args...)
|
|
357
411
|
cmd := exec.CommandContext(ctx, plugin.Binary, allArgs...)
|
|
358
412
|
cmd.Dir = s.cwd
|
|
@@ -100,6 +100,21 @@ type LSPDocumentVersion struct {
|
|
|
100
100
|
Version *int
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
// LSPProjectDiagnostics is one project-scoped diagnostic publication. URI is
|
|
104
|
+
// the logical selected config URI and Diagnostics use a zero-width start range.
|
|
105
|
+
type LSPProjectDiagnostics struct {
|
|
106
|
+
URI string `json:"uri"`
|
|
107
|
+
Diagnostics []LSPDiagnostic `json:"diagnostics"`
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// LSPDiagnosticsResult separates diagnostics for the requested document from
|
|
111
|
+
// the current project publication so the proxy never copies a project finding
|
|
112
|
+
// onto every open source document.
|
|
113
|
+
type LSPDiagnosticsResult struct {
|
|
114
|
+
Document []LSPDiagnostic `json:"document"`
|
|
115
|
+
Project *LSPProjectDiagnostics `json:"project,omitempty"`
|
|
116
|
+
}
|
|
117
|
+
|
|
103
118
|
// PluginSource is the seam between the LSP proxy and ttsc's plugin
|
|
104
119
|
// pipeline. Returning empty slices/nil is a valid "no contribution"
|
|
105
120
|
// answer; ttscserver still forwards the upstream tsgo response verbatim.
|
|
@@ -112,7 +127,7 @@ type PluginSource interface {
|
|
|
112
127
|
// the field. The proxy appends these to whatever upstream tsgo
|
|
113
128
|
// published, so duplicates between ttsc and tsgo must be deduplicated
|
|
114
129
|
// on the source side.
|
|
115
|
-
Diagnostics(doc LSPDocumentVersion)
|
|
130
|
+
Diagnostics(doc LSPDocumentVersion) LSPDiagnosticsResult
|
|
116
131
|
|
|
117
132
|
// CodeActions contributes additional actions for the given range. The
|
|
118
133
|
// proxy appends them to upstream responses, or answers locally when
|
|
@@ -138,7 +153,9 @@ type PluginSource interface {
|
|
|
138
153
|
type NullPluginSource struct{}
|
|
139
154
|
|
|
140
155
|
// Diagnostics returns no plugin diagnostics.
|
|
141
|
-
func (NullPluginSource) Diagnostics(LSPDocumentVersion)
|
|
156
|
+
func (NullPluginSource) Diagnostics(LSPDocumentVersion) LSPDiagnosticsResult {
|
|
157
|
+
return LSPDiagnosticsResult{}
|
|
158
|
+
}
|
|
142
159
|
|
|
143
160
|
// CodeActions returns no plugin code actions.
|
|
144
161
|
func (NullPluginSource) CodeActions(string, LSPRange, LSPCodeActionContext) []LSPCodeAction {
|
|
@@ -44,6 +44,11 @@ const (
|
|
|
44
44
|
// formats the live (possibly dirty) buffer rather than the on-disk file.
|
|
45
45
|
const formatDocumentCommand = "ttsc.format.document"
|
|
46
46
|
|
|
47
|
+
// utf8BOM is the UTF-8 byte-order mark (U+FEFF, bytes EF BB BF). It is stripped
|
|
48
|
+
// from both the on-disk bytes and the editor buffer before a clean/dirty
|
|
49
|
+
// comparison so a BOM present on only one side does not read as an edit.
|
|
50
|
+
const utf8BOM = "\uFEFF"
|
|
51
|
+
|
|
47
52
|
// ProxyOptions wires the byte-level proxy together. ttscserver creates
|
|
48
53
|
// the upstream pipes around `tsgo --lsp --stdio` and hands the proxy
|
|
49
54
|
// editor stdio plus those pipe ends.
|
|
@@ -64,10 +69,18 @@ type ProxyOptions struct {
|
|
|
64
69
|
// avoid collisions; incoming prefixed ids are mapped back before dispatch.
|
|
65
70
|
ExecuteCommandIDPrefix string
|
|
66
71
|
// SymbolProvider answers textDocument/documentSymbol and
|
|
67
|
-
// textDocument/references
|
|
68
|
-
//
|
|
69
|
-
//
|
|
72
|
+
// textDocument/references from ttsc's compiler-backed code graph. tsgo
|
|
73
|
+
// implements both methods, so the proxy forwards to tsgo whenever it
|
|
74
|
+
// advertises the capability and consults this provider only as a fallback
|
|
75
|
+
// (tsgo did not advertise) or when ForceLocalSymbolProvider is set. Nil leaves
|
|
76
|
+
// both methods forwarded to tsgo unconditionally.
|
|
70
77
|
SymbolProvider SymbolProvider
|
|
78
|
+
// ForceLocalSymbolProvider answers documentSymbol/references from
|
|
79
|
+
// SymbolProvider even when upstream tsgo advertises the capability. It serves
|
|
80
|
+
// a raw-LSP graph consumer (such as @samchon/graph) that wants graph-derived
|
|
81
|
+
// declarations and usages instead of tsgo's language-service answers. Ignored
|
|
82
|
+
// when SymbolProvider is nil.
|
|
83
|
+
ForceLocalSymbolProvider bool
|
|
71
84
|
}
|
|
72
85
|
|
|
73
86
|
// Proxy bridges the editor and an upstream tsgo LSP process, intercepting
|
|
@@ -83,6 +96,7 @@ type Proxy struct {
|
|
|
83
96
|
suppressedExecuteCommandIDs map[string]struct{}
|
|
84
97
|
executeCommandIDPrefix string
|
|
85
98
|
symbolProvider SymbolProvider
|
|
99
|
+
forceLocalSymbolProvider bool
|
|
86
100
|
|
|
87
101
|
writeMu sync.Mutex // serializes WriteFrame calls to editorOut
|
|
88
102
|
upstreamWriteMu sync.Mutex // serializes writes to upstreamIn
|
|
@@ -95,16 +109,21 @@ type Proxy struct {
|
|
|
95
109
|
pendingCommands map[string]struct{}
|
|
96
110
|
pendingInitialize map[string]struct{}
|
|
97
111
|
|
|
98
|
-
capabilityMu
|
|
99
|
-
upstreamCodeActionProvider
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
112
|
+
capabilityMu sync.Mutex
|
|
113
|
+
upstreamCodeActionProvider bool
|
|
114
|
+
upstreamDocumentSymbolProvider bool
|
|
115
|
+
upstreamReferencesProvider bool
|
|
116
|
+
|
|
117
|
+
diagnosticsMu sync.Mutex
|
|
118
|
+
upstreamDiagnostics map[string]cachedDiagnostics
|
|
119
|
+
pluginDiagnostics map[string]cachedDiagnostics
|
|
120
|
+
projectDiagnostics cachedDiagnostics
|
|
121
|
+
projectDiagnosticsURI string
|
|
122
|
+
projectDiagnosticGeneration uint64
|
|
123
|
+
diagnosticGeneration map[string]uint64
|
|
124
|
+
documentGeneration map[string]uint64
|
|
125
|
+
dirtyDocuments map[string]struct{}
|
|
126
|
+
dirtyVersions map[string]*int
|
|
108
127
|
// documentText caches the live editor buffer per uri so the
|
|
109
128
|
// textDocument/formatting handler can format the in-memory text instead
|
|
110
129
|
// of the on-disk file. didOpen / full-sync didChange seed it with the full
|
|
@@ -152,6 +171,7 @@ func NewProxy(opts ProxyOptions) *Proxy {
|
|
|
152
171
|
suppressedExecuteCommandIDs: commandIDSet(opts.SuppressedExecuteCommandIDs),
|
|
153
172
|
executeCommandIDPrefix: opts.ExecuteCommandIDPrefix,
|
|
154
173
|
symbolProvider: opts.SymbolProvider,
|
|
174
|
+
forceLocalSymbolProvider: opts.ForceLocalSymbolProvider,
|
|
155
175
|
asyncErrCh: make(chan error, 1),
|
|
156
176
|
pendingActions: make(map[string]pendingCodeActionRequest),
|
|
157
177
|
pendingAugmentingActions: make(map[string]struct{}),
|
|
@@ -268,10 +288,12 @@ func (p *Proxy) handleEditorEnvelope(env Envelope, body []byte) (bool, error) {
|
|
|
268
288
|
}
|
|
269
289
|
case methodDidSave:
|
|
270
290
|
if env.IsNotification() {
|
|
291
|
+
p.invalidateSymbolProvider()
|
|
271
292
|
p.publishPluginDiagnosticsForDocumentNotification(env)
|
|
272
293
|
}
|
|
273
294
|
case methodDidChange:
|
|
274
295
|
if env.IsNotification() {
|
|
296
|
+
p.invalidateSymbolProvider()
|
|
275
297
|
p.cacheDidChangeText(env)
|
|
276
298
|
if err := p.markDocumentDirty(env); err != nil {
|
|
277
299
|
return false, err
|
|
@@ -427,6 +449,16 @@ func (p *Proxy) takePendingCommand(key string) bool {
|
|
|
427
449
|
return true
|
|
428
450
|
}
|
|
429
451
|
|
|
452
|
+
type codeActionPositionWire struct {
|
|
453
|
+
Line *int `json:"line"`
|
|
454
|
+
Character *int `json:"character"`
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
type codeActionRangeWire struct {
|
|
458
|
+
Start *codeActionPositionWire `json:"start"`
|
|
459
|
+
End *codeActionPositionWire `json:"end"`
|
|
460
|
+
}
|
|
461
|
+
|
|
430
462
|
// decodeCodeActionRequest extracts the request payload so the matching
|
|
431
463
|
// response from upstream can be augmented with ttsc-owned code actions
|
|
432
464
|
// for the same range.
|
|
@@ -435,15 +467,37 @@ func (p *Proxy) decodeCodeActionRequest(env Envelope) (pendingCodeActionRequest,
|
|
|
435
467
|
TextDocument struct {
|
|
436
468
|
URI string `json:"uri"`
|
|
437
469
|
} `json:"textDocument"`
|
|
438
|
-
Range
|
|
470
|
+
Range *codeActionRangeWire `json:"range"`
|
|
439
471
|
Context LSPCodeActionContext `json:"context"`
|
|
440
472
|
}
|
|
441
473
|
if err := json.Unmarshal(env.Params, ¶ms); err != nil {
|
|
442
474
|
return pendingCodeActionRequest{}, false
|
|
443
475
|
}
|
|
476
|
+
if params.TextDocument.URI == "" || params.Range == nil ||
|
|
477
|
+
params.Range.Start == nil || params.Range.End == nil ||
|
|
478
|
+
params.Range.Start.Line == nil || params.Range.Start.Character == nil ||
|
|
479
|
+
params.Range.End.Line == nil || params.Range.End.Character == nil {
|
|
480
|
+
return pendingCodeActionRequest{}, false
|
|
481
|
+
}
|
|
482
|
+
rng := LSPRange{
|
|
483
|
+
Start: LSPPosition{
|
|
484
|
+
Line: *params.Range.Start.Line,
|
|
485
|
+
Character: *params.Range.Start.Character,
|
|
486
|
+
},
|
|
487
|
+
End: LSPPosition{
|
|
488
|
+
Line: *params.Range.End.Line,
|
|
489
|
+
Character: *params.Range.End.Character,
|
|
490
|
+
},
|
|
491
|
+
}
|
|
492
|
+
if rng.Start.Line < 0 || rng.Start.Character < 0 ||
|
|
493
|
+
rng.End.Line < 0 || rng.End.Character < 0 ||
|
|
494
|
+
rng.Start.Line > rng.End.Line ||
|
|
495
|
+
(rng.Start.Line == rng.End.Line && rng.Start.Character > rng.End.Character) {
|
|
496
|
+
return pendingCodeActionRequest{}, false
|
|
497
|
+
}
|
|
444
498
|
return pendingCodeActionRequest{
|
|
445
499
|
uri: params.TextDocument.URI,
|
|
446
|
-
rng:
|
|
500
|
+
rng: rng,
|
|
447
501
|
ctx: params.Context,
|
|
448
502
|
}, true
|
|
449
503
|
}
|
|
@@ -928,9 +982,9 @@ func (p *Proxy) prepareUpstreamPublishDiagnostics(env Envelope) func() {
|
|
|
928
982
|
return nil
|
|
929
983
|
}
|
|
930
984
|
p.rememberUpstreamDiagnostics(params.URI, params.Version, params.Diagnostics)
|
|
931
|
-
generation := p.
|
|
985
|
+
generation, projectGeneration := p.nextDiagnosticsGenerations(params.URI)
|
|
932
986
|
return func() {
|
|
933
|
-
p.publishMergedPluginDiagnostics(params.URI, params.Version, true, generation)
|
|
987
|
+
p.publishMergedPluginDiagnostics(params.URI, params.Version, true, generation, projectGeneration)
|
|
934
988
|
}
|
|
935
989
|
}
|
|
936
990
|
|
|
@@ -945,8 +999,8 @@ func (p *Proxy) publishPluginDiagnosticsForDocumentNotification(env Envelope) {
|
|
|
945
999
|
return
|
|
946
1000
|
}
|
|
947
1001
|
p.markDocumentClean(params.TextDocument.URI)
|
|
948
|
-
generation := p.
|
|
949
|
-
go p.publishMergedPluginDiagnostics(params.TextDocument.URI, params.TextDocument.Version, false, generation)
|
|
1002
|
+
generation, projectGeneration := p.nextDiagnosticsGenerations(params.TextDocument.URI)
|
|
1003
|
+
go p.publishMergedPluginDiagnostics(params.TextDocument.URI, params.TextDocument.Version, false, generation, projectGeneration)
|
|
950
1004
|
}
|
|
951
1005
|
|
|
952
1006
|
func (p *Proxy) publishPluginDiagnosticsForDidOpen(env Envelope) error {
|
|
@@ -967,12 +1021,12 @@ func (p *Proxy) publishPluginDiagnosticsForDidOpen(env Envelope) error {
|
|
|
967
1021
|
return nil
|
|
968
1022
|
}
|
|
969
1023
|
p.markDocumentClean(params.TextDocument.URI)
|
|
970
|
-
generation := p.
|
|
971
|
-
go p.publishMergedPluginDiagnostics(params.TextDocument.URI, params.TextDocument.Version, false, generation)
|
|
1024
|
+
generation, projectGeneration := p.nextDiagnosticsGenerations(params.TextDocument.URI)
|
|
1025
|
+
go p.publishMergedPluginDiagnostics(params.TextDocument.URI, params.TextDocument.Version, false, generation, projectGeneration)
|
|
972
1026
|
return nil
|
|
973
1027
|
}
|
|
974
1028
|
|
|
975
|
-
func (p *Proxy) publishMergedPluginDiagnostics(uri string, version *int, adoptCachedVersion bool, generation uint64) {
|
|
1029
|
+
func (p *Proxy) publishMergedPluginDiagnostics(uri string, version *int, adoptCachedVersion bool, generation uint64, projectGeneration uint64) {
|
|
976
1030
|
if !p.isLatestPluginDiagnosticsGeneration(uri, generation) || p.isDocumentDirty(uri) {
|
|
977
1031
|
return
|
|
978
1032
|
}
|
|
@@ -980,11 +1034,13 @@ func (p *Proxy) publishMergedPluginDiagnostics(uri string, version *int, adoptCa
|
|
|
980
1034
|
URI: uri,
|
|
981
1035
|
Version: version,
|
|
982
1036
|
})
|
|
983
|
-
version, merged, ok := p.prepareMergedPluginDiagnostics(uri, version, adoptCachedVersion, generation, diagnostics)
|
|
984
|
-
if
|
|
985
|
-
|
|
1037
|
+
version, merged, ok := p.prepareMergedPluginDiagnostics(uri, version, adoptCachedVersion, generation, diagnostics.Document)
|
|
1038
|
+
if ok {
|
|
1039
|
+
p.reportAsyncError(p.writePublishDiagnosticsIfCurrent(uri, version, merged, generation))
|
|
1040
|
+
}
|
|
1041
|
+
if diagnostics.Project != nil {
|
|
1042
|
+
p.reportAsyncError(p.writeProjectDiagnosticsIfCurrent(diagnostics.Project, projectGeneration))
|
|
986
1043
|
}
|
|
987
|
-
p.reportAsyncError(p.writePublishDiagnosticsIfCurrent(uri, version, merged, generation))
|
|
988
1044
|
}
|
|
989
1045
|
|
|
990
1046
|
func (p *Proxy) writePublishDiagnostics(uri string, version *int, diagnostics []json.RawMessage) error {
|
|
@@ -1005,6 +1061,71 @@ func (p *Proxy) writePublishDiagnosticsIfCurrent(uri string, version *int, diagn
|
|
|
1005
1061
|
return WriteFrame(p.editorOut, body)
|
|
1006
1062
|
}
|
|
1007
1063
|
|
|
1064
|
+
func (p *Proxy) writeProjectDiagnosticsIfCurrent(publication *LSPProjectDiagnostics, generation uint64) error {
|
|
1065
|
+
if publication == nil || publication.URI == "" {
|
|
1066
|
+
return nil
|
|
1067
|
+
}
|
|
1068
|
+
diagnostics := append([]LSPDiagnostic(nil), publication.Diagnostics...)
|
|
1069
|
+
for index := range diagnostics {
|
|
1070
|
+
diagnostics[index].Range = LSPRange{}
|
|
1071
|
+
}
|
|
1072
|
+
rawDiagnostics := marshalLSPDiagnostics(diagnostics)
|
|
1073
|
+
p.writeMu.Lock()
|
|
1074
|
+
defer p.writeMu.Unlock()
|
|
1075
|
+
p.diagnosticsMu.Lock()
|
|
1076
|
+
defer p.diagnosticsMu.Unlock()
|
|
1077
|
+
if p.projectDiagnosticGeneration != generation {
|
|
1078
|
+
return nil
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
previousURI := p.projectDiagnosticsURI
|
|
1082
|
+
previousHadDiagnostics := len(p.projectDiagnostics.diagnostics) > 0
|
|
1083
|
+
changedURI := previousURI != "" && previousURI != publication.URI
|
|
1084
|
+
if changedURI {
|
|
1085
|
+
p.projectDiagnosticsURI = ""
|
|
1086
|
+
p.projectDiagnostics = cachedDiagnostics{}
|
|
1087
|
+
if err := WriteFrame(
|
|
1088
|
+
p.editorOut,
|
|
1089
|
+
p.publishDiagnosticsBody(previousURI, nil, p.mergedDiagnosticsLocked(previousURI)),
|
|
1090
|
+
); err != nil {
|
|
1091
|
+
return err
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
p.projectDiagnosticsURI = publication.URI
|
|
1096
|
+
p.projectDiagnostics = cachedDiagnostics{diagnostics: copyRawDiagnostics(rawDiagnostics)}
|
|
1097
|
+
if len(rawDiagnostics) == 0 && !previousHadDiagnostics && !changedURI {
|
|
1098
|
+
return nil
|
|
1099
|
+
}
|
|
1100
|
+
return WriteFrame(
|
|
1101
|
+
p.editorOut,
|
|
1102
|
+
p.publishDiagnosticsBody(publication.URI, nil, p.mergedDiagnosticsLocked(publication.URI)),
|
|
1103
|
+
)
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
func marshalLSPDiagnostics(diagnostics []LSPDiagnostic) []json.RawMessage {
|
|
1107
|
+
rawDiagnostics := make([]json.RawMessage, 0, len(diagnostics))
|
|
1108
|
+
for _, diagnostic := range diagnostics {
|
|
1109
|
+
raw, _ := json.Marshal(diagnostic)
|
|
1110
|
+
rawDiagnostics = append(rawDiagnostics, raw)
|
|
1111
|
+
}
|
|
1112
|
+
return rawDiagnostics
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
func (p *Proxy) mergedDiagnosticsLocked(uri string) []json.RawMessage {
|
|
1116
|
+
upstream := p.upstreamDiagnostics[uri].diagnostics
|
|
1117
|
+
document := p.pluginDiagnostics[uri].diagnostics
|
|
1118
|
+
project := []json.RawMessage(nil)
|
|
1119
|
+
if p.projectDiagnosticsURI == uri {
|
|
1120
|
+
project = p.projectDiagnostics.diagnostics
|
|
1121
|
+
}
|
|
1122
|
+
merged := make([]json.RawMessage, 0, len(upstream)+len(document)+len(project))
|
|
1123
|
+
merged = append(merged, copyRawDiagnostics(upstream)...)
|
|
1124
|
+
merged = append(merged, copyRawDiagnostics(document)...)
|
|
1125
|
+
merged = append(merged, copyRawDiagnostics(project)...)
|
|
1126
|
+
return merged
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1008
1129
|
func (p *Proxy) writeLocalCodeActionsResultIfCurrent(id json.RawMessage, actions []LSPCodeAction, pending pendingCodeActionRequest) error {
|
|
1009
1130
|
p.writeMu.Lock()
|
|
1010
1131
|
defer p.writeMu.Unlock()
|
|
@@ -1288,6 +1409,7 @@ func (p *Proxy) markDocumentDirtyURI(uri string, version *int) bool {
|
|
|
1288
1409
|
delete(p.pluginDiagnostics, uri)
|
|
1289
1410
|
delete(p.upstreamDiagnostics, uri)
|
|
1290
1411
|
p.diagnosticGeneration[uri]++
|
|
1412
|
+
p.projectDiagnosticGeneration++
|
|
1291
1413
|
p.documentGeneration[uri]++
|
|
1292
1414
|
return len(previous.diagnostics) > 0
|
|
1293
1415
|
}
|
|
@@ -1338,7 +1460,14 @@ func documentTextMatchesDisk(uri string, text string) bool {
|
|
|
1338
1460
|
return false
|
|
1339
1461
|
}
|
|
1340
1462
|
disk, err := os.ReadFile(file)
|
|
1341
|
-
|
|
1463
|
+
if err != nil {
|
|
1464
|
+
return false
|
|
1465
|
+
}
|
|
1466
|
+
// Editors commonly strip a leading UTF-8 BOM from the buffer text they send
|
|
1467
|
+
// while it stays on disk (or add one the disk lacks). A raw byte compare would
|
|
1468
|
+
// then misclassify an unedited file as dirty and suppress plugin diagnostics
|
|
1469
|
+
// until the first save, so a single leading BOM is dropped from both sides.
|
|
1470
|
+
return strings.TrimPrefix(string(disk), utf8BOM) == strings.TrimPrefix(text, utf8BOM)
|
|
1342
1471
|
}
|
|
1343
1472
|
|
|
1344
1473
|
func filePathFromURI(raw string) (string, bool) {
|
|
@@ -1363,12 +1492,13 @@ func filePathFromURI(raw string) (string, bool) {
|
|
|
1363
1492
|
return abs, true
|
|
1364
1493
|
}
|
|
1365
1494
|
|
|
1366
|
-
func (p *Proxy)
|
|
1495
|
+
func (p *Proxy) nextDiagnosticsGenerations(uri string) (uint64, uint64) {
|
|
1367
1496
|
p.diagnosticsMu.Lock()
|
|
1368
1497
|
defer p.diagnosticsMu.Unlock()
|
|
1369
1498
|
next := p.diagnosticGeneration[uri] + 1
|
|
1370
1499
|
p.diagnosticGeneration[uri] = next
|
|
1371
|
-
|
|
1500
|
+
p.projectDiagnosticGeneration++
|
|
1501
|
+
return next, p.projectDiagnosticGeneration
|
|
1372
1502
|
}
|
|
1373
1503
|
|
|
1374
1504
|
func (p *Proxy) isLatestPluginDiagnosticsGeneration(uri string, generation uint64) bool {
|
|
@@ -1401,11 +1531,7 @@ func (p *Proxy) prepareMergedPluginDiagnostics(
|
|
|
1401
1531
|
if uri == "" {
|
|
1402
1532
|
return nil, nil, false
|
|
1403
1533
|
}
|
|
1404
|
-
rawDiagnostics :=
|
|
1405
|
-
for _, diagnostic := range diagnostics {
|
|
1406
|
-
raw, _ := json.Marshal(diagnostic)
|
|
1407
|
-
rawDiagnostics = append(rawDiagnostics, raw)
|
|
1408
|
-
}
|
|
1534
|
+
rawDiagnostics := marshalLSPDiagnostics(diagnostics)
|
|
1409
1535
|
inputVersion := copyIntPtr(version)
|
|
1410
1536
|
|
|
1411
1537
|
p.diagnosticsMu.Lock()
|
|
@@ -1433,10 +1559,7 @@ func (p *Proxy) prepareMergedPluginDiagnostics(
|
|
|
1433
1559
|
if len(rawDiagnostics) == 0 && !previousPluginDiagnostics {
|
|
1434
1560
|
return nil, nil, false
|
|
1435
1561
|
}
|
|
1436
|
-
|
|
1437
|
-
merged := make([]json.RawMessage, 0, len(cachedDiagnostics)+len(rawDiagnostics))
|
|
1438
|
-
merged = append(merged, cachedDiagnostics...)
|
|
1439
|
-
merged = append(merged, rawDiagnostics...)
|
|
1562
|
+
merged := p.mergedDiagnosticsLocked(uri)
|
|
1440
1563
|
return copyIntPtr(version), merged, true
|
|
1441
1564
|
}
|
|
1442
1565
|
|
|
@@ -1483,6 +1606,11 @@ func (p *Proxy) augmentInitializeResult(env Envelope) ([]byte, bool) {
|
|
|
1483
1606
|
}
|
|
1484
1607
|
codeActionProvider := caps["codeActionProvider"]
|
|
1485
1608
|
p.setUpstreamCodeActionProvider(codeActionProvider)
|
|
1609
|
+
// Record whether upstream tsgo answers documentSymbol/references itself. The
|
|
1610
|
+
// per-method handlers forward to tsgo when it does, so this must be set before
|
|
1611
|
+
// the shouldAnswer* checks below read it.
|
|
1612
|
+
p.setUpstreamDocumentSymbolProvider(caps["documentSymbolProvider"])
|
|
1613
|
+
p.setUpstreamReferencesProvider(caps["referencesProvider"])
|
|
1486
1614
|
codeActionKinds := p.pluginCodeActionKinds()
|
|
1487
1615
|
changed := false
|
|
1488
1616
|
if (len(sourceCommands) > 0 || len(codeActionKinds) > 0) && codeActionProvider == nil {
|
|
@@ -1516,20 +1644,19 @@ func (p *Proxy) augmentInitializeResult(env Envelope) ([]byte, bool) {
|
|
|
1516
1644
|
changed = true
|
|
1517
1645
|
}
|
|
1518
1646
|
}
|
|
1519
|
-
// Advertise documentSymbol/references when
|
|
1520
|
-
//
|
|
1521
|
-
//
|
|
1522
|
-
//
|
|
1523
|
-
//
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
}
|
|
1647
|
+
// Advertise documentSymbol/references only when the proxy will actually answer
|
|
1648
|
+
// them locally — upstream tsgo did not advertise the capability (fallback) or
|
|
1649
|
+
// ForceLocalSymbolProvider is set. When tsgo advertises and the flag is off,
|
|
1650
|
+
// the method is forwarded to tsgo, whose own capability already tells the
|
|
1651
|
+
// editor to send the request, so its (possibly richer) capability is left
|
|
1652
|
+
// untouched here.
|
|
1653
|
+
if p.shouldAnswerDocumentSymbolLocally() && !capabilityAdvertised(caps["documentSymbolProvider"]) {
|
|
1654
|
+
caps["documentSymbolProvider"] = true
|
|
1655
|
+
changed = true
|
|
1656
|
+
}
|
|
1657
|
+
if p.shouldAnswerReferencesLocally() && !capabilityAdvertised(caps["referencesProvider"]) {
|
|
1658
|
+
caps["referencesProvider"] = true
|
|
1659
|
+
changed = true
|
|
1533
1660
|
}
|
|
1534
1661
|
if !changed {
|
|
1535
1662
|
return nil, false
|
|
@@ -1635,6 +1762,71 @@ func (p *Proxy) setUpstreamCodeActionProvider(value any) {
|
|
|
1635
1762
|
p.upstreamCodeActionProvider = provides
|
|
1636
1763
|
}
|
|
1637
1764
|
|
|
1765
|
+
func (p *Proxy) setUpstreamDocumentSymbolProvider(value any) {
|
|
1766
|
+
p.capabilityMu.Lock()
|
|
1767
|
+
defer p.capabilityMu.Unlock()
|
|
1768
|
+
p.upstreamDocumentSymbolProvider = capabilityAdvertised(value)
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
func (p *Proxy) setUpstreamReferencesProvider(value any) {
|
|
1772
|
+
p.capabilityMu.Lock()
|
|
1773
|
+
defer p.capabilityMu.Unlock()
|
|
1774
|
+
p.upstreamReferencesProvider = capabilityAdvertised(value)
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
// shouldAnswerDocumentSymbolLocally reports whether the proxy answers
|
|
1778
|
+
// textDocument/documentSymbol from the local SymbolProvider instead of
|
|
1779
|
+
// forwarding to upstream tsgo: a provider must be wired, and either the caller
|
|
1780
|
+
// forced it or tsgo did not advertise the capability.
|
|
1781
|
+
func (p *Proxy) shouldAnswerDocumentSymbolLocally() bool {
|
|
1782
|
+
if p.symbolProvider == nil {
|
|
1783
|
+
return false
|
|
1784
|
+
}
|
|
1785
|
+
if p.forceLocalSymbolProvider {
|
|
1786
|
+
return true
|
|
1787
|
+
}
|
|
1788
|
+
p.capabilityMu.Lock()
|
|
1789
|
+
defer p.capabilityMu.Unlock()
|
|
1790
|
+
return !p.upstreamDocumentSymbolProvider
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
// shouldAnswerReferencesLocally mirrors shouldAnswerDocumentSymbolLocally for
|
|
1794
|
+
// textDocument/references.
|
|
1795
|
+
func (p *Proxy) shouldAnswerReferencesLocally() bool {
|
|
1796
|
+
if p.symbolProvider == nil {
|
|
1797
|
+
return false
|
|
1798
|
+
}
|
|
1799
|
+
if p.forceLocalSymbolProvider {
|
|
1800
|
+
return true
|
|
1801
|
+
}
|
|
1802
|
+
p.capabilityMu.Lock()
|
|
1803
|
+
defer p.capabilityMu.Unlock()
|
|
1804
|
+
return !p.upstreamReferencesProvider
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
// invalidateSymbolProvider drops the SymbolProvider's cached compiler state so
|
|
1808
|
+
// the next documentSymbol/references request reflects the latest sources. It is
|
|
1809
|
+
// a no-op when no provider is wired.
|
|
1810
|
+
func (p *Proxy) invalidateSymbolProvider() {
|
|
1811
|
+
if p.symbolProvider != nil {
|
|
1812
|
+
p.symbolProvider.Invalidate()
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
// capabilityAdvertised reports whether an LSP server-capability value means the
|
|
1817
|
+
// server implements the capability: boolean true, or an options object. Absent
|
|
1818
|
+
// (nil), false, or any other shape counts as unsupported.
|
|
1819
|
+
func capabilityAdvertised(value any) bool {
|
|
1820
|
+
switch v := value.(type) {
|
|
1821
|
+
case bool:
|
|
1822
|
+
return v
|
|
1823
|
+
case map[string]any:
|
|
1824
|
+
return v != nil
|
|
1825
|
+
default:
|
|
1826
|
+
return false
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1638
1830
|
func mergeCommandIDs(existing any, additions []string) []string {
|
|
1639
1831
|
seen := map[string]struct{}{}
|
|
1640
1832
|
out := make([]string, 0, len(additions))
|