mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-21 09:08:53 +01:00
Fixes #3494: [snippets] [debt] don't allow snippet syntax in default values
This commit is contained in:
@@ -5,42 +5,43 @@
|
||||
'use strict';
|
||||
|
||||
export interface IJSONSchema {
|
||||
id?:string;
|
||||
id?: string;
|
||||
$schema?: string;
|
||||
type?:any;
|
||||
title?:string;
|
||||
default?:any;
|
||||
definitions?:IJSONSchemaMap;
|
||||
description?:string;
|
||||
type?: string | string[];
|
||||
title?: string;
|
||||
default?: any;
|
||||
definitions?: IJSONSchemaMap;
|
||||
description?: string;
|
||||
properties?: IJSONSchemaMap;
|
||||
patternProperties?:IJSONSchemaMap;
|
||||
additionalProperties?:any;
|
||||
minProperties?:number;
|
||||
maxProperties?:number;
|
||||
dependencies?:any;
|
||||
items?:any;
|
||||
minItems?:number;
|
||||
maxItems?:number;
|
||||
uniqueItems?:boolean;
|
||||
additionalItems?:boolean;
|
||||
pattern?:string;
|
||||
minLength?:number;
|
||||
maxLength?:number;
|
||||
minimum?:number;
|
||||
maximum?:number;
|
||||
exclusiveMinimum?:boolean;
|
||||
exclusiveMaximum?:boolean;
|
||||
multipleOf?:number;
|
||||
required?:string[];
|
||||
$ref?:string;
|
||||
anyOf?:IJSONSchema[];
|
||||
allOf?:IJSONSchema[];
|
||||
oneOf?:IJSONSchema[];
|
||||
not?:IJSONSchema;
|
||||
enum?:any[];
|
||||
patternProperties?: IJSONSchemaMap;
|
||||
additionalProperties?: boolean | IJSONSchema;
|
||||
minProperties?: number;
|
||||
maxProperties?: number;
|
||||
dependencies?: IJSONSchemaMap | string[];
|
||||
items?: IJSONSchema | IJSONSchema[];
|
||||
minItems?: number;
|
||||
maxItems?: number;
|
||||
uniqueItems?: boolean;
|
||||
additionalItems?: boolean;
|
||||
pattern?: string;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
minimum?: number;
|
||||
maximum?: number;
|
||||
exclusiveMinimum?: boolean;
|
||||
exclusiveMaximum?: boolean;
|
||||
multipleOf?: number;
|
||||
required?: string[];
|
||||
$ref?: string;
|
||||
anyOf?: IJSONSchema[];
|
||||
allOf?: IJSONSchema[];
|
||||
oneOf?: IJSONSchema[];
|
||||
not?: IJSONSchema;
|
||||
enum?: any[];
|
||||
format?: string;
|
||||
|
||||
errorMessage?:string; // VS code internal
|
||||
|
||||
defaultSnippets?: { label?: string; description?: string; body: any; }[]; // VSCode extension
|
||||
errorMessage?: string; // VSCode extension
|
||||
}
|
||||
|
||||
export interface IJSONSchemaMap {
|
||||
|
||||
@@ -172,7 +172,7 @@ export class JSONCompletion {
|
||||
if (schemaProperties) {
|
||||
Object.keys(schemaProperties).forEach((key: string) => {
|
||||
let propertySchema = schemaProperties[key];
|
||||
collector.add({ kind: CompletionItemKind.Property, label: key, insertText: this.getSnippetForProperty(key, propertySchema, addValue, isLast), documentation: propertySchema.description || '' });
|
||||
collector.add({ kind: CompletionItemKind.Property, label: key, insertText: this.getTextForProperty(key, propertySchema, addValue, isLast), documentation: propertySchema.description || '' });
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,7 @@ export class JSONCompletion {
|
||||
let collectSuggestionsForSimilarObject = (obj: Parser.ObjectASTNode) => {
|
||||
obj.properties.forEach((p) => {
|
||||
let key = p.key.value;
|
||||
collector.add({ kind: CompletionItemKind.Property, label: key, insertText: this.getSnippetForSimilarProperty(key, p.value), documentation: '' });
|
||||
collector.add({ kind: CompletionItemKind.Property, label: key, insertText: this.getTextForSimilarProperty(key, p.value), documentation: '' });
|
||||
});
|
||||
};
|
||||
if (node.parent) {
|
||||
@@ -206,14 +206,14 @@ export class JSONCompletion {
|
||||
}
|
||||
}
|
||||
if (!currentKey && currentWord.length > 0) {
|
||||
collector.add({ kind: CompletionItemKind.Property, label: JSON.stringify(currentWord), insertText: this.getSnippetForProperty(currentWord, null, true, isLast), documentation: '' });
|
||||
collector.add({ kind: CompletionItemKind.Property, label: this.getLabelForValue(currentWord), insertText: this.getTextForProperty(currentWord, null, true, isLast), documentation: '' });
|
||||
}
|
||||
}
|
||||
|
||||
private getSchemaLessValueSuggestions(doc: Parser.JSONDocument, node: Parser.ASTNode, offset: number, document: ITextDocument, collector: ISuggestionsCollector): void {
|
||||
let collectSuggestionsForValues = (value: Parser.ASTNode) => {
|
||||
if (!value.contains(offset)) {
|
||||
let content = this.getMatchingSnippet(value, document);
|
||||
let content = this.getTextForMatchingNode(value, document);
|
||||
collector.add({ kind: this.getSuggestionKind(value.type), label: content, insertText: content, documentation: '' });
|
||||
}
|
||||
if (value.type === 'boolean') {
|
||||
@@ -347,6 +347,16 @@ export class JSONCompletion {
|
||||
detail: nls.localize('json.suggest.default', 'Default value'),
|
||||
});
|
||||
}
|
||||
if (Array.isArray(schema.defaultSnippets)) {
|
||||
schema.defaultSnippets.forEach(s => {
|
||||
collector.add({
|
||||
kind: CompletionItemKind.Snippet,
|
||||
label: this.getLabelForSnippetValue(s.body),
|
||||
insertText: this.getTextForSnippetValue(s.body)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (Array.isArray(schema.allOf)) {
|
||||
schema.allOf.forEach((s) => this.addDefaultSuggestion(s, collector));
|
||||
}
|
||||
@@ -360,7 +370,15 @@ export class JSONCompletion {
|
||||
|
||||
private getLabelForValue(value: any): string {
|
||||
let label = JSON.stringify(value);
|
||||
label = label.replace('{{', '').replace('}}', '');
|
||||
if (label.length > 57) {
|
||||
return label.substr(0, 57).trim() + '...';
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
private getLabelForSnippetValue(value: any): string {
|
||||
let label = JSON.stringify(value);
|
||||
label = label.replace(/\{\{|\}\}/g, '');
|
||||
if (label.length > 57) {
|
||||
return label.substr(0, 57).trim() + '...';
|
||||
}
|
||||
@@ -368,11 +386,17 @@ export class JSONCompletion {
|
||||
}
|
||||
|
||||
private getTextForValue(value: any): string {
|
||||
var text = JSON.stringify(value, null, '\t');
|
||||
text = text.replace(/[\\\{\}]/g, '\\$&');
|
||||
return text;
|
||||
}
|
||||
|
||||
private getTextForSnippetValue(value: any): string {
|
||||
return JSON.stringify(value, null, '\t');
|
||||
}
|
||||
|
||||
private getSnippetForValue(value: any): string {
|
||||
let snippet = JSON.stringify(value, null, '\t');
|
||||
private getTextForEnumValue(value: any): string {
|
||||
let snippet = this.getTextForValue(value);
|
||||
switch (typeof value) {
|
||||
case 'object':
|
||||
if (value === null) {
|
||||
@@ -405,7 +429,7 @@ export class JSONCompletion {
|
||||
}
|
||||
|
||||
|
||||
private getMatchingSnippet(node: Parser.ASTNode, document: ITextDocument): string {
|
||||
private getTextForMatchingNode(node: Parser.ASTNode, document: ITextDocument): string {
|
||||
switch (node.type) {
|
||||
case 'array':
|
||||
return '[]';
|
||||
@@ -417,9 +441,9 @@ export class JSONCompletion {
|
||||
}
|
||||
}
|
||||
|
||||
private getSnippetForProperty(key: string, propertySchema: JsonSchema.IJSONSchema, addValue: boolean, isLast: boolean): string {
|
||||
private getTextForProperty(key: string, propertySchema: JsonSchema.IJSONSchema, addValue: boolean, isLast: boolean): string {
|
||||
|
||||
let result = '"' + key + '"';
|
||||
let result = this.getTextForValue(key);
|
||||
if (!addValue) {
|
||||
return result;
|
||||
}
|
||||
@@ -428,9 +452,9 @@ export class JSONCompletion {
|
||||
if (propertySchema) {
|
||||
let defaultVal = propertySchema.default;
|
||||
if (typeof defaultVal !== 'undefined') {
|
||||
result = result + this.getSnippetForValue(defaultVal);
|
||||
result = result + this.getTextForEnumValue(defaultVal);
|
||||
} else if (propertySchema.enum && propertySchema.enum.length > 0) {
|
||||
result = result + this.getSnippetForValue(propertySchema.enum[0]);
|
||||
result = result + this.getTextForEnumValue(propertySchema.enum[0]);
|
||||
} else {
|
||||
var type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type;
|
||||
switch (type) {
|
||||
@@ -465,8 +489,8 @@ export class JSONCompletion {
|
||||
return result;
|
||||
}
|
||||
|
||||
private getSnippetForSimilarProperty(key: string, templateValue: Parser.ASTNode): string {
|
||||
return '"' + key + '"';
|
||||
private getTextForSimilarProperty(key: string, templateValue: Parser.ASTNode): string {
|
||||
return this.getTextForValue(key);
|
||||
}
|
||||
|
||||
private getCurrentWord(document: ITextDocument, offset: number) {
|
||||
|
||||
@@ -103,7 +103,7 @@ export class ASTNode {
|
||||
if ((<string[]>schema.type).indexOf(this.type) === -1) {
|
||||
validationResult.warnings.push({
|
||||
location: { start: this.start, end: this.end },
|
||||
message: nls.localize('typeArrayMismatchWarning', 'Incorrect type. Expected one of {0}', schema.type.join(', '))
|
||||
message: nls.localize('typeArrayMismatchWarning', 'Incorrect type. Expected one of {0}', (<string[]>schema.type).join(', '))
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -277,14 +277,14 @@ export class ArrayASTNode extends ASTNode {
|
||||
super.validate(schema, validationResult, matchingSchemas, offset);
|
||||
|
||||
if (Array.isArray(schema.items)) {
|
||||
let subSchemas: JsonSchema.IJSONSchema[] = schema.items;
|
||||
let subSchemas = <JsonSchema.IJSONSchema[]> schema.items;
|
||||
subSchemas.forEach((subSchema, index) => {
|
||||
let itemValidationResult = new ValidationResult();
|
||||
let item = this.items[index];
|
||||
if (item) {
|
||||
item.validate(subSchema, itemValidationResult, matchingSchemas, offset);
|
||||
validationResult.mergePropertyMatch(itemValidationResult);
|
||||
} else if (this.items.length >= schema.items.length) {
|
||||
} else if (this.items.length >= subSchemas.length) {
|
||||
validationResult.propertiesValueMatches++;
|
||||
}
|
||||
});
|
||||
@@ -294,8 +294,8 @@ export class ArrayASTNode extends ASTNode {
|
||||
location: { start: this.start, end: this.end },
|
||||
message: nls.localize('additionalItemsWarning', 'Array has too many items according to schema. Expected {0} or fewer', subSchemas.length)
|
||||
});
|
||||
} else if (this.items.length >= schema.items.length) {
|
||||
validationResult.propertiesValueMatches += (this.items.length - schema.items.length);
|
||||
} else if (this.items.length >= subSchemas.length) {
|
||||
validationResult.propertiesValueMatches += (this.items.length - subSchemas.length);
|
||||
}
|
||||
}
|
||||
else if (schema.items) {
|
||||
|
||||
@@ -24,7 +24,7 @@ suite('JSON Completion', () => {
|
||||
var matches = completions.filter(function(completion: CompletionItem) {
|
||||
return completion.label === label && (!documentation || completion.documentation === documentation);
|
||||
});
|
||||
assert.equal(matches.length, 1, label + " should only existing once");
|
||||
assert.equal(matches.length, 1, label + " should only existing once: Actual: " + completions.map(c => c.label).join(', '));
|
||||
if (document && resultText) {
|
||||
assert.equal(applyEdits(document, [ matches[0].textEdit ]), resultText);
|
||||
}
|
||||
@@ -51,8 +51,6 @@ suite('JSON Completion', () => {
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
|
||||
test('Complete keys no schema', function(testDone) {
|
||||
Promise.all([
|
||||
testSuggestionsFor('[ { "name": "John", "age": 44 }, { /**/ }', '/**/', null, result => {
|
||||
@@ -478,4 +476,44 @@ suite('JSON Completion', () => {
|
||||
}),
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
});
|
||||
|
||||
test('Escaping no schema', function(testDone) {
|
||||
Promise.all([
|
||||
testSuggestionsFor('[ { "\\\\{{}}": "John" }, { "/**/" }', '/**/', null, result => {
|
||||
assertSuggestion(result, '\\{{}}');
|
||||
}),
|
||||
testSuggestionsFor('[ { "\\\\{{}}": "John" }, { /**/ }', '/**/', null, (result, document) => {
|
||||
assertSuggestion(result, '\\{{}}', null, document, '[ { "\\\\{{}}": "John" }, { "\\\\\\\\\\{\\{\\}\\}"/**/ }');
|
||||
}),
|
||||
testSuggestionsFor('[ { "name": "\\{" }, { "name": /**/ }', '/**/', null, result => {
|
||||
assertSuggestion(result, '"\\{"');
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
test('Escaping with schema', function(testDone) {
|
||||
var schema: JsonSchema.IJSONSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'{\\}': {
|
||||
default: "{\\}",
|
||||
defaultSnippets: [ { body: "{{var}}"} ],
|
||||
enum: ['John{\\}']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Promise.all([
|
||||
testSuggestionsFor('{ /**/ }', '/**/', schema, (result, document) => {
|
||||
assertSuggestion(result, '{\\}', null, document, '{ "\\{\\\\\\\\\\}": "{{\\{\\\\\\\\\\}}}"/**/ }');
|
||||
}),
|
||||
testSuggestionsFor('{ "{\\\\}": /**/ }', '/**/', schema, (result, document) => {
|
||||
assertSuggestion(result, '"{\\\\}"', null, document, '{ "{\\\\}": "\\{\\\\\\\\\\}"/**/ }');
|
||||
assertSuggestion(result, '"John{\\\\}"', null, document, '{ "{\\\\}": "John\\{\\\\\\\\\\}"/**/ }');
|
||||
assertSuggestion(result, '"var"', null, document, '{ "{\\\\}": "{{var}}"/**/ }');
|
||||
})
|
||||
]).then(() => testDone(), (error) => testDone(error));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user