Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

append the title and description of anyOf schemas while hover #762

Merged
merged 2 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion src/languageservice/services/yamlHover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import { setKubernetesParserOption } from '../parser/isKubernetes';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { yamlDocumentsCache } from '../parser/yaml-documents';
import { SingleYAMLDocument } from '../parser/yamlParser07';
import { getNodeValue } from '../parser/jsonParser07';
import { getNodeValue, IApplicableSchema } from '../parser/jsonParser07';
import { JSONSchema } from '../jsonSchema';
import { URI } from 'vscode-uri';
import * as path from 'path';
import { Telemetry } from '../../languageserver/telemetry';
import { convertErrorToTelemetryMsg } from '../utils/objects';
import { ASTNode } from 'vscode-json-languageservice';

export class YAMLHover {
private shouldHover: boolean;
Expand Down Expand Up @@ -96,6 +97,10 @@ export class YAMLHover {
return result;
};

const removePipe = (value: string): string => {
return value.replace(/\|\|\s*$/, '');
};

return this.schemaService.getSchemaForResource(document.uri, doc).then((schema) => {
if (schema && node && !schema.errors.length) {
const matchingSchemas = doc.getMatchingSchemas(schema.schema, node.offset);
Expand Down Expand Up @@ -124,6 +129,21 @@ export class YAMLHover {
}
}
}
if (s.schema.anyOf && isAllSchemasMatched(node, matchingSchemas, s.schema)) {
//if append title and description of all matched schemas on hover
title = '';
markdownDescription = '';
s.schema.anyOf.forEach((childSchema: JSONSchema, index: number) => {
title += childSchema.title || s.schema.closestTitle || '';
markdownDescription += childSchema.markdownDescription || toMarkdown(childSchema.description) || '';
if (index !== s.schema.anyOf.length - 1) {
title += ' || ';
markdownDescription += ' || ';
}
});
title = removePipe(title);
markdownDescription = removePipe(markdownDescription);
}
if (s.schema.examples) {
s.schema.examples.forEach((example) => {
markdownExamples.push(JSON.stringify(example));
Expand Down Expand Up @@ -198,3 +218,24 @@ function toMarkdownCodeBlock(content: string): string {
}
return content;
}

/**
* check all the schemas which is inside anyOf presented or not in matching schema.
* @param node node
* @param matchingSchemas all matching schema
* @param schema scheam which is having anyOf
* @returns true if all the schemas which inside anyOf presents in matching schema
*/
function isAllSchemasMatched(node: ASTNode, matchingSchemas: IApplicableSchema[], schema: JSONSchema): boolean {
let count = 0;
for (const matchSchema of matchingSchemas) {
if (node === matchSchema.node && matchSchema.schema !== schema) {
schema.anyOf.forEach((childSchema: JSONSchema) => {
if (matchSchema.schema === childSchema) {
count++;
}
});
}
}
return count === schema.anyOf.length;
}
91 changes: 91 additions & 0 deletions test/hover.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,97 @@ Source: [${SCHEMA_ID}](file:///${SCHEMA_ID})`
});
});

describe('Hover on anyOf', () => {
it('should show all matched schemas in anyOf', async () => {
languageService.addSchema(SCHEMA_ID, {
title: 'The Root',
description: 'Root Object',
type: 'object',
properties: {
child: {
title: 'Child',
anyOf: [
{
$ref: '#/definitions/FirstChoice',
},
{
$ref: '#/definitions/SecondChoice',
},
],
},
},
required: ['child'],
additionalProperties: false,
definitions: {
FirstChoice: {
title: 'FirstChoice',
description: 'The first choice',
type: 'object',
properties: {
choice: {
title: 'Choice',
default: 'first',
enum: ['first'],
type: 'string',
},
property_a: {
title: 'Property A',
type: 'string',
},
},
required: ['property_a'],
},
SecondChoice: {
title: 'SecondChoice',
description: 'The second choice',
type: 'object',
properties: {
choice: {
title: 'Choice',
default: 'second',
enum: ['second'],
type: 'string',
},
property_b: {
title: 'Property B',
type: 'string',
},
},
required: ['property_b'],
},
},
});
let content = 'ch|i|ld:';
let result = await parseSetup(content);
assert.strictEqual(MarkupContent.is(result.contents), true);
assert.strictEqual(
(result.contents as MarkupContent).value,
`#### FirstChoice || SecondChoice\n\nThe first choice || The second choice\n\nSource: [${SCHEMA_ID}](file:///${SCHEMA_ID})`
);
expect(telemetry.messages).to.be.empty;

//use case 1:
content = 'ch|i|ld: \n property_a: test';
result = await parseSetup(content);
assert.strictEqual(MarkupContent.is(result.contents), true);
assert.strictEqual(
(result.contents as MarkupContent).value,
`#### FirstChoice\n\nThe first choice\n\nSource: [${SCHEMA_ID}](file:///${SCHEMA_ID})`
);
expect(telemetry.messages).to.be.empty;

//use case 2:
content = 'ch|i|ld: \n property_b: test';
result = await parseSetup(content);
assert.strictEqual(MarkupContent.is(result.contents), true);
assert.strictEqual(
(result.contents as MarkupContent).value,
`#### SecondChoice\n\nThe second choice\n\nSource: [${SCHEMA_ID}](file:///${SCHEMA_ID})`
);
expect(telemetry.messages).to.be.empty;
});
});

describe('Bug fixes', () => {
it('should convert binary data correctly', async () => {
const content =
Expand Down