diff --git a/client/src/webview/script.spec.ts b/client/src/webview/script.spec.ts
index 45c428e..dbd0e44 100644
--- a/client/src/webview/script.spec.ts
+++ b/client/src/webview/script.spec.ts
@@ -329,6 +329,31 @@ describe('webview script', () => {
expect(root.querySelector('.mermaid')?.textContent).toContain('ljStateDimmed');
});
+ it('counts the errors of the current file on Verification, and its state errors in a quieter badge', () => {
+ showDiagnostics([
+ diagnostic(currentFile, 'Refinement error'),
+ stateError('State refinement error', errorMachine('Counter', 'close'), 4),
+ diagnostic(otherFile, 'Other file error'),
+ ]);
+ const badge = (tab: string) => root.querySelector(`[data-tab="${tab}"] .nav-badge`);
+
+ expect(badge('diagnostics')?.textContent).toBe('2');
+ expect(badge('diagnostics')?.getAttribute('title')).toBe('2 errors');
+ expect(badge('diagnostics')?.classList.contains('nav-badge-error')).toBe(true);
+ expect(badge('fsm')?.textContent).toBe('1');
+ expect(badge('fsm')?.classList.contains('nav-badge-state-error')).toBe(true);
+ expect(badge('context')).toBeNull();
+
+ // the badges stay up to date on the State Machine tab
+ click('[data-tab="fsm"]');
+ receive({ type: 'diagnostics', diagnostics: [stateError('State refinement error', errorMachine('Counter', 'close'), 4)] });
+ expect(badge('diagnostics')?.textContent).toBe('1');
+
+ receive({ type: 'diagnostics', diagnostics: [] });
+ expect(badge('diagnostics')).toBeNull();
+ expect(badge('fsm')).toBeNull();
+ });
+
it('counts only the state errors of the current file, as listed in the Verification tab', () => {
showDiagnostics([
stateError('State refinement error', errorMachine('Counter', 'close'), 4),
diff --git a/client/src/webview/script.ts b/client/src/webview/script.ts
index 843dd1a..608fc18 100644
--- a/client/src/webview/script.ts
+++ b/client/src/webview/script.ts
@@ -9,7 +9,7 @@ import type { Range } from "../types/context";
import type { LJStateMachine } from "../types/fsm";
import type { ExtensionStatus } from "../state";
import { setHintOpen, type NavTab } from "./views/sections";
-import { copyDiagnosticToClipboard, getDisplayDiagnostics, renderDiagnosticsView } from "./views/diagnostics/diagnostics";
+import { copyDiagnosticToClipboard, getCurrentFileErrorCount, getDisplayDiagnostics, renderDiagnosticsView } from "./views/diagnostics/diagnostics";
import type { LJContext } from "../types/context";
import { ContextSectionState, renderContextView } from "./views/context/context";
import type { DiagnosticRevealTarget } from "../types/diagnostics";
@@ -421,11 +421,12 @@ export function getScript(vscode: VSCodeApi, document: Document, window: Window)
}
const stateErrors = getCurrentStateErrors();
+ const badges = { errors: getCurrentFileErrorCount(diagnostics, currentFile), stateErrors: stateErrors.length };
renderedStateMachineView = undefined;
switch (selectedTab) {
case 'diagnostics':
root.innerHTML = diagnostics
- ? renderDiagnosticsView(diagnostics, showAllDiagnostics, currentFile, stateErrors.length)
+ ? renderDiagnosticsView(diagnostics, showAllDiagnostics, currentFile, badges)
: renderLoading();
break;
case 'fsm': {
@@ -439,13 +440,13 @@ export function getScript(vscode: VSCodeApi, document: Document, window: Window)
.map(sm => createMermaidDiagram({ ...sm, errorContext: null }, diagramOrientation, showDiagramConditions))
.join('\n\n');
renderStateMachineView(root, displayedStateMachines, diagrams, diagramOrientation, showDiagramConditions, focusOnError,
- { index: shownIndex, count: stateErrors.length, line: getStateErrorLine(shownError) });
+ { index: shownIndex, count: stateErrors.length, line: getStateErrorLine(shownError) }, badges);
renderedStateMachineView = getStateMachineViewKey();
if (displayedStateMachines.length > 0) renderMermaidDiagram(document, window);
break;
}
case 'context':
- root.innerHTML = renderContextView(context, currentFile, contextSectionState, errorAtCursor, stateErrors.length);
+ root.innerHTML = renderContextView(context, currentFile, contextSectionState, errorAtCursor, badges);
break;
}
}
@@ -511,7 +512,8 @@ export function getScript(vscode: VSCodeApi, document: Document, window: Window)
function getStateMachineViewKey(): string {
const stateErrors = getCurrentStateErrors();
const { error: shownError } = getShownStateError(stateErrors);
- return JSON.stringify([shownError?.stateMachines ?? stateMachines, shownError ? getStateErrorKey(shownError) : null, stateErrors.length]);
+ return JSON.stringify([shownError?.stateMachines ?? stateMachines, shownError ? getStateErrorKey(shownError) : null, stateErrors.length,
+ getCurrentFileErrorCount(diagnostics, currentFile)]);
}
function revealDiagnostic(target: DiagnosticRevealTarget) {
diff --git a/client/src/webview/styles.spec.ts b/client/src/webview/styles.spec.ts
index d25def6..722e3d0 100644
--- a/client/src/webview/styles.spec.ts
+++ b/client/src/webview/styles.spec.ts
@@ -74,17 +74,27 @@ describe('state error styles', () => {
render(state('ljStateDimmed') + ' ${method}
No errors were found by the LiquidJava verifier.
' : ''} ${ @@ -64,6 +64,12 @@ function renderScopeButton(scope: 'file' | 'workspace', label: string, count: nu `; } +// the errors of the current file, as counted by the Current file scope, or of the workspace without a current file +export function getCurrentFileErrorCount(diagnostics: LJDiagnostic[] | undefined, currentFile: string | undefined): number { + const errors = (diagnostics ?? []).filter(d => d.category === 'error'); + return currentFile ? errors.filter(d => d.file?.toLowerCase() === currentFile.toLowerCase() || !d.file).length : errors.length; +} + export function getDisplayDiagnostics(diagnostics: LJDiagnostic[], showAll: boolean, currentFile: string | undefined): LJDiagnostic[] { if (showAll) return diagnostics; return diagnostics.filter(diagnostic => diagnostic.file?.toLowerCase() === currentFile?.toLowerCase() || !diagnostic.file); diff --git a/client/src/webview/views/fsm/fsm.ts b/client/src/webview/views/fsm/fsm.ts index 992bdcd..289da27 100644 --- a/client/src/webview/views/fsm/fsm.ts +++ b/client/src/webview/views/fsm/fsm.ts @@ -2,7 +2,7 @@ import type { LJStateMachine } from "../../../types/fsm"; import { INVALID_STATE_ICON, VALID_STATE_ICON, getRejectedCallText, getStateErrorFocuses, type StateErrorFocus } from "../../diagram-focus"; import { renderCodiconButton } from "../../icons"; import { escapeHtml } from "../../utils"; -import { renderMainHeader } from "../sections"; +import { renderMainHeader, type NavBadges } from "../sections"; /** * The position of the shown state error among the state errors of the current file @@ -22,13 +22,13 @@ const NO_STATE_ERROR_NAVIGATION: StateErrorNavigation = { index: -1, count: 0 }; * @param sms the state machines, one per state set * @param diagrams the Mermaid diagram of each state machine */ -export function renderStateMachineView(root: HTMLElement, sms: LJStateMachine[], diagrams: string[], orientation: "LR" | "TB", showConditions: boolean, focusOnError = false, stateErrors = NO_STATE_ERROR_NAVIGATION) { +export function renderStateMachineView(root: HTMLElement, sms: LJStateMachine[], diagrams: string[], orientation: "LR" | "TB", showConditions: boolean, focusOnError = false, stateErrors = NO_STATE_ERROR_NAVIGATION, badges: NavBadges = { errors: 0, stateErrors: stateErrors.count }) { const previousDiagramContainer = root.querySelector('.diagram-container') as HTMLElement | null; const diagramHeight = previousDiagramContainer?.offsetHeight; - root.innerHTML = renderStateMachineViewHtml(sms, diagrams, orientation, showConditions, focusOnError, stateErrors, diagramHeight); + root.innerHTML = renderStateMachineViewHtml(sms, diagrams, orientation, showConditions, focusOnError, stateErrors, badges, diagramHeight); } -function renderStateMachineViewHtml(sms: LJStateMachine[], diagrams: string[], orientation: "LR" | "TB", showConditions: boolean, focusOnError: boolean, stateErrors: StateErrorNavigation, diagramHeight?: number): string { +function renderStateMachineViewHtml(sms: LJStateMachine[], diagrams: string[], orientation: "LR" | "TB", showConditions: boolean, focusOnError: boolean, stateErrors: StateErrorNavigation, badges: NavBadges, diagramHeight?: number): string { const className = sms[0]?.className; const errorFocuses = getStateErrorFocuses(sms); const hasConditionExpansions = sms.some(sm => @@ -40,7 +40,7 @@ function renderStateMachineViewHtml(sms: LJStateMachine[], diagrams: string[], o return /*html*/`