Skip to content

Commit

Permalink
feat: quickFix for enum, const, property
Browse files Browse the repository at this point in the history
  • Loading branch information
p-spacek committed May 29, 2024
1 parent 7203630 commit 5baa96e
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 7 deletions.
3 changes: 3 additions & 0 deletions src/languageservice/parser/jsonParser07.ts
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,7 @@ function validate(
),
source: getSchemaSource(schema, originalSchema),
schemaUri: getSchemaUri(schema, originalSchema),
data: { values: schema.enum },
});
}
}
Expand All @@ -907,6 +908,7 @@ function validate(
source: getSchemaSource(schema, originalSchema),
schemaUri: getSchemaUri(schema, originalSchema),
problemArgs: [JSON.stringify(schema.const)],
data: { values: [schema.const] },
});
validationResult.enumValueMatch = false;
} else {
Expand Down Expand Up @@ -1385,6 +1387,7 @@ function validate(
length: propertyNode.keyNode.length,
},
severity: DiagnosticSeverity.Warning,
code: ErrorCode.PropertyExpected,
message: schema.errorMessage || localize('DisallowedExtraPropWarning', MSG_PROPERTY_NOT_ALLOWED, propertyName),
source: getSchemaSource(schema, originalSchema),
schemaUri: getSchemaUri(schema, originalSchema),
Expand Down
57 changes: 52 additions & 5 deletions src/languageservice/services/yamlCodeActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ import { FlowStyleRewriter } from '../utils/flow-style-rewriter';
import { ASTNode } from '../jsonASTTypes';
import * as _ from 'lodash';
import { SourceToken } from 'yaml/dist/parse/cst';
import { ErrorCode } from 'vscode-json-languageservice';

interface YamlDiagnosticData {
schemaUri: string[];
values?: string[];
properties?: string[];
}
export class YamlCodeActions {
private indentation = ' ';
Expand All @@ -54,6 +57,7 @@ export class YamlCodeActions {
result.push(...this.getUnusedAnchorsDelete(params.context.diagnostics, document));
result.push(...this.getConvertToBlockStyleActions(params.context.diagnostics, document));
result.push(...this.getKeyOrderActions(params.context.diagnostics, document));
result.push(...this.getQuickFixForPropertyOrValueMismatch(params.context.diagnostics, document));

return result;
}
Expand Down Expand Up @@ -221,7 +225,7 @@ export class YamlCodeActions {
const results: CodeAction[] = [];
for (const diagnostic of diagnostics) {
if (diagnostic.code === 'flowMap' || diagnostic.code === 'flowSeq') {
const node = getNodeforDiagnostic(document, diagnostic);
const node = getNodeForDiagnostic(document, diagnostic);
if (isMap(node.internalNode) || isSeq(node.internalNode)) {
const blockTypeDescription = isMap(node.internalNode) ? 'map' : 'sequence';
const rewriter = new FlowStyleRewriter(this.indentation);
Expand All @@ -242,7 +246,7 @@ export class YamlCodeActions {
const results: CodeAction[] = [];
for (const diagnostic of diagnostics) {
if (diagnostic?.code === 'mapKeyOrder') {
let node = getNodeforDiagnostic(document, diagnostic);
let node = getNodeForDiagnostic(document, diagnostic);
while (node && node.type !== 'object') {
node = node.parent;
}
Expand Down Expand Up @@ -292,8 +296,8 @@ export class YamlCodeActions {
item.value.end.splice(newLineIndex, 1);
}
} else if (item.value?.type === 'block-scalar') {
const nwline = item.value.props.find((p) => p.type === 'newline');
if (!nwline) {
const newline = item.value.props.find((p) => p.type === 'newline');
if (!newline) {
item.value.props.push({ type: 'newline', indent: 0, offset: item.value.offset, source: '\n' } as SourceToken);
}
}
Expand All @@ -312,9 +316,52 @@ export class YamlCodeActions {
}
return results;
}

/**
* Check if diagnostic contains info for quick fix
* Supports Enum/Const/Property mismatch
*/
private getPossibleQuickFixValues(diagnostic: Diagnostic): string[] | undefined {
if (typeof diagnostic.data !== 'object') {
return;
}
if (
diagnostic.code === ErrorCode.EnumValueMismatch &&
'values' in diagnostic.data &&
Array.isArray((diagnostic.data as YamlDiagnosticData).values)
) {
return (diagnostic.data as YamlDiagnosticData).values;
} else if (
diagnostic.code === ErrorCode.PropertyExpected &&
'properties' in diagnostic.data &&
Array.isArray((diagnostic.data as YamlDiagnosticData).properties)
) {
return (diagnostic.data as YamlDiagnosticData).properties;
}
}

private getQuickFixForPropertyOrValueMismatch(diagnostics: Diagnostic[], document: TextDocument): CodeAction[] {
const results: CodeAction[] = [];
for (const diagnostic of diagnostics) {
const values = this.getPossibleQuickFixValues(diagnostic);
if (!values?.length) {
continue;
}
for (const value of values) {
results.push(
CodeAction.create(
value,
createWorkspaceEdit(document.uri, [TextEdit.replace(diagnostic.range, value)]),
CodeActionKind.QuickFix
)
);
}
}
return results;
}
}

function getNodeforDiagnostic(document: TextDocument, diagnostic: Diagnostic): ASTNode {
function getNodeForDiagnostic(document: TextDocument, diagnostic: Diagnostic): ASTNode {
const yamlDocuments = yamlDocumentsCache.getYamlDocument(document);
const startOffset = document.offsetAt(diagnostic.range.start);
const yamlDoc = matchOffsetToDocument(startOffset, yamlDocuments);
Expand Down
5 changes: 4 additions & 1 deletion test/schemaValidation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { KUBERNETES_SCHEMA_URL } from '../src/languageservice/utils/schemaUrls';
import { IProblem } from '../src/languageservice/parser/jsonParser07';
import { JSONSchema } from '../src/languageservice/jsonSchema';
import { TestTelemetry } from './utils/testsTypes';
import { ErrorCode } from 'vscode-json-languageservice';

describe('Validation Tests', () => {
let languageSettingsSetup: ServiceSetup;
Expand Down Expand Up @@ -396,7 +397,8 @@ describe('Validation Tests', () => {
4,
DiagnosticSeverity.Error,
`yaml-schema: file:///${SCHEMA_ID}`,
`file:///${SCHEMA_ID}`
`file:///${SCHEMA_ID}`,
ErrorCode.PropertyExpected
)
);
})
Expand Down Expand Up @@ -1312,6 +1314,7 @@ obj:
DiagnosticSeverity.Error,
'yaml-schema: Drone CI configuration file',
'https://json.schemastore.org/drone',
ErrorCode.PropertyExpected,
{
properties: [
'type',
Expand Down
12 changes: 11 additions & 1 deletion test/utils/verifyError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,19 @@ export function createDiagnosticWithData(
severity: DiagnosticSeverity = 1,
source = 'YAML',
schemaUri: string | string[],
code: string | number = ErrorCode.Undefined,
data: Record<string, unknown> = {}
): Diagnostic {
const diagnostic: Diagnostic = createExpectedError(message, startLine, startCharacter, endLine, endCharacter, severity, source);
const diagnostic: Diagnostic = createExpectedError(
message,
startLine,
startCharacter,
endLine,
endCharacter,
severity,
source,
code
);
diagnostic.data = { schemaUri: typeof schemaUri === 'string' ? [schemaUri] : schemaUri, ...data };
return diagnostic;
}
Expand Down
55 changes: 55 additions & 0 deletions test/yamlCodeActions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { setupTextDocument, TEST_URI } from './utils/testHelper';
import { createDiagnosticWithData, createExpectedError, createUnusedAnchorDiagnostic } from './utils/verifyError';
import { YamlCommands } from '../src/commands';
import { LanguageSettings } from '../src';
import { ErrorCode } from 'vscode-json-languageservice';

const expect = chai.expect;
chai.use(sinonChai);
Expand Down Expand Up @@ -377,4 +378,58 @@ animals: [dog , cat , mouse] `;
]);
});
});

describe('Enum value or property mismatch quick fix', () => {
it('should generate proper action for enum mismatch', () => {
const doc = setupTextDocument('foo: value1');
const diagnostic = createDiagnosticWithData(
'message',
0,
5,
0,
11,
DiagnosticSeverity.Hint,
'YAML',
'schemaUri',
ErrorCode.EnumValueMismatch,
{ values: ['valueX', 'valueY'] }
);
const params: CodeActionParams = {
context: CodeActionContext.create([diagnostic]),
range: undefined,
textDocument: TextDocumentIdentifier.create(TEST_URI),
};
const actions = new YamlCodeActions(clientCapabilities);
const result = actions.getCodeAction(doc, params);
expect(result.map((r) => r.title)).deep.equal(['valueX', 'valueY']);
expect(result[0].edit.changes[TEST_URI]).deep.equal([TextEdit.replace(Range.create(0, 5, 0, 11), 'valueX')]);
});

it('should generate proper action for wrong property', () => {
const doc = setupTextDocument('foo: value1');
const diagnostic = createDiagnosticWithData(
'message',
0,
0,
0,
3,
DiagnosticSeverity.Hint,
'YAML',
'schemaUri',
ErrorCode.PropertyExpected,
{
properties: ['fooX', 'fooY'],
}
);
const params: CodeActionParams = {
context: CodeActionContext.create([diagnostic]),
range: undefined,
textDocument: TextDocumentIdentifier.create(TEST_URI),
};
const actions = new YamlCodeActions(clientCapabilities);
const result = actions.getCodeAction(doc, params);
expect(result.map((r) => r.title)).deep.equal(['fooX', 'fooY']);
expect(result[0].edit.changes[TEST_URI]).deep.equal([TextEdit.replace(Range.create(0, 0, 0, 3), 'fooX')]);
});
});
});

0 comments on commit 5baa96e

Please sign in to comment.