Skip to content

Commit c5993b9

Browse files
committed
Support Custom Dialects
1 parent 6a6d6c0 commit c5993b9

File tree

7 files changed

+214
-9
lines changed

7 files changed

+214
-9
lines changed

language-server/src/build-server.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { TextDocuments } from "vscode-languageserver";
22
import { TextDocument } from "vscode-languageserver-textdocument";
3+
import { removeMediaTypePlugin } from "@hyperjump/browser";
34

45
// Hyperjump
56
import "@hyperjump/json-schema/draft-2020-12";
@@ -22,6 +23,9 @@ import "@hyperjump/json-schema/draft-04";
2223
* }} Feature
2324
*/
2425

26+
removeMediaTypePlugin("http");
27+
removeMediaTypePlugin("https");
28+
2529
/** @type (connection: Connection, features: Feature[]) => void */
2630
export const buildServer = (connection, features) => {
2731
const documents = new TextDocuments(TextDocument);

language-server/src/features/hover.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ describe("Feature - Hover", () => {
196196
["type", "\"object\""]
197197
])("%s should have a message", async (keyword, value) => {
198198
documentUri = await client.openDocument("./subject.schema.json", `{
199-
"$schema": "https://json-schema.org/draft/2020-12/schema",
199+
"$schema": "https://json-schema.org/draft/2020-12/schema",${keyword === "$vocabulary" ? `"$id": "https://example.com/schema",` : ""}
200200
"${keyword}": ${value}
201201
}`);
202202

@@ -317,7 +317,7 @@ describe("Feature - Hover", () => {
317317
["type", "\"object\""]
318318
])("%s should have a message", async (keyword, value) => {
319319
documentUri = await client.openDocument("./subject.schema.json", `{
320-
"$schema": "https://json-schema.org/draft/2019-09/schema",
320+
"$schema": "https://json-schema.org/draft/2019-09/schema",${keyword === "$vocabulary" ? `"$id": "https://example.com/schema",` : ""}
321321
"${keyword}": ${value}
322322
}`);
323323

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
2+
import {
3+
PublishDiagnosticsNotification
4+
} from "vscode-languageserver";
5+
import { TestClient } from "../test-client.js";
6+
import documentSettings from "./document-settings.js";
7+
import semanticTokens from "./semantic-tokens.js";
8+
import schemaRegistry from "./schema-registry.js";
9+
import workspace from "./workspace.js";
10+
import validationErrorsFeature from "./validation-errors.js";
11+
import { setupWorkspace, tearDownWorkspace } from "../test-utils.js";
12+
13+
import type { Diagnostic } from "vscode-languageserver";
14+
import type { DocumentSettings } from "./document-settings.js";
15+
import { resolveIri } from "@hyperjump/uri";
16+
17+
18+
describe("Feature - Register Schema", () => {
19+
let client: TestClient<DocumentSettings>;
20+
let workspaceFolder: string;
21+
22+
beforeEach(async () => {
23+
client = new TestClient([
24+
workspace,
25+
documentSettings,
26+
semanticTokens,
27+
schemaRegistry,
28+
validationErrorsFeature
29+
]);
30+
workspaceFolder = await setupWorkspace({
31+
"subjectB.schema.json": ``,
32+
"subject.schema.json": ``
33+
});
34+
35+
await client.start({
36+
workspaceFolders: [
37+
{
38+
name: "root",
39+
uri: workspaceFolder
40+
}
41+
]
42+
});
43+
});
44+
45+
afterEach(async () => {
46+
await client.stop();
47+
await tearDownWorkspace(workspaceFolder);
48+
});
49+
50+
test("Registered schema ", async () => {
51+
const diagnosticsPromise = new Promise<Diagnostic[]>((resolve) => {
52+
client.onNotification(PublishDiagnosticsNotification.type, (params) => {
53+
resolve(params.diagnostics);
54+
});
55+
});
56+
57+
const documentUriB = resolveIri("./subjectB.schema.json", `${workspaceFolder}/`);
58+
59+
await client.openDocument(documentUriB, `{
60+
"$id": "https://example.com/my-dialect",
61+
"$schema": "https://json-schema.org/draft/2020-12/schema",
62+
63+
"$vocabulary": {
64+
"https://json-schema.org/draft/2020-12/vocab/core": true,
65+
"https://json-schema.org/draft/2020-12/vocab/applicator": true,
66+
"https://json-schema.org/draft/2020-12/vocab/validation": true
67+
}
68+
}`);
69+
70+
const documentUri = resolveIri("./subjectB.schema.json", `${workspaceFolder}/`);
71+
await client.openDocument(documentUri, `{
72+
"$schema": "https://example.com/my-dialect",
73+
}`);
74+
75+
const diagnostics = await diagnosticsPromise;
76+
expect(diagnostics).to.eql([]);
77+
});
78+
});

language-server/src/features/schema-registry.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { getDocumentSettings } from "./document-settings.js";
22
import * as SchemaDocument from "../schema-document.js";
3+
import { registerSchema, unregisterSchema } from "@hyperjump/json-schema/draft-2020-12";
4+
import * as SchemaNode from "../schema-node.js";
5+
import { publishAsync } from "../pubsub.js";
6+
import { keywordNameFor } from "../util.js";
37

48
/**
59
* @import { Connection } from "vscode-languageserver"
@@ -12,10 +16,21 @@ import * as SchemaDocument from "../schema-document.js";
1216

1317
/** @type Feature */
1418
export default {
15-
load(_connection, documents) {
19+
load(connection, documents) {
1620
documents.onDidClose(({ document }) => {
1721
schemaDocuments.delete(document.uri);
1822
});
23+
24+
connection.workspace.onDidDeleteFiles(async (params) => {
25+
for (const file of params.files) {
26+
const textDocument = documents.get(file.uri);
27+
const schemaDocument = textDocument && await getSchemaDocument(connection, textDocument);
28+
for (const schemaResource of schemaDocument?.schemaResources ?? []) {
29+
unregisterSchema(schemaResource.baseUri);
30+
await publishAsync("workspaceChanged", { changes: [] });
31+
}
32+
}
33+
});
1934
},
2035

2136
onInitialize() {
@@ -41,6 +56,13 @@ export const getSchemaDocument = async (connection, textDocument) => {
4156

4257
schemaDocuments.set(textDocument.uri, { version: textDocument.version, schemaDocument });
4358
}
59+
for (const schemaResource of schemaDocument.schemaResources) {
60+
const vocabToken = keywordNameFor("https://json-schema.org/keyword/vocabulary", schemaResource.dialectUri);
61+
const vocabularyNode = vocabToken && await SchemaNode.step(vocabToken, schemaResource);
62+
if (vocabularyNode) {
63+
registerSchema(SchemaNode.value(schemaResource), textDocument.uri);
64+
}
65+
}
4466

4567
return schemaDocument;
4668
};

language-server/src/features/semantic-tokens.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ const schemaHandler = function* (schemaResource) {
191191
};
192192

193193
/** @type (keywordName: string, dialectUri?: string) => string | undefined */
194-
const keywordIdFor = (keywordName, dialectUri) => {
194+
export const keywordIdFor = (keywordName, dialectUri) => {
195195
if (!dialectUri) {
196196
return;
197197
}

language-server/src/features/semantic-tokens.test.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ describe("Feature - Semantic Tokens", () => {
114114
await client.closeDocument(documentUri);
115115
});
116116

117-
118117
test.each([
119118
// Applicators
120119
["prefixItems", "[{}]", [1, 2, 9, 1, 0, 1, 2, 13, 1, 0]],
@@ -144,7 +143,7 @@ describe("Feature - Semantic Tokens", () => {
144143
["$ref", "\"\"", [1, 2, 9, 1, 0, 1, 2, 6, 1, 0]],
145144
["$dynamicRef", "\"\"", [1, 2, 9, 1, 0, 1, 2, 13, 1, 0]],
146145
["$dynamicAnchor", "\"foo\"", [1, 2, 9, 1, 0, 1, 2, 16, 1, 0]],
147-
["$vocabulary", "{}", [1, 2, 9, 1, 0, 1, 2, 13, 1, 0]],
146+
["$vocabulary", "{}", [1, 2, 9, 1, 0, 0, 58, 5, 1, 0, 1, 2, 13, 1, 0]],
148147
["$comment", "\"\"", [1, 2, 9, 1, 0, 1, 2, 14, 2, 0]],
149148
["$defs", "{}", [1, 2, 9, 1, 0, 1, 2, 7, 1, 0]],
150149

@@ -187,7 +186,7 @@ describe("Feature - Semantic Tokens", () => {
187186
["type", "\"object\"", [1, 2, 9, 1, 0, 1, 2, 6, 1, 0]]
188187
])("%s should be highlighted", async (keyword, value, expected) => {
189188
documentUri = await client.openDocument("./subject.schema.json", `{
190-
"$schema": "https://json-schema.org/draft/2020-12/schema",
189+
"$schema": "https://json-schema.org/draft/2020-12/schema",${keyword === "$vocabulary" ? `"$id": "https://example.com/schema",` : ""}
191190
"${keyword}": ${value}
192191
}`);
193192

@@ -261,7 +260,7 @@ describe("Feature - Semantic Tokens", () => {
261260
["$ref", "\"\"", [1, 2, 9, 1, 0, 1, 2, 6, 1, 0]],
262261
["$recursiveRef", "\"\"", [1, 2, 9, 1, 0, 1, 2, 15, 1, 0]],
263262
["$recursiveAnchor", "true", [1, 2, 9, 1, 0, 1, 2, 18, 1, 0]],
264-
["$vocabulary", "{}", [1, 2, 9, 1, 0, 1, 2, 13, 1, 0]],
263+
["$vocabulary", "{}", [1, 2, 9, 1, 0, 0, 58, 5, 1, 0, 1, 2, 13, 1, 0]],
265264
["$comment", "\"\"", [1, 2, 9, 1, 0, 1, 2, 14, 2, 0]],
266265
["$defs", "{}", [1, 2, 9, 1, 0, 1, 2, 7, 1, 0]],
267266

@@ -300,7 +299,7 @@ describe("Feature - Semantic Tokens", () => {
300299
["type", "\"object\"", [1, 2, 9, 1, 0, 1, 2, 6, 1, 0]]
301300
])("%s should be highlighted", async (keyword, value, expected) => {
302301
documentUri = await client.openDocument("./subject.schema.json", `{
303-
"$schema": "https://json-schema.org/draft/2019-09/schema",
302+
"$schema": "https://json-schema.org/draft/2019-09/schema",${keyword === "$vocabulary" ? `"$id": "https://example.com/schema",` : ""}
304303
"${keyword}": ${value}
305304
}`);
306305

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
2+
import {
3+
DidDeleteFilesNotification,
4+
PublishDiagnosticsNotification,
5+
WorkDoneProgress,
6+
WorkDoneProgressCreateRequest
7+
} from "vscode-languageserver";
8+
import { rm } from "node:fs/promises";
9+
import { fileURLToPath } from "node:url";
10+
import { TestClient } from "../test-client.js";
11+
import documentSettings from "./document-settings.js";
12+
import semanticTokens from "./semantic-tokens.js";
13+
import schemaRegistry from "./schema-registry.js";
14+
import workspace from "./workspace.js";
15+
import validationErrorsFeature from "./validation-errors.js";
16+
import { setupWorkspace, tearDownWorkspace } from "../test-utils.js";
17+
18+
import type { Diagnostic } from "vscode-languageserver";
19+
import type { DocumentSettings } from "./document-settings.js";
20+
import { resolveIri } from "@hyperjump/uri";
21+
22+
23+
describe("Feature - Register Schema", () => {
24+
let client: TestClient<DocumentSettings>;
25+
let workspaceFolder: string;
26+
27+
beforeEach(async () => {
28+
client = new TestClient([
29+
workspace,
30+
documentSettings,
31+
semanticTokens,
32+
schemaRegistry,
33+
validationErrorsFeature
34+
]);
35+
workspaceFolder = await setupWorkspace({
36+
"subjectB.schema.json": ``,
37+
"subject.schema.json": ``
38+
});
39+
40+
await client.start({
41+
workspaceFolders: [
42+
{
43+
name: "root",
44+
uri: workspaceFolder
45+
}
46+
]
47+
});
48+
});
49+
50+
afterEach(async () => {
51+
await client.stop();
52+
await tearDownWorkspace(workspaceFolder);
53+
});
54+
55+
test("Unregisterschema ", async () => {
56+
const diagnosticsPromise = new Promise<Diagnostic[]>((resolve) => {
57+
let diagnostics: Diagnostic[];
58+
59+
client.onRequest(WorkDoneProgressCreateRequest.type, ({ token }) => {
60+
client.onProgress(WorkDoneProgress.type, token, ({ kind }) => {
61+
if (kind === "end") {
62+
resolve(diagnostics);
63+
}
64+
});
65+
});
66+
67+
client.onNotification(PublishDiagnosticsNotification.type, (params) => {
68+
diagnostics = params.diagnostics;
69+
});
70+
});
71+
72+
const documentUriB = resolveIri("./subjectB.schema.json", `${workspaceFolder}/`);
73+
74+
const subjectDocumentUri = await client.openDocument(documentUriB, `{
75+
"$id": "https://example.com/my-dialect",
76+
"$schema": "https://json-schema.org/draft/2020-12/schema",
77+
78+
"$vocabulary": {
79+
"https://json-schema.org/draft/2020-12/vocab/core": true,
80+
"https://json-schema.org/draft/2020-12/vocab/applicator": true,
81+
"https://json-schema.org/draft/2020-12/vocab/validation": true
82+
}
83+
}`);
84+
85+
const documentUri = resolveIri("./subject.schema.json", `${workspaceFolder}/`);
86+
87+
await client.openDocument(documentUri, `{
88+
"$schema": "https://example.com/my-dialect",
89+
}`);
90+
91+
await rm(fileURLToPath(subjectDocumentUri));
92+
93+
await client.sendNotification(DidDeleteFilesNotification.type, {
94+
files: [{
95+
uri: subjectDocumentUri
96+
}]
97+
});
98+
99+
const diagnostics = await diagnosticsPromise;
100+
expect(diagnostics[0]?.message).to.eql("Unknown dialect");
101+
});
102+
});

0 commit comments

Comments
 (0)