@hydranium/conformance 1.0.0-next.31 → 1.0.0-next.32
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 +7 -0
- package/lib/lsp/index.d.ts +21 -2
- package/lib/lsp/index.d.ts.map +1 -1
- package/lib/lsp/index.js +66 -1
- package/lib/lsp/index.js.map +1 -1
- package/lib/model.d.ts +43 -0
- package/lib/model.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/lsp/index.ts +105 -4
- package/src/model.ts +44 -0
package/README.md
CHANGED
|
@@ -29,6 +29,13 @@ author, and that keeps checking you as the framework moves.
|
|
|
29
29
|
- **A false-green guard in the fixture type.** `LanguageFixture` requires **both** a `valid` and an
|
|
30
30
|
`invalid` model, so an "invalid" model that in fact parses clean fails the diagnostics checks
|
|
31
31
|
instead of passing vacuously.
|
|
32
|
+
- **Server-side rendering, opt-in.** Supply `renderedDiagnostic: { locale, expected,
|
|
33
|
+
absentWithLocale }` on a fixture and the `/lsp` battery declares that locale at `initialize`,
|
|
34
|
+
then asserts the fragment appears — and, from `absentWithLocale`, that the untranslated fragment
|
|
35
|
+
DISAPPEARS with the locale and is present without it. It is opt-in because the framework ships no
|
|
36
|
+
catalogue and selects no locale, so a server that renders nothing is correct; and it is a PAIR
|
|
37
|
+
because "the message contains X" alone passes for a server whose English contains X. Omit
|
|
38
|
+
`absentWithLocale` and the control reports skipped rather than being quietly dropped.
|
|
32
39
|
|
|
33
40
|
## Install
|
|
34
41
|
|
package/lib/lsp/index.d.ts
CHANGED
|
@@ -9,6 +9,16 @@
|
|
|
9
9
|
import type { Harness } from '@hydranium/protocol/testing';
|
|
10
10
|
import type { ConformanceCheck } from '../conformance-suite.js';
|
|
11
11
|
import { type LanguageFixture } from '../model.js';
|
|
12
|
+
/**
|
|
13
|
+
* Structural minimum of the `initialize` params the kit sends — only `locale`,
|
|
14
|
+
* which is the one field a check needs to vary.
|
|
15
|
+
*
|
|
16
|
+
* A real `InitializeParams` is assignable to this, so a driver typed against
|
|
17
|
+
* upstream's shape satisfies the port with no adapter.
|
|
18
|
+
*/
|
|
19
|
+
export interface LspConformanceInitializeParams {
|
|
20
|
+
readonly locale?: string;
|
|
21
|
+
}
|
|
12
22
|
/** Structural minimum of an LSP `InitializeResult` — only the baseline capabilities the kit asserts. */
|
|
13
23
|
export interface LspConformanceInitializeResult {
|
|
14
24
|
readonly capabilities: {
|
|
@@ -41,8 +51,17 @@ export interface LspConformanceDiagnostic {
|
|
|
41
51
|
* gives the kit the universal `dispose()` teardown.
|
|
42
52
|
*/
|
|
43
53
|
export interface LspConformanceDriver extends Harness {
|
|
44
|
-
/**
|
|
45
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Drive the `initialize` → `initialized` handshake; resolve with the
|
|
56
|
+
* (structurally-minimal) result.
|
|
57
|
+
*
|
|
58
|
+
* `params` is optional and every field in it is too, so a driver that ignores
|
|
59
|
+
* the argument entirely still satisfies this port — which is what keeps
|
|
60
|
+
* adding a field here from being a breaking change. `locale` is the reading
|
|
61
|
+
* user's language, declared by the client because only the client knows it;
|
|
62
|
+
* the render check below is the only caller that passes anything.
|
|
63
|
+
*/
|
|
64
|
+
initialize(params?: LspConformanceInitializeParams): Promise<LspConformanceInitializeResult>;
|
|
46
65
|
/** Send `didOpen` for `uri` with full `text` under `languageId`. */
|
|
47
66
|
openDocument(uri: string, text: string, languageId: string, version?: number): void;
|
|
48
67
|
/** Send `didChange` for `uri` as a single full-text replacement at `version`. */
|
package/lib/lsp/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lsp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAkBlF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lsp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAkBlF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAyB,KAAK,eAAe,EAAiC,MAAM,aAAa,CAAC;AAEzG;;;;;;GAMG;AACH,MAAM,WAAW,8BAA8B;IAC5C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,wGAAwG;AACxG,MAAM,WAAW,8BAA8B;IAC5C,QAAQ,CAAC,YAAY,EAAE;QACpB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;QACpC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;KACxC,CAAC;CACJ;AAED,gGAAgG;AAChG,MAAM,WAAW,4BAA4B;IAC1C,QAAQ,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,CAAC;CACrC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,wBAAwB;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG;QAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CACxD;AAkBD;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAqB,SAAQ,OAAO;IAClD;;;;;;;;;OASG;IACH,UAAU,CAAC,MAAM,CAAC,EAAE,8BAA8B,GAAG,OAAO,CAAC,8BAA8B,CAAC,CAAC;IAC7F,oEAAoE;IACpE,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpF,iFAAiF;IACjF,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACjE,uGAAuG;IACvG,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,wBAAwB,EAAE,CAAC,CAAC;IAC/F,8DAA8D;IAC9D,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAAC;IAC9G,6CAA6C;IAC7C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAED,uCAAuC;AACvC,MAAM,WAAW,qBAAqB;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC7E,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IACnD,0DAA0D;IAC1D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC/B;AAKD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,gBAAgB,EAAE,CAkNjF"}
|
package/lib/lsp/index.js
CHANGED
|
@@ -108,7 +108,7 @@ export function buildLspChecks(options) {
|
|
|
108
108
|
}
|
|
109
109
|
});
|
|
110
110
|
for (const language of options.languages) {
|
|
111
|
-
const { valid, invalid, completionPosition } = language;
|
|
111
|
+
const { valid, invalid, completionPosition, renderedDiagnostic } = language;
|
|
112
112
|
const tag = `[${valid.languageId}]`;
|
|
113
113
|
checks.push({
|
|
114
114
|
title: `didOpen(valid) publishes empty diagnostics ${tag}`,
|
|
@@ -202,7 +202,72 @@ export function buildLspChecks(options) {
|
|
|
202
202
|
skipReason: 'fixture supplied no completionPosition'
|
|
203
203
|
});
|
|
204
204
|
}
|
|
205
|
+
// Opt-in: server-side rendering runs only when the fixture names a locale
|
|
206
|
+
// and the sentence it expects in it. The framework ships no catalogue, so
|
|
207
|
+
// a server that renders nothing is CORRECT and must not be failed.
|
|
208
|
+
const renderTitle = `a diagnostic is published rendered in the locale initialize declared ${tag}`;
|
|
209
|
+
const renderControlTitle = `the same diagnostic is NOT rendered when no locale is declared ${tag}`;
|
|
210
|
+
if (renderedDiagnostic) {
|
|
211
|
+
checks.push({
|
|
212
|
+
title: renderTitle,
|
|
213
|
+
body: async () => {
|
|
214
|
+
const published = await publishedMessagesFor(connect, invalid, { locale: renderedDiagnostic.locale });
|
|
215
|
+
assert.ok(published.some(message => message.includes(renderedDiagnostic.expected)), `no published diagnostic contained ${JSON.stringify(renderedDiagnostic.expected)}: ${JSON.stringify(published)}`);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
// The second half of the pair. "Contains X" also passes for a server
|
|
219
|
+
// whose English contains X, and for one that renders whatever the
|
|
220
|
+
// locale — so the discriminating read is the fragment that must
|
|
221
|
+
// disappear. Reported as skipped rather than silently dropped when the
|
|
222
|
+
// fixture omits it, because a lone containment check IS weaker and a
|
|
223
|
+
// reader has to be able to see that from the report.
|
|
224
|
+
if (renderedDiagnostic.absentWithLocale) {
|
|
225
|
+
const { absentWithLocale } = renderedDiagnostic;
|
|
226
|
+
checks.push({
|
|
227
|
+
title: renderControlTitle,
|
|
228
|
+
body: async () => {
|
|
229
|
+
const withLocale = await publishedMessagesFor(connect, invalid, { locale: renderedDiagnostic.locale });
|
|
230
|
+
assert.ok(!withLocale.some(message => message.includes(absentWithLocale)), `a diagnostic still contained ${JSON.stringify(absentWithLocale)} with the locale declared: ${JSON.stringify(withLocale)}`);
|
|
231
|
+
// And present without it, which is what rules out a fragment
|
|
232
|
+
// that never appears in either state — a typo in the fixture
|
|
233
|
+
// would otherwise make the assertion above pass for free.
|
|
234
|
+
const withoutLocale = await publishedMessagesFor(connect, invalid);
|
|
235
|
+
assert.ok(withoutLocale.some(message => message.includes(absentWithLocale)), `no diagnostic contained ${JSON.stringify(absentWithLocale)} with no locale declared, so it cannot witness the render: ${JSON.stringify(withoutLocale)}`);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
checks.push({
|
|
241
|
+
title: renderControlTitle,
|
|
242
|
+
skipReason: 'fixture supplied no absentWithLocale, so the render check is a containment test only'
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
checks.push({ title: renderTitle, skipReason: 'fixture supplied no renderedDiagnostic (server-side rendering is opt-in)' });
|
|
248
|
+
checks.push({ title: renderControlTitle, skipReason: 'fixture supplied no renderedDiagnostic (server-side rendering is opt-in)' });
|
|
249
|
+
}
|
|
205
250
|
}
|
|
206
251
|
return checks;
|
|
207
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Open `model` on a fresh driver and return the messages of the diagnostics
|
|
255
|
+
* published for it, as plain strings.
|
|
256
|
+
*
|
|
257
|
+
* A fresh driver per call because `initialize` is once-only per connection and
|
|
258
|
+
* the locale rides it — so the two states this compares cannot share one.
|
|
259
|
+
*/
|
|
260
|
+
async function publishedMessagesFor(connect, model, params) {
|
|
261
|
+
const driver = await connect();
|
|
262
|
+
try {
|
|
263
|
+
await driver.initialize(params);
|
|
264
|
+
const resolved = resolveModel(model);
|
|
265
|
+
const diagnostics = driver.nextDiagnostics(resolved.uri);
|
|
266
|
+
driver.openDocument(resolved.uri, resolved.text, resolved.languageId);
|
|
267
|
+
return (await diagnostics).map(diagnostic => typeof diagnostic.message === 'string' ? diagnostic.message : (diagnostic.message?.value ?? ''));
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
driver.dispose();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
208
273
|
//# sourceMappingURL=index.js.map
|
package/lib/lsp/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lsp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF;;;;;;;;;;;;;GAaG;AAEH,OAAO,MAAM,MAAM,oBAAoB,CAAC;AAGxC,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lsp/index.ts"],"names":[],"mappings":"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"}
|
package/lib/model.d.ts
CHANGED
|
@@ -154,5 +154,48 @@ export interface LanguageFixture {
|
|
|
154
154
|
* only**, and only when the driver exposes the opt-in reference surface.
|
|
155
155
|
*/
|
|
156
156
|
readonly referenceQuery?: ReferenceQuerySpec;
|
|
157
|
+
/**
|
|
158
|
+
* Optional: a locale plus the sentence the server must publish in it. Read
|
|
159
|
+
* by the **LSP slice only**.
|
|
160
|
+
*/
|
|
161
|
+
readonly renderedDiagnostic?: RenderedDiagnosticSpec;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* A locale, and one sentence the server must produce in it for the `invalid`
|
|
165
|
+
* fixture.
|
|
166
|
+
*
|
|
167
|
+
* **Opt-in, and it has to be.** The framework ships no catalogue and selects no
|
|
168
|
+
* locale, so a server that installs no renderer correctly publishes English —
|
|
169
|
+
* mandating this check would fail every adopter without i18n for doing the right
|
|
170
|
+
* thing. Supplying the field is the adopter saying "I render server-side, hold me
|
|
171
|
+
* to it".
|
|
172
|
+
*
|
|
173
|
+
* `expected` is a SUBSTRING, not the whole message. The kit owns no grammar, so
|
|
174
|
+
* it cannot know how many diagnostics `invalid` produces or in what order, and
|
|
175
|
+
* an adopter should be able to pin the translated fragment without restating a
|
|
176
|
+
* sentence they may reword. A substring long enough to be wrong if the render
|
|
177
|
+
* did not happen is the whole requirement.
|
|
178
|
+
*
|
|
179
|
+
* `absentWithoutLocale` is what makes the check a pair rather than a single
|
|
180
|
+
* assertion: "the message contains X" also passes for a server whose English
|
|
181
|
+
* happens to contain X, and for one that renders regardless of locale. Naming
|
|
182
|
+
* the fragment that must DISAPPEAR when no locale is declared is what
|
|
183
|
+
* distinguishes those.
|
|
184
|
+
*/
|
|
185
|
+
export interface RenderedDiagnosticSpec {
|
|
186
|
+
/** The locale to declare at `initialize` — the tag whose catalogue the server has. */
|
|
187
|
+
readonly locale: string;
|
|
188
|
+
/** A fragment of the translated sentence, present in some diagnostic of the `invalid` fixture. */
|
|
189
|
+
readonly expected: string;
|
|
190
|
+
/**
|
|
191
|
+
* A fragment that must be absent once `locale` is declared, and present
|
|
192
|
+
* without it — normally a piece of the server's own English.
|
|
193
|
+
*
|
|
194
|
+
* Optional only because a catalogue may translate a message whose English
|
|
195
|
+
* shares no distinctive fragment with it. Omitting it drops the second half
|
|
196
|
+
* of the pair and leaves a check that a render-nothing server can pass; the
|
|
197
|
+
* kit reports that rather than pretending otherwise.
|
|
198
|
+
*/
|
|
199
|
+
readonly absentWithLocale?: string;
|
|
157
200
|
}
|
|
158
201
|
//# sourceMappingURL=model.d.ts.map
|
package/lib/model.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAExC;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC9B,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CAClC;AAED,iFAAiF;AACjF,MAAM,WAAW,wBAAwB;IACtC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACxB;AAED,8CAA8C;AAC9C,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAExD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,gBAAgB,GAAG,wBAAwB,CAM9E;AAED;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACtB,gGAAgG;IAChG,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;CAC9C;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,kBAAkB;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6GAA6G;IAC7G,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,iBAAiB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAClF;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACtC;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,WAAW,eAAe;IAC7B,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC;IACzB,kFAAkF;IAClF,QAAQ,CAAC,kBAAkB,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACpF;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAExC;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC9B,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CAClC;AAED,iFAAiF;AACjF,MAAM,WAAW,wBAAwB;IACtC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACxB;AAED,8CAA8C;AAC9C,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAExD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,gBAAgB,GAAG,wBAAwB,CAM9E;AAED;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACtB,gGAAgG;IAChG,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;CAC9C;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,kBAAkB;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6GAA6G;IAC7G,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,iBAAiB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAClF;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACtC;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,WAAW,eAAe;IAC7B,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC;IACzB,kFAAkF;IAClF,QAAQ,CAAC,kBAAkB,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACpF;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,kBAAkB,CAAC;IAC7C;;;OAGG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,sBAAsB,CAAC;CACvD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,sBAAsB;IACpC,sFAAsF;IACtF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kGAAkG;IAClG,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;;;;;OAQG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACrC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hydranium/conformance",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.32",
|
|
4
4
|
"description": "Protocol conformance kit (TCK) for hydranium adopters. Per-head slices (/data, /lsp, /glsp) of fixture-parameterized, protocol-only smoke checks an adopter runs against its own server.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hydranium",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"watch": "tsc -b -w --preserveWatchOutput"
|
|
89
89
|
},
|
|
90
90
|
"devDependencies": {
|
|
91
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
91
|
+
"@hydranium/protocol": "1.0.0-next.32",
|
|
92
92
|
"@jest/globals": "^29.7.0",
|
|
93
93
|
"@types/jest": "^29.5.12",
|
|
94
94
|
"jest": "^29.7.0",
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"vitest": "^4.0.0"
|
|
99
99
|
},
|
|
100
100
|
"peerDependencies": {
|
|
101
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
101
|
+
"@hydranium/protocol": "1.0.0-next.32",
|
|
102
102
|
"@jest/globals": "^29.0.0",
|
|
103
103
|
"vitest": "^4.0.0"
|
|
104
104
|
},
|
package/src/lsp/index.ts
CHANGED
|
@@ -25,7 +25,18 @@
|
|
|
25
25
|
import assert from 'node:assert/strict';
|
|
26
26
|
import type { Harness } from '@hydranium/protocol/testing';
|
|
27
27
|
import type { ConformanceCheck } from '../conformance-suite.js';
|
|
28
|
-
import { type LanguageFixture, resolveDeferred, resolveModel } from '../model.js';
|
|
28
|
+
import { type ConformanceModel, type LanguageFixture, resolveDeferred, resolveModel } from '../model.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Structural minimum of the `initialize` params the kit sends — only `locale`,
|
|
32
|
+
* which is the one field a check needs to vary.
|
|
33
|
+
*
|
|
34
|
+
* A real `InitializeParams` is assignable to this, so a driver typed against
|
|
35
|
+
* upstream's shape satisfies the port with no adapter.
|
|
36
|
+
*/
|
|
37
|
+
export interface LspConformanceInitializeParams {
|
|
38
|
+
readonly locale?: string;
|
|
39
|
+
}
|
|
29
40
|
|
|
30
41
|
/** Structural minimum of an LSP `InitializeResult` — only the baseline capabilities the kit asserts. */
|
|
31
42
|
export interface LspConformanceInitializeResult {
|
|
@@ -76,8 +87,17 @@ function hasTextMessage(diagnostic: LspConformanceDiagnostic): boolean {
|
|
|
76
87
|
* gives the kit the universal `dispose()` teardown.
|
|
77
88
|
*/
|
|
78
89
|
export interface LspConformanceDriver extends Harness {
|
|
79
|
-
/**
|
|
80
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Drive the `initialize` → `initialized` handshake; resolve with the
|
|
92
|
+
* (structurally-minimal) result.
|
|
93
|
+
*
|
|
94
|
+
* `params` is optional and every field in it is too, so a driver that ignores
|
|
95
|
+
* the argument entirely still satisfies this port — which is what keeps
|
|
96
|
+
* adding a field here from being a breaking change. `locale` is the reading
|
|
97
|
+
* user's language, declared by the client because only the client knows it;
|
|
98
|
+
* the render check below is the only caller that passes anything.
|
|
99
|
+
*/
|
|
100
|
+
initialize(params?: LspConformanceInitializeParams): Promise<LspConformanceInitializeResult>;
|
|
81
101
|
/** Send `didOpen` for `uri` with full `text` under `languageId`. */
|
|
82
102
|
openDocument(uri: string, text: string, languageId: string, version?: number): void;
|
|
83
103
|
/** Send `didChange` for `uri` as a single full-text replacement at `version`. */
|
|
@@ -177,7 +197,7 @@ export function buildLspChecks(options: LspConformanceOptions): ConformanceCheck
|
|
|
177
197
|
});
|
|
178
198
|
|
|
179
199
|
for (const language of options.languages) {
|
|
180
|
-
const { valid, invalid, completionPosition } = language;
|
|
200
|
+
const { valid, invalid, completionPosition, renderedDiagnostic } = language;
|
|
181
201
|
const tag = `[${valid.languageId}]`;
|
|
182
202
|
|
|
183
203
|
checks.push({
|
|
@@ -273,7 +293,88 @@ export function buildLspChecks(options: LspConformanceOptions): ConformanceCheck
|
|
|
273
293
|
skipReason: 'fixture supplied no completionPosition'
|
|
274
294
|
});
|
|
275
295
|
}
|
|
296
|
+
|
|
297
|
+
// Opt-in: server-side rendering runs only when the fixture names a locale
|
|
298
|
+
// and the sentence it expects in it. The framework ships no catalogue, so
|
|
299
|
+
// a server that renders nothing is CORRECT and must not be failed.
|
|
300
|
+
const renderTitle = `a diagnostic is published rendered in the locale initialize declared ${tag}`;
|
|
301
|
+
const renderControlTitle = `the same diagnostic is NOT rendered when no locale is declared ${tag}`;
|
|
302
|
+
if (renderedDiagnostic) {
|
|
303
|
+
checks.push({
|
|
304
|
+
title: renderTitle,
|
|
305
|
+
body: async () => {
|
|
306
|
+
const published = await publishedMessagesFor(connect, invalid, { locale: renderedDiagnostic.locale });
|
|
307
|
+
assert.ok(
|
|
308
|
+
published.some(message => message.includes(renderedDiagnostic.expected)),
|
|
309
|
+
`no published diagnostic contained ${JSON.stringify(renderedDiagnostic.expected)}: ${JSON.stringify(published)}`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// The second half of the pair. "Contains X" also passes for a server
|
|
315
|
+
// whose English contains X, and for one that renders whatever the
|
|
316
|
+
// locale — so the discriminating read is the fragment that must
|
|
317
|
+
// disappear. Reported as skipped rather than silently dropped when the
|
|
318
|
+
// fixture omits it, because a lone containment check IS weaker and a
|
|
319
|
+
// reader has to be able to see that from the report.
|
|
320
|
+
if (renderedDiagnostic.absentWithLocale) {
|
|
321
|
+
const { absentWithLocale } = renderedDiagnostic;
|
|
322
|
+
checks.push({
|
|
323
|
+
title: renderControlTitle,
|
|
324
|
+
body: async () => {
|
|
325
|
+
const withLocale = await publishedMessagesFor(connect, invalid, { locale: renderedDiagnostic.locale });
|
|
326
|
+
assert.ok(
|
|
327
|
+
!withLocale.some(message => message.includes(absentWithLocale)),
|
|
328
|
+
`a diagnostic still contained ${JSON.stringify(absentWithLocale)} with the locale declared: ${JSON.stringify(withLocale)}`
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
// And present without it, which is what rules out a fragment
|
|
332
|
+
// that never appears in either state — a typo in the fixture
|
|
333
|
+
// would otherwise make the assertion above pass for free.
|
|
334
|
+
const withoutLocale = await publishedMessagesFor(connect, invalid);
|
|
335
|
+
assert.ok(
|
|
336
|
+
withoutLocale.some(message => message.includes(absentWithLocale)),
|
|
337
|
+
`no diagnostic contained ${JSON.stringify(absentWithLocale)} with no locale declared, so it cannot witness the render: ${JSON.stringify(withoutLocale)}`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
} else {
|
|
342
|
+
checks.push({
|
|
343
|
+
title: renderControlTitle,
|
|
344
|
+
skipReason: 'fixture supplied no absentWithLocale, so the render check is a containment test only'
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
} else {
|
|
348
|
+
checks.push({ title: renderTitle, skipReason: 'fixture supplied no renderedDiagnostic (server-side rendering is opt-in)' });
|
|
349
|
+
checks.push({ title: renderControlTitle, skipReason: 'fixture supplied no renderedDiagnostic (server-side rendering is opt-in)' });
|
|
350
|
+
}
|
|
276
351
|
}
|
|
277
352
|
|
|
278
353
|
return checks;
|
|
279
354
|
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Open `model` on a fresh driver and return the messages of the diagnostics
|
|
358
|
+
* published for it, as plain strings.
|
|
359
|
+
*
|
|
360
|
+
* A fresh driver per call because `initialize` is once-only per connection and
|
|
361
|
+
* the locale rides it — so the two states this compares cannot share one.
|
|
362
|
+
*/
|
|
363
|
+
async function publishedMessagesFor(
|
|
364
|
+
connect: LspConformanceOptions['connect'],
|
|
365
|
+
model: ConformanceModel,
|
|
366
|
+
params?: LspConformanceInitializeParams
|
|
367
|
+
): Promise<string[]> {
|
|
368
|
+
const driver = await connect();
|
|
369
|
+
try {
|
|
370
|
+
await driver.initialize(params);
|
|
371
|
+
const resolved = resolveModel(model);
|
|
372
|
+
const diagnostics = driver.nextDiagnostics(resolved.uri);
|
|
373
|
+
driver.openDocument(resolved.uri, resolved.text, resolved.languageId);
|
|
374
|
+
return (await diagnostics).map(diagnostic =>
|
|
375
|
+
typeof diagnostic.message === 'string' ? diagnostic.message : (diagnostic.message?.value ?? '')
|
|
376
|
+
);
|
|
377
|
+
} finally {
|
|
378
|
+
driver.dispose();
|
|
379
|
+
}
|
|
380
|
+
}
|
package/src/model.ts
CHANGED
|
@@ -167,4 +167,48 @@ export interface LanguageFixture {
|
|
|
167
167
|
* only**, and only when the driver exposes the opt-in reference surface.
|
|
168
168
|
*/
|
|
169
169
|
readonly referenceQuery?: ReferenceQuerySpec;
|
|
170
|
+
/**
|
|
171
|
+
* Optional: a locale plus the sentence the server must publish in it. Read
|
|
172
|
+
* by the **LSP slice only**.
|
|
173
|
+
*/
|
|
174
|
+
readonly renderedDiagnostic?: RenderedDiagnosticSpec;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* A locale, and one sentence the server must produce in it for the `invalid`
|
|
179
|
+
* fixture.
|
|
180
|
+
*
|
|
181
|
+
* **Opt-in, and it has to be.** The framework ships no catalogue and selects no
|
|
182
|
+
* locale, so a server that installs no renderer correctly publishes English —
|
|
183
|
+
* mandating this check would fail every adopter without i18n for doing the right
|
|
184
|
+
* thing. Supplying the field is the adopter saying "I render server-side, hold me
|
|
185
|
+
* to it".
|
|
186
|
+
*
|
|
187
|
+
* `expected` is a SUBSTRING, not the whole message. The kit owns no grammar, so
|
|
188
|
+
* it cannot know how many diagnostics `invalid` produces or in what order, and
|
|
189
|
+
* an adopter should be able to pin the translated fragment without restating a
|
|
190
|
+
* sentence they may reword. A substring long enough to be wrong if the render
|
|
191
|
+
* did not happen is the whole requirement.
|
|
192
|
+
*
|
|
193
|
+
* `absentWithoutLocale` is what makes the check a pair rather than a single
|
|
194
|
+
* assertion: "the message contains X" also passes for a server whose English
|
|
195
|
+
* happens to contain X, and for one that renders regardless of locale. Naming
|
|
196
|
+
* the fragment that must DISAPPEAR when no locale is declared is what
|
|
197
|
+
* distinguishes those.
|
|
198
|
+
*/
|
|
199
|
+
export interface RenderedDiagnosticSpec {
|
|
200
|
+
/** The locale to declare at `initialize` — the tag whose catalogue the server has. */
|
|
201
|
+
readonly locale: string;
|
|
202
|
+
/** A fragment of the translated sentence, present in some diagnostic of the `invalid` fixture. */
|
|
203
|
+
readonly expected: string;
|
|
204
|
+
/**
|
|
205
|
+
* A fragment that must be absent once `locale` is declared, and present
|
|
206
|
+
* without it — normally a piece of the server's own English.
|
|
207
|
+
*
|
|
208
|
+
* Optional only because a catalogue may translate a message whose English
|
|
209
|
+
* shares no distinctive fragment with it. Omitting it drops the second half
|
|
210
|
+
* of the pair and leaves a check that a render-nothing server can pass; the
|
|
211
|
+
* kit reports that rather than pretending otherwise.
|
|
212
|
+
*/
|
|
213
|
+
readonly absentWithLocale?: string;
|
|
170
214
|
}
|