Use links for $ref
This commit is contained in:
Родитель
0ee941df50
Коммит
159063b2e1
|
@ -1,3 +1,7 @@
|
|||
3.10.0 / 2020-11-03
|
||||
=================
|
||||
* new API `findLinks` return links for local `$ref` links. Replaces `findDefinition` which no longer returns results ( kept for API compatibility)
|
||||
|
||||
3.9.0 / 2020-09-28
|
||||
=================
|
||||
* new API `DocumentLanguageSettings.schemaValidation`. The severity of problems from schema validation. If set to 'ignore', schema validation will be skipped. If not set, 'warning' is used.
|
||||
|
|
|
@ -25,7 +25,8 @@ import {
|
|||
Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic,
|
||||
TextEdit, FormattingOptions, DocumentSymbol, DefinitionLink, MatchingSchema
|
||||
} from './jsonLanguageTypes';
|
||||
import { findDefinition } from './services/jsonDefinition';
|
||||
import { findLinks } from './services/jsonLinks';
|
||||
import { DocumentLink } from 'vscode-languageserver-types';
|
||||
|
||||
export type JSONDocument = {
|
||||
root: ASTNode | undefined;
|
||||
|
@ -53,6 +54,7 @@ export interface LanguageService {
|
|||
getFoldingRanges(document: TextDocument, context?: FoldingRangesContext): FoldingRange[];
|
||||
getSelectionRanges(document: TextDocument, positions: Position[], doc: JSONDocument): SelectionRange[];
|
||||
findDefinition(document: TextDocument, position: Position, doc: JSONDocument): Thenable<DefinitionLink[]>;
|
||||
findLinks(document: TextDocument, doc: JSONDocument): Thenable<DocumentLink[]>;
|
||||
}
|
||||
|
||||
|
||||
|
@ -92,7 +94,8 @@ export function getLanguageService(params: LanguageServiceParams): LanguageServi
|
|||
doHover: jsonHover.doHover.bind(jsonHover),
|
||||
getFoldingRanges,
|
||||
getSelectionRanges,
|
||||
findDefinition,
|
||||
findDefinition: () => Promise.resolve([]),
|
||||
findLinks,
|
||||
format: (d, r, o) => {
|
||||
let range: JSONCRange | undefined = undefined;
|
||||
if (r) {
|
||||
|
|
|
@ -3,44 +3,31 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { JSONSchemaRef, JSONSchema } from '../jsonSchema';
|
||||
import { DefinitionLink, Position, TextDocument, ASTNode, PropertyASTNode, Range, Thenable } from '../jsonLanguageTypes';
|
||||
import { DocumentLink } from 'vscode-languageserver-types';
|
||||
import { TextDocument, ASTNode, PropertyASTNode, Range, Thenable } from '../jsonLanguageTypes';
|
||||
import { JSONDocument } from '../parser/jsonParser';
|
||||
|
||||
export function findDefinition(document: TextDocument, position: Position, doc: JSONDocument): Thenable<DefinitionLink[]> {
|
||||
const offset = document.offsetAt(position);
|
||||
const node = doc.getNodeFromOffset(offset, true);
|
||||
if (!node || !isRef(node)) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
const propertyNode: PropertyASTNode = node.parent as PropertyASTNode;
|
||||
const valueNode = propertyNode.valueNode as ASTNode;
|
||||
const path = valueNode.value as string;
|
||||
const targetNode = findTargetNode(doc, path);
|
||||
if (!targetNode) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
const definition: DefinitionLink = {
|
||||
targetUri: document.uri,
|
||||
originSelectionRange: createRange(document, valueNode),
|
||||
targetRange: createRange(document, targetNode),
|
||||
targetSelectionRange: createRange(document, targetNode)
|
||||
};
|
||||
return Promise.resolve([definition]);
|
||||
export function findLinks(document: TextDocument, doc: JSONDocument): Thenable<DocumentLink[]> {
|
||||
const links: DocumentLink[] = [];
|
||||
doc.visit(node => {
|
||||
if (node.type === "property" && node.keyNode.value === "$ref" && node.valueNode?.type === 'string') {
|
||||
const path = node.valueNode.value;
|
||||
const targetNode = findTargetNode(doc, path);
|
||||
if (targetNode) {
|
||||
const targetPos = document.positionAt(targetNode.offset);
|
||||
links.push({
|
||||
target: `${document.uri}#${targetPos.line + 1},${targetPos.character + 1}`,
|
||||
range: createRange(document, node.valueNode)
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return Promise.resolve(links);
|
||||
}
|
||||
|
||||
function createRange(document: TextDocument, node: ASTNode): Range {
|
||||
return Range.create(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
|
||||
}
|
||||
|
||||
function isRef(node: ASTNode): boolean {
|
||||
return node.type === 'string' &&
|
||||
node.parent &&
|
||||
node.parent.type === 'property' &&
|
||||
node.parent.valueNode === node &&
|
||||
node.parent.keyNode.value === "$ref" ||
|
||||
false;
|
||||
return Range.create(document.positionAt(node.offset + 1), document.positionAt(node.offset + node.length - 1));
|
||||
}
|
||||
|
||||
function findTargetNode(doc: JSONDocument, path: string): ASTNode | null {
|
|
@ -1,66 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { getLanguageService, JSONSchema, TextDocument, ClientCapabilities, CompletionList, CompletionItemKind, Position, MarkupContent } from '../jsonLanguageService';
|
||||
import { repeat } from '../utils/strings';
|
||||
import { DefinitionLink } from 'vscode-languageserver-types';
|
||||
|
||||
suite('JSON Find Definitions', () => {
|
||||
const testFindDefinitionFor = function (value: string, expected: {offset: number, length: number} | null): PromiseLike<void> {
|
||||
const offset = value.indexOf('|');
|
||||
value = value.substr(0, offset) + value.substr(offset + 1);
|
||||
|
||||
const ls = getLanguageService({ clientCapabilities: ClientCapabilities.LATEST });
|
||||
const document = TextDocument.create('test://test/test.json', 'json', 0, value);
|
||||
const position = Position.create(0, offset);
|
||||
const jsonDoc = ls.parseJSONDocument(document);
|
||||
return ls.findDefinition(document, position, jsonDoc).then(list => {
|
||||
if (expected) {
|
||||
assert.notDeepEqual(list, []);
|
||||
const startOffset = list[0].targetRange.start.character;
|
||||
assert.equal(startOffset, expected.offset);
|
||||
assert.equal(list[0].targetRange.end.character - startOffset, expected.length);
|
||||
} else {
|
||||
assert.deepEqual(list, []);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
test('FindDefinition invalid ref', async function () {
|
||||
await testFindDefinitionFor('{|}', null);
|
||||
await testFindDefinitionFor('{"name": |"John"}', null);
|
||||
await testFindDefinitionFor('{"|name": "John"}', null);
|
||||
await testFindDefinitionFor('{"name": "|John"}', null);
|
||||
await testFindDefinitionFor('{"name": "John", "$ref": "#/|john/name"}', null);
|
||||
await testFindDefinitionFor('{"name": "John", "$ref|": "#/name"}', null);
|
||||
await testFindDefinitionFor('{"name": "John", "$ref": "#/|"}', null);
|
||||
});
|
||||
|
||||
test('FindDefinition valid ref', async function () {
|
||||
await testFindDefinitionFor('{"name": "John", "$ref": "#/n|ame"}', {offset: 9, length: 6});
|
||||
await testFindDefinitionFor('{"name": "John", "$ref": "|#/name"}', {offset: 9, length: 6});
|
||||
await testFindDefinitionFor('{"name": "John", "$ref": |"#/name"}', {offset: 9, length: 6});
|
||||
await testFindDefinitionFor('{"name": "John", "$ref": "#/name"|}', {offset: 9, length: 6});
|
||||
await testFindDefinitionFor('{"name": "John", "$ref": "#/name|"}', {offset: 9, length: 6});
|
||||
await testFindDefinitionFor('{"name": "John", "$ref": "#|"}', {offset: 0, length: 29});
|
||||
|
||||
const doc = (ref: string) => `{"foo": ["bar", "baz"],"": 0,"a/b": 1,"c%d": 2,"e^f": 3,"i\\\\j": 5,"k\\"l": 6," ": 7,"m~n": 8, "$ref": "|${ref}"}`;
|
||||
await testFindDefinitionFor(doc('#'), {offset: 0, length: 105});
|
||||
await testFindDefinitionFor(doc('#/foo'), {offset: 8, length: 14});
|
||||
await testFindDefinitionFor(doc('#/foo/0'), {offset: 9, length: 5});
|
||||
await testFindDefinitionFor(doc('#/foo/1'), {offset: 16, length: 5});
|
||||
await testFindDefinitionFor(doc('#/foo/01'), null);
|
||||
await testFindDefinitionFor(doc('#/'), {offset: 27, length: 1});
|
||||
await testFindDefinitionFor(doc('#/a~1b'), {offset: 36, length: 1});
|
||||
await testFindDefinitionFor(doc('#/c%d'), {offset: 45, length: 1});
|
||||
await testFindDefinitionFor(doc('#/e^f'), {offset: 54, length: 1});
|
||||
await testFindDefinitionFor(doc('#/i\\\\j'), {offset: 64, length: 1});
|
||||
await testFindDefinitionFor(doc('#/k\\"l'), {offset: 74, length: 1});
|
||||
await testFindDefinitionFor(doc('#/ '), {offset: 81, length: 1});
|
||||
await testFindDefinitionFor(doc('#/m~0n'), {offset: 90, length: 1});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,55 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
import { getLanguageService, Range, TextDocument, ClientCapabilities } from '../jsonLanguageService';
|
||||
|
||||
suite('JSON Find Links', () => {
|
||||
const testFindLinksFor = function (value: string, expected: {offset: number, length: number, target: number} | null): PromiseLike<void> {
|
||||
|
||||
const ls = getLanguageService({ clientCapabilities: ClientCapabilities.LATEST });
|
||||
const document = TextDocument.create('test://test/test.json', 'json', 0, value);
|
||||
const jsonDoc = ls.parseJSONDocument(document);
|
||||
return ls.findLinks(document, jsonDoc).then(list => {
|
||||
if (expected) {
|
||||
assert.notDeepEqual(list, []);
|
||||
const expectedPos = document.positionAt(expected.target);
|
||||
const expectedTarget = `${document.uri}#${expectedPos.line + 1},${expectedPos.character + 1}`;
|
||||
assert.equal(list[0].target, expectedTarget);
|
||||
assert.deepEqual(list[0].range, Range.create(document.positionAt(expected.offset), document.positionAt(expected.offset + expected.length)));
|
||||
} else {
|
||||
assert.deepEqual(list, []);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
test('FindDefinition invalid ref', async function () {
|
||||
await testFindLinksFor('{}', null);
|
||||
await testFindLinksFor('{"name": "John"}', null);
|
||||
await testFindLinksFor('{"name": "John", "$ref": "#/john/name"}', null);
|
||||
await testFindLinksFor('{"name": "John", "$ref": "#/"}', null);
|
||||
});
|
||||
|
||||
test('FindDefinition valid ref', async function () {
|
||||
await testFindLinksFor('{"name": "John", "$ref": "#/name"}', {target: 9, offset: 26, length: 6});
|
||||
await testFindLinksFor('{"name": "John", "$ref": "#"}', {target: 0, offset: 26, length: 1});
|
||||
|
||||
const doc = (ref: string) => `{"foo": ["bar", "baz"],"": 0,"a/b": 1,"c%d": 2,"e^f": 3,"i\\\\j": 5,"k\\"l": 6," ": 7,"m~n": 8, "$ref": "${ref}"}`;
|
||||
await testFindLinksFor(doc('#'), {target: 0, offset: 102, length: 1});
|
||||
await testFindLinksFor(doc('#/foo'), {target: 8, offset: 102, length: 5});
|
||||
await testFindLinksFor(doc('#/foo/0'), {target: 9, offset: 102, length: 7});
|
||||
await testFindLinksFor(doc('#/foo/1'), {target: 16, offset: 102, length: 7});
|
||||
await testFindLinksFor(doc('#/foo/01'), null);
|
||||
await testFindLinksFor(doc('#/'), {target: 27, offset: 102, length: 2});
|
||||
await testFindLinksFor(doc('#/a~1b'), {target: 36, offset: 102, length: 6});
|
||||
await testFindLinksFor(doc('#/c%d'), {target: 45, offset: 102, length: 5});
|
||||
await testFindLinksFor(doc('#/e^f'), {target: 54, offset: 102, length: 5});
|
||||
await testFindLinksFor(doc('#/i\\\\j'), {target: 64, offset: 102, length: 6});
|
||||
await testFindLinksFor(doc('#/k\\"l'), {target: 74, offset: 102, length: 6});
|
||||
await testFindLinksFor(doc('#/ '), {target: 81, offset: 102, length: 3});
|
||||
await testFindLinksFor(doc('#/m~0n'), {target: 90, offset: 102, length: 6});
|
||||
});
|
||||
});
|
Загрузка…
Ссылка в новой задаче