diff --git a/src/services/jsonHover.ts b/src/services/jsonHover.ts index 11d2fe1..afaf25b 100644 --- a/src/services/jsonHover.ts +++ b/src/services/jsonHover.ts @@ -60,61 +60,64 @@ export class JSONHover { } return this.schemaService.getSchemaForResource(document.uri, doc).then((schema) => { - if (schema && node) { - const matchingSchemas = doc.getMatchingSchemas(schema.schema, node.offset); - - let title: string | undefined = undefined; - let markdownDescription: string | undefined = undefined; - let markdownEnumValueDescription: string | undefined = undefined, enumValue: string | undefined = undefined; - matchingSchemas.every((s) => { - if (s.node === node && !s.inverted && s.schema) { - title = title || s.schema.title; - markdownDescription = markdownDescription || s.schema.markdownDescription || toMarkdown(s.schema.description); - if (s.schema.enum) { - const idx = s.schema.enum.indexOf(Parser.getNodeValue(node)); - if (s.schema.markdownEnumDescriptions) { - markdownEnumValueDescription = s.schema.markdownEnumDescriptions[idx]; - } else if (s.schema.enumDescriptions) { - markdownEnumValueDescription = toMarkdown(s.schema.enumDescriptions[idx]); - } - if (markdownEnumValueDescription) { - enumValue = s.schema.enum[idx]; - if (typeof enumValue !== 'string') { - enumValue = JSON.stringify(enumValue); - } - } + if (!schema) { + return null + } + + let title: string | undefined = undefined; + let markdownDescription: string | undefined = undefined; + let markdownEnumValueDescription: string | undefined = undefined, enumValue: string | undefined = undefined; + + const matchingSchemas = doc.getMatchingSchemas(schema.schema, node.offset).filter((s) => s.node === node && !s.inverted).map((s) => s.schema); + for (const schema of matchingSchemas) { + title = title || schema.title; + markdownDescription = markdownDescription || schema.markdownDescription || toMarkdown(schema.description); + if (schema.enum) { + const idx = schema.enum.indexOf(Parser.getNodeValue(node)); + if (schema.markdownEnumDescriptions) { + markdownEnumValueDescription = schema.markdownEnumDescriptions[idx]; + } else if (schema.enumDescriptions) { + markdownEnumValueDescription = toMarkdown(schema.enumDescriptions[idx]); + } + if (markdownEnumValueDescription) { + enumValue = schema.enum[idx]; + if (typeof enumValue !== 'string') { + enumValue = JSON.stringify(enumValue); } } - return true; - }); - let result = ''; - if (title) { - result = toMarkdown(title); } - if (markdownDescription) { - if (result.length > 0) { - result += "\n\n"; - } - result += markdownDescription; + } + + let result = ''; + if (title) { + result = toMarkdown(title); + } + if (markdownDescription) { + if (result.length > 0) { + result += "\n\n"; } - if (markdownEnumValueDescription) { - if (result.length > 0) { - result += "\n\n"; - } - result += `\`${toMarkdownCodeBlock(enumValue!)}\`: ${markdownEnumValueDescription}`; + result += markdownDescription; + } + if (markdownEnumValueDescription) { + if (result.length > 0) { + result += "\n\n"; } - return createHover([result]); + result += `\`${toMarkdownCodeBlock(enumValue!)}\`: ${markdownEnumValueDescription}`; } - return null; + return createHover([result]); }); } } + function toMarkdown(plain: string): string; function toMarkdown(plain: string | undefined): string | undefined; function toMarkdown(plain: string | undefined): string | undefined { if (plain) { - const res = plain.replace(/([^\n\r])(\r?\n)([^\n\r])/gm, '$1\n\n$3'); // single new lines to \n\n (Markdown paragraph) - return res.replace(/[\\`*_{}[\]()#+\-.!]/g, "\\$&"); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash + return plain + .trim() + .replace(/[\\`*_{}[\]()<>#+\-.!]/g, '\\$&') // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash + .replace(/([ \t]+)/g, (_match, g1) => ' '.repeat(g1.length)) // escape spaces tabs + .replace(/\n/g, '\\\n'); // escape new lines } return undefined; } diff --git a/src/test/hover.test.ts b/src/test/hover.test.ts index 342656b..6b9917c 100644 --- a/src/test/hover.test.ts +++ b/src/test/hover.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; -import { Hover, Position, MarkedString, TextDocument, getLanguageService, JSONSchema, LanguageServiceParams } from '../jsonLanguageService'; +import { Hover, Position, TextDocument, getLanguageService, JSONSchema, LanguageServiceParams } from '../jsonLanguageService'; suite('JSON Hover', () => { @@ -33,7 +33,7 @@ suite('JSON Hover', () => { test('Simple schema', async function () { - const content = '{"a": 42, "b": "hello", "c": false}'; + const content = '{"a": 42, "b": "hello", "c": false, "complex-description": false}'; const schema: JSONSchema = { type: 'object', description: 'a very special object', @@ -49,20 +49,27 @@ suite('JSON Hover', () => { 'c': { type: 'boolean', description: 'C' - } + }, + 'complex-description': { + type: 'boolean', + description: 'For example:\n\n\n\n Test [1]' + }, } }; await testComputeInfo(content, schema, { line: 0, character: 0 }).then((result) => { - assert.deepEqual(result.contents, [MarkedString.fromPlainText('a very special object')]); + assert.deepEqual(result.contents, ['a very special object']); }); await testComputeInfo(content, schema, { line: 0, character: 1 }).then((result) => { - assert.deepEqual(result.contents, [MarkedString.fromPlainText('A')]); + assert.deepEqual(result.contents, ['A']); }); await testComputeInfo(content, schema, { line: 0, character: 32 }).then((result) => { - assert.deepEqual(result.contents, [MarkedString.fromPlainText('C')]); + assert.deepEqual(result.contents, ['C']); + }); + await testComputeInfo(content, schema, { line: 0, character: 37 }).then((result) => { + assert.deepEqual(result.contents, ['For example:\\\n\\\n\\\\\n  alert\\(1\\)\\\n\\\\\n\\\n    Test \\[1\\]']); }); await testComputeInfo(content, schema, { line: 0, character: 7 }).then((result) => { - assert.deepEqual(result.contents, [MarkedString.fromPlainText('A')]); + assert.deepEqual(result.contents, ['A']); }); }); @@ -89,13 +96,13 @@ suite('JSON Hover', () => { }] }; await testComputeInfo(content, schema, { line: 0, character: 0 }).then((result) => { - assert.deepEqual(result.contents, [MarkedString.fromPlainText('a very special object')]); + assert.deepEqual(result.contents, ['a very special object']); }); await testComputeInfo(content, schema, { line: 0, character: 1 }).then((result) => { - assert.deepEqual(result.contents, [MarkedString.fromPlainText('A')]); + assert.deepEqual(result.contents, ['A']); }); await testComputeInfo(content, schema, { line: 0, character: 10 }).then((result) => { - assert.deepEqual(result.contents, [MarkedString.fromPlainText('B\n\nIt\'s B')]); + assert.deepEqual(result.contents, ['B\n\nIt\'s B']); }); }); @@ -154,10 +161,10 @@ suite('JSON Hover', () => { }; await testComputeInfo('{ "prop1": "e1', schema, { line: 0, character: 12 }).then(result => { - assert.deepEqual(result.contents, ['line1\n\nline2\n\nline3\n\n\nline4\n']); + assert.deepEqual(result.contents, ['line1\\\nline2\\\n\\\nline3\\\n\\\n\\\nline4']); }); await testComputeInfo('{ "prop2": "e1', schema, { line: 0, character: 12 }).then(result => { - assert.deepEqual(result.contents, ['line1\n\nline2\r\n\r\nline3']); + assert.deepEqual(result.contents, ['line1\r\\\nline2\r\\\n\r\\\nline3']); }); });