Skip to content

Commit 11eff5f

Browse files
Fix ENUM yes no values (#1038)
* added support for enum yes and no Signed-off-by: msivasubramaniaan <[email protected]> * added support for enum yes and no Signed-off-by: msivasubramaniaan <[email protected]> * added test case Signed-off-by: msivasubramaniaan <[email protected]> * Quote were appended based on the yaml settings --------- Signed-off-by: msivasubramaniaan <[email protected]>
1 parent c1cab4d commit 11eff5f

File tree

6 files changed

+88
-61
lines changed

6 files changed

+88
-61
lines changed

src/languageservice/jsonASTTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export interface NumberASTNode extends BaseASTNode {
5656
export interface BooleanASTNode extends BaseASTNode {
5757
readonly type: 'boolean';
5858
readonly value: boolean;
59+
readonly source: string;
5960
}
6061
export interface NullASTNode extends BaseASTNode {
6162
readonly type: 'null';

src/languageservice/parser/ast-converter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ function convertScalar(node: Scalar, parent: ASTNode): ASTNode {
137137
return result;
138138
}
139139
case 'boolean':
140-
return new BooleanASTNodeImpl(parent, node, node.value, ...toOffsetLength(node.range));
140+
return new BooleanASTNodeImpl(parent, node, node.value, node.source, ...toOffsetLength(node.range));
141141
case 'number': {
142142
const result = new NumberASTNodeImpl(parent, node, ...toOffsetLength(node.range));
143143
result.value = node.value;

src/languageservice/parser/jsonParser07.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,12 @@ export class NullASTNodeImpl extends ASTNodeImpl implements NullASTNode {
169169
export class BooleanASTNodeImpl extends ASTNodeImpl implements BooleanASTNode {
170170
public type: 'boolean' = 'boolean' as const;
171171
public value: boolean;
172+
public source: string;
172173

173-
constructor(parent: ASTNode, internalNode: Node, boolValue: boolean, offset: number, length?: number) {
174+
constructor(parent: ASTNode, internalNode: Node, boolValue: boolean, boolSource: string, offset: number, length?: number) {
174175
super(parent, internalNode, offset, length);
175176
this.value = boolValue;
177+
this.source = boolSource;
176178
}
177179
}
178180

@@ -502,8 +504,9 @@ export function getNodeValue(node: ASTNode): any {
502504
case 'null':
503505
case 'string':
504506
case 'number':
505-
case 'boolean':
506507
return node.value;
508+
case 'boolean':
509+
return node.source;
507510
default:
508511
return undefined;
509512
}
@@ -867,7 +870,7 @@ function validate(
867870
const val = getNodeValue(node);
868871
let enumValueMatch = false;
869872
for (const e of schema.enum) {
870-
if (equals(val, e) || (callFromAutoComplete && isString(val) && isString(e) && val && e.startsWith(val))) {
873+
if (val === e || (callFromAutoComplete && isString(val) && isString(e) && val && e.startsWith(val))) {
871874
enumValueMatch = true;
872875
break;
873876
}

src/languageservice/services/yamlCompletion.ts

Lines changed: 61 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { indexOf, isInComment, isMapContainsEmptyPair } from '../utils/astUtils'
3737
import { isModeline } from './modelineUtil';
3838
import { getSchemaTypeName, isAnyOfAllOfOneOfType, isPrimitiveType } from '../utils/schemaUtils';
3939
import { YamlNode } from '../jsonASTTypes';
40+
import { SettingsState } from '../../yamlSettings';
4041

4142
const localize = nls.loadMessageBundle();
4243

@@ -74,6 +75,7 @@ export class YamlCompletion {
7475
private completionEnabled = true;
7576
private configuredIndentation: string | undefined;
7677
private yamlVersion: YamlVersion;
78+
private isSingleQuote: boolean;
7779
private indentation: string;
7880
private arrayPrefixIndentation = '';
7981
private supportsMarkdown: boolean | undefined;
@@ -87,12 +89,13 @@ export class YamlCompletion {
8789
private readonly telemetry?: Telemetry
8890
) {}
8991

90-
configure(languageSettings: LanguageSettings): void {
92+
configure(languageSettings: LanguageSettings, yamlSettings?: SettingsState): void {
9193
if (languageSettings) {
9294
this.completionEnabled = languageSettings.completion;
9395
}
9496
this.customTags = languageSettings.customTags;
9597
this.yamlVersion = languageSettings.yamlVersion;
98+
this.isSingleQuote = yamlSettings?.yamlFormatterSettings?.singleQuote || false;
9699
this.configuredIndentation = languageSettings.indentation;
97100
this.disableDefaultProperties = languageSettings.disableDefaultProperties;
98101
this.parentSkeletonSelectedFirst = languageSettings.parentSkeletonSelectedFirst;
@@ -622,7 +625,7 @@ export class YamlCompletion {
622625
};
623626

624627
result.items.forEach((completionItem) => {
625-
if (isParentCompletionItem(completionItem)) {
628+
if (this.isParentCompletionItem(completionItem)) {
626629
const indent = completionItem.parent.indent || '';
627630

628631
const reindexedTexts = reindexText(completionItem.parent.insertTexts);
@@ -1021,7 +1024,7 @@ export class YamlCompletion {
10211024
if (propertySchema.const) {
10221025
if (!value) {
10231026
value = this.getInsertTextForGuessedValue(propertySchema.const, '', type);
1024-
value = evaluateTab1Symbol(value); // prevent const being selected after snippet insert
1027+
value = this.evaluateTab1Symbol(value); // prevent const being selected after snippet insert
10251028
value = ' ' + value;
10261029
}
10271030
nValueProposals++;
@@ -1117,7 +1120,7 @@ export class YamlCompletion {
11171120
let value = propertySchema.default || propertySchema.const;
11181121
if (value) {
11191122
if (type === 'string') {
1120-
value = convertToStringValue(value);
1123+
value = this.convertToStringValue(value);
11211124
}
11221125
insertText += `${indent}${key}: \${${insertIndex++}:${value}}\n`;
11231126
} else {
@@ -1165,7 +1168,7 @@ export class YamlCompletion {
11651168
}: \${${insertIndex++}:${propertySchema.default}}\n`;
11661169
break;
11671170
case 'string':
1168-
insertText += `${indent}${key}: \${${insertIndex++}:${convertToStringValue(propertySchema.default)}}\n`;
1171+
insertText += `${indent}${key}: \${${insertIndex++}:${this.convertToStringValue(propertySchema.default)}}\n`;
11691172
break;
11701173
case 'array':
11711174
case 'object':
@@ -1232,7 +1235,7 @@ export class YamlCompletion {
12321235
snippetValue = snippetValue.substr(1, snippetValue.length - 2); // remove quotes
12331236
snippetValue = this.getInsertTextForPlainText(snippetValue); // escape \ and }
12341237
if (type === 'string') {
1235-
snippetValue = convertToStringValue(snippetValue);
1238+
snippetValue = this.convertToStringValue(snippetValue);
12361239
}
12371240
return '${1:' + snippetValue + '}' + separatorAfter;
12381241
}
@@ -1263,7 +1266,7 @@ export class YamlCompletion {
12631266
}
12641267
type = Array.isArray(type) ? type[0] : type;
12651268
if (type === 'string') {
1266-
value = convertToStringValue(value);
1269+
value = this.convertToStringValue(value);
12671270
}
12681271
return this.getInsertTextForPlainText(value + separatorAfter);
12691272
}
@@ -1667,65 +1670,67 @@ export class YamlCompletion {
16671670

16681671
return 0;
16691672
}
1670-
}
16711673

1672-
const isNumberExp = /^\d+$/;
1673-
function convertToStringValue(param: unknown): string {
1674-
let value: string;
1675-
if (typeof param === 'string') {
1676-
value = param;
1677-
} else {
1678-
value = '' + param;
1679-
}
1680-
if (value.length === 0) {
1681-
return value;
1682-
}
1674+
isNumberExp = /^\d+$/;
1675+
convertToStringValue(param: unknown): string {
1676+
let value: string;
1677+
if (typeof param === 'string') {
1678+
//support YAML spec 1.1 boolean values
1679+
const quote = this.isSingleQuote ? `'` : `"`;
1680+
value = ['on', 'off', 'true', 'false', 'yes', 'no'].includes(param.toLowerCase()) ? `${quote}${param}${quote}` : param;
1681+
} else {
1682+
value = '' + param;
1683+
}
1684+
if (value.length === 0) {
1685+
return value;
1686+
}
16831687

1684-
if (value === 'true' || value === 'false' || value === 'null' || isNumberExp.test(value)) {
1685-
return `"${value}"`;
1686-
}
1688+
if (value === 'true' || value === 'false' || value === 'null' || this.isNumberExp.test(value)) {
1689+
return `"${value}"`;
1690+
}
16871691

1688-
if (value.indexOf('"') !== -1) {
1689-
value = value.replace(doubleQuotesEscapeRegExp, '"');
1690-
}
1692+
if (value.indexOf('"') !== -1) {
1693+
value = value.replace(doubleQuotesEscapeRegExp, '"');
1694+
}
16911695

1692-
let doQuote = !isNaN(parseInt(value)) || value.charAt(0) === '@';
1696+
let doQuote = !isNaN(parseInt(value)) || value.charAt(0) === '@';
1697+
1698+
if (!doQuote) {
1699+
// need to quote value if in `foo: bar`, `foo : bar` (mapping) or `foo:` (partial map) format
1700+
// but `foo:bar` and `:bar` (colon without white-space after it) are just plain string
1701+
let idx = value.indexOf(':', 0);
1702+
for (; idx > 0 && idx < value.length; idx = value.indexOf(':', idx + 1)) {
1703+
if (idx === value.length - 1) {
1704+
// `foo:` (partial map) format
1705+
doQuote = true;
1706+
break;
1707+
}
16931708

1694-
if (!doQuote) {
1695-
// need to quote value if in `foo: bar`, `foo : bar` (mapping) or `foo:` (partial map) format
1696-
// but `foo:bar` and `:bar` (colon without white-space after it) are just plain string
1697-
let idx = value.indexOf(':', 0);
1698-
for (; idx > 0 && idx < value.length; idx = value.indexOf(':', idx + 1)) {
1699-
if (idx === value.length - 1) {
1700-
// `foo:` (partial map) format
1701-
doQuote = true;
1702-
break;
1709+
// there are only two valid kinds of white-space in yaml: space or tab
1710+
// ref: https://yaml.org/spec/1.2.1/#id2775170
1711+
const nextChar = value.charAt(idx + 1);
1712+
if (nextChar === '\t' || nextChar === ' ') {
1713+
doQuote = true;
1714+
break;
1715+
}
17031716
}
1717+
}
17041718

1705-
// there are only two valid kinds of white-space in yaml: space or tab
1706-
// ref: https://yaml.org/spec/1.2.1/#id2775170
1707-
const nextChar = value.charAt(idx + 1);
1708-
if (nextChar === '\t' || nextChar === ' ') {
1709-
doQuote = true;
1710-
break;
1711-
}
1719+
if (doQuote) {
1720+
value = `"${value}"`;
17121721
}
1713-
}
17141722

1715-
if (doQuote) {
1716-
value = `"${value}"`;
1723+
return value;
17171724
}
17181725

1719-
return value;
1720-
}
1721-
1722-
/**
1723-
* simplify `{$1:value}` to `value`
1724-
*/
1725-
function evaluateTab1Symbol(value: string): string {
1726-
return value.replace(/\$\{1:(.*)\}/, '$1');
1727-
}
1726+
/**
1727+
* simplify `{$1:value}` to `value`
1728+
*/
1729+
evaluateTab1Symbol(value: string): string {
1730+
return value.replace(/\$\{1:(.*)\}/, '$1');
1731+
}
17281732

1729-
function isParentCompletionItem(item: CompletionItemBase): item is CompletionItem {
1730-
return 'parent' in item;
1733+
isParentCompletionItem(item: CompletionItemBase): item is CompletionItem {
1734+
return 'parent' in item;
1735+
}
17311736
}

src/languageservice/yamlLanguageService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ export function getLanguageService(params: {
222222
}
223223
yamlValidation.configure(settings);
224224
hover.configure(settings);
225-
completer.configure(settings);
225+
completer.configure(settings, params.yamlSettings);
226226
formatter.configure(settings);
227227
yamlCodeActions.configure(settings);
228228
},

test/autoCompletionFix.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,4 +1329,22 @@ test1:
13291329
expect(completion.items[0].insertText).to.be.equal('${1:property}: ');
13301330
expect(completion.items[0].documentation).to.be.equal('Property Description');
13311331
});
1332+
it('should suggest enum based on type', async () => {
1333+
const schema: JSONSchema = {
1334+
type: 'object',
1335+
additionalProperties: false,
1336+
properties: {
1337+
test: {
1338+
type: 'string',
1339+
enum: ['YES', 'NO'],
1340+
},
1341+
},
1342+
};
1343+
schemaProvider.addSchema(SCHEMA_ID, schema);
1344+
const content = 'test: ';
1345+
const completion = await parseSetup(content, 0, content.length);
1346+
expect(completion.items.length).equal(2);
1347+
expect(completion.items[0].insertText).to.be.equal('"YES"');
1348+
expect(completion.items[1].insertText).to.be.equal('"NO"');
1349+
});
13321350
});

0 commit comments

Comments
 (0)