diff --git a/src/languageservice/services/yamlCompletion.ts b/src/languageservice/services/yamlCompletion.ts index 5f384e50..ddfdc156 100644 --- a/src/languageservice/services/yamlCompletion.ts +++ b/src/languageservice/services/yamlCompletion.ts @@ -30,7 +30,7 @@ import { stringifyObject, StringifySettings } from '../utils/json'; import { guessIndentation } from '../utils/indentationGuesser'; import { TextBuffer } from '../utils/textBuffer'; import { setKubernetesParserOption } from '../parser/isKubernetes'; -import { ClientCapabilities } from 'vscode-languageserver'; +import { ClientCapabilities, MarkupContent } from 'vscode-languageserver'; const localize = nls.loadMessageBundle(); export class YAMLCompletion extends JSONCompletion { @@ -388,6 +388,25 @@ export class YAMLCompletion extends JSONCompletion { insertTextFormat: InsertTextFormat.Snippet, }); this.addSchemaValueCompletions(s.schema.items, separatorAfter, collector, types); + } else if (typeof s.schema.items === 'object' && s.schema.items.anyOf) { + s.schema.items.anyOf + .filter((i) => typeof i === 'object') + .forEach((i: JSONSchema, index) => { + const insertText = `- ${this.getInsertTextForObject(i, separatorAfter).insertText.trimLeft()}`; + //append insertText to documentation + const documentation = this.getDocumentationWithMarkdownText( + `Create an item of an array${s.schema.description === undefined ? '' : '(' + s.schema.description + ')'}`, + insertText + ); + collector.add({ + kind: super.getSuggestionKind(i.type), + label: '- (array item) ' + (index + 1), + documentation: documentation, + insertText: insertText, + insertTextFormat: InsertTextFormat.Snippet, + }); + }); + this.addSchemaValueCompletions(s.schema.items, separatorAfter, collector, types); } else { this.addSchemaValueCompletions(s.schema.items, separatorAfter, collector, types); } @@ -999,6 +1018,19 @@ export class YAMLCompletion extends JSONCompletion { private is_EOL(c: number): boolean { return c === 0x0a /* LF */ || c === 0x0d /* CR */; } + + private getDocumentationWithMarkdownText(documentation: string, insertText: string): string | MarkupContent { + let res: string | MarkupContent = documentation; + if (super.doesSupportMarkdown()) { + insertText = insertText + .replace(/\${[0-9]+[:|](.*)}/g, (s, arg) => { + return arg; + }) + .replace(/\$([0-9]+)/g, ''); + res = super.fromMarkup(`${documentation}\n \`\`\`\n${insertText}\n\`\`\``) as MarkupContent; + } + return res; + } } const isNumberExp = /^\d+$/; diff --git a/test/autoCompletion.test.ts b/test/autoCompletion.test.ts index acfcb3a8..a974dd68 100644 --- a/test/autoCompletion.test.ts +++ b/test/autoCompletion.test.ts @@ -1710,5 +1710,83 @@ suite('Auto Completion Tests', () => { ); }); }); + describe('Array completion', () => { + it('Simple array object completion with "-" without any item', async () => { + const schema = require(path.join(__dirname, './fixtures/testArrayCompletionSchema.json')); + languageService.addSchema(SCHEMA_ID, schema); + const content = 'test_simpleArrayObject:\n -'; + const completion = parseSetup(content, content.length); + completion.then(function (result) { + assert.equal(result.items.length, 1); + assert.equal(result.items[0].label, '- (array item)'); + }); + }); + + it('Simple array object completion without "-" after array item', async () => { + const schema = require(path.join(__dirname, './fixtures/testArrayCompletionSchema.json')); + languageService.addSchema(SCHEMA_ID, schema); + const content = 'test_simpleArrayObject:\n - obj1:\n name: 1\n '; + const completion = parseSetup(content, content.length); + completion.then(function (result) { + assert.equal(result.items.length, 1); + assert.equal(result.items[0].label, '- (array item)'); + }); + }); + + it('Simple array object completion with "-" after array item', async () => { + const schema = require(path.join(__dirname, './fixtures/testArrayCompletionSchema.json')); + languageService.addSchema(SCHEMA_ID, schema); + const content = 'test_simpleArrayObject:\n - obj1:\n name: 1\n -'; + const completion = parseSetup(content, content.length); + completion.then(function (result) { + assert.equal(result.items.length, 1); + assert.equal(result.items[0].label, '- (array item)'); + }); + }); + + it('Array anyOf two objects completion with "- " without any item', async () => { + const schema = require(path.join(__dirname, './fixtures/testArrayCompletionSchema.json')); + languageService.addSchema(SCHEMA_ID, schema); + const content = 'test_array_anyOf_2objects:\n - '; + const completion = parseSetup(content, content.length); + completion.then(function (result) { + assert.equal(result.items.length, 2); + assert.equal(result.items[0].label, 'obj1'); + }); + }); + + it('Array anyOf two objects completion with "-" without any item', async () => { + const schema = require(path.join(__dirname, './fixtures/testArrayCompletionSchema.json')); + languageService.addSchema(SCHEMA_ID, schema); + const content = 'test_array_anyOf_2objects:\n -'; + const completion = parseSetup(content, content.length); + completion.then(function (result) { + assert.equal(result.items.length, 2); + assert.equal(result.items[0].label, '- (array item) 1'); + }); + }); + + it('Array anyOf two objects completion without "-" after array item', async () => { + const schema = require(path.join(__dirname, './fixtures/testArrayCompletionSchema.json')); + languageService.addSchema(SCHEMA_ID, schema); + const content = 'test_array_anyOf_2objects:\n - obj1:\n name: 1\n '; + const completion = parseSetup(content, content.length); + completion.then(function (result) { + assert.equal(result.items.length, 2); + assert.equal(result.items[0].label, '- (array item) 1'); + }); + }); + + it('Array anyOf two objects completion with "-" after array item', async () => { + const schema = require(path.join(__dirname, './fixtures/testArrayCompletionSchema.json')); + languageService.addSchema(SCHEMA_ID, schema); + const content = 'test_array_anyOf_2objects:\n - obj1:\n name: 1\n -'; + const completion = parseSetup(content, content.length); + completion.then(function (result) { + assert.equal(result.items.length, 2); + assert.equal(result.items[0].label, '- (array item) 1'); + }); + }); + }); }); }); diff --git a/test/fixtures/testArrayCompletionSchema.json b/test/fixtures/testArrayCompletionSchema.json new file mode 100644 index 00000000..0accf9ee --- /dev/null +++ b/test/fixtures/testArrayCompletionSchema.json @@ -0,0 +1,85 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "obj1": { + "properties": { + "obj1": { + "type": "object" + } + }, + "required": [ + "obj1" + ], + "type": "object" + }, + "obj2": { + "properties": { + "obj2": { + "type": "object" + } + }, + "required": [ + "obj2" + ], + "type": "object" + } + }, + "properties": { + "test_simpleArrayObject": { + "items": { + "$ref": "#/definitions/obj1" + }, + "type": "array" + }, + "test_array_anyOf_2objects": { + "items": { + "anyOf": [ + { + "$ref": "#/definitions/obj1" + }, + { + "$ref": "#/definitions/obj2" + } + ] + }, + "type": "array" + }, + "test_array_anyOf_strAndObj": { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/obj1" + } + ] + }, + "type": "array" + }, + "test_anyOfObjectAndNull": { + "anyOf": [ + { + "$ref": "#/definitions/obj1" + }, + { + "type": "null" + } + ] + }, + "test_anyOfArrAndNull": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "null" + } + ] + } + }, + "type": "object" + }