From d354c58a480dd9d3ef6b754377352b481163f60a Mon Sep 17 00:00:00 2001 From: Skip Baney Date: Tue, 19 Dec 2023 08:48:09 -0600 Subject: [PATCH] fix: show all enums on hover (#942) --- src/languageservice/services/yamlHover.ts | 67 +++++++++++++++-------- test/hover.test.ts | 41 +++++++++++++- 2 files changed, 82 insertions(+), 26 deletions(-) diff --git a/src/languageservice/services/yamlHover.ts b/src/languageservice/services/yamlHover.ts index d19ce53b..eb163f6a 100644 --- a/src/languageservice/services/yamlHover.ts +++ b/src/languageservice/services/yamlHover.ts @@ -12,7 +12,7 @@ import { setKubernetesParserOption } from '../parser/isKubernetes'; import { TextDocument } from 'vscode-languageserver-textdocument'; import { yamlDocumentsCache } from '../parser/yaml-documents'; import { SingleYAMLDocument } from '../parser/yamlParser07'; -import { getNodeValue, IApplicableSchema } from '../parser/jsonParser07'; +import { IApplicableSchema } from '../parser/jsonParser07'; import { JSONSchema } from '../jsonSchema'; import { URI } from 'vscode-uri'; import * as path from 'path'; @@ -113,27 +113,31 @@ export class YAMLHover { let title: string | undefined = undefined; let markdownDescription: string | undefined = undefined; - let markdownEnumValueDescription: string | undefined = undefined; - let enumValue: string | undefined = undefined; + let markdownEnumDescriptions: string[] = []; const markdownExamples: string[] = []; + const markdownEnums: markdownEnum[] = []; matchingSchemas.every((s) => { if ((s.node === node || (node.type === 'property' && node.valueNode === s.node)) && !s.inverted && s.schema) { title = title || s.schema.title || s.schema.closestTitle; markdownDescription = markdownDescription || s.schema.markdownDescription || toMarkdown(s.schema.description); if (s.schema.enum) { - const idx = s.schema.enum.indexOf(getNodeValue(node)); if (s.schema.markdownEnumDescriptions) { - markdownEnumValueDescription = s.schema.markdownEnumDescriptions[idx]; + markdownEnumDescriptions = s.schema.markdownEnumDescriptions; } else if (s.schema.enumDescriptions) { - markdownEnumValueDescription = toMarkdown(s.schema.enumDescriptions[idx]); + markdownEnumDescriptions = s.schema.enumDescriptions.map(toMarkdown); + } else { + markdownEnumDescriptions = []; } - if (markdownEnumValueDescription) { - enumValue = s.schema.enum[idx]; + s.schema.enum.forEach((enumValue, idx) => { if (typeof enumValue !== 'string') { enumValue = JSON.stringify(enumValue); } - } + markdownEnums.push({ + value: enumValue, + description: markdownEnumDescriptions[idx], + }); + }); } if (s.schema.anyOf && isAllSchemasMatched(node, matchingSchemas, s.schema)) { //if append title and description of all matched schemas on hover @@ -163,28 +167,30 @@ export class YAMLHover { result = '#### ' + toMarkdown(title); } if (markdownDescription) { - if (result.length > 0) { - result += '\n\n'; - } + result = ensureLineBreak(result); result += markdownDescription; } - if (markdownEnumValueDescription) { - if (result.length > 0) { - result += '\n\n'; - } - result += `\`${toMarkdownCodeBlock(enumValue)}\`: ${markdownEnumValueDescription}`; + if (markdownEnums.length !== 0) { + result = ensureLineBreak(result); + result += 'Allowed Values:\n\n'; + markdownEnums.forEach((me) => { + if (me.description) { + result += `* \`${toMarkdownCodeBlock(me.value)}\`: ${me.description}\n`; + } else { + result += `* \`${toMarkdownCodeBlock(me.value)}\`\n`; + } + }); } if (markdownExamples.length !== 0) { - if (result.length > 0) { - result += '\n\n'; - } - result += 'Examples:'; + result = ensureLineBreak(result); + result += 'Examples:\n\n'; markdownExamples.forEach((example) => { - result += `\n\n\`\`\`${example}\`\`\``; + result += `* \`\`\`${example}\`\`\`\n`; }); } if (result.length > 0 && schema.schema.url) { - result += `\n\nSource: [${getSchemaName(schema.schema)}](${schema.schema.url})`; + result = ensureLineBreak(result); + result += `Source: [${getSchemaName(schema.schema)}](${schema.schema.url})`; } return createHover(result); } @@ -193,6 +199,21 @@ export class YAMLHover { } } +interface markdownEnum { + value: string; + description: string; +} + +function ensureLineBreak(content: string): string { + if (content.length === 0) { + return content; + } + if (!content.endsWith('\n')) { + content += '\n'; + } + return content + '\n'; +} + function getSchemaName(schema: JSONSchema): string { let result = 'JSON Schema'; const urlString = schema.url; diff --git a/test/hover.test.ts b/test/hover.test.ts index afd28690..83cf9932 100644 --- a/test/hover.test.ts +++ b/test/hover.test.ts @@ -556,6 +556,37 @@ users: ); }); + it('Hover displays enum descriptions if present', async () => { + schemaProvider.addSchema(SCHEMA_ID, { + type: 'object', + properties: { + animal: { + type: 'string', + description: 'should return this description', + enum: ['cat', 'dog', 'non'], + enumDescriptions: ['', 'Canis familiaris'], + }, + }, + }); + const content = 'animal:\n ca|t|'; // len: 13, pos: 12 + const result = await parseSetup(content); + + assert.strictEqual(MarkupContent.is(result.contents), true); + assert.strictEqual((result.contents as MarkupContent).kind, 'markdown'); + assert.strictEqual( + (result.contents as MarkupContent).value, + `should return this description + +Allowed Values: + +* \`cat\` +* \`dog\`: Canis familiaris +* \`non\` + +Source: [${SCHEMA_ID}](file:///${SCHEMA_ID})` + ); + }); + it('Hover works on examples', async () => { schemaProvider.addSchema(SCHEMA_ID, { type: 'object', @@ -577,11 +608,15 @@ users: (result.contents as MarkupContent).value, `should return this description -Examples: +Allowed Values: + +* \`cat\` +* \`dog\` -\`\`\`"cat"\`\`\` +Examples: -\`\`\`"dog"\`\`\` +* \`\`\`"cat"\`\`\` +* \`\`\`"dog"\`\`\` Source: [${SCHEMA_ID}](file:///${SCHEMA_ID})` );