Skip to content

Commit 7257b89

Browse files
Show every declared-here position and its gloss in the IDE panel
The server sends the verifier's declaration positions and declaration notes (the "where ... means ..." lines) of refinement and state errors, and the Locations section shows a link to every position, with the notes under them. Needs liquidjava-verifier 0.0.38: getDeclarationNotes() comes from liquid-java/liquidjava#517 and getDeclarationPositions() from liquid-java/liquidjava#525. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent a87b171 commit 7257b89

10 files changed

Lines changed: 64 additions & 9 deletions

File tree

‎client/src/types/diagnostics.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ export type RefinementError = BaseDiagnostic & {
7373
customMessage: string;
7474
counterexample: Counterexample;
7575
declarationPosition: SourcePosition | null;
76+
// where each part of the refinement is declared, the first being declarationPosition
77+
declarationPositions: SourcePosition[];
78+
// lines that explain the declaration, shown under its locations
79+
declarationNotes: string[];
7680
}
7781

7882
export type Counterexample = {
@@ -99,6 +103,10 @@ export type StateRefinementError = BaseDiagnostic & {
99103
found: VCSimplificationResult;
100104
customMessage: string;
101105
declarationPosition: SourcePosition | null;
106+
// where each entry state that makes up expected is declared, the first being declarationPosition
107+
declarationPositions: SourcePosition[];
108+
// lines that explain the declaration, shown under its locations
109+
declarationNotes: string[];
102110
// one state machine per state set of the receiver's class
103111
stateMachines: LJStateMachine[];
104112
}

‎client/src/webview/script.spec.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ function stateError(title: string, sm: LJStateMachine, line = position.lineStart
6363
title, message: `${title} message`, hints: [], file, position: errorPosition,
6464
category: 'error', type: 'state-refinement-error', translationTable: {}, expected: 'Open(counter)',
6565
found: { implication: null as never, origin: null, simplification: 'Closed(counter)' },
66-
customMessage: '', declarationPosition: null, stateMachines: [sm],
66+
customMessage: '', declarationPosition: null, declarationPositions: [], declarationNotes: [], stateMachines: [sm],
6767
};
6868
}
6969

‎client/src/webview/state-errors.spec.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ function stateError(line: number, overrides: Partial<StateRefinementError> = {})
2626
found: { implication: null as never, origin: null, simplification: null },
2727
customMessage: '',
2828
declarationPosition: { file: refinementsFile, lineStart: 21, colStart: 29, lineEnd: 21, colEnd: 51 },
29+
declarationPositions: [{ file: refinementsFile, lineStart: 21, colStart: 29, lineEnd: 21, colEnd: 51 }],
30+
declarationNotes: [],
2931
stateMachines: [machine()],
3032
...overrides,
3133
};

‎client/src/webview/styles.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,12 @@ export function getStyles(): string {
377377
.link:hover {
378378
text-decoration: underline;
379379
}
380+
.location-note {
381+
display: block;
382+
padding-left: 2ch;
383+
text-indent: -2ch;
384+
color: var(--vscode-descriptionForeground);
385+
}
380386
.lj-expression,
381387
.lj-expression-code {
382388
font-family: var(--vscode-editor-font-family);

‎client/src/webview/views/diagnostics/diagnostics.spec.ts‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { afterEach, describe, expect, it } from 'vitest';
2-
import type { CustomError, CustomWarning } from '../../../types/diagnostics';
2+
import type { CustomError, CustomWarning, StateRefinementError } from '../../../types/diagnostics';
3+
import { renderLocation } from '../sections';
34
import { renderDiagnosticsView } from './diagnostics';
45

56
const currentFile = 'file:///workspace/Current.java';
@@ -86,6 +87,22 @@ describe('diagnostics view', () => {
8687
expect(document.querySelector('#show-all-button')).toBeNull();
8788
});
8889

90+
it('shows every declaration of a refinement with their notes under them', () => {
91+
const declaredAt = (lineStart: number) =>
92+
({ file: '/workspace/DoorRefinements.java', lineStart, colStart: 29, lineEnd: lineStart, colEnd: 58 });
93+
document.body.innerHTML = renderLocation({
94+
...error(), type: 'state-refinement-error', declarationPosition: declaredAt(12),
95+
declarationPositions: [declaredAt(12), declaredAt(13)],
96+
declarationNotes: ['where closed(this) means doorState(this) == closed', 'where ajar(this) means doorState(this) == ajar'],
97+
} as unknown as StateRefinementError);
98+
99+
expect(Array.from(document.querySelectorAll('pre > *')).map(element => element.textContent)).toEqual([
100+
'DoorRefinements.java:13', 'DoorRefinements.java:14',
101+
'where closed(this) means doorState(this) == closed', 'where ajar(this) means doorState(this) == ajar',
102+
]);
103+
expect(document.querySelector('pre > :last-child')?.className).toBe('location-note');
104+
});
105+
89106
it.each([false, true])('keeps warnings in the passed state (showAll=%s)', showAll => {
90107
document.body.innerHTML = renderDiagnosticsView([warning(), warning(otherFile)], showAll, currentFile);
91108

‎client/src/webview/views/sections.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,19 @@ export const renderLocation = (diagnostic: LJDiagnostic): string => {
8686
const positions: SourcePosition[] = [];
8787
if (diagnostic.position && diagnostic.file) positions.push(diagnostic.position);
8888

89-
const declarationPosition = diagnostic.type === 'refinement-error' || diagnostic.type === 'state-refinement-error'
90-
? diagnostic.declarationPosition
89+
const declaration = diagnostic.type === 'refinement-error' || diagnostic.type === 'state-refinement-error'
90+
? diagnostic
9191
: null;
92-
if (declarationPosition?.file) positions.push(declarationPosition);
92+
const declarationPositions = declaration?.declarationPositions.filter(position => position.file) ?? [];
93+
positions.push(...declarationPositions);
9394

9495
if (positions.length === 0) return "";
9596
const title = positions.length === 1 ? "Location" : "Locations";
9697
const links = positions.map(renderLocationLink).join("\n");
97-
return renderCustomSection(title, /*html*/`<pre>${links}</pre>`);
98+
// the notes explain the declarations, so they are shown only under their links
99+
const notes = declarationPositions.length > 0 ? declaration?.declarationNotes ?? [] : [];
100+
const noteLines = notes.map(note => `<span class="location-note">${escapeHtml(note)}</span>`).join("");
101+
return renderCustomSection(title, /*html*/`<pre>${links}${noteLines}</pre>`);
98102
};
99103

100104
export function renderVariableHighlightButton(variable: LJVariable): string {

‎server/pom.xml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@
167167
<dependency>
168168
<groupId>io.github.liquid-java</groupId>
169169
<artifactId>liquidjava-verifier</artifactId>
170-
<version>0.0.37</version>
170+
<version>0.0.38</version>
171171
</dependency>
172172
<dependency>
173173
<groupId>io.github.liquid-java</groupId>

‎server/src/main/java/dtos/errors/RefinementErrorDTO.java‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package dtos.errors;
22

3+
import java.util.List;
4+
35
import dtos.diagnostics.CounterexampleDTO;
46
import dtos.diagnostics.SourcePositionDTO;
57
import dtos.diagnostics.VCSimplificationResultDTO;
@@ -16,6 +18,10 @@ public class RefinementErrorDTO extends LJErrorDTO {
1618
public final String customMessage;
1719
public final CounterexampleDTO counterexample;
1820
public final SourcePositionDTO declarationPosition;
21+
/** Where each part of the refinement is declared, the first being declarationPosition */
22+
public final List<SourcePositionDTO> declarationPositions;
23+
/** Lines that explain the declaration, such as what the states it names mean */
24+
public final List<String> declarationNotes;
1925

2026
public RefinementErrorDTO(RefinementError error) {
2127
super("refinement-error", error);
@@ -24,6 +30,8 @@ public RefinementErrorDTO(RefinementError error) {
2430
this.customMessage = error.getCustomMessage();
2531
this.counterexample = CounterexampleDTO.from(error.getCounterexample());
2632
this.declarationPosition = SourcePositionDTO.from(error.getDeclarationPosition());
33+
this.declarationPositions = error.getDeclarationPositions().stream().map(SourcePositionDTO::from).toList();
34+
this.declarationNotes = error.getDeclarationNotes();
2735
}
2836

2937
public static RefinementErrorDTO from(RefinementError error) {

‎server/src/main/java/dtos/errors/StateRefinementErrorDTO.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ public class StateRefinementErrorDTO extends LJErrorDTO {
2727
public final VCSimplificationResultDTO found;
2828
public final String customMessage;
2929
public final SourcePositionDTO declarationPosition;
30+
/** Where each part of the refinement is declared, the first being declarationPosition */
31+
public final List<SourcePositionDTO> declarationPositions;
32+
/** Lines that explain the declaration, such as what the states it names mean */
33+
public final List<String> declarationNotes;
3034
public final List<StateMachineDTO> stateMachines;
3135

3236
public StateRefinementErrorDTO(StateRefinementError error) {
@@ -35,6 +39,8 @@ public StateRefinementErrorDTO(StateRefinementError error) {
3539
this.found = VCSimplificationResultDTO.from(error.getFoundSimplification());
3640
this.customMessage = error.getCustomMessage();
3741
this.declarationPosition = SourcePositionDTO.from(error.getDeclarationPosition());
42+
this.declarationPositions = error.getDeclarationPositions().stream().map(SourcePositionDTO::from).toList();
43+
this.declarationNotes = error.getDeclarationNotes();
3844
this.stateMachines = declarationPosition == null || declarationPosition.file() == null ? List.of()
3945
: stateMachines(declarationPosition.file(), specFiles(), error.getContext());
4046
}

‎server/src/test/java/utils/DiagnosticConverterTest.java‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ void preservesRefinementDetailsAndSimplificationHistory() {
159159
assertEquals("true", dto.expected);
160160
assertEquals("expected true", dto.customMessage);
161161
assertEquals(dto.position, dto.declarationPosition);
162+
assertEquals(List.of(dto.position), dto.declarationPositions);
162163
assertEquals("false", dto.found.implication().predicate());
163164
assertEquals("constant folding", dto.found.simplification());
164165
assertEquals("true", dto.found.origin().implication().predicate());
@@ -170,15 +171,18 @@ void preservesRefinementDetailsAndSimplificationHistory() {
170171
@Test
171172
void preservesStateRefinementDetailsWithoutDeclarationFile() {
172173
StateRefinementErrorDTO dto = (StateRefinementErrorDTO) DiagnosticConverter.convertToDTO(
173-
new StateRefinementError(position, null, new Predicate(new LiteralBoolean(false)),
174+
new StateRefinementError(position, List.of(), new Predicate(new LiteralBoolean(false)),
174175
new VCSimplificationResult(new VCImplication(new Predicate())), null, "expected closed",
175-
List.of(), null, null, UnaryOperator.identity()));
176+
List.of(), null, null, UnaryOperator.identity(),
177+
List.of("where closed(this) means doorState(this) == closed")));
176178
assertEquals("error", dto.category);
177179
assertEquals("state-refinement-error", dto.type);
178180
assertEquals("false", dto.expected);
179181
assertEquals("true", dto.found.implication().predicate());
180182
assertEquals("expected closed", dto.customMessage);
181183
assertNull(dto.declarationPosition);
184+
assertEquals(List.of(), dto.declarationPositions);
185+
assertEquals(List.of("where closed(this) means doorState(this) == closed"), dto.declarationNotes);
182186
assertEquals(List.of(), dto.stateMachines);
183187
}
184188

0 commit comments

Comments
 (0)