Skip to content

Commit 114f058

Browse files
committed
Tests for semantic tokens
1 parent af45af5 commit 114f058

File tree

7 files changed

+551
-8
lines changed

7 files changed

+551
-8
lines changed

language-server/src/features/deprecated.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import { DiagnosticSeverity, DiagnosticTag } from "vscode-languageserver";
22
import * as SchemaNode from "../schema-node.js";
3-
import { subscribe } from "../pubsub.js";
3+
import { subscribe, unsubscribe } from "../pubsub.js";
44

55
/** @import { Feature } from "../build-server.js" */
66

77

88
const annotationDialectUri = "https://json-schema.org/draft/2020-12/schema";
9+
/** @type string */
10+
let subscriptionToken;
911

1012
/** @type Feature */
1113
export default {
1214
load() {
13-
subscribe("diagnostics", async (_message, { schemaDocument, diagnostics }) => {
15+
subscriptionToken = subscribe("diagnostics", async (_message, { schemaDocument, diagnostics }) => {
1416
for (const schemaResource of schemaDocument.schemaResources) {
1517
for (const deprecated of SchemaNode.annotatedWith(schemaResource, "deprecated", annotationDialectUri)) {
1618
if (SchemaNode.annotation(deprecated, "deprecated", annotationDialectUri).some((deprecated) => deprecated)) {
@@ -34,5 +36,6 @@ export default {
3436
},
3537

3638
onShutdown() {
39+
unsubscribe("diagnostics", subscriptionToken);
3740
}
3841
};

language-server/src/features/if-then-completion.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { CompletionItemKind, InsertTextFormat } from "vscode-languageserver";
22
import * as SchemaDocument from "../schema-document.js";
3-
import { subscribe } from "../pubsub.js";
3+
import { subscribe, unsubscribe } from "../pubsub.js";
44

55
/** @import { Feature } from "../build-server.js" */
66

7+
/** @type string */
8+
let subscriptionToken;
79

810
/** @type Feature */
911
export default {
1012
load() {
11-
subscribe("completions", async (_message, { schemaDocument, offset, completions }) => {
13+
subscriptionToken = subscribe("completions", async (_message, { schemaDocument, offset, completions }) => {
1214
const currentProperty = SchemaDocument.findNodeAtOffset(schemaDocument, offset);
1315
if (currentProperty && currentProperty.pointer.endsWith("/if") && currentProperty.type === "property") {
1416
completions.push(...ifThenPatternCompletion);
@@ -24,6 +26,7 @@ export default {
2426
},
2527

2628
onShutdown() {
29+
unsubscribe("completions", subscriptionToken);
2730
}
2831
};
2932

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { CompletionItemKind } from "vscode-languageserver";
22
import { getDialectIds } from "@hyperjump/json-schema/experimental";
33
import * as SchemaDocument from "../schema-document.js";
4-
import { subscribe } from "../pubsub.js";
4+
import { subscribe, unsubscribe } from "../pubsub.js";
55

66
/** @import { Feature } from "../build-server.js"; */
77

8+
/** @type string */
9+
let subscriptionToken;
810

911
/** @type Feature */
1012
export default {
@@ -16,7 +18,7 @@ export default {
1618
]);
1719
const shouldHaveTrailingHash = (/** @type string */ uri) => trailingHashDialects.has(uri);
1820

19-
subscribe("completions", async (_message, { schemaDocument, offset, completions }) => {
21+
subscriptionToken = subscribe("completions", async (_message, { schemaDocument, offset, completions }) => {
2022
const currentProperty = SchemaDocument.findNodeAtOffset(schemaDocument, offset);
2123
if (currentProperty && currentProperty.pointer.endsWith("/$schema") && currentProperty.type === "string") {
2224
completions.push(...getDialectIds().map((uri) => ({
@@ -35,5 +37,6 @@ export default {
3537
},
3638

3739
onShutdown() {
40+
unsubscribe("completions", subscriptionToken);
3841
}
3942
};
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import { beforeAll, afterAll, describe, expect, test } from "vitest";
2+
import { SemanticTokensRequest } from "vscode-languageserver";
3+
import { TestClient } from "../test-client.js";
4+
import semanticTokensFeature from "./semantic-tokens.js";
5+
import workspace from "./workspace.js";
6+
import type { DocumentSettings } from "./document-settings.js";
7+
import documentSettings from "./document-settings.js";
8+
9+
10+
describe("Feature - Semantic Tokens", () => {
11+
let client: TestClient<DocumentSettings>;
12+
let documentUri: string;
13+
14+
beforeAll(async () => {
15+
client = new TestClient([
16+
documentSettings,
17+
semanticTokensFeature,
18+
workspace
19+
]);
20+
21+
await client.start();
22+
});
23+
24+
afterAll(async () => {
25+
await client.stop();
26+
});
27+
28+
test("semantic tokens on a watched file", async () => {
29+
await client.changeConfiguration({ "schemaFilePatterns": ["**/subject.schema.json"] });
30+
documentUri = await client.openDocument("subject.schema.json", `{"$schema":"http://json-schema.org/draft-07/schema#",
31+
"type": "string",
32+
"minLength": 10,
33+
"maxLength": 5
34+
}`);
35+
36+
const response = await client.sendRequest(SemanticTokensRequest.type, {
37+
textDocument: { uri: documentUri }
38+
});
39+
40+
expect(response?.data).to.eql([0, 1, 9, 1, 0, 1, 0, 6, 1, 0, 1, 0, 11, 1, 0, 1, 0, 11, 1, 0]);
41+
});
42+
43+
test("no semantic tokens", async () => {
44+
documentUri = await client.openDocument("subject.schema.json", `{
45+
"type": "string",
46+
"minLength": 10,
47+
"maxLength": 5}`);
48+
49+
const response = await client.sendRequest(SemanticTokensRequest.type, {
50+
textDocument: { uri: documentUri }
51+
});
52+
53+
expect(response?.data).to.eql([]);
54+
});
55+
56+
test("no semantic tokens on an unwatched file", async () => {
57+
await client.changeConfiguration({ "schemaFilePatterns": ["**/subject.schema.json"] });
58+
documentUri = await client.openDocument("subjectB.schema.json", `{"$schema":"http://json-schema.org/draft-07/schema#",
59+
"type": "string",
60+
"minLength": 10,
61+
"maxLength": 5
62+
}`);
63+
64+
const response = await client.sendRequest(SemanticTokensRequest.type, {
65+
textDocument: { uri: documentUri }
66+
});
67+
68+
expect(response?.data).to.eql([]);
69+
});
70+
71+
test("change in watch file patterns refreshes tokens", async () => {
72+
documentUri = await client.openDocument("subject.schema.json", `{"$schema":"http://json-schema.org/draft-07/schema#",
73+
"type": "string",
74+
"minLength": 10,
75+
"maxLength": 5
76+
}`);
77+
78+
await client.changeConfiguration({ "schemaFilePatterns": ["**/subjectC.schema.json"] });
79+
80+
const response = await client.sendRequest(SemanticTokensRequest.type, {
81+
textDocument: { uri: documentUri }
82+
});
83+
84+
expect(response?.data).to.eql([]);
85+
});
86+
87+
test("a property in not in a schema should not be highlighted", async () => {
88+
await client.changeConfiguration({ "schemaFilePatterns": ["**/subject.schema.json"] });
89+
documentUri = await client.openDocument("subject.schema.json", `{
90+
"$schema":"http://json-schema.org/draft-07/schema#",
91+
"properties": {
92+
"items": {}
93+
}
94+
}`);
95+
96+
const response = await client.sendRequest(SemanticTokensRequest.type, {
97+
textDocument: { uri: documentUri }
98+
});
99+
100+
const expected: number[] = [1, 0, 9, 1, 0, 1, 0, 12, 1, 0];
101+
expect(response?.data).to.eql(expected);
102+
});
103+
104+
describe("2020-12", () => {
105+
let documentUri: string;
106+
107+
afterAll(async () => {
108+
await client.closeDocument(documentUri);
109+
});
110+
111+
112+
test.each([
113+
// Applicators
114+
["prefixItems", "[{}]", [1, 2, 9, 1, 0, 1, 2, 13, 1, 0]],
115+
["items", "{}", [1, 2, 9, 1, 0, 1, 2, 7, 1, 0]],
116+
["contains", "{}", [1, 2, 9, 1, 0, 1, 2, 10, 1, 0]],
117+
["additionalProperties", "{}", [1, 2, 9, 1, 0, 1, 2, 22, 1, 0]],
118+
["properties", "{}", [1, 2, 9, 1, 0, 1, 2, 12, 1, 0]],
119+
["patternProperties", "{}", [1, 2, 9, 1, 0, 1, 2, 19, 1, 0]],
120+
["dependentSchemas", "{}", [1, 2, 9, 1, 0, 1, 2, 18, 1, 0]],
121+
["propertyNames", "{}", [1, 2, 9, 1, 0, 1, 2, 15, 1, 0]],
122+
["if", "{}", [1, 2, 9, 1, 0, 1, 2, 4, 1, 0]],
123+
["then", "{}", [1, 2, 9, 1, 0, 1, 2, 6, 1, 0]],
124+
["else", "{}", [1, 2, 9, 1, 0, 1, 2, 6, 1, 0]],
125+
["allOf", "[{}]", [1, 2, 9, 1, 0, 1, 2, 7, 1, 0]],
126+
["anyOf", "[{}]", [1, 2, 9, 1, 0, 1, 2, 7, 1, 0]],
127+
["oneOf", "[{}]", [1, 2, 9, 1, 0, 1, 2, 7, 1, 0]],
128+
["not", "{}", [1, 2, 9, 1, 0, 1, 2, 5, 1, 0]],
129+
130+
// Content
131+
["contentMediaType", "\"\"", [1, 2, 9, 1, 0, 1, 2, 18, 1, 0]],
132+
["contentEncoding", "\"\"", [1, 2, 9, 1, 0, 1, 2, 17, 1, 0]],
133+
["contentSchema", "{}", [1, 2, 9, 1, 0, 1, 2, 15, 1, 0]],
134+
135+
// Core
136+
["$id", "\"\"", [1, 2, 9, 1, 0, 1, 2, 5, 1, 0]],
137+
["$anchor", "\"foo\"", [1, 2, 9, 1, 0, 1, 2, 9, 1, 0]],
138+
["$ref", "\"\"", [1, 2, 9, 1, 0, 1, 2, 6, 1, 0]],
139+
["$dynamicRef", "\"\"", [1, 2, 9, 1, 0, 1, 2, 13, 1, 0]],
140+
["$dynamicAnchor", "\"foo\"", [1, 2, 9, 1, 0, 1, 2, 16, 1, 0]],
141+
["$vocabulary", "{}", [1, 2, 9, 1, 0, 1, 2, 13, 1, 0]],
142+
["$comment", "\"\"", [1, 2, 9, 1, 0, 1, 2, 14, 2, 0]],
143+
["$defs", "{}", [1, 2, 9, 1, 0, 1, 2, 7, 1, 0]],
144+
145+
// Format
146+
["format", "\"\"", [1, 2, 9, 1, 0, 1, 2, 8, 1, 0]],
147+
148+
// Meta-data
149+
["title", "\"\"", [1, 2, 9, 1, 0, 1, 2, 7, 1, 0]],
150+
["description", "\"\"", [1, 2, 9, 1, 0, 1, 2, 13, 1, 0]],
151+
["default", "true", [1, 2, 9, 1, 0, 1, 2, 9, 1, 0]],
152+
["deprecated", "false", [1, 2, 9, 1, 0, 1, 2, 12, 1, 0]],
153+
["readOnly", "true", [1, 2, 9, 1, 0, 1, 2, 10, 1, 0]],
154+
["writeOnly", "false", [1, 2, 9, 1, 0, 1, 2, 11, 1, 0]],
155+
["examples", "[]", [1, 2, 9, 1, 0, 1, 2, 10, 1, 0]],
156+
157+
// Unevaluated
158+
["unevaluatedItems", "true", [1, 2, 9, 1, 0, 1, 2, 18, 1, 0]],
159+
["unevaluatedProperties", "true", [1, 2, 9, 1, 0, 1, 2, 23, 1, 0]],
160+
161+
// Validation
162+
["multipleOf", "1", [1, 2, 9, 1, 0, 1, 2, 12, 1, 0]],
163+
["maximum", "42", [1, 2, 9, 1, 0, 1, 2, 9, 1, 0]],
164+
["exclusiveMaximum", "42", [1, 2, 9, 1, 0, 1, 2, 18, 1, 0]],
165+
["minimum", "42", [1, 2, 9, 1, 0, 1, 2, 9, 1, 0]],
166+
["exclusiveMinimum", "42", [1, 2, 9, 1, 0, 1, 2, 18, 1, 0]],
167+
["maxLength", "42", [1, 2, 9, 1, 0, 1, 2, 11, 1, 0]],
168+
["minLength", "42", [1, 2, 9, 1, 0, 1, 2, 11, 1, 0]],
169+
["pattern", "\"\"", [1, 2, 9, 1, 0, 1, 2, 9, 1, 0]],
170+
["maxItems", "42", [1, 2, 9, 1, 0, 1, 2, 10, 1, 0]],
171+
["minItems", "42", [1, 2, 9, 1, 0, 1, 2, 10, 1, 0]],
172+
["uniqueItems", "false", [1, 2, 9, 1, 0, 1, 2, 13, 1, 0]],
173+
["maxContains", "1", [1, 2, 9, 1, 0, 1, 2, 13, 1, 0]],
174+
["minContains", "1", [1, 2, 9, 1, 0, 1, 2, 13, 1, 0]],
175+
["maxProperties", "1", [1, 2, 9, 1, 0, 1, 2, 15, 1, 0]],
176+
["minProperties", "1", [1, 2, 9, 1, 0, 1, 2, 15, 1, 0]],
177+
["required", "[]", [1, 2, 9, 1, 0, 1, 2, 10, 1, 0]],
178+
["dependentRequired", "{}", [1, 2, 9, 1, 0, 1, 2, 19, 1, 0]],
179+
["const", "true", [1, 2, 9, 1, 0, 1, 2, 7, 1, 0]],
180+
["enum", "[]", [1, 2, 9, 1, 0, 1, 2, 6, 1, 0]],
181+
["type", "\"object\"", [1, 2, 9, 1, 0, 1, 2, 6, 1, 0]]
182+
])("%s should be highlighted", async (keyword, value, expected) => {
183+
documentUri = await client.openDocument("./subject.schema.json", `{
184+
"$schema": "https://json-schema.org/draft/2020-12/schema",
185+
"${keyword}": ${value}
186+
}`);
187+
188+
const response = await client.sendRequest(SemanticTokensRequest.type, {
189+
textDocument: { uri: documentUri }
190+
});
191+
192+
expect(response?.data).to.eql(expected);
193+
});
194+
195+
196+
test.each([
197+
// Applicators
198+
["additionalItems", "true", [1, 2, 9, 1, 0]],
199+
["dependencies", "{}", [1, 2, 9, 1, 0]],
200+
201+
// Core
202+
["id", "\"\"", [1, 2, 9, 1, 0]],
203+
["$recursiveRef", "\"#\"", [1, 2, 9, 1, 0]],
204+
["$recursiveAnchor", "true", [1, 2, 9, 1, 0]],
205+
["definitions", "{}", [1, 2, 9, 1, 0]]
206+
])("%s should not be highlighted", async (keyword, value, expected) => {
207+
documentUri = await client.openDocument("./subject.schema.json", `{
208+
"$schema": "https://json-schema.org/draft/2020-12/schema",
209+
"${keyword}": ${value}
210+
}`);
211+
212+
const response = await client.sendRequest(SemanticTokensRequest.type, {
213+
textDocument: { uri: documentUri }
214+
});
215+
216+
expect(response?.data).to.eql(expected);
217+
});
218+
});
219+
});
220+

language-server/src/features/validate-references.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as SchemaNode from "../schema-node.js";
2-
import { subscribe } from "../pubsub.js";
2+
import { subscribe, unsubscribe } from "../pubsub.js";
33
import { keywordNameFor } from "../util.js";
44

55
/**
@@ -8,10 +8,13 @@ import { keywordNameFor } from "../util.js";
88
*/
99

1010

11+
/** @type string */
12+
let subscriptionToken;
13+
1114
/** @type Feature */
1215
export default {
1316
load() {
14-
subscribe("diagnostics", async (_message, { schemaDocument, diagnostics }) => {
17+
subscriptionToken = subscribe("diagnostics", async (_message, { schemaDocument, diagnostics }) => {
1518
for (const schemaResource of schemaDocument.schemaResources) {
1619
for (const node of references(schemaResource)) {
1720
const reference = SchemaNode.value(node);
@@ -32,6 +35,7 @@ export default {
3235
},
3336

3437
onShutdown() {
38+
unsubscribe("diagnostics", subscriptionToken);
3539
}
3640
};
3741

0 commit comments

Comments
 (0)