diff --git a/src/aspectValidation.ts b/src/aspectValidation.ts index 2f7bf5d..c122d6a 100644 --- a/src/aspectValidation.ts +++ b/src/aspectValidation.ts @@ -14,21 +14,14 @@ import * as vscode from 'vscode'; import type {ExtensionLogger} from './outputChannel'; -export const VALIDATE_DOCUMENT_REQUEST = 'turtle/aspectValidation/validateDocument'; +export const VALIDATE_DOCUMENT_REQUEST = 'turtle/aspectValidation/validateDocumentV2'; export const VALIDATE_DOCUMENT_COMMAND = 'semantic-models.validateDocumentNow'; export const SUPPORTED_DOCUMENT_SCHEMES = ['file', 'untitled']; const STATUS_MESSAGE_TIMEOUT_MS = 5000; export type AspectValidationTrigger = 'manual' | 'save'; -export interface TurtleDiagnostic { - code: string; - message: string; -} - -export interface DiagnosticReport { - diagnostics: Array; -} +export type ValidationResult = 'validation_successful' | 'validation_failed'; export interface RequestClient { sendRequest(method: string, params?: unknown): Thenable; @@ -79,7 +72,7 @@ export class AspectValidationController { async validateDocument( document: Pick | undefined, trigger: AspectValidationTrigger, - ): Promise { + ): Promise { if (!document || document.languageId !== 'turtle' || !SUPPORTED_DOCUMENT_SCHEMES.includes(document.uri.scheme)) { if (trigger === 'manual') { await this.window.showWarningMessage('Open a Turtle file before running aspect validation.'); @@ -89,7 +82,7 @@ export class AspectValidationController { const binding = this.binding; const request = () => - binding.client.sendRequest(VALIDATE_DOCUMENT_REQUEST, { + binding.client.sendRequest(VALIDATE_DOCUMENT_REQUEST, { uri: document.uri.toString(), reason: trigger, }); @@ -101,8 +94,8 @@ export class AspectValidationController { title: string, trigger: AspectValidationTrigger, binding: RequestClientBinding, - request: () => Thenable, - ): Promise { + request: () => Thenable, + ): Promise { try { const result = await this.runWithProgress(title, trigger, request); if (this.binding !== binding) { @@ -124,8 +117,8 @@ export class AspectValidationController { private runWithProgress( title: string, trigger: AspectValidationTrigger, - request: () => Thenable, - ): Promise { + request: () => Thenable, + ): Promise { if (trigger === 'save') { const disposable = this.window.setStatusBarMessage(`${title} in progress...`, STATUS_MESSAGE_TIMEOUT_MS); return Promise.resolve(request()).finally(() => disposable.dispose()); @@ -143,7 +136,7 @@ export class AspectValidationController { ); } - private async showSummary(result: DiagnosticReport, trigger: AspectValidationTrigger): Promise { + private async showSummary(result: ValidationResult, trigger: AspectValidationTrigger): Promise { const summary = this.formatSummary(result); this.outputChannel.info(`[validation] ${summary}`); @@ -152,11 +145,10 @@ export class AspectValidationController { return; } - const hasViolations = (result.diagnostics?.length ?? 0) > 0; - if (hasViolations) { - await this.window.showErrorMessage(summary); - } else { + if (result === 'validation_successful') { await this.window.showInformationMessage(summary); + } else { + await this.window.showErrorMessage(summary); } } @@ -172,12 +164,11 @@ export class AspectValidationController { await this.window.showErrorMessage(summary); } - private formatSummary(result: DiagnosticReport): string { - const violationCount = result.diagnostics?.length ?? 0; - if (violationCount === 0) { + private formatSummary(result: ValidationResult): string { + if (result === 'validation_successful') { return 'Aspect validation completed without issues.'; } - return result.diagnostics.map(x => x.message).join(', '); + return 'Aspect validation failed. See the Problems view for details.'; } private toFailureMessage(error: unknown): string { diff --git a/src/test/aspectValidationController.test.ts b/src/test/aspectValidationController.test.ts index 5d8785f..93875d7 100644 --- a/src/test/aspectValidationController.test.ts +++ b/src/test/aspectValidationController.test.ts @@ -32,13 +32,13 @@ async function withStubbedRegisterCommand(run: () => void | Promise): Prom suite('AspectValidationController', () => { test('ignores a stale successful request after client replacement', async () => { - let resolveRequest: ((value: {diagnostics: []}) => void) | undefined; - const request = new Promise<{diagnostics: []}>(resolve => (resolveRequest = resolve)); + let resolveRequest: ((value: 'validation_successful') => void) | undefined; + const request = new Promise<'validation_successful'>(resolve => (resolveRequest = resolve)); const harness = createValidationControllerHarness({request: async () => request as Promise}); const validation = harness.controller.validateDocument(createValidationDocument('/tmp/model.ttl'), 'manual'); - harness.controller.setClient({sendRequest: async () => ({diagnostics: []}) as R}, 2); - resolveRequest?.({diagnostics: []}); + harness.controller.setClient({sendRequest: async () => ('validation_successful') as R}, 2); + resolveRequest?.('validation_successful'); assert.strictEqual(await validation, undefined); assert.deepStrictEqual(harness.window.infoMessages, []); @@ -52,7 +52,7 @@ suite('AspectValidationController', () => { const harness = createValidationControllerHarness({request: async () => request as Promise}); const validation = harness.controller.validateDocument(createValidationDocument('/tmp/model.ttl'), 'manual'); - harness.controller.setClient({sendRequest: async () => ({diagnostics: []}) as R}, 2); + harness.controller.setClient({sendRequest: async () => ('validation_successful') as R}, 2); rejectRequest?.(new Error('old connection failed')); assert.strictEqual(await validation, undefined); @@ -61,14 +61,35 @@ suite('AspectValidationController', () => { }); test('reports the current successful request', async () => { - const harness = createValidationControllerHarness({response: {diagnostics: []}}); + const harness = createValidationControllerHarness({response: 'validation_successful'}); const result = await harness.controller.validateDocument(createValidationDocument('/tmp/model.ttl'), 'manual'); - assert.deepStrictEqual(result, {diagnostics: []}); + assert.deepStrictEqual(result, 'validation_successful'); assert.deepStrictEqual(harness.window.infoMessages, ['Aspect validation completed without issues.']); assert.strictEqual(harness.sentRequests[0].method, VALIDATE_DOCUMENT_REQUEST); }); + test('reports an Error result and points to the Problems view on manual validation', async () => { + const harness = createValidationControllerHarness({response: 'validation_failed'}); + const result = await harness.controller.validateDocument(createValidationDocument('/tmp/model.ttl'), 'manual'); + + assert.strictEqual(result, 'validation_failed'); + assert.deepStrictEqual(harness.window.errorMessages, ['Aspect validation failed. See the Problems view for details.']); + assert.deepStrictEqual(harness.window.infoMessages, []); + }); + + test('shows the summary in the status bar on save-triggered validation', async () => { + const success = createValidationControllerHarness({response: 'validation_successful'}); + await success.controller.validateDocument(createValidationDocument('/tmp/model.ttl'), 'save'); + assert.ok(success.window.statusMessages.includes('Aspect validation completed without issues.')); + assert.deepStrictEqual(success.window.infoMessages, []); + + const failure = createValidationControllerHarness({response: 'validation_failed'}); + await failure.controller.validateDocument(createValidationDocument('/tmp/model.ttl'), 'save'); + assert.ok(failure.window.statusMessages.includes('Aspect validation failed. See the Problems view for details.')); + assert.deepStrictEqual(failure.window.errorMessages, []); + }); + test('reports the current failed request', async () => { const harness = createValidationControllerHarness({error: new Error('connection failed')}); const result = await harness.controller.validateDocument(createValidationDocument('/tmp/model.ttl'), 'manual'); @@ -78,7 +99,7 @@ suite('AspectValidationController', () => { }); test('does not validate documents with an unsupported scheme', async () => { - const harness = createValidationControllerHarness({response: {diagnostics: []}}); + const harness = createValidationControllerHarness({response: 'validation_successful'}); const virtualDoc = createValidationDocument(vscode.Uri.parse('chat-editing-text-model:/tmp/Model.ttl')); const result = await harness.controller.validateDocument(virtualDoc, 'manual'); @@ -90,12 +111,12 @@ suite('AspectValidationController', () => { }); test('validates untitled documents', async () => { - const harness = createValidationControllerHarness({response: {diagnostics: []}}); + const harness = createValidationControllerHarness({response: 'validation_successful'}); const untitledDoc = createValidationDocument(vscode.Uri.parse('untitled:Untitled-1')); const result = await harness.controller.validateDocument(untitledDoc, 'manual'); - assert.deepStrictEqual(result, {diagnostics: []}); + assert.deepStrictEqual(result, 'validation_successful'); assert.strictEqual(harness.sentRequests.length, 1); }); diff --git a/src/test/validationTestHarness.ts b/src/test/validationTestHarness.ts index f1bfb0e..ea45cb6 100644 --- a/src/test/validationTestHarness.ts +++ b/src/test/validationTestHarness.ts @@ -14,7 +14,7 @@ import * as vscode from 'vscode'; import { AspectValidationController, - DiagnosticReport, + ValidationResult, RequestClient, ValidationOutputChannel, ValidationWindow, @@ -22,7 +22,7 @@ import { import type {ExtensionLogger} from '../outputChannel'; type ValidationHarnessOptions = { - response?: DiagnosticReport; + response?: ValidationResult; error?: Error; request?: (method: string, params?: unknown) => Promise; }; @@ -61,7 +61,7 @@ export function createValidationControllerHarness(options: ValidationHarnessOpti throw options.error; } - return (options.response ?? {valid: true}) as R; + return (options.response ?? 'validation_successful') as R; }, };