Skip to content

Commit bcf97e5

Browse files
committed
create links for values of format: uri-reference
1 parent bbba2d5 commit bcf97e5

4 files changed

Lines changed: 112 additions & 8 deletions

File tree

src/jsonLanguageService.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic,
2727
TextEdit, FormattingOptions, DocumentSymbol, DefinitionLink, MatchingSchema, JSONLanguageStatus, SortOptions
2828
} from './jsonLanguageTypes';
29-
import { findLinks } from './services/jsonLinks';
29+
import { JSONLinks } from './services/jsonLinks';
3030
import { DocumentLink } from 'vscode-languageserver-types';
3131

3232
export type JSONDocument = {
@@ -67,6 +67,7 @@ export function getLanguageService(params: LanguageServiceParams): LanguageServi
6767

6868
const jsonCompletion = new JSONCompletion(jsonSchemaService, params.contributions, promise, params.clientCapabilities);
6969
const jsonHover = new JSONHover(jsonSchemaService, params.contributions, promise);
70+
const jsonLinks = new JSONLinks(jsonSchemaService);
7071
const jsonDocumentSymbols = new JSONDocumentSymbols(jsonSchemaService);
7172
const jsonValidation = new JSONValidation(jsonSchemaService, promise);
7273

@@ -92,7 +93,7 @@ export function getLanguageService(params: LanguageServiceParams): LanguageServi
9293
getFoldingRanges,
9394
getSelectionRanges,
9495
findDefinition: () => Promise.resolve([]),
95-
findLinks,
96+
findLinks: jsonLinks.findLinks.bind(jsonLinks),
9697
format: (document: TextDocument, range: Range, options: FormattingOptions) => format(document, options, range),
9798
sort: (document: TextDocument, options: FormattingOptions) => sort(document, options)
9899
};

src/services/jsonLinks.ts

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,71 @@
66
import { DocumentLink } from 'vscode-languageserver-types';
77
import { TextDocument, ASTNode, PropertyASTNode, Range, Thenable } from '../jsonLanguageTypes';
88
import { JSONDocument } from '../parser/jsonParser';
9+
import { IJSONSchemaService } from './jsonSchemaService';
10+
import { URI } from 'vscode-uri';
11+
import { existsSync as fileExistsSync } from 'fs';
12+
import { resolve as resolvePath } from 'path';
913

10-
export function findLinks(document: TextDocument, doc: JSONDocument): Thenable<DocumentLink[]> {
11-
const links: DocumentLink[] = [];
14+
export class JSONLinks {
15+
private schemaService: IJSONSchemaService;
16+
17+
constructor(schemaService: IJSONSchemaService) {
18+
this.schemaService = schemaService;
19+
}
20+
21+
public findLinks(document: TextDocument, doc: JSONDocument): Thenable<DocumentLink[]> {
22+
return findLinks(document, doc, this.schemaService);
23+
}
24+
}
25+
26+
export function findLinks(document: TextDocument, doc: JSONDocument, schemaService?: IJSONSchemaService): Thenable<DocumentLink[]> {
27+
const promises: Thenable<DocumentLink[]>[] = [];
28+
29+
const refLinks: DocumentLink[] = [];
1230
doc.visit(node => {
13-
if (node.type === "property" && node.keyNode.value === "$ref" && node.valueNode?.type === 'string') {
31+
if (node.type === "property" && node.valueNode?.type === 'string' && node.keyNode.value === "$ref") {
1432
const path = node.valueNode.value;
1533
const targetNode = findTargetNode(doc, path);
1634
if (targetNode) {
1735
const targetPos = document.positionAt(targetNode.offset);
18-
links.push({
36+
refLinks.push({
1937
target: `${document.uri}#${targetPos.line + 1},${targetPos.character + 1}`,
2038
range: createRange(document, node.valueNode)
2139
});
2240
}
2341
}
42+
if (node.type === "property" && node.valueNode?.type === 'string' && schemaService) {
43+
const pathNode = node.valueNode;
44+
const pathLinks: DocumentLink[] = [];
45+
const promise = schemaService.getSchemaForResource(document.uri, doc).then((schema) => {
46+
if (schema) {
47+
const matchingSchemas = doc.getMatchingSchemas(schema.schema, pathNode.offset);
48+
matchingSchemas.forEach((s) => {
49+
if (s.node === pathNode && !s.inverted && s.schema) {
50+
if (s.schema.format === 'uri-reference') {
51+
const path = resolvePath(pathNode.value);
52+
const uri = URI.file(path);
53+
if (fileExistsSync(path)) {
54+
pathLinks.push({
55+
target: uri.toString(),
56+
range: createRange(document, pathNode)
57+
});
58+
}
59+
}
60+
}
61+
});
62+
}
63+
return pathLinks;
64+
});
65+
promises.push(promise);
66+
}
2467
return true;
2568
});
26-
return Promise.resolve(links);
69+
70+
promises.push(Promise.resolve(refLinks));
71+
return Promise.all(promises).then(values => {
72+
return values.flat();
73+
});
2774
}
2875

2976
function createRange(document: TextDocument, node: ASTNode): Range {

src/test/fixtures/uri-reference.txt

Whitespace-only changes.

src/test/links.test.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@
55

66
import * as assert from 'assert';
77

8-
import { getLanguageService, Range, TextDocument, ClientCapabilities } from '../jsonLanguageService';
8+
import {
9+
ClientCapabilities,
10+
DocumentLink,
11+
getLanguageService,
12+
JSONSchema,
13+
Range,
14+
TextDocument,
15+
} from '../jsonLanguageService';
16+
import { resolve as resolvePath } from 'path';
917

1018
suite('JSON Find Links', () => {
1119
const testFindLinksFor = function (value: string, expected: {offset: number, length: number, target: number} | null): PromiseLike<void> {
@@ -26,6 +34,20 @@ suite('JSON Find Links', () => {
2634
});
2735
};
2836

37+
let requestService = function (uri: string): Promise<string> {
38+
return Promise.reject<string>('Resource not found');
39+
};
40+
41+
function testFindLinksWithSchema(document: TextDocument, schema: JSONSchema): PromiseLike<DocumentLink[]> {
42+
const schemaUri = "http://myschemastore/test1";
43+
44+
const ls = getLanguageService({ clientCapabilities: ClientCapabilities.LATEST });
45+
ls.configure({ schemas: [{ fileMatch: ["*.json"], uri: schemaUri, schema }] });
46+
const jsonDoc = ls.parseJSONDocument(document);
47+
48+
return ls.findLinks(document, jsonDoc);
49+
}
50+
2951
test('FindDefinition invalid ref', async function () {
3052
await testFindLinksFor('{}', null);
3153
await testFindLinksFor('{"name": "John"}', null);
@@ -52,4 +74,38 @@ suite('JSON Find Links', () => {
5274
await testFindLinksFor(doc('#/ '), {target: 81, offset: 102, length: 3});
5375
await testFindLinksFor(doc('#/m~0n'), {target: 90, offset: 102, length: 6});
5476
});
77+
78+
test('URI reference link', async function () {
79+
const relativePath = './src/test/fixtures/uri-reference.txt';
80+
const content = `{"stringProp": "string-value", "uriProp": "${relativePath}", "uriPropNotFound": "./does/not/exist.txt"}`;
81+
const document = TextDocument.create('test://test.json', 'json', 0, content);
82+
const schema: JSONSchema = {
83+
type: 'object',
84+
properties: {
85+
'stringProp': {
86+
type: 'string',
87+
},
88+
'uriProp': {
89+
type: 'string',
90+
format: 'uri-reference'
91+
},
92+
'uriPropNotFound': {
93+
type: 'string',
94+
format: 'uri-reference'
95+
}
96+
}
97+
};
98+
await testFindLinksWithSchema(document, schema).then((links) => {
99+
assert.notDeepEqual(links, []);
100+
101+
const absPath = resolvePath(relativePath);
102+
assert.equal(links[0].target, `file://${absPath}`);
103+
104+
const startOffset = content.indexOf(relativePath);
105+
const endOffset = startOffset + relativePath.length;
106+
const range = Range.create(document.positionAt(startOffset), document.positionAt(endOffset));
107+
assert.deepEqual(links[0].range, range);
108+
});
109+
});
110+
55111
});

0 commit comments

Comments
 (0)