ttsc 0.21.0 → 0.23.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/cmd/ttscserver/main.go +79 -3
- package/cmd/ttscserver/run_lsp_uses_native_plugin_manifest_source_test.go +96 -16
- package/driver/lsp.go +4 -0
- package/go.mod +1 -1
- package/internal/lspserver/lsp_document_diagnostics_omits_cached_project_when_current_result_has_none_test.go +48 -0
- package/internal/lspserver/lsp_executable_project_input_change_requests_launcher_restart_test.go +98 -0
- package/internal/lspserver/lsp_external_watched_change_retains_program_test.go +53 -0
- package/internal/lspserver/lsp_hints_refresh_scheduled_by_editor_events_test.go +9 -0
- package/internal/lspserver/lsp_launcher_reload_directory_fingerprint_parity_windows_test.go +94 -0
- package/internal/lspserver/lsp_native_plugin_source.go +171 -55
- package/internal/lspserver/lsp_native_resident.go +110 -9
- package/internal/lspserver/lsp_plugin.go +6 -0
- package/internal/lspserver/lsp_project_context_transport_identity_test.go +43 -0
- package/internal/lspserver/lsp_project_diagnostics.go +235 -0
- package/internal/lspserver/lsp_project_diagnostics_capability_is_independent_test.go +67 -0
- package/internal/lspserver/lsp_project_diagnostics_program_input_overlap_refreshes_all_producers_test.go +78 -0
- package/internal/lspserver/lsp_project_diagnostics_refresh_accumulates_owner_freshness_test.go +65 -0
- package/internal/lspserver/lsp_project_diagnostics_refresh_keeps_each_producer_last_good_test.go +90 -0
- package/internal/lspserver/lsp_project_diagnostics_refreshes_only_input_owners_test.go +62 -0
- package/internal/lspserver/lsp_project_diagnostics_resident_unsupported_falls_back_test.go +123 -0
- package/internal/lspserver/lsp_project_diagnostics_resume_preserves_generation_test.go +52 -0
- package/internal/lspserver/lsp_project_diagnostics_translates_relative_client_project_test.go +90 -0
- package/internal/lspserver/lsp_project_input_glob_path_other.go +16 -0
- package/internal/lspserver/lsp_project_input_glob_path_windows.go +36 -0
- package/internal/lspserver/lsp_project_input_ownership_resolves_declared_spellings_test.go +95 -0
- package/internal/lspserver/lsp_project_input_path_key_other.go +17 -0
- package/internal/lspserver/lsp_project_input_path_key_windows.go +228 -0
- package/internal/lspserver/lsp_project_input_path_key_windows_test.go +460 -0
- package/internal/lspserver/lsp_project_input_registration.go +504 -0
- package/internal/lspserver/lsp_project_input_registration_rechecks_selection_baseline_test.go +71 -0
- package/internal/lspserver/lsp_project_input_registration_reconciles_rejected_pending_request_test.go +268 -0
- package/internal/lspserver/lsp_project_input_registration_shapes_patterns_test.go +94 -0
- package/internal/lspserver/lsp_project_input_watchers_anchor_missing_paths_test.go +48 -0
- package/internal/lspserver/lsp_project_inputs.go +1012 -0
- package/internal/lspserver/lsp_project_inputs_match_missing_exact_and_glob_test.go +129 -0
- package/internal/lspserver/lsp_project_inputs_reject_invalid_and_foreign_snapshots_test.go +110 -0
- package/internal/lspserver/lsp_proxy.go +591 -28
- package/internal/lspserver/lsp_reload_directories_compare_immediate_topology_test.go +188 -0
- package/internal/lspserver/lsp_reload_directories_separate_glob_territory_test.go +181 -0
- package/internal/lspserver/lsp_reload_file_fingerprint_preserves_raw_symlink_target_test.go +47 -0
- package/internal/lspserver/lsp_resident_falls_back_when_serve_unsupported_test.go +1 -1
- package/internal/lspserver/lsp_resident_invalidation_broadcasts_program_input_overlap_test.go +60 -0
- package/internal/lspserver/lsp_resident_invalidation_targets_project_input_owners_test.go +51 -0
- package/internal/lspserver/lsp_same_transport_entries_are_deduplicated_test.go +66 -0
- package/lib/compiler/internal/project/readProjectConfig.d.ts +5 -3
- package/lib/compiler/internal/project/readProjectConfig.js +46 -14
- package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
- package/lib/compiler/internal/residentCheckProcess.d.ts +46 -0
- package/lib/compiler/internal/residentCheckProcess.js +227 -0
- package/lib/compiler/internal/residentCheckProcess.js.map +1 -0
- package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +2 -1
- package/lib/compiler/internal/resolveEmittedJavaScript.js +35 -18
- package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +64 -1
- package/lib/compiler/internal/runBuild.js +484 -7
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/runSingleFileEmit.js +1 -0
- package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
- package/lib/flags/parser.js +13 -17
- package/lib/flags/parser.js.map +1 -1
- package/lib/flags/schema.d.ts +10 -3
- package/lib/flags/schema.js +99 -0
- package/lib/flags/schema.js.map +1 -1
- package/lib/internal/projectInputPathIdentity.d.ts +31 -0
- package/lib/internal/projectInputPathIdentity.js +276 -0
- package/lib/internal/projectInputPathIdentity.js.map +1 -0
- package/lib/internal/windowsDirectoryCaseSensitivity.d.ts +10 -0
- package/lib/internal/windowsDirectoryCaseSensitivity.js +34 -0
- package/lib/internal/windowsDirectoryCaseSensitivity.js.map +1 -0
- package/lib/launcher/internal/runTtsc.d.ts +17 -0
- package/lib/launcher/internal/runTtsc.js +126 -88
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.d.ts +13 -1
- package/lib/launcher/internal/runTtscserver.js +468 -47
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +61 -7
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +6 -5
- package/lib/launcher/internal/runtimeHooks.js +7 -15
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/launcher/internal/singleFileOutput.d.ts +16 -0
- package/lib/launcher/internal/singleFileOutput.js +103 -0
- package/lib/launcher/internal/singleFileOutput.js.map +1 -0
- package/lib/launcher/internal/watchTopology.d.ts +288 -2
- package/lib/launcher/internal/watchTopology.js +1921 -54
- package/lib/launcher/internal/watchTopology.js.map +1 -1
- package/lib/plugin/internal/descriptorProcessFailure.d.ts +32 -0
- package/lib/plugin/internal/descriptorProcessFailure.js +73 -0
- package/lib/plugin/internal/descriptorProcessFailure.js.map +1 -0
- package/lib/plugin/internal/loadProjectPlugins.d.ts +1 -0
- package/lib/plugin/internal/loadProjectPlugins.js +34 -8
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/structures/ITtscPluginCapabilities.d.ts +31 -0
- package/lib/structures/internal/ITtscProjectInputSnapshot.d.ts +29 -0
- package/lib/structures/internal/ITtscProjectInputSnapshot.js +3 -0
- package/lib/structures/internal/ITtscProjectInputSnapshot.js.map +1 -0
- package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +5 -0
- package/package.json +12 -8
- package/src/compiler/internal/project/readProjectConfig.ts +62 -8
- package/src/compiler/internal/residentCheckProcess.ts +279 -0
- package/src/compiler/internal/resolveEmittedJavaScript.ts +43 -21
- package/src/compiler/internal/runBuild.ts +728 -12
- package/src/compiler/internal/runSingleFileEmit.ts +1 -0
- package/src/flags/parser.ts +17 -17
- package/src/flags/schema.ts +112 -3
- package/src/internal/projectInputPathIdentity.ts +377 -0
- package/src/internal/windowsDirectoryCaseSensitivity.ts +33 -0
- package/src/launcher/internal/runTtsc.ts +148 -117
- package/src/launcher/internal/runTtscserver.ts +633 -56
- package/src/launcher/internal/runTtsx.ts +87 -10
- package/src/launcher/internal/runtimeHooks.ts +16 -16
- package/src/launcher/internal/singleFileOutput.ts +133 -0
- package/src/launcher/internal/watchTopology.ts +2494 -74
- package/src/plugin/internal/descriptorProcessFailure.ts +100 -0
- package/src/plugin/internal/loadProjectPlugins.ts +49 -8
- package/src/structures/ITtscPluginCapabilities.ts +34 -0
- package/src/structures/internal/ITtscProjectInputSnapshot.ts +29 -0
- package/src/structures/internal/TtscSingleFileEmitOptions.ts +5 -0
- package/tools/shim_audit/baseline.json +0 -9
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
package lspserver
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
type mutableProjectInputRegistrationSource struct {
|
|
11
|
+
NullPluginSource
|
|
12
|
+
snapshot LSPProjectInputSnapshot
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
func (s *mutableProjectInputRegistrationSource) ProjectInputs() LSPProjectInputSnapshot {
|
|
16
|
+
return copyProjectInputSnapshot(s.snapshot)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// TestProjectInputRegistrationReconcilesRejectedPendingRequest verifies
|
|
20
|
+
// rejected dynamic-registration requests cannot strand a newer project-input
|
|
21
|
+
// snapshot.
|
|
22
|
+
//
|
|
23
|
+
// A rejected replacement must preserve the last-good registration while the
|
|
24
|
+
// proxy advances to the latest desired snapshot. Rejected stale cleanup must
|
|
25
|
+
// likewise retain its work without blocking a newer registration or creating a
|
|
26
|
+
// duplicate active registration.
|
|
27
|
+
//
|
|
28
|
+
// 1. Register snapshot A, then leave replacement B pending.
|
|
29
|
+
// 2. Publish C and reject B; assert C registers while A remains active.
|
|
30
|
+
// 3. Register D, leave cleanup of C pending, then publish E and reject cleanup.
|
|
31
|
+
// 4. Assert E registers while the rejected cleanup remains deferred.
|
|
32
|
+
// 5. Publish F and assert retained cleanup completes once before F replaces E.
|
|
33
|
+
func TestProjectInputRegistrationReconcilesRejectedPendingRequest(
|
|
34
|
+
t *testing.T,
|
|
35
|
+
) {
|
|
36
|
+
root := t.TempDir()
|
|
37
|
+
snapshot := func(name string) LSPProjectInputSnapshot {
|
|
38
|
+
return LSPProjectInputSnapshot{
|
|
39
|
+
Root: filepath.ToSlash(root),
|
|
40
|
+
Files: []string{
|
|
41
|
+
filepath.ToSlash(filepath.Join(root, "docs", name+".md")),
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
source := &mutableProjectInputRegistrationSource{snapshot: snapshot("a")}
|
|
46
|
+
proxy := NewProxy(ProxyOptions{
|
|
47
|
+
EditorOut: &bytes.Buffer{},
|
|
48
|
+
Source: source,
|
|
49
|
+
})
|
|
50
|
+
proxy.projectInputWatchReady = true
|
|
51
|
+
proxy.projectInputWatchDynamic = true
|
|
52
|
+
proxy.projectInputWatchRelative = true
|
|
53
|
+
|
|
54
|
+
proxy.projectInputsRefreshed()
|
|
55
|
+
respondToPendingProjectInputWatchRequest(t, proxy, false, "")
|
|
56
|
+
registrationA := proxy.projectInputWatchActive
|
|
57
|
+
assertProjectInputWatchState(
|
|
58
|
+
t,
|
|
59
|
+
proxy,
|
|
60
|
+
projectInputWatchRegistrationForSnapshot(snapshot("a")).Signature,
|
|
61
|
+
nil,
|
|
62
|
+
false,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
source.snapshot = snapshot("b")
|
|
66
|
+
proxy.projectInputsRefreshed()
|
|
67
|
+
source.snapshot = snapshot("c")
|
|
68
|
+
proxy.projectInputsRefreshed()
|
|
69
|
+
respondToPendingProjectInputWatchRequest(t, proxy, true, "reject B")
|
|
70
|
+
if proxy.projectInputWatchActive.ID != registrationA.ID {
|
|
71
|
+
t.Fatalf(
|
|
72
|
+
"rejected B replaced last-good A: active = %#v, A = %#v",
|
|
73
|
+
proxy.projectInputWatchActive,
|
|
74
|
+
registrationA,
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
assertProjectInputWatchState(
|
|
78
|
+
t,
|
|
79
|
+
proxy,
|
|
80
|
+
projectInputWatchRegistrationForSnapshot(snapshot("c")).Signature,
|
|
81
|
+
nil,
|
|
82
|
+
true,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
respondToPendingProjectInputWatchRequest(t, proxy, false, "")
|
|
86
|
+
registrationC := proxy.projectInputWatchActive
|
|
87
|
+
if registrationC.Signature !=
|
|
88
|
+
projectInputWatchRegistrationForSnapshot(snapshot("c")).Signature {
|
|
89
|
+
t.Fatalf("active after C success = %#v", registrationC)
|
|
90
|
+
}
|
|
91
|
+
assertProjectInputWatchState(
|
|
92
|
+
t,
|
|
93
|
+
proxy,
|
|
94
|
+
registrationC.Signature,
|
|
95
|
+
[]string{registrationA.ID},
|
|
96
|
+
true,
|
|
97
|
+
)
|
|
98
|
+
respondToPendingProjectInputWatchRequest(t, proxy, false, "")
|
|
99
|
+
assertProjectInputWatchState(t, proxy, registrationC.Signature, nil, false)
|
|
100
|
+
|
|
101
|
+
source.snapshot = snapshot("d")
|
|
102
|
+
proxy.projectInputsRefreshed()
|
|
103
|
+
respondToPendingProjectInputWatchRequest(t, proxy, false, "")
|
|
104
|
+
registrationD := proxy.projectInputWatchActive
|
|
105
|
+
assertProjectInputWatchState(
|
|
106
|
+
t,
|
|
107
|
+
proxy,
|
|
108
|
+
registrationD.Signature,
|
|
109
|
+
[]string{registrationC.ID},
|
|
110
|
+
true,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
source.snapshot = snapshot("e")
|
|
114
|
+
proxy.projectInputsRefreshed()
|
|
115
|
+
respondToPendingProjectInputWatchRequest(
|
|
116
|
+
t,
|
|
117
|
+
proxy,
|
|
118
|
+
true,
|
|
119
|
+
"reject cleanup C",
|
|
120
|
+
)
|
|
121
|
+
if proxy.projectInputWatchActive.ID != registrationD.ID {
|
|
122
|
+
t.Fatalf(
|
|
123
|
+
"rejected cleanup replaced last-good D: active = %#v, D = %#v",
|
|
124
|
+
proxy.projectInputWatchActive,
|
|
125
|
+
registrationD,
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
assertProjectInputWatchState(
|
|
129
|
+
t,
|
|
130
|
+
proxy,
|
|
131
|
+
projectInputWatchRegistrationForSnapshot(snapshot("e")).Signature,
|
|
132
|
+
[]string{registrationC.ID},
|
|
133
|
+
true,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
respondToPendingProjectInputWatchRequest(t, proxy, false, "")
|
|
137
|
+
registrationE := proxy.projectInputWatchActive
|
|
138
|
+
assertProjectInputWatchState(
|
|
139
|
+
t,
|
|
140
|
+
proxy,
|
|
141
|
+
registrationE.Signature,
|
|
142
|
+
[]string{registrationC.ID, registrationD.ID},
|
|
143
|
+
false,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
source.snapshot = snapshot("f")
|
|
147
|
+
proxy.projectInputsRefreshed()
|
|
148
|
+
respondToPendingProjectInputWatchRequest(t, proxy, false, "")
|
|
149
|
+
assertProjectInputWatchState(
|
|
150
|
+
t,
|
|
151
|
+
proxy,
|
|
152
|
+
projectInputWatchRegistrationForSnapshot(snapshot("f")).Signature,
|
|
153
|
+
[]string{registrationD.ID},
|
|
154
|
+
true,
|
|
155
|
+
)
|
|
156
|
+
if proxy.projectInputWatchActive.ID != registrationE.ID {
|
|
157
|
+
t.Fatal("retrying cleanup registered F before removing retained C")
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
respondToPendingProjectInputWatchRequest(t, proxy, false, "")
|
|
161
|
+
assertProjectInputWatchState(
|
|
162
|
+
t,
|
|
163
|
+
proxy,
|
|
164
|
+
projectInputWatchRegistrationForSnapshot(snapshot("f")).Signature,
|
|
165
|
+
nil,
|
|
166
|
+
true,
|
|
167
|
+
)
|
|
168
|
+
if proxy.projectInputWatchActive.ID != registrationE.ID {
|
|
169
|
+
t.Fatal("retrying cleanup registered F before removing stale D")
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
respondToPendingProjectInputWatchRequest(t, proxy, false, "")
|
|
173
|
+
registrationF := proxy.projectInputWatchActive
|
|
174
|
+
assertProjectInputWatchState(
|
|
175
|
+
t,
|
|
176
|
+
proxy,
|
|
177
|
+
registrationF.Signature,
|
|
178
|
+
[]string{registrationE.ID},
|
|
179
|
+
true,
|
|
180
|
+
)
|
|
181
|
+
respondToPendingProjectInputWatchRequest(t, proxy, false, "")
|
|
182
|
+
assertProjectInputWatchState(t, proxy, registrationF.Signature, nil, false)
|
|
183
|
+
|
|
184
|
+
if proxy.projectInputWatchRegistrationSequence != 6 {
|
|
185
|
+
t.Fatalf(
|
|
186
|
+
"registration attempts = %d, want A through F exactly once",
|
|
187
|
+
proxy.projectInputWatchRegistrationSequence,
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
func respondToPendingProjectInputWatchRequest(
|
|
193
|
+
t *testing.T,
|
|
194
|
+
proxy *Proxy,
|
|
195
|
+
rejected bool,
|
|
196
|
+
message string,
|
|
197
|
+
) {
|
|
198
|
+
t.Helper()
|
|
199
|
+
proxy.pendingMu.Lock()
|
|
200
|
+
if len(proxy.pendingClientRequests) != 1 {
|
|
201
|
+
proxy.pendingMu.Unlock()
|
|
202
|
+
t.Fatalf(
|
|
203
|
+
"pending client requests = %d, want 1",
|
|
204
|
+
len(proxy.pendingClientRequests),
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
var rawID json.RawMessage
|
|
208
|
+
for key := range proxy.pendingClientRequests {
|
|
209
|
+
rawID = json.RawMessage(key)
|
|
210
|
+
}
|
|
211
|
+
proxy.pendingMu.Unlock()
|
|
212
|
+
|
|
213
|
+
response := Envelope{
|
|
214
|
+
JSONRPC: "2.0",
|
|
215
|
+
ID: rawID,
|
|
216
|
+
Result: json.RawMessage("null"),
|
|
217
|
+
}
|
|
218
|
+
if rejected {
|
|
219
|
+
response.Result = nil
|
|
220
|
+
response.Error, _ = json.Marshal(map[string]any{
|
|
221
|
+
"code": -32603,
|
|
222
|
+
"message": message,
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
if !proxy.handleClientRequestResponse(response) {
|
|
226
|
+
t.Fatalf("pending client response %s was not handled", rawID)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
func assertProjectInputWatchState(
|
|
231
|
+
t *testing.T,
|
|
232
|
+
proxy *Proxy,
|
|
233
|
+
desiredSignature string,
|
|
234
|
+
staleIDs []string,
|
|
235
|
+
pending bool,
|
|
236
|
+
) {
|
|
237
|
+
t.Helper()
|
|
238
|
+
proxy.projectInputWatchMu.Lock()
|
|
239
|
+
defer proxy.projectInputWatchMu.Unlock()
|
|
240
|
+
if proxy.projectInputWatchDesired.Signature != desiredSignature ||
|
|
241
|
+
!equalProjectInputWatchIDs(proxy.projectInputWatchStaleIDs, staleIDs) ||
|
|
242
|
+
proxy.projectInputWatchPending != pending {
|
|
243
|
+
t.Fatalf(
|
|
244
|
+
"watch state = {desired:%q active:%#v stale:%#v pending:%v failed:%q retryBlocked:%v}, want desired %q stale %#v pending %v",
|
|
245
|
+
proxy.projectInputWatchDesired.Signature,
|
|
246
|
+
proxy.projectInputWatchActive,
|
|
247
|
+
proxy.projectInputWatchStaleIDs,
|
|
248
|
+
proxy.projectInputWatchPending,
|
|
249
|
+
proxy.projectInputWatchFailedSignature,
|
|
250
|
+
proxy.projectInputWatchUnregisterRetryBlocked,
|
|
251
|
+
desiredSignature,
|
|
252
|
+
staleIDs,
|
|
253
|
+
pending,
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
func equalProjectInputWatchIDs(left []string, right []string) bool {
|
|
259
|
+
if len(left) != len(right) {
|
|
260
|
+
return false
|
|
261
|
+
}
|
|
262
|
+
for index := range left {
|
|
263
|
+
if left[index] != right[index] {
|
|
264
|
+
return false
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return true
|
|
268
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
package lspserver
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"path/filepath"
|
|
5
|
+
"testing"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// TestProjectInputRegistrationShapesRelativePatterns verifies exact paths and
|
|
9
|
+
// glob populations become deterministic LSP RelativePatterns without treating
|
|
10
|
+
// literal filename metacharacters as wildcards.
|
|
11
|
+
//
|
|
12
|
+
// Reload directories need two registrations: immediate children carry
|
|
13
|
+
// topology changes, while the exact directory identity carries deletion and
|
|
14
|
+
// replacement when a child-rooted watcher can no longer observe the object.
|
|
15
|
+
//
|
|
16
|
+
// 1. Declare duplicate exact files, portable globs, and one reload directory.
|
|
17
|
+
// 2. Build and index the deduplicated relative-pattern registrations.
|
|
18
|
+
// 3. Assert literals are escaped while portable wildcard segments survive.
|
|
19
|
+
// 4. Assert reload-directory identity and immediate-child patterns both exist.
|
|
20
|
+
// 5. Assert drive and UNC file URIs preserve their authority and escaping.
|
|
21
|
+
func TestProjectInputRegistrationShapesRelativePatterns(t *testing.T) {
|
|
22
|
+
root := t.TempDir()
|
|
23
|
+
exact := filepath.Join(
|
|
24
|
+
root,
|
|
25
|
+
"docs",
|
|
26
|
+
"contract *?[v1]{draft} #%25.md",
|
|
27
|
+
)
|
|
28
|
+
snapshot := LSPProjectInputSnapshot{
|
|
29
|
+
Root: filepath.ToSlash(root),
|
|
30
|
+
Files: []string{
|
|
31
|
+
filepath.ToSlash(exact),
|
|
32
|
+
filepath.ToSlash(exact),
|
|
33
|
+
},
|
|
34
|
+
Globs: []string{
|
|
35
|
+
filepath.ToSlash(filepath.Join(root, "api", "**", "*.json")),
|
|
36
|
+
filepath.ToSlash(filepath.Join(
|
|
37
|
+
root,
|
|
38
|
+
"api",
|
|
39
|
+
"**",
|
|
40
|
+
"v[12]",
|
|
41
|
+
"{openapi,swagger}.yaml",
|
|
42
|
+
)),
|
|
43
|
+
},
|
|
44
|
+
ReloadDirectories: []string{
|
|
45
|
+
filepath.ToSlash(filepath.Join(root, "config-deps")),
|
|
46
|
+
},
|
|
47
|
+
ReloadFiles: []string{
|
|
48
|
+
filepath.ToSlash(exact),
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
watchers := projectInputFileWatchers(snapshot)
|
|
52
|
+
if len(watchers) != 5 {
|
|
53
|
+
t.Fatalf("watchers = %#v, want 5 deduplicated entries", watchers)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
byPattern := map[string]projectInputFileWatcher{}
|
|
57
|
+
for _, watcher := range watchers {
|
|
58
|
+
if watcher.Kind != watchedFileKindAll {
|
|
59
|
+
t.Fatalf("watcher kind = %d, want %d", watcher.Kind, watchedFileKindAll)
|
|
60
|
+
}
|
|
61
|
+
byPattern[watcher.GlobPattern.Pattern] = watcher
|
|
62
|
+
}
|
|
63
|
+
literal := "docs/contract [*][?][[]v1[]][{]draft[}] #%25.md"
|
|
64
|
+
if watcher, ok := byPattern[literal]; !ok {
|
|
65
|
+
t.Fatalf("escaped exact pattern missing from %#v", byPattern)
|
|
66
|
+
} else if watcher.GlobPattern.BaseURI == "" {
|
|
67
|
+
t.Fatal("exact watcher has an empty base URI")
|
|
68
|
+
}
|
|
69
|
+
if watcher, ok := byPattern["api/**/*.json"]; !ok {
|
|
70
|
+
t.Fatalf("glob pattern missing from %#v", byPattern)
|
|
71
|
+
} else if watcher.GlobPattern.BaseURI == "" {
|
|
72
|
+
t.Fatal("glob watcher has an empty base URI")
|
|
73
|
+
}
|
|
74
|
+
literalMetacharacters :=
|
|
75
|
+
"api/**/v[[]12[]]/[{]openapi,swagger[}].yaml"
|
|
76
|
+
if _, ok := byPattern[literalMetacharacters]; !ok {
|
|
77
|
+
t.Fatalf("literal glob metacharacters were not escaped in %#v", byPattern)
|
|
78
|
+
}
|
|
79
|
+
if _, ok := byPattern["config-deps/*"]; !ok {
|
|
80
|
+
t.Fatalf("reload-directory child watcher missing from %#v", byPattern)
|
|
81
|
+
}
|
|
82
|
+
if _, ok := byPattern["config-deps"]; !ok {
|
|
83
|
+
t.Fatalf("reload-directory identity watcher missing from %#v", byPattern)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if got := projectInputFileURI("C:/Program Files/a#b%25"); got !=
|
|
87
|
+
"file:///C:/Program%20Files/a%23b%2525" {
|
|
88
|
+
t.Fatalf("drive URI = %q", got)
|
|
89
|
+
}
|
|
90
|
+
if got := projectInputFileURI("//server/share/a b#c"); got !=
|
|
91
|
+
"file://server/share/a%20b%23c" {
|
|
92
|
+
t.Fatalf("UNC URI = %q", got)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
package lspserver
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"path/filepath"
|
|
5
|
+
"testing"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// TestProjectInputWatchersAnchorMissingPaths verifies dynamic registrations use
|
|
9
|
+
// an existing ancestor while retaining the missing path in the glob.
|
|
10
|
+
//
|
|
11
|
+
// Editors cannot reliably root a RelativePattern at a directory that does not
|
|
12
|
+
// exist yet. Anchoring at the physical project lets the registration observe
|
|
13
|
+
// later directory creation without broadening beyond the declared pattern.
|
|
14
|
+
//
|
|
15
|
+
// 1. Declare one exact file and one glob under missing nested directories.
|
|
16
|
+
// 2. Build the client watcher registrations before either directory exists.
|
|
17
|
+
// 3. Assert both use the project URI and preserve their missing path segments.
|
|
18
|
+
func TestProjectInputWatchersAnchorMissingPaths(t *testing.T) {
|
|
19
|
+
root := t.TempDir()
|
|
20
|
+
watchers := projectInputFileWatchers(LSPProjectInputSnapshot{
|
|
21
|
+
Root: root,
|
|
22
|
+
Files: []string{
|
|
23
|
+
filepath.Join(root, "docs", "missing", "spec.md"),
|
|
24
|
+
},
|
|
25
|
+
Globs: []string{
|
|
26
|
+
filepath.Join(root, "api", "v1", "**", "*.json"),
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
if len(watchers) != 2 {
|
|
30
|
+
t.Fatalf("watchers = %#v", watchers)
|
|
31
|
+
}
|
|
32
|
+
wantBase := projectInputFileURI(root)
|
|
33
|
+
patterns := map[string]bool{}
|
|
34
|
+
for _, watcher := range watchers {
|
|
35
|
+
if watcher.GlobPattern.BaseURI != wantBase {
|
|
36
|
+
t.Fatalf(
|
|
37
|
+
"watcher base = %q, want %q",
|
|
38
|
+
watcher.GlobPattern.BaseURI,
|
|
39
|
+
wantBase,
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
patterns[watcher.GlobPattern.Pattern] = true
|
|
43
|
+
}
|
|
44
|
+
if !patterns["docs/missing/spec.md"] ||
|
|
45
|
+
!patterns["api/v1/**/*.json"] {
|
|
46
|
+
t.Fatalf("watcher patterns = %#v", patterns)
|
|
47
|
+
}
|
|
48
|
+
}
|