Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions language-server/src/build-server.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Server } from "./services/server.ts";
import { JsonDocuments } from "./services/JsonDocuments.ts";
import { Diagnostics } from "./features/Diagnostics.ts";
import { SyntaxValidation } from "./features/SyntaxValidation.ts";
import { SchemaValidation } from "./features/SchemaValidation.ts";

import "@hyperjump/json-schema/draft-2020-12";
import "@hyperjump/json-schema/draft-2019-09";
import "@hyperjump/json-schema/draft-07";
import "@hyperjump/json-schema/draft-06";
import "@hyperjump/json-schema/draft-04";
import { TextDocuments } from "vscode-languageserver";
import { TextDocument } from "vscode-languageserver-textdocument";
import { Server } from "./services/server.ts";
import { Diagnostics } from "./features/Diagnostics.ts";
import { SyntaxValidation } from "./features/SyntaxValidation.ts";
import { SchemaValidation } from "./features/SchemaValidation.ts";

import type { Connection } from "vscode-languageserver";

Expand All @@ -18,7 +18,7 @@ export type LanguageServerSettings = {
export const buildServer = (connection: Connection): Connection => {
const server = new Server(connection);

const documents = new TextDocuments(TextDocument);
const documents = new JsonDocuments(server);
documents.listen(server);

new Diagnostics(server, documents, [
Expand Down
20 changes: 5 additions & 15 deletions language-server/src/features/Diagnostics.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,19 @@
import { TextDocuments, TextDocumentSyncKind } from "vscode-languageserver";
import { TextDocument } from "vscode-languageserver-textdocument";
import { Server } from "../services/server.ts";
import { JsonDocuments } from "../services/JsonDocuments.ts";
import { JsonDocument } from "../models/JsonDocument.ts";

import type { ServerCapabilities, Diagnostic } from "vscode-languageserver";
import type { Diagnostic } from "vscode-languageserver";

export type DiagnosticsProvider = {
getDiagnostics(textDocument: TextDocument): Promise<Diagnostic[]>;
getDiagnostics(jsonDocument: JsonDocument): Promise<Diagnostic[]>;
};

export class Diagnostics {
private providers: DiagnosticsProvider[];

constructor(server: Server, documents: TextDocuments<TextDocument>, providers: DiagnosticsProvider[]) {
constructor(server: Server, documents: JsonDocuments, providers: DiagnosticsProvider[]) {
this.providers = providers;

server.onInitialize(() => {
const serverCapabilities: ServerCapabilities = {
textDocumentSync: TextDocumentSyncKind.Incremental
};

return {
capabilities: serverCapabilities
};
});

documents.onDidChangeContent(async (change) => {
const diagnostics = [];
for (const provider of this.providers) {
Expand Down
212 changes: 133 additions & 79 deletions language-server/src/features/SchemaValidation.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { describe, test, expect, beforeAll, afterAll, afterEach } from "vitest";
import { TestClient } from "../test/test-client.ts";
import { registerSchema, unregisterSchema } from "@hyperjump/json-schema";
import { Diagnostic, PublishDiagnosticsParams } from "vscode-languageserver";
import { unregisterSchema } from "@hyperjump/json-schema";

import type { Diagnostic, PublishDiagnosticsParams } from "vscode-languageserver";

describe("Schema Validation", () => {
let client: TestClient;
const fixtureSchemaUri = "https://example.com/person";
let fixtureSchemaUri: string;

beforeAll(async () => {
client = new TestClient();
Expand All @@ -27,16 +28,14 @@ describe("Schema Validation", () => {
});
});

const testSchema = {
$schema: "https://json-schema.org/draft/2020-12/schema",
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" }
fixtureSchemaUri = await client.writeDocument("schema.json", `{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "number" }
}
};

registerSchema(testSchema, fixtureSchemaUri);
}`);

await client.writeDocument("instance.json", `{
"$schema": "${fixtureSchemaUri}",
Expand All @@ -56,16 +55,14 @@ describe("Schema Validation", () => {
});
});

const testSchema = {
$schema: "https://json-schema.org/draft/2020-12/schema",
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" }
fixtureSchemaUri = await client.writeDocument("schema.json", `{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "number" }
}
};

registerSchema(testSchema, fixtureSchemaUri);
}`);

await client.writeDocument("instance.json", `{
"$schema": "${fixtureSchemaUri}",
Expand All @@ -85,27 +82,52 @@ describe("Schema Validation", () => {
);
});

test("schema validation is skipped if the JSON is invalid", async () => {
const diagnosticsPromise = new Promise<Diagnostic[]>((resolve) => {
client.onNotification("textDocument/publishDiagnostics", (params: PublishDiagnosticsParams) => {
resolve(params.diagnostics);
});
});

fixtureSchemaUri = await client.writeDocument("schema.json", `{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "number" }
}
}`);

await client.writeDocument("instance.json", `{
"$schema": "${fixtureSchemaUri}",
"name" 42,
"age" : "not a number"
}`);
await client.openDocument("instance.json");

const diagnostics = await diagnosticsPromise;
expect(diagnostics).toHaveLength(1);
});

test("JSON Validation using Hyperjump - anyOf Formatting Case", async () => {
const diagnosticsPromise = new Promise<Diagnostic[]>((resolve) => {
client.onNotification("textDocument/publishDiagnostics", (params: PublishDiagnosticsParams) => {
resolve(params.diagnostics);
});
});

const testSchema = {
$schema: "https://json-schema.org/draft/2020-12/schema",
type: "object",
properties: {
value: {
anyOf: [
{ type: "string" },
{ type: "number" }
fixtureSchemaUri = await client.writeDocument("schema.json", `{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"value": {
"anyOf": [
{ "type": "string" },
{ "type": "number" }
]
}
}
};

registerSchema(testSchema, fixtureSchemaUri);
}`);

await client.writeDocument("instance.json", `{
"$schema": "${fixtureSchemaUri}",
Expand All @@ -126,24 +148,22 @@ describe("Schema Validation", () => {
});
});

const testSchema = {
$schema: "https://json-schema.org/draft/2020-12/schema",
type: "object",
properties: {
value: {
oneOf: [
{ type: "string" },
{ type: "number" }
fixtureSchemaUri = await client.writeDocument("schema.json", `{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"value": {
"oneOf": [
{ "type": "string" },
{ "type": "number" }
]
}
}
};

registerSchema(testSchema, fixtureSchemaUri);
}`);

await client.writeDocument("instance.json", `{
"$schema": "${fixtureSchemaUri}",
"value": true
"$schema": "${fixtureSchemaUri}",
"value": true
}`);
await client.openDocument("instance.json");

Expand All @@ -160,15 +180,13 @@ describe("Schema Validation", () => {
});
});

const testSchema = {
$schema: "https://json-schema.org/draft/2020-12/schema",
type: "object",
properties: {
"foo/bar": { type: "string" }
fixtureSchemaUri = await client.writeDocument("schema.json", `{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"foo/bar": { "type": "string" }
}
};

registerSchema(testSchema, fixtureSchemaUri);
}`);

await client.writeDocument("instance.json", `{
"$schema": "${fixtureSchemaUri}",
Expand All @@ -189,15 +207,13 @@ describe("Schema Validation", () => {
});
});

const testSchema = {
$schema: "https://json-schema.org/draft/2020-12/schema",
type: "object",
properties: {
0: { type: "string" }
fixtureSchemaUri = await client.writeDocument("schema.json", `{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"0": { "type": "string" }
}
};

registerSchema(testSchema, fixtureSchemaUri);
}`);

await client.writeDocument("instance.json", `{
"$schema": "${fixtureSchemaUri}",
Expand All @@ -217,15 +233,13 @@ describe("Schema Validation", () => {
});
});

const testSchema = {
$schema: "https://json-schema.org/draft/2020-12/schema",
type: "object",
properties: {
"foo bar": { type: "string" }
fixtureSchemaUri = await client.writeDocument("schema.json", `{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"foo bar": { "type": "string" }
}
};

registerSchema(testSchema, fixtureSchemaUri);
}`);

await client.writeDocument("instance.json", `{
"$schema": "${fixtureSchemaUri}",
Expand All @@ -245,18 +259,16 @@ describe("Schema Validation", () => {
});
});

const testSchema = {
$schema: "https://json-schema.org/draft/2020-12/schema",
type: "object",
properties: {
42: {
type: "array",
items: { type: "number" }
fixtureSchemaUri = await client.writeDocument("schema.json", `{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"42": {
"type": "array",
"items": { "type": "number" }
}
}
};

registerSchema(testSchema, fixtureSchemaUri);
}`);

await client.writeDocument("instance.json", `{
"$schema": "${fixtureSchemaUri}",
Expand All @@ -268,4 +280,46 @@ describe("Schema Validation", () => {
expect(diagnostics).toHaveLength(1);
expect((diagnostics[0].message as string).replace(/[\u2068\u2069]/g, "")).toBe("Expected a number");
});

test("after fixing schema validation errors, it should not return a diagnostic", async () => {
const diagnosticsPromise1 = new Promise<Diagnostic[]>((resolve) => {
client.onNotification("textDocument/publishDiagnostics", (params: PublishDiagnosticsParams) => {
resolve(params.diagnostics);
});
});

fixtureSchemaUri = await client.writeDocument("schema.json", `{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "number" }
}
}`);

await client.writeDocument("instance.json", `{
"$schema": "${fixtureSchemaUri}",
"name": "Alice",
"age" : "not a number"
}`);
await client.openDocument("instance.json");

const diagnostics1 = await diagnosticsPromise1;
expect(diagnostics1).toHaveLength(1);

const diagnosticsPromise2 = new Promise((resolve) => {
client.onNotification("textDocument/publishDiagnostics", (params) => {
resolve(params.diagnostics);
});
});

await client.changeDocument("instance.json", `{
"$schema": "${fixtureSchemaUri}",
"name": "Alice",
"age" : 39
}`);

const diagnostics2 = await diagnosticsPromise2;
expect(diagnostics2).toHaveLength(0);
});
});
Loading
Loading