@birdcc/lsp 0.0.1-alpha.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/.oxfmtrc.json +16 -0
- package/LICENSE +674 -0
- package/README.md +343 -0
- package/dist/completion.d.ts +8 -0
- package/dist/completion.d.ts.map +1 -0
- package/dist/completion.js +137 -0
- package/dist/completion.js.map +1 -0
- package/dist/definition.d.ts +5 -0
- package/dist/definition.d.ts.map +1 -0
- package/dist/definition.js +21 -0
- package/dist/definition.js.map +1 -0
- package/dist/diagnostic.d.ts +6 -0
- package/dist/diagnostic.d.ts.map +1 -0
- package/dist/diagnostic.js +38 -0
- package/dist/diagnostic.js.map +1 -0
- package/dist/document-symbol.d.ts +4 -0
- package/dist/document-symbol.d.ts.map +1 -0
- package/dist/document-symbol.js +20 -0
- package/dist/document-symbol.js.map +1 -0
- package/dist/hover-docs.d.ts +5 -0
- package/dist/hover-docs.d.ts.map +1 -0
- package/dist/hover-docs.js +141 -0
- package/dist/hover-docs.js.map +1 -0
- package/dist/hover-docs.yaml +600 -0
- package/dist/hover.d.ts +5 -0
- package/dist/hover.d.ts.map +1 -0
- package/dist/hover.js +81 -0
- package/dist/hover.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/lsp-server.d.ts +3 -0
- package/dist/lsp-server.d.ts.map +1 -0
- package/dist/lsp-server.js +250 -0
- package/dist/lsp-server.js.map +1 -0
- package/dist/references.d.ts +5 -0
- package/dist/references.d.ts.map +1 -0
- package/dist/references.js +48 -0
- package/dist/references.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +4 -0
- package/dist/server.js.map +1 -0
- package/dist/shared.d.ts +17 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +150 -0
- package/dist/shared.js.map +1 -0
- package/dist/symbol-utils.d.ts +17 -0
- package/dist/symbol-utils.d.ts.map +1 -0
- package/dist/symbol-utils.js +84 -0
- package/dist/symbol-utils.js.map +1 -0
- package/dist/validation.d.ts +21 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +47 -0
- package/dist/validation.js.map +1 -0
- package/package.json +45 -0
- package/scripts/copy-hover-yaml.mjs +14 -0
- package/src/completion.ts +223 -0
- package/src/definition.ts +50 -0
- package/src/diagnostic.ts +48 -0
- package/src/document-symbol.ts +27 -0
- package/src/hover-docs.ts +223 -0
- package/src/hover-docs.yaml +600 -0
- package/src/hover.ts +122 -0
- package/src/index.ts +8 -0
- package/src/lsp-server.ts +350 -0
- package/src/references.ts +107 -0
- package/src/server.ts +4 -0
- package/src/shared.ts +182 -0
- package/src/symbol-utils.ts +126 -0
- package/src/validation.ts +85 -0
- package/test/hover-docs.test.ts +18 -0
- package/test/lsp.test.ts +304 -0
- package/test/perf-baseline.test.ts +96 -0
- package/test/validation.test.ts +212 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
createValidationScheduler,
|
|
4
|
+
type ValidationDocument,
|
|
5
|
+
type ValidationPublishPayload,
|
|
6
|
+
} from "../src/validation.js";
|
|
7
|
+
|
|
8
|
+
interface MockDiagnostic {
|
|
9
|
+
code: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface MockDocument extends ValidationDocument {
|
|
13
|
+
text: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const createDocument = (
|
|
17
|
+
uri: string,
|
|
18
|
+
text: string,
|
|
19
|
+
version = 1,
|
|
20
|
+
): MockDocument => ({
|
|
21
|
+
uri,
|
|
22
|
+
version,
|
|
23
|
+
text,
|
|
24
|
+
getText(): string {
|
|
25
|
+
return this.text;
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("@birdcc/lsp validation scheduler", () => {
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
vi.useRealTimers();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("debounces rapid document changes and validates only latest text", async () => {
|
|
35
|
+
vi.useFakeTimers();
|
|
36
|
+
const validate = vi.fn(
|
|
37
|
+
async (document: MockDocument): Promise<MockDiagnostic[]> => {
|
|
38
|
+
return [{ code: document.getText() }];
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
const publish =
|
|
42
|
+
vi.fn<(payload: ValidationPublishPayload<MockDiagnostic>) => void>();
|
|
43
|
+
const scheduler = createValidationScheduler<MockDocument, MockDiagnostic>({
|
|
44
|
+
debounceMs: 100,
|
|
45
|
+
validate,
|
|
46
|
+
publish,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const docV1 = createDocument("file:///bird.conf", "v1");
|
|
50
|
+
const docV2 = createDocument("file:///bird.conf", "v2");
|
|
51
|
+
|
|
52
|
+
scheduler.schedule(docV1);
|
|
53
|
+
scheduler.schedule(docV2);
|
|
54
|
+
|
|
55
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
56
|
+
|
|
57
|
+
expect(validate).toHaveBeenCalledTimes(1);
|
|
58
|
+
expect(validate).toHaveBeenCalledWith(docV2);
|
|
59
|
+
expect(publish).toHaveBeenCalledTimes(1);
|
|
60
|
+
expect(publish).toHaveBeenCalledWith({
|
|
61
|
+
uri: "file:///bird.conf",
|
|
62
|
+
version: 1,
|
|
63
|
+
diagnostics: [{ code: "v2" }],
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("drops stale validation result after close", async () => {
|
|
68
|
+
vi.useFakeTimers();
|
|
69
|
+
|
|
70
|
+
let resolveValidation: ((value: MockDiagnostic[]) => void) | null = null;
|
|
71
|
+
const validate = vi.fn(
|
|
72
|
+
(document: MockDocument): Promise<MockDiagnostic[]> =>
|
|
73
|
+
new Promise((resolve) => {
|
|
74
|
+
resolveValidation = resolve;
|
|
75
|
+
void document;
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
const publish =
|
|
79
|
+
vi.fn<(payload: ValidationPublishPayload<MockDiagnostic>) => void>();
|
|
80
|
+
const scheduler = createValidationScheduler<MockDocument, MockDiagnostic>({
|
|
81
|
+
debounceMs: 1,
|
|
82
|
+
validate,
|
|
83
|
+
publish,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const uri = "file:///bird.conf";
|
|
87
|
+
scheduler.schedule(createDocument(uri, "v1"));
|
|
88
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
89
|
+
scheduler.close(uri);
|
|
90
|
+
|
|
91
|
+
resolveValidation?.([{ code: "stale-result" }]);
|
|
92
|
+
await vi.waitUntil(() => publish.mock.calls.length === 1);
|
|
93
|
+
|
|
94
|
+
expect(publish).toHaveBeenCalledTimes(1);
|
|
95
|
+
expect(publish).toHaveBeenCalledWith({
|
|
96
|
+
uri,
|
|
97
|
+
version: 1,
|
|
98
|
+
diagnostics: [],
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("publishes reopened document diagnostics after close while dropping older in-flight results", async () => {
|
|
103
|
+
vi.useFakeTimers();
|
|
104
|
+
|
|
105
|
+
let resolveFirstValidation: ((value: MockDiagnostic[]) => void) | null =
|
|
106
|
+
null;
|
|
107
|
+
const validate = vi.fn(
|
|
108
|
+
(document: MockDocument): Promise<MockDiagnostic[]> => {
|
|
109
|
+
if (document.version === 1) {
|
|
110
|
+
return new Promise((resolve) => {
|
|
111
|
+
resolveFirstValidation = resolve;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return Promise.resolve([{ code: `v${document.version}` }]);
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
const publish =
|
|
119
|
+
vi.fn<(payload: ValidationPublishPayload<MockDiagnostic>) => void>();
|
|
120
|
+
const scheduler = createValidationScheduler<MockDocument, MockDiagnostic>({
|
|
121
|
+
debounceMs: 1,
|
|
122
|
+
validate,
|
|
123
|
+
publish,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const uri = "file:///bird.conf";
|
|
127
|
+
scheduler.schedule(createDocument(uri, "v1", 1));
|
|
128
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
129
|
+
scheduler.close(uri);
|
|
130
|
+
|
|
131
|
+
scheduler.schedule(createDocument(uri, "v2", 2));
|
|
132
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
133
|
+
|
|
134
|
+
resolveFirstValidation?.([{ code: "stale-v1" }]);
|
|
135
|
+
await vi.waitUntil(() => publish.mock.calls.length === 2);
|
|
136
|
+
|
|
137
|
+
expect(publish).toHaveBeenCalledTimes(2);
|
|
138
|
+
expect(publish.mock.calls[0]?.[0]).toEqual({
|
|
139
|
+
uri,
|
|
140
|
+
version: 1,
|
|
141
|
+
diagnostics: [],
|
|
142
|
+
});
|
|
143
|
+
expect(publish.mock.calls[1]?.[0]).toEqual({
|
|
144
|
+
uri,
|
|
145
|
+
version: 2,
|
|
146
|
+
diagnostics: [{ code: "v2" }],
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("drops older in-flight diagnostics when a newer validation for same uri completes", async () => {
|
|
151
|
+
vi.useFakeTimers();
|
|
152
|
+
|
|
153
|
+
let resolveFirstValidation: ((value: MockDiagnostic[]) => void) | null =
|
|
154
|
+
null;
|
|
155
|
+
const validate = vi.fn(
|
|
156
|
+
(document: MockDocument): Promise<MockDiagnostic[]> => {
|
|
157
|
+
if (document.version === 1) {
|
|
158
|
+
return new Promise((resolve) => {
|
|
159
|
+
resolveFirstValidation = resolve;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return Promise.resolve([{ code: "fresh-v2" }]);
|
|
164
|
+
},
|
|
165
|
+
);
|
|
166
|
+
const publish =
|
|
167
|
+
vi.fn<(payload: ValidationPublishPayload<MockDiagnostic>) => void>();
|
|
168
|
+
const scheduler = createValidationScheduler<MockDocument, MockDiagnostic>({
|
|
169
|
+
debounceMs: 1,
|
|
170
|
+
validate,
|
|
171
|
+
publish,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const uri = "file:///bird.conf";
|
|
175
|
+
scheduler.schedule(createDocument(uri, "v1", 1));
|
|
176
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
177
|
+
|
|
178
|
+
scheduler.schedule(createDocument(uri, "v2", 2));
|
|
179
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
180
|
+
await vi.waitUntil(() => publish.mock.calls.length === 1);
|
|
181
|
+
|
|
182
|
+
resolveFirstValidation?.([{ code: "stale-v1" }]);
|
|
183
|
+
await vi.waitUntil(() => publish.mock.calls.length === 1);
|
|
184
|
+
|
|
185
|
+
expect(publish.mock.calls[0]?.[0]).toEqual({
|
|
186
|
+
uri,
|
|
187
|
+
version: 2,
|
|
188
|
+
diagnostics: [{ code: "fresh-v2" }],
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("publishes empty diagnostics when closing unknown uri", () => {
|
|
193
|
+
const validate = vi.fn(async () => []);
|
|
194
|
+
const publish =
|
|
195
|
+
vi.fn<(payload: ValidationPublishPayload<MockDiagnostic>) => void>();
|
|
196
|
+
const scheduler = createValidationScheduler<MockDocument, MockDiagnostic>({
|
|
197
|
+
debounceMs: 1,
|
|
198
|
+
validate,
|
|
199
|
+
publish,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
scheduler.close("file:///missing.conf");
|
|
203
|
+
|
|
204
|
+
expect(publish).toHaveBeenCalledTimes(1);
|
|
205
|
+
expect(publish).toHaveBeenCalledWith({
|
|
206
|
+
uri: "file:///missing.conf",
|
|
207
|
+
version: undefined,
|
|
208
|
+
diagnostics: [],
|
|
209
|
+
});
|
|
210
|
+
expect(validate).not.toHaveBeenCalled();
|
|
211
|
+
});
|
|
212
|
+
});
|