ttsc 0.22.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/go.mod +1 -1
- package/internal/lspserver/lsp_launcher_reload_directory_fingerprint_parity_windows_test.go +94 -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_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_inputs.go +117 -49
- package/internal/lspserver/lsp_reload_directories_separate_glob_territory_test.go +181 -0
- package/lib/flags/schema.js +1 -1
- package/lib/flags/schema.js.map +1 -1
- package/lib/internal/projectInputPathIdentity.d.ts +13 -5
- package/lib/internal/projectInputPathIdentity.js +95 -41
- package/lib/internal/projectInputPathIdentity.js.map +1 -1
- 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.js +5 -0
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.js +59 -8
- 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/watchTopology.d.ts +82 -8
- package/lib/launcher/internal/watchTopology.js +535 -187
- 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.js +29 -8
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/package.json +12 -8
- package/src/flags/schema.ts +2 -1
- package/src/internal/projectInputPathIdentity.ts +150 -46
- package/src/internal/windowsDirectoryCaseSensitivity.ts +33 -0
- package/src/launcher/internal/runTtsc.ts +7 -0
- package/src/launcher/internal/runTtscserver.ts +81 -11
- package/src/launcher/internal/runTtsx.ts +87 -10
- package/src/launcher/internal/runtimeHooks.ts +16 -16
- package/src/launcher/internal/watchTopology.ts +679 -208
- package/src/plugin/internal/descriptorProcessFailure.ts +100 -0
- package/src/plugin/internal/loadProjectPlugins.ts +41 -8
- package/tools/shim_audit/baseline.json +0 -9
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
//go:build windows
|
|
2
|
+
|
|
3
|
+
package lspserver
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"os"
|
|
7
|
+
"os/exec"
|
|
8
|
+
"path/filepath"
|
|
9
|
+
"reflect"
|
|
10
|
+
"slices"
|
|
11
|
+
"strings"
|
|
12
|
+
"testing"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
// TestProjectInputPathKeyRespectsDirectoryCaseSemantics verifies the Go host
|
|
16
|
+
// keeps case-distinct Windows dependencies without splitting ordinary aliases.
|
|
17
|
+
//
|
|
18
|
+
// The original global case fold merged distinct files below an opted-in
|
|
19
|
+
// case-sensitive directory, while preserving every spelling instead split
|
|
20
|
+
// ordinary NTFS, UNC, and recreated-directory aliases. Identity must instead
|
|
21
|
+
// follow the case semantics of the directory that owns each path segment.
|
|
22
|
+
//
|
|
23
|
+
// 1. Enable case sensitivity on a disposable directory and create two real
|
|
24
|
+
// dependencies whose paths differ only by case.
|
|
25
|
+
// 2. Prove merged publication and owner matching retain both identities.
|
|
26
|
+
// 3. Prove glob matching and reload containment obey each owning directory.
|
|
27
|
+
// 4. Prove missing suffixes also retain case under an opted-in directory.
|
|
28
|
+
// 5. On an ordinary directory, prove existing and missing aliases converge.
|
|
29
|
+
// 6. Change a live directory's flag and normalize UNC volume aliases.
|
|
30
|
+
func TestProjectInputPathKeyRespectsDirectoryCaseSemantics(t *testing.T) {
|
|
31
|
+
sensitiveRoot := t.TempDir()
|
|
32
|
+
enableProjectInputCaseSensitivity(t, sensitiveRoot)
|
|
33
|
+
|
|
34
|
+
firstDirectory := filepath.Join(sensitiveRoot, "Project")
|
|
35
|
+
secondDirectory := filepath.Join(sensitiveRoot, "project")
|
|
36
|
+
if err := os.Mkdir(firstDirectory, 0o755); err != nil {
|
|
37
|
+
t.Fatal(err)
|
|
38
|
+
}
|
|
39
|
+
if err := os.Mkdir(secondDirectory, 0o755); err != nil {
|
|
40
|
+
t.Fatal(err)
|
|
41
|
+
}
|
|
42
|
+
enableProjectInputCaseSensitivity(t, firstDirectory)
|
|
43
|
+
enableProjectInputCaseSensitivity(t, secondDirectory)
|
|
44
|
+
|
|
45
|
+
firstInput := filepath.Join(firstDirectory, "Spec.md")
|
|
46
|
+
secondInput := filepath.Join(secondDirectory, "Spec.md")
|
|
47
|
+
if err := os.WriteFile(firstInput, []byte("first\n"), 0o644); err != nil {
|
|
48
|
+
t.Fatal(err)
|
|
49
|
+
}
|
|
50
|
+
if err := os.WriteFile(secondInput, []byte("second\n"), 0o644); err != nil {
|
|
51
|
+
t.Fatal(err)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
first := NativeLSPPluginEntry{
|
|
55
|
+
Binary: "ttsc-case-sensitive-first",
|
|
56
|
+
Name: "@ttsc/case-sensitive-first",
|
|
57
|
+
ProjectInputs: true,
|
|
58
|
+
}
|
|
59
|
+
second := NativeLSPPluginEntry{
|
|
60
|
+
Binary: "ttsc-case-sensitive-second",
|
|
61
|
+
Name: "@ttsc/case-sensitive-second",
|
|
62
|
+
ProjectInputs: true,
|
|
63
|
+
}
|
|
64
|
+
source := &NativePluginSource{
|
|
65
|
+
plugins: []NativeLSPPluginEntry{first, second},
|
|
66
|
+
}
|
|
67
|
+
source.storeProjectInputs(first, 1, LSPProjectInputSnapshot{
|
|
68
|
+
Root: filepath.ToSlash(sensitiveRoot),
|
|
69
|
+
Files: []string{filepath.ToSlash(firstInput)},
|
|
70
|
+
})
|
|
71
|
+
source.storeProjectInputs(second, 1, LSPProjectInputSnapshot{
|
|
72
|
+
Root: filepath.ToSlash(sensitiveRoot),
|
|
73
|
+
Files: []string{filepath.ToSlash(secondInput)},
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
merged := source.ProjectInputs()
|
|
77
|
+
if !reflect.DeepEqual(
|
|
78
|
+
merged.Files,
|
|
79
|
+
[]string{filepath.ToSlash(firstInput), filepath.ToSlash(secondInput)},
|
|
80
|
+
) {
|
|
81
|
+
t.Fatalf("case-sensitive merged files = %#v", merged.Files)
|
|
82
|
+
}
|
|
83
|
+
assertProjectInputOwners(t, source, firstInput, []string{pluginKey(first)})
|
|
84
|
+
assertProjectInputOwners(t, source, secondInput, []string{pluginKey(second)})
|
|
85
|
+
if projectInputPathContains(firstDirectory, secondInput) {
|
|
86
|
+
t.Fatal("case-sensitive sibling was classified as a descendant")
|
|
87
|
+
}
|
|
88
|
+
reloadSource := &NativePluginSource{
|
|
89
|
+
plugins: []NativeLSPPluginEntry{first},
|
|
90
|
+
}
|
|
91
|
+
reloadSource.storeProjectInputs(first, 1, LSPProjectInputSnapshot{
|
|
92
|
+
Root: filepath.ToSlash(sensitiveRoot),
|
|
93
|
+
ReloadDirectories: []string{filepath.ToSlash(firstDirectory)},
|
|
94
|
+
})
|
|
95
|
+
if reloadSource.ProjectInputReloadMatchesURI(testFileURI(secondInput)) {
|
|
96
|
+
t.Fatal("case-sensitive sibling was classified as an immediate reload entry")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
upperJSON := filepath.Join(firstDirectory, "Upper.JSON")
|
|
100
|
+
lowerJSON := filepath.Join(firstDirectory, "lower.json")
|
|
101
|
+
if err := os.WriteFile(upperJSON, []byte("{}\n"), 0o644); err != nil {
|
|
102
|
+
t.Fatal(err)
|
|
103
|
+
}
|
|
104
|
+
if err := os.WriteFile(lowerJSON, []byte("{}\n"), 0o644); err != nil {
|
|
105
|
+
t.Fatal(err)
|
|
106
|
+
}
|
|
107
|
+
sensitiveGlobSource := &NativePluginSource{
|
|
108
|
+
plugins: []NativeLSPPluginEntry{first},
|
|
109
|
+
}
|
|
110
|
+
sensitiveGlobSource.storeProjectInputs(first, 1, LSPProjectInputSnapshot{
|
|
111
|
+
Root: filepath.ToSlash(sensitiveRoot),
|
|
112
|
+
Globs: []string{filepath.ToSlash(filepath.Join(firstDirectory, "*.json"))},
|
|
113
|
+
})
|
|
114
|
+
assertProjectInputOwners(
|
|
115
|
+
t,
|
|
116
|
+
sensitiveGlobSource,
|
|
117
|
+
lowerJSON,
|
|
118
|
+
[]string{pluginKey(first)},
|
|
119
|
+
)
|
|
120
|
+
assertProjectInputOwners(t, sensitiveGlobSource, upperJSON, nil)
|
|
121
|
+
|
|
122
|
+
insensitiveChild := filepath.Join(sensitiveRoot, "Insensitive")
|
|
123
|
+
if err := os.Mkdir(insensitiveChild, 0o755); err != nil {
|
|
124
|
+
t.Fatal(err)
|
|
125
|
+
}
|
|
126
|
+
disableProjectInputCaseSensitivity(t, insensitiveChild)
|
|
127
|
+
mixedJSON := filepath.Join(insensitiveChild, "Schema.JSON")
|
|
128
|
+
if err := os.WriteFile(mixedJSON, []byte("{}\n"), 0o644); err != nil {
|
|
129
|
+
t.Fatal(err)
|
|
130
|
+
}
|
|
131
|
+
mixedGlobSource := &NativePluginSource{
|
|
132
|
+
plugins: []NativeLSPPluginEntry{first},
|
|
133
|
+
}
|
|
134
|
+
mixedGlobSource.storeProjectInputs(first, 1, LSPProjectInputSnapshot{
|
|
135
|
+
Root: filepath.ToSlash(sensitiveRoot),
|
|
136
|
+
Globs: []string{
|
|
137
|
+
filepath.ToSlash(filepath.Join(insensitiveChild, "*.json")),
|
|
138
|
+
},
|
|
139
|
+
})
|
|
140
|
+
assertProjectInputOwners(
|
|
141
|
+
t,
|
|
142
|
+
mixedGlobSource,
|
|
143
|
+
mixedJSON,
|
|
144
|
+
[]string{pluginKey(first)},
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
firstMissing := filepath.Join(firstDirectory, "Missing.json")
|
|
148
|
+
secondMissing := filepath.Join(firstDirectory, "missing.json")
|
|
149
|
+
if projectInputPathKey(firstMissing) == projectInputPathKey(secondMissing) {
|
|
150
|
+
t.Fatal("case-sensitive missing suffixes collapsed")
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
ordinaryRoot := t.TempDir()
|
|
154
|
+
if queryProjectInputDirectoryCaseSensitivity(ordinaryRoot) {
|
|
155
|
+
t.Skip("ordinary-volume negative twin requires a case-insensitive temp root")
|
|
156
|
+
}
|
|
157
|
+
ordinaryInput := filepath.Join(ordinaryRoot, "Alias.md")
|
|
158
|
+
ordinaryAlias := filepath.Join(ordinaryRoot, "aLIAS.MD")
|
|
159
|
+
if err := os.WriteFile(ordinaryInput, []byte("ordinary\n"), 0o644); err != nil {
|
|
160
|
+
t.Fatal(err)
|
|
161
|
+
}
|
|
162
|
+
if _, err := os.Stat(ordinaryAlias); err != nil {
|
|
163
|
+
t.Skipf("temp volume does not resolve case aliases: %v", err)
|
|
164
|
+
}
|
|
165
|
+
if projectInputPathKey(ordinaryInput) != projectInputPathKey(ordinaryAlias) {
|
|
166
|
+
t.Fatal("ordinary existing aliases split")
|
|
167
|
+
}
|
|
168
|
+
if projectInputPathKey(filepath.Join(ordinaryRoot, "Missing.json")) !=
|
|
169
|
+
projectInputPathKey(filepath.Join(ordinaryRoot, "missing.json")) {
|
|
170
|
+
t.Fatal("ordinary missing aliases split")
|
|
171
|
+
}
|
|
172
|
+
upperJSON = filepath.Join(ordinaryRoot, "Schema.JSON")
|
|
173
|
+
if err := os.WriteFile(upperJSON, []byte("{}\n"), 0o644); err != nil {
|
|
174
|
+
t.Fatal(err)
|
|
175
|
+
}
|
|
176
|
+
ordinaryGlobSource := &NativePluginSource{
|
|
177
|
+
plugins: []NativeLSPPluginEntry{first},
|
|
178
|
+
}
|
|
179
|
+
ordinaryGlobSource.storeProjectInputs(first, 1, LSPProjectInputSnapshot{
|
|
180
|
+
Root: filepath.ToSlash(ordinaryRoot),
|
|
181
|
+
Globs: []string{filepath.ToSlash(filepath.Join(ordinaryRoot, "*.json"))},
|
|
182
|
+
})
|
|
183
|
+
assertProjectInputOwners(
|
|
184
|
+
t,
|
|
185
|
+
ordinaryGlobSource,
|
|
186
|
+
upperJSON,
|
|
187
|
+
[]string{pluginKey(first)},
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
ordinarySource := &NativePluginSource{
|
|
191
|
+
plugins: []NativeLSPPluginEntry{first, second},
|
|
192
|
+
}
|
|
193
|
+
ordinarySource.storeProjectInputs(first, 1, LSPProjectInputSnapshot{
|
|
194
|
+
Root: filepath.ToSlash(ordinaryRoot),
|
|
195
|
+
Files: []string{filepath.ToSlash(ordinaryInput)},
|
|
196
|
+
})
|
|
197
|
+
ordinarySource.storeProjectInputs(second, 1, LSPProjectInputSnapshot{
|
|
198
|
+
Root: filepath.ToSlash(ordinaryRoot),
|
|
199
|
+
Files: []string{filepath.ToSlash(ordinaryAlias)},
|
|
200
|
+
})
|
|
201
|
+
if got := ordinarySource.ProjectInputs().Files; len(got) != 1 {
|
|
202
|
+
t.Fatalf("ordinary aliases produced %d merged files: %#v", len(got), got)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
mutableParent := t.TempDir()
|
|
206
|
+
mutableRoot := filepath.Join(mutableParent, "mutable")
|
|
207
|
+
if err := os.Mkdir(mutableRoot, 0o755); err != nil {
|
|
208
|
+
t.Fatal(err)
|
|
209
|
+
}
|
|
210
|
+
firstMissing = filepath.Join(mutableRoot, "Missing.json")
|
|
211
|
+
secondMissing = filepath.Join(mutableRoot, "missing.json")
|
|
212
|
+
if projectInputPathKey(firstMissing) != projectInputPathKey(secondMissing) {
|
|
213
|
+
t.Fatal("ordinary mutable directory began case-sensitive")
|
|
214
|
+
}
|
|
215
|
+
enableProjectInputCaseSensitivity(t, mutableRoot)
|
|
216
|
+
if projectInputPathKey(firstMissing) == projectInputPathKey(secondMissing) {
|
|
217
|
+
t.Fatal("case-sensitivity change was hidden by stale identity state")
|
|
218
|
+
}
|
|
219
|
+
if err := os.Remove(mutableRoot); err != nil {
|
|
220
|
+
t.Fatal(err)
|
|
221
|
+
}
|
|
222
|
+
if err := os.Mkdir(mutableRoot, 0o755); err != nil {
|
|
223
|
+
t.Fatal(err)
|
|
224
|
+
}
|
|
225
|
+
if projectInputPathKey(firstMissing) != projectInputPathKey(secondMissing) {
|
|
226
|
+
t.Fatal("recreated ordinary directory retained stale sensitivity")
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
upperUNC := windowsProjectInputKey(
|
|
230
|
+
`\\SERVER\Share\Folder\File.json`,
|
|
231
|
+
[]string{"folder", "file.json"},
|
|
232
|
+
)
|
|
233
|
+
lowerUNC := windowsProjectInputKey(
|
|
234
|
+
`\\server\share\Folder\File.json`,
|
|
235
|
+
[]string{"folder", "file.json"},
|
|
236
|
+
)
|
|
237
|
+
if upperUNC != lowerUNC || upperUNC != "//server/share/folder/file.json" {
|
|
238
|
+
t.Fatalf("UNC volume aliases = %q and %q", upperUNC, lowerUNC)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
t.Run("extended UNC aliases", func(t *testing.T) {
|
|
242
|
+
volume := filepath.VolumeName(ordinaryRoot)
|
|
243
|
+
if len(volume) != 2 || volume[1] != ':' {
|
|
244
|
+
t.Skipf("temporary directory has no drive volume: %q", volume)
|
|
245
|
+
}
|
|
246
|
+
tail := strings.TrimPrefix(ordinaryRoot, volume)
|
|
247
|
+
unc := `\\localhost\` + strings.ToUpper(volume[:1]) + `$` + tail
|
|
248
|
+
if _, err := os.Stat(unc); err != nil {
|
|
249
|
+
t.Skipf("administrative UNC share is unavailable: %v", err)
|
|
250
|
+
}
|
|
251
|
+
extended := `\\?\UNC\` + strings.TrimPrefix(unc, `\\`)
|
|
252
|
+
if projectInputPathKey(realProjectInputPath(unc)) !=
|
|
253
|
+
projectInputPathKey(realProjectInputPath(extended)) {
|
|
254
|
+
t.Fatalf("standard and extended UNC aliases split: %q, %q", unc, extended)
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
t.Run("junction physical identity", func(t *testing.T) {
|
|
259
|
+
declarationRoot := t.TempDir()
|
|
260
|
+
physicalRoot := t.TempDir()
|
|
261
|
+
for _, directory := range []string{
|
|
262
|
+
filepath.Join(physicalRoot, "docs"),
|
|
263
|
+
filepath.Join(physicalRoot, "api"),
|
|
264
|
+
} {
|
|
265
|
+
if err := os.MkdirAll(directory, 0o755); err != nil {
|
|
266
|
+
t.Fatal(err)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
exact := filepath.Join(physicalRoot, "docs", "spec.md")
|
|
270
|
+
globbed := filepath.Join(physicalRoot, "api", "schema.json")
|
|
271
|
+
for _, location := range []string{exact, globbed} {
|
|
272
|
+
if err := os.WriteFile(location, []byte("{}\n"), 0o644); err != nil {
|
|
273
|
+
t.Fatal(err)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
junction := filepath.Join(declarationRoot, "alias")
|
|
277
|
+
createProjectInputJunction(t, junction, physicalRoot)
|
|
278
|
+
snapshot, err := normalizeLSPProjectInputSnapshot(
|
|
279
|
+
LSPProjectInputSnapshot{
|
|
280
|
+
Root: declarationRoot,
|
|
281
|
+
Files: []string{
|
|
282
|
+
filepath.Join(junction, "docs", "spec.md"),
|
|
283
|
+
},
|
|
284
|
+
Globs: []string{
|
|
285
|
+
filepath.Join(junction, "api", "**", "*.json"),
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
declarationRoot,
|
|
289
|
+
)
|
|
290
|
+
if err != nil {
|
|
291
|
+
t.Fatalf("normalize junction inputs: %v", err)
|
|
292
|
+
}
|
|
293
|
+
junctionSource := &NativePluginSource{
|
|
294
|
+
plugins: []NativeLSPPluginEntry{first},
|
|
295
|
+
}
|
|
296
|
+
junctionSource.storeProjectInputs(first, 1, snapshot)
|
|
297
|
+
if !junctionSource.ProjectInputReloadFingerprintsAreCurrent() {
|
|
298
|
+
t.Fatal("junction reload baseline began stale")
|
|
299
|
+
}
|
|
300
|
+
assertProjectInputOwners(
|
|
301
|
+
t,
|
|
302
|
+
junctionSource,
|
|
303
|
+
exact,
|
|
304
|
+
[]string{pluginKey(first)},
|
|
305
|
+
)
|
|
306
|
+
assertProjectInputOwners(
|
|
307
|
+
t,
|
|
308
|
+
junctionSource,
|
|
309
|
+
globbed,
|
|
310
|
+
[]string{pluginKey(first)},
|
|
311
|
+
)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
t.Run("external junction selects reload", func(t *testing.T) {
|
|
315
|
+
reloadRoot := t.TempDir()
|
|
316
|
+
externalRoot := t.TempDir()
|
|
317
|
+
junction := filepath.Join(reloadRoot, "api")
|
|
318
|
+
snapshot, err := normalizeLSPProjectInputSnapshot(
|
|
319
|
+
LSPProjectInputSnapshot{
|
|
320
|
+
Root: reloadRoot,
|
|
321
|
+
Globs: []string{filepath.Join(junction, "**", "*.json")},
|
|
322
|
+
ReloadDirectories: []string{reloadRoot},
|
|
323
|
+
},
|
|
324
|
+
reloadRoot,
|
|
325
|
+
)
|
|
326
|
+
if err != nil {
|
|
327
|
+
t.Fatalf("normalize reload inputs: %v", err)
|
|
328
|
+
}
|
|
329
|
+
junctionSource := &NativePluginSource{
|
|
330
|
+
plugins: []NativeLSPPluginEntry{first},
|
|
331
|
+
}
|
|
332
|
+
junctionSource.storeProjectInputs(first, 1, snapshot)
|
|
333
|
+
createProjectInputJunction(t, junction, externalRoot)
|
|
334
|
+
created := fileChangeTypeCreated
|
|
335
|
+
if !junctionSource.ProjectInputReloadMatchesChange(
|
|
336
|
+
testFileURI(junction),
|
|
337
|
+
&created,
|
|
338
|
+
) {
|
|
339
|
+
t.Fatal("external junction creation did not select plugin reload")
|
|
340
|
+
}
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
t.Run("junction reload directory retains entry identity", func(t *testing.T) {
|
|
344
|
+
parent := t.TempDir()
|
|
345
|
+
firstTarget := t.TempDir()
|
|
346
|
+
secondTarget := t.TempDir()
|
|
347
|
+
junction := filepath.Join(parent, "selection")
|
|
348
|
+
createProjectInputJunction(t, junction, firstTarget)
|
|
349
|
+
snapshot, err := normalizeLSPProjectInputSnapshot(
|
|
350
|
+
LSPProjectInputSnapshot{
|
|
351
|
+
Root: parent,
|
|
352
|
+
ReloadDirectories: []string{junction},
|
|
353
|
+
},
|
|
354
|
+
parent,
|
|
355
|
+
)
|
|
356
|
+
if err != nil {
|
|
357
|
+
t.Fatalf("normalize junction reload directory: %v", err)
|
|
358
|
+
}
|
|
359
|
+
if len(snapshot.ReloadDirectories) != 2 {
|
|
360
|
+
t.Fatalf(
|
|
361
|
+
"junction reload identities = %#v, want physical and entry spellings",
|
|
362
|
+
snapshot.ReloadDirectories,
|
|
363
|
+
)
|
|
364
|
+
}
|
|
365
|
+
junctionSource := &NativePluginSource{
|
|
366
|
+
plugins: []NativeLSPPluginEntry{first},
|
|
367
|
+
}
|
|
368
|
+
junctionSource.storeProjectInputs(first, 1, snapshot)
|
|
369
|
+
|
|
370
|
+
if err := os.Remove(junction); err != nil {
|
|
371
|
+
t.Fatal(err)
|
|
372
|
+
}
|
|
373
|
+
deleted := fileChangeTypeDeleted
|
|
374
|
+
if !junctionSource.ProjectInputReloadMatchesChange(
|
|
375
|
+
testFileURI(junction),
|
|
376
|
+
&deleted,
|
|
377
|
+
) {
|
|
378
|
+
t.Fatal("junction reload directory deletion was not selected")
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
createProjectInputJunction(t, junction, secondTarget)
|
|
382
|
+
if junctionSource.ProjectInputReloadFingerprintsAreCurrent() {
|
|
383
|
+
t.Fatal("same-topology junction retarget kept startup fingerprint current")
|
|
384
|
+
}
|
|
385
|
+
changed := fileChangeTypeChanged
|
|
386
|
+
if !junctionSource.ProjectInputReloadMatchesChange(
|
|
387
|
+
testFileURI(junction),
|
|
388
|
+
&changed,
|
|
389
|
+
) {
|
|
390
|
+
t.Fatal("junction reload directory retarget was not selected")
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
func enableProjectInputCaseSensitivity(t *testing.T, directory string) {
|
|
396
|
+
t.Helper()
|
|
397
|
+
command := exec.Command(
|
|
398
|
+
"fsutil.exe",
|
|
399
|
+
"file",
|
|
400
|
+
"setCaseSensitiveInfo",
|
|
401
|
+
directory,
|
|
402
|
+
"enable",
|
|
403
|
+
)
|
|
404
|
+
if output, err := command.CombinedOutput(); err != nil {
|
|
405
|
+
t.Skipf(
|
|
406
|
+
"per-directory case sensitivity is unavailable: %v\n%s",
|
|
407
|
+
err,
|
|
408
|
+
output,
|
|
409
|
+
)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
func disableProjectInputCaseSensitivity(t *testing.T, directory string) {
|
|
414
|
+
t.Helper()
|
|
415
|
+
command := exec.Command(
|
|
416
|
+
"fsutil.exe",
|
|
417
|
+
"file",
|
|
418
|
+
"setCaseSensitiveInfo",
|
|
419
|
+
directory,
|
|
420
|
+
"disable",
|
|
421
|
+
)
|
|
422
|
+
if output, err := command.CombinedOutput(); err != nil {
|
|
423
|
+
t.Fatalf("failed to disable per-directory case sensitivity: %v\n%s", err, output)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
func createProjectInputJunction(
|
|
428
|
+
t *testing.T,
|
|
429
|
+
junction string,
|
|
430
|
+
target string,
|
|
431
|
+
) {
|
|
432
|
+
t.Helper()
|
|
433
|
+
command := exec.Command(
|
|
434
|
+
"cmd.exe",
|
|
435
|
+
"/d",
|
|
436
|
+
"/c",
|
|
437
|
+
"mklink",
|
|
438
|
+
"/J",
|
|
439
|
+
junction,
|
|
440
|
+
target,
|
|
441
|
+
)
|
|
442
|
+
if output, err := command.CombinedOutput(); err != nil {
|
|
443
|
+
t.Skipf("directory junction is unavailable: %v\n%s", err, output)
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
func assertProjectInputOwners(
|
|
448
|
+
t *testing.T,
|
|
449
|
+
source *NativePluginSource,
|
|
450
|
+
input string,
|
|
451
|
+
want []string,
|
|
452
|
+
) {
|
|
453
|
+
t.Helper()
|
|
454
|
+
if got := source.ProjectInputOwnersForURI(testFileURI(input)); !slices.Equal(
|
|
455
|
+
got,
|
|
456
|
+
want,
|
|
457
|
+
) {
|
|
458
|
+
t.Fatalf("%s owners = %#v, want %#v", input, got, want)
|
|
459
|
+
}
|
|
460
|
+
}
|