Skip to content

Commit

Permalink
added goto-references
Browse files Browse the repository at this point in the history
  • Loading branch information
diyaayay committed Jul 30, 2024
1 parent 0e8ed5f commit aee31ce
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 1 deletion.
80 changes: 80 additions & 0 deletions language-server/src/features/references.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import * as SchemaDocument from "../schema-document.js";
import * as SchemaNode from "../schema-node.js";
import { getSchemaDocument, allSchemaDocuments } from "./schema-registry.js";
import { references } from "./validate-references.js";

/** @import { Feature } from "../build-server.js" */
/** @import { SchemaNode as SchemaNodeType } from "../schema-node.js" */

/** @type Feature */
export default {
load(connection, documents) {
const highlightBlockDialects = new Set([
"http://json-schema.org/draft-04/schema",
"http://json-schema.org/draft-06/schema",
"http://json-schema.org/draft-07/schema"
]);
const shouldHighlightBlock = (/** @type {string | undefined} */ uri) => {
if (uri === undefined) {
return false;
}
return highlightBlockDialects.has(uri);
};

connection.onReferences(async ({ textDocument, position }) => {
const document = documents.get(textDocument.uri);
if (!document) {
return [];
}

const schemaDocument = await getSchemaDocument(connection, document);
const offset = document.offsetAt(position);
const node = SchemaDocument.findNodeAtOffset(schemaDocument, offset);

if (!node) {
return [];
}

const targetSchemaUri = SchemaNode.uri(node);
const schemaReferences = [];

for (const schemaDocument of allSchemaDocuments()) {
for (const schemaResource of schemaDocument.schemaResources) {
for (const referenceNode of references(schemaResource)) {
const reference = SchemaNode.value(referenceNode);
const referencedSchema = SchemaNode.get(reference, schemaResource);
if (!referencedSchema) {
continue;
}

if (SchemaNode.uri(referencedSchema) === targetSchemaUri) {
const hightlightNode = /** @type SchemaNodeType */ (shouldHighlightBlock(referenceNode.dialectUri)
? referenceNode.parent?.parent
: referenceNode);
schemaReferences.push({
uri: schemaDocument.textDocument.uri,
range: {
start: schemaDocument.textDocument.positionAt(hightlightNode.offset),
end: schemaDocument.textDocument.positionAt(hightlightNode.offset + hightlightNode.textLength)
}
});
}
}
}
}
return schemaReferences;
});
},

onInitialize() {
return {
referencesProvider: true
};
},

async onInitialized() {
},

onShutdown() {
}
};
203 changes: 203 additions & 0 deletions language-server/src/features/references.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { ReferencesRequest } from "vscode-languageserver";
import { TestClient } from "../test-client.js";
import documentSettings from "./document-settings.js";
import schemaRegistry from "./schema-registry.js";
import workspace from "./workspace.js";
import ReferencesFeature from "./references.js";

import type { DocumentSettings } from "./document-settings.js";


describe("Feature - References", () => {
let client: TestClient<DocumentSettings>;

beforeEach(async () => {
client = new TestClient([
workspace,
documentSettings,
schemaRegistry,
ReferencesFeature
]);
await client.start();
});

afterEach(async () => {
await client.stop();
});

test("no references", async () => {
const documentUri = await client.openDocument("./subject.schema.json", `{}`);

const response = await client.sendRequest(ReferencesRequest.type, {
textDocument: { uri: documentUri },
position: {
line: 0,
character: 1
},
context: { includeDeclaration: false }
});

expect(response).to.eql([]);
});

test("don't return references that do not match location", async () => {
const documentUri = await client.openDocument("./subject.schema.json", `{
"$schema":"https://json-schema.org/draft/2020-12/schema",
"$ref": "#/definitions/locations",
"definitions":{
"names": {
},
"locations": {
}
},
}`);

const response = await client.sendRequest(ReferencesRequest.type, {
textDocument: { uri: documentUri },
position: {
line: 5,
character: 4
},
context: { includeDeclaration: false }
});

expect(response).to.eql([]);
});


test("match one reference", async () => {
const documentUri = await client.openDocument("./subject.schema.json", `{
"$schema":"https://json-schema.org/draft/2020-12/schema",
"$ref": "#/$defs/names",
"$defs":{
"names": {
}
},
}`);

const response = await client.sendRequest(ReferencesRequest.type, {
textDocument: { uri: documentUri },
position: {
line: 5,
character: 4
},
context: { includeDeclaration: false }
});

expect(response).to.eql([
{
"uri": documentUri,
"range": {
"start": { "line": 2, "character": 10 },
"end": { "line": 2, "character": 25 }
}
}
]);
});

// test("cross file reference", async () => {
// const documentUriA = await client.openDocument("./subjectA.schema.json", `{
// "$schema": "http://json-schema.org/draft-07/schema#",
// "definitions": {
// "person": {

// }
// }
// }
// `);
// const documentUriB = await client.openDocument("./subjectB.schema.json", `{
// "$schema": "http://json-schema.org/draft-07/schema#",
// "$ref": "./subjectA.schema.json#/definitions/person"
// }
// `);

// const response = await client.sendRequest(ReferencesRequest.type, {
// textDocument: { uri: documentUriA },
// position: {
// line: 4,
// character: 4
// },
// context: { includeDeclaration: false }
// });

// expect(response).to.eql([
// {
// "uri": documentUriB,
// "range": {
// "start": { "line": 0, "character": 0 },
// "end": { "line": 3, "character": 3 }
// }
// }
// ]);
// });

test("match self identified externally", async () => {
const documentUri = await client.openDocument("./subject.schema.json", `{
"$schema":"http://json-schema.org/draft-07/schema#",
"$ref": "https://example.com/schemas/two#/definitions/names",
}`);

const documentUriB = await client.openDocument("./subjectB.schema.json", `{
"$schema":"http://json-schema.org/draft-07/schema#",
"$id": "https://example.com/schemas/two",
"definitions":{
"names": {
}
}
}`);

const response = await client.sendRequest(ReferencesRequest.type, {
textDocument: { uri: documentUriB },
position: {
line: 5,
character: 4
},
context: { includeDeclaration: false }
});

expect(response).to.eql([
{
"uri": documentUri,
"range": {
"start": { "line": 0, "character": 0 },
"end": { "line": 3, "character": 1 }
}
}
]);
});

test("match self identified internally", async () => {
const documentUri = await client.openDocument("./subject.schema.json", `{
"$schema":"http://json-schema.org/draft-07/schema#",
"$id": "https://example.com/person.json",
"type": "object",
"properties": {
"names": { "$ref": "https://example.com/person.json" }
}
}`);

const response = await client.sendRequest(ReferencesRequest.type, {
textDocument: { uri: documentUri },
position: {
line: 2,
character: 46
},
context: { includeDeclaration: false }
});

expect(response).to.eql([
{
"uri": documentUri,
"range": {
"start": { "line": 5, "character": 13 },
"end": { "line": 5, "character": 58 }
}
}
]);
});
});
2 changes: 1 addition & 1 deletion language-server/src/features/validate-references.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default {
};

/** @type Type.references */
const references = function* (schemaResource) {
export const references = function* (schemaResource) {
const refToken = keywordNameFor("https://json-schema.org/keyword/ref", schemaResource.dialectUri);
const legacyRefToken = keywordNameFor("https://json-schema.org/keyword/draft-04/ref", schemaResource.dialectUri);

Expand Down
2 changes: 2 additions & 0 deletions language-server/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import completionFeature from "./features/completion.js";
import ifThenCompletionFeature from "./features/if-then-completion.js";
import schemaCompletion from "./features/schema-completion.js";
import hoverFeature from "./features/hover.js";
import referencesFeature from "./features/references.js";


const features = [
Expand All @@ -28,6 +29,7 @@ const features = [
schemaCompletion,
ifThenCompletionFeature,
hoverFeature,
referencesFeature,
workspaceFeature
];

Expand Down

0 comments on commit aee31ce

Please sign in to comment.