Skip to content

Commit 1bffa52

Browse files
committed
feat: quickFix for enum, const, property
1 parent c2c2979 commit 1bffa52

File tree

5 files changed

+125
-7
lines changed

5 files changed

+125
-7
lines changed

src/languageservice/parser/jsonParser07.ts

+3
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,7 @@ function validate(
888888
),
889889
source: getSchemaSource(schema, originalSchema),
890890
schemaUri: getSchemaUri(schema, originalSchema),
891+
data: { values: schema.enum },
891892
});
892893
}
893894
}
@@ -907,6 +908,7 @@ function validate(
907908
source: getSchemaSource(schema, originalSchema),
908909
schemaUri: getSchemaUri(schema, originalSchema),
909910
problemArgs: [JSON.stringify(schema.const)],
911+
data: { values: [schema.const] },
910912
});
911913
validationResult.enumValueMatch = false;
912914
} else {
@@ -1385,6 +1387,7 @@ function validate(
13851387
length: propertyNode.keyNode.length,
13861388
},
13871389
severity: DiagnosticSeverity.Warning,
1390+
code: ErrorCode.PropertyExpected,
13881391
message: schema.errorMessage || localize('DisallowedExtraPropWarning', MSG_PROPERTY_NOT_ALLOWED, propertyName),
13891392
source: getSchemaSource(schema, originalSchema),
13901393
schemaUri: getSchemaUri(schema, originalSchema),

src/languageservice/services/yamlCodeActions.ts

+52-5
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@ import { FlowStyleRewriter } from '../utils/flow-style-rewriter';
2828
import { ASTNode } from '../jsonASTTypes';
2929
import * as _ from 'lodash';
3030
import { SourceToken } from 'yaml/dist/parse/cst';
31+
import { ErrorCode } from 'vscode-json-languageservice';
3132

3233
interface YamlDiagnosticData {
3334
schemaUri: string[];
35+
values?: string[];
36+
properties?: string[];
3437
}
3538
export class YamlCodeActions {
3639
private indentation = ' ';
@@ -54,6 +57,7 @@ export class YamlCodeActions {
5457
result.push(...this.getUnusedAnchorsDelete(params.context.diagnostics, document));
5558
result.push(...this.getConvertToBlockStyleActions(params.context.diagnostics, document));
5659
result.push(...this.getKeyOrderActions(params.context.diagnostics, document));
60+
result.push(...this.getQuickFixForPropertyOrValueMismatch(params.context.diagnostics, document));
5761

5862
return result;
5963
}
@@ -221,7 +225,7 @@ export class YamlCodeActions {
221225
const results: CodeAction[] = [];
222226
for (const diagnostic of diagnostics) {
223227
if (diagnostic.code === 'flowMap' || diagnostic.code === 'flowSeq') {
224-
const node = getNodeforDiagnostic(document, diagnostic);
228+
const node = getNodeForDiagnostic(document, diagnostic);
225229
if (isMap(node.internalNode) || isSeq(node.internalNode)) {
226230
const blockTypeDescription = isMap(node.internalNode) ? 'map' : 'sequence';
227231
const rewriter = new FlowStyleRewriter(this.indentation);
@@ -242,7 +246,7 @@ export class YamlCodeActions {
242246
const results: CodeAction[] = [];
243247
for (const diagnostic of diagnostics) {
244248
if (diagnostic?.code === 'mapKeyOrder') {
245-
let node = getNodeforDiagnostic(document, diagnostic);
249+
let node = getNodeForDiagnostic(document, diagnostic);
246250
while (node && node.type !== 'object') {
247251
node = node.parent;
248252
}
@@ -292,8 +296,8 @@ export class YamlCodeActions {
292296
item.value.end.splice(newLineIndex, 1);
293297
}
294298
} else if (item.value?.type === 'block-scalar') {
295-
const nwline = item.value.props.find((p) => p.type === 'newline');
296-
if (!nwline) {
299+
const newline = item.value.props.find((p) => p.type === 'newline');
300+
if (!newline) {
297301
item.value.props.push({ type: 'newline', indent: 0, offset: item.value.offset, source: '\n' } as SourceToken);
298302
}
299303
}
@@ -312,9 +316,52 @@ export class YamlCodeActions {
312316
}
313317
return results;
314318
}
319+
320+
/**
321+
* Check if diagnostic contains info for quick fix
322+
* Supports Enum/Const/Property mismatch
323+
*/
324+
private getPossibleQuickFixValues(diagnostic: Diagnostic): string[] | undefined {
325+
if (typeof diagnostic.data !== 'object') {
326+
return;
327+
}
328+
if (
329+
diagnostic.code === ErrorCode.EnumValueMismatch &&
330+
'values' in diagnostic.data &&
331+
Array.isArray((diagnostic.data as YamlDiagnosticData).values)
332+
) {
333+
return (diagnostic.data as YamlDiagnosticData).values;
334+
} else if (
335+
diagnostic.code === ErrorCode.PropertyExpected &&
336+
'properties' in diagnostic.data &&
337+
Array.isArray((diagnostic.data as YamlDiagnosticData).properties)
338+
) {
339+
return (diagnostic.data as YamlDiagnosticData).properties;
340+
}
341+
}
342+
343+
private getQuickFixForPropertyOrValueMismatch(diagnostics: Diagnostic[], document: TextDocument): CodeAction[] {
344+
const results: CodeAction[] = [];
345+
for (const diagnostic of diagnostics) {
346+
const values = this.getPossibleQuickFixValues(diagnostic);
347+
if (!values?.length) {
348+
continue;
349+
}
350+
for (const value of values) {
351+
results.push(
352+
CodeAction.create(
353+
value,
354+
createWorkspaceEdit(document.uri, [TextEdit.replace(diagnostic.range, value)]),
355+
CodeActionKind.QuickFix
356+
)
357+
);
358+
}
359+
}
360+
return results;
361+
}
315362
}
316363

317-
function getNodeforDiagnostic(document: TextDocument, diagnostic: Diagnostic): ASTNode {
364+
function getNodeForDiagnostic(document: TextDocument, diagnostic: Diagnostic): ASTNode {
318365
const yamlDocuments = yamlDocumentsCache.getYamlDocument(document);
319366
const startOffset = document.offsetAt(diagnostic.range.start);
320367
const yamlDoc = matchOffsetToDocument(startOffset, yamlDocuments);

test/schemaValidation.test.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { KUBERNETES_SCHEMA_URL } from '../src/languageservice/utils/schemaUrls';
2626
import { IProblem } from '../src/languageservice/parser/jsonParser07';
2727
import { JSONSchema } from '../src/languageservice/jsonSchema';
2828
import { TestTelemetry } from './utils/testsTypes';
29+
import { ErrorCode } from 'vscode-json-languageservice';
2930

3031
describe('Validation Tests', () => {
3132
let languageSettingsSetup: ServiceSetup;
@@ -396,7 +397,8 @@ describe('Validation Tests', () => {
396397
4,
397398
DiagnosticSeverity.Error,
398399
`yaml-schema: file:///${SCHEMA_ID}`,
399-
`file:///${SCHEMA_ID}`
400+
`file:///${SCHEMA_ID}`,
401+
ErrorCode.PropertyExpected
400402
)
401403
);
402404
})
@@ -1312,6 +1314,7 @@ obj:
13121314
DiagnosticSeverity.Error,
13131315
'yaml-schema: Drone CI configuration file',
13141316
'https://json.schemastore.org/drone',
1317+
ErrorCode.PropertyExpected,
13151318
{
13161319
properties: [
13171320
'type',

test/utils/verifyError.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,19 @@ export function createDiagnosticWithData(
3939
severity: DiagnosticSeverity = 1,
4040
source = 'YAML',
4141
schemaUri: string | string[],
42+
code: string | number = ErrorCode.Undefined,
4243
data: Record<string, unknown> = {}
4344
): Diagnostic {
44-
const diagnostic: Diagnostic = createExpectedError(message, startLine, startCharacter, endLine, endCharacter, severity, source);
45+
const diagnostic: Diagnostic = createExpectedError(
46+
message,
47+
startLine,
48+
startCharacter,
49+
endLine,
50+
endCharacter,
51+
severity,
52+
source,
53+
code
54+
);
4555
diagnostic.data = { schemaUri: typeof schemaUri === 'string' ? [schemaUri] : schemaUri, ...data };
4656
return diagnostic;
4757
}

test/yamlCodeActions.test.ts

+55
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { setupTextDocument, TEST_URI } from './utils/testHelper';
2222
import { createDiagnosticWithData, createExpectedError, createUnusedAnchorDiagnostic } from './utils/verifyError';
2323
import { YamlCommands } from '../src/commands';
2424
import { LanguageSettings } from '../src';
25+
import { ErrorCode } from 'vscode-json-languageservice';
2526

2627
const expect = chai.expect;
2728
chai.use(sinonChai);
@@ -377,4 +378,58 @@ animals: [dog , cat , mouse] `;
377378
]);
378379
});
379380
});
381+
382+
describe('Enum value or property mismatch quick fix', () => {
383+
it('should generate proper action for enum mismatch', () => {
384+
const doc = setupTextDocument('foo: value1');
385+
const diagnostic = createDiagnosticWithData(
386+
'message',
387+
0,
388+
5,
389+
0,
390+
11,
391+
DiagnosticSeverity.Hint,
392+
'YAML',
393+
'schemaUri',
394+
ErrorCode.EnumValueMismatch,
395+
{ values: ['valueX', 'valueY'] }
396+
);
397+
const params: CodeActionParams = {
398+
context: CodeActionContext.create([diagnostic]),
399+
range: undefined,
400+
textDocument: TextDocumentIdentifier.create(TEST_URI),
401+
};
402+
const actions = new YamlCodeActions(clientCapabilities);
403+
const result = actions.getCodeAction(doc, params);
404+
expect(result.map((r) => r.title)).deep.equal(['valueX', 'valueY']);
405+
expect(result[0].edit.changes[TEST_URI]).deep.equal([TextEdit.replace(Range.create(0, 5, 0, 11), 'valueX')]);
406+
});
407+
408+
it('should generate proper action for wrong property', () => {
409+
const doc = setupTextDocument('foo: value1');
410+
const diagnostic = createDiagnosticWithData(
411+
'message',
412+
0,
413+
0,
414+
0,
415+
3,
416+
DiagnosticSeverity.Hint,
417+
'YAML',
418+
'schemaUri',
419+
ErrorCode.PropertyExpected,
420+
{
421+
properties: ['fooX', 'fooY'],
422+
}
423+
);
424+
const params: CodeActionParams = {
425+
context: CodeActionContext.create([diagnostic]),
426+
range: undefined,
427+
textDocument: TextDocumentIdentifier.create(TEST_URI),
428+
};
429+
const actions = new YamlCodeActions(clientCapabilities);
430+
const result = actions.getCodeAction(doc, params);
431+
expect(result.map((r) => r.title)).deep.equal(['fooX', 'fooY']);
432+
expect(result[0].edit.changes[TEST_URI]).deep.equal([TextEdit.replace(Range.create(0, 0, 0, 3), 'fooX')]);
433+
});
434+
});
380435
});

0 commit comments

Comments
 (0)