diff --git a/src/languageservice/services/yamlCompletion.ts b/src/languageservice/services/yamlCompletion.ts index 513a23d6..25188cbe 100644 --- a/src/languageservice/services/yamlCompletion.ts +++ b/src/languageservice/services/yamlCompletion.ts @@ -1116,7 +1116,7 @@ export class YamlCompletion { case 'anyOf': { let value = propertySchema.default || propertySchema.const; if (value) { - if (type === 'string') { + if (type === 'string' || typeof value === 'string') { value = convertToStringValue(value); } insertText += `${indent}${key}: \${${insertIndex++}:${value}}\n`; @@ -1230,7 +1230,7 @@ export class YamlCompletion { case 'string': { let snippetValue = JSON.stringify(value); snippetValue = snippetValue.substr(1, snippetValue.length - 2); // remove quotes - snippetValue = this.getInsertTextForPlainText(snippetValue); // escape \ and } + snippetValue = getInsertTextForPlainText(snippetValue); // escape \ and } if (type === 'string') { snippetValue = convertToStringValue(snippetValue); } @@ -1243,10 +1243,6 @@ export class YamlCompletion { return this.getInsertTextForValue(value, separatorAfter, type); } - private getInsertTextForPlainText(text: string): string { - return text.replace(/[\\$}]/g, '\\$&'); // escape $, \ and } - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any private getInsertTextForValue(value: any, separatorAfter: string, type: string | string[]): string { if (value === null) { @@ -1259,13 +1255,13 @@ export class YamlCompletion { } case 'number': case 'boolean': - return this.getInsertTextForPlainText(value + separatorAfter); + return getInsertTextForPlainText(value + separatorAfter); } type = Array.isArray(type) ? type[0] : type; if (type === 'string') { value = convertToStringValue(value); } - return this.getInsertTextForPlainText(value + separatorAfter); + return getInsertTextForPlainText(value + separatorAfter); } private getInsertTemplateForValue( @@ -1290,14 +1286,14 @@ export class YamlCompletion { if (typeof element === 'object') { valueTemplate = `${this.getInsertTemplateForValue(element, indent + this.indentation, navOrder, separatorAfter)}`; } else { - valueTemplate = ` \${${navOrder.index++}:${this.getInsertTextForPlainText(element + separatorAfter)}}\n`; + valueTemplate = ` \${${navOrder.index++}:${getInsertTextForPlainText(element + separatorAfter)}}\n`; } insertText += `${valueTemplate}`; } } return insertText; } - return this.getInsertTextForPlainText(value + separatorAfter); + return getInsertTextForPlainText(value + separatorAfter); } private addSchemaValueCompletions( @@ -1669,6 +1665,13 @@ export class YamlCompletion { } } +/** + * escape $, \ and } + */ +function getInsertTextForPlainText(text: string): string { + return text.replace(/[\\$}]/g, '\\$&'); // +} + const isNumberExp = /^\d+$/; function convertToStringValue(param: unknown): string { let value: string; @@ -1681,6 +1684,8 @@ function convertToStringValue(param: unknown): string { return value; } + value = getInsertTextForPlainText(value); // escape $, \ and } + if (value === 'true' || value === 'false' || value === 'null' || isNumberExp.test(value)) { return `"${value}"`; } diff --git a/test/autoCompletion.test.ts b/test/autoCompletion.test.ts index eba8fa6d..173eb37b 100644 --- a/test/autoCompletion.test.ts +++ b/test/autoCompletion.test.ts @@ -931,6 +931,34 @@ describe('Auto Completion Tests', () => { ); }); + it('Autocompletion should escape $ in defaultValue in anyOf', async () => { + schemaProvider.addSchema(SCHEMA_ID, { + type: 'object', + properties: { + car: { + type: 'object', + required: ['engine'], + properties: { + engine: { + anyOf: [ + { + type: 'object', + }, + { + type: 'string', + }, + ], + default: 'type$1234', + }, + }, + }, + }, + }); + const content = ''; + const completion = await parseSetup(content, 0); + expect(completion.items.map((i) => i.insertText)).to.deep.equal(['car:\n engine: ${1:type\\$1234}']); + }); + it('Autocompletion should escape colon when indicating map', async () => { schemaProvider.addSchema(SCHEMA_ID, { type: 'object',