This commit is contained in:
Martin Aeschlimann 2020-11-03 22:11:33 +01:00
Родитель 0ee941df50
Коммит 159063b2e1
5 изменённых файлов: 84 добавлений и 101 удалений

Просмотреть файл

@ -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});
});
});

55
src/test/links.test.ts Normal file
Просмотреть файл

@ -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});
});
});