Skip to content

Commit 6aa25b3

Browse files
Explain the verifier's generated names on hover and link them to their lines (#199)
Closes #195 Names the verifier gives values of the code, such as `#if_condition`, `#getUndoManager_result` or `#case_condition@74`, now explain themselves on hover in plain words. When their source line is known, the hover gives it and a click goes there. ## What the verifier already sends **No verifier change is needed.** Since 0.0.38 (#196), the error's translation table is keyed by the name as shown, and it holds a position for every generated name in the error: - `#if_condition` → the condition, `#getUndoManager_result` → the call, `#case_condition@74` → its `case`, and so on. - Several values of one kind get distinct names (`#case_condition@71` … `@74`), so they no longer share one entry. The issue's note about this is out of date. - In A1_56, all 11 generated names in the 19 steps are in the table. The Context tab has no translation table. There, the server leaves out generated variables. This PR adds one field, `generatedPlaces` (name as shown → place). The server code is in this repo, so this is still a vscode-only change. ## What the hover says The text is the title of the name, with "at line N" when the line is known and "Click to go to it." when the name is a link. Every title ends with "LiquidJava gives it the name #…". | Name | Hover | |---|---| | `#if_condition` | The condition of the if at line N | | `#loop_condition` | The condition of the loop at line N | | `#ternary_condition` | The condition of the ? : at line N | | `#switch_selector` | The value the switch at line N checks | | `#case_condition` | Whether the case at line N matches | | `#try_branch` | Whether the try block at line N runs to its end, rather than a catch block | | `#catch_branch` | Whether this catch block of the try at line N runs, rather than a later one | | `#array_length` | The length of the array created at line N | | `#new_File` | The new File object created at line N | | `#size_result` | The value returned by the call to size() at line N | | `#count_value` | The value of the field count read at line N | | `#operation_result` | The result of the operation at line N | | `#expression_value` | The value of the expression at line N | **Where the line comes from:** - First, the error's translation table (the Context tab uses `generatedPlaces`). The name is then a link. - Otherwise, the name's line tag (`@74`). The line is shown, but the name is not a link. - Otherwise, the line is left out. **`extended#1`** is not a generated name. It is a line tag with no line, on a program variable, and #196 already explains it ("the 1st value of extended (line unknown)"). ## Where - **Expected.** - **Found steps:** - the binder column: the title of `∀#if_condition` was "Go to source (boolean)"; it is now the explanation, and the click still highlights the source; - the predicates. - **The step diff:** both the changed fragments and the unchanged tokens. - **The counterexample.** - **The Context tab's refinements.** The names have a dotted underline, like the binders, and a pointer cursor when they are links. A link uses the same `location-link` click as the message references from #193. ## Screenshots A1_56 EditCommand, Found at step 7/20, in headless VS Code (openvscode-server) with verifier 0.0.38. **Headless screenshots do not capture native tooltips.** The box under the name is drawn by the screenshot script from the element's real `title` attribute. On main, the same box shows "(no tooltip)" for the predicate and "Go to source (boolean)" for the binder. | | before (main) | after | |---|---|---| | `#getUndoManager_result` in a predicate, dark | ![](https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/195/A1_56-predicate-before-dark.png) | ![](https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/195/A1_56-predicate-after-dark.png) | | `∀#if_condition` binder, dark | ![](https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/195/A1_56-binder-before-dark.png) | ![](https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/195/A1_56-binder-after-dark.png) | | `#getUndoManager_result`, light | ![](https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/195/A1_56-predicate-before-light.png) | ![](https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/195/A1_56-predicate-after-light.png) | | `∀#if_condition` binder, light | ![](https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/195/A1_56-binder-before-light.png) | ![](https://raw.githubusercontent.com/liquid-java/vscode-liquidjava/pr-screenshots/195/A1_56-binder-after-light.png) | There is no Context tab screenshot. In A1_56, the Context tab replaces each generated value by its expression or drops it, so no generated name is left to show. The unit test covers the Context tab. ## Changes - `client/src/webview/generated-names.ts` (new): the kinds and their wording, the hover text, and the rendered name. - `highlighting.ts`: the highlighter takes an optional `places` table (name → place). A generated name, together with its line tag, is rendered by `renderGeneratedName`. - `vc-changes.ts`, `errors.ts`, `counterexample.ts`, `context/*.ts`: pass the table through. `renderBinder` uses the explanation as its title. - Server: `ContextHistoryDTO.generatedPlaces`, filled by `ContextHistoryConverter`. ## Tests - `vc-changes.spec`: one test, which fails on main. It checks: - a binder linked through the table; - a predicate name that is linked, one with no place, and one with only a tag line; - `#ret`, which is left alone; - a binder with only a tag; - the diff path. - `context.spec`: one test, which fails on main: a refinement's name linked through `generatedPlaces`. - `ContextHistoryConverterTest`: `#array_length` of the Door fixture has its place. - Local runs: - `npm run test:unit` (142), `test:typecheck`, `lint`; - `mvn -B package` (45); - `VSCODE_TEST_VERSION=minimum xvfb-run -a npm test` (9 + 1 passing). ## Not in this PR - The headline message (`found … #getUndoManager_result …`) and the editor hover are markdown or plain text with no translation table, so their names are not explained. - `#ret` (a method's return value) is not one of the verifier's generated kinds and is left as is. - A method named `operation` or a field named `expression` would be described as an operation result or expression value, because the verifier names them alike. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 4794132 commit 6aa25b3

15 files changed

Lines changed: 247 additions & 40 deletions

File tree

‎client/src/types/context.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SourcePosition } from "./diagnostics";
1+
import { SourcePosition, TranslationTable } from "./diagnostics";
22

33
// Type definitions used for LiquidJava context information
44

@@ -54,6 +54,7 @@ export type LJContext = {
5454
visibleVars: LJVariable[]; // variables visible in the current selection
5555
allVars: LJVariable[]; // instance vars + global vars + vars in scope
5656
fileScopes: Record<string, Range[]>; // file -> scopes
57+
generatedPlaces: TranslationTable; // where each value the verifier introduces, such as #if_condition, comes from
5758
}
5859

5960
export type Range = {
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import type { PlacementInCode, SourcePosition } from "../types/diagnostics";
2+
import { escapeHtml } from "./utils";
3+
4+
// what a value the verifier names stands for, given where it is (" at line 12", or empty when unknown)
5+
type Description = (at: string) => string;
6+
7+
const NAMED_VALUES = new Map<string, Description>(Object.entries({
8+
if_condition: at => `the condition of the if${at}`,
9+
loop_condition: at => `the condition of the loop${at}`,
10+
ternary_condition: at => `the condition of the ? :${at}`,
11+
switch_selector: at => `the value the switch${at} checks`,
12+
case_condition: at => `whether the case${at} matches`,
13+
try_branch: at => `whether the try block${at} runs to its end, rather than a catch block`,
14+
catch_branch: at => `whether this catch block of the try${at} runs, rather than a later one`,
15+
array_length: at => `the length of the array created${at}`,
16+
operation_result: at => `the result of the operation${at}`,
17+
expression_value: at => `the value of the expression${at}`,
18+
} satisfies Record<string, Description>));
19+
20+
// values named after the code they come from: a call, a new object or a field read
21+
const VALUES_OF_CODE: [RegExp, (code: string) => Description][] = [
22+
[/^new_(\w+)$/, type => at => `the new ${type} object created${at}`],
23+
[/^(\w+)_result$/, method => at => `the value returned by the call to ${method}()${at}`],
24+
[/^(\w+)_value$/, field => at => `the value of the field ${field} read${at}`],
25+
];
26+
27+
function describe(name: string): Description | undefined {
28+
const kind = name.slice(1);
29+
if (!name.startsWith("#") || !kind) return undefined;
30+
const named = NAMED_VALUES.get(kind);
31+
if (named) return named;
32+
for (const [pattern, describeCode] of VALUES_OF_CODE) {
33+
const code = pattern.exec(kind)?.[1];
34+
if (code) return describeCode(code);
35+
}
36+
return undefined;
37+
}
38+
39+
/** Whether a name, written without its line tag, is one the verifier gives a value of the code, such as #if_condition */
40+
export function isGeneratedName(name: string): boolean {
41+
return describe(name) !== undefined;
42+
}
43+
44+
// says in plain words what a value the verifier names stands for, such as "The condition of the if at line 12"
45+
function explain(name: string, line?: number): string {
46+
const description = describe(name)?.(line === undefined ? "" : ` at line ${line}`) ?? "";
47+
return `${description.charAt(0).toUpperCase()}${description.slice(1)}. LiquidJava gives it the name ${name}.`;
48+
}
49+
50+
/**
51+
* The hover text of a name the verifier gives a value of the code: what the value is, and where when it is known
52+
* @param name the name without its line tag
53+
* @param position where the value comes from, if known; it is a link when it has a file
54+
* @param tagLine the line of the name's line tag, used when the position is not known
55+
*/
56+
export function generatedNameTitle(name: string, position?: SourcePosition | null, tagLine?: number): string {
57+
const explanation = explain(name, position ? position.lineStart + 1 : tagLine);
58+
return position?.file ? `${explanation} Click to go to it.` : explanation;
59+
}
60+
61+
/**
62+
* Renders a name the verifier gives a value of the code, explained on hover and, when its place is known, a link to it
63+
* @param name the name without its line tag
64+
* @param content the name as rendered, with its line tag
65+
* @param tagLine the line of its line tag, if it has one
66+
* @param place where the value comes from, if known
67+
*/
68+
export function renderGeneratedName(name: string, content: string, tagLine?: number, place?: PlacementInCode): string {
69+
const position = place?.position;
70+
const title = escapeHtml(generatedNameTitle(name, position, tagLine));
71+
if (!position?.file) return `<span class="lj-generated-name" title="${title}">${content}</span>`;
72+
return /*html*/`<span class="lj-generated-name location-link" title="${title}" data-file="${escapeHtml(position.file)}" data-line="${position.lineStart}" data-column="${position.colStart}">${content}</span>`;
73+
}

‎client/src/webview/highlighting.ts‎

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import * as liquidJavaGrammar from "../../syntaxes/liquidjava.json";
2+
import type { TranslationTable } from "../types/diagnostics";
3+
import { isGeneratedName, renderGeneratedName } from "./generated-names";
24
import { escapeHtml } from "./utils";
35

46
type GrammarPattern = {
@@ -22,6 +24,8 @@ type ScopeColorRule = {
2224
// numbered when values share a line or have none, x@12#2 and x#2
2325
export const LINE_TAG = "@\\d+(?:#\\d+)?\\b|(?<=[\\w)])#\\d+\\b";
2426
const LINE_TAG_SCOPE = "line-tag";
27+
const VARIABLE_SCOPE = "variable.other.liquidjava";
28+
const LINE_TAG_AT_INDEX = new RegExp(LINE_TAG, "y");
2529
// string and character literals, whose text is shown as it is even where it looks like a line tag
2630
export const LITERAL = `"(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'`;
2731
const LINE_TAGS_OUTSIDE_LITERALS = new RegExp(`${LITERAL}|${LINE_TAG}`, "g");
@@ -44,7 +48,7 @@ const expressionPatterns: GrammarPattern[] = [
4448
{ match: "\\bnull\\b", name: "constant.language.null.liquidjava.webview" },
4549
...patterns("literals"),
4650
{ match: "[(){}.,;\\[\\]]", name: "punctuation.separator.liquidjava" },
47-
{ match: "#*[a-zA-Z_](?:[a-zA-Z0-9_]|#(?!\\d))*", name: "variable.other.liquidjava" }
51+
{ match: "#*[a-zA-Z_](?:[a-zA-Z0-9_]|#(?!\\d))*", name: VARIABLE_SCOPE }
4852
];
4953

5054
const highlightRules: HighlightRule[] = expressionPatterns
@@ -117,12 +121,38 @@ export function renderLineTags(text: string): string {
117121
return html + escapeHtml(text.slice(index));
118122
}
119123

124+
/** The line of the line tag a name ends with, x@12 or x@12#2, if it has one */
125+
export function lineOfTag(name: string): number | undefined {
126+
const line = /@(\d+)(?:#\d+)?$/.exec(name)?.[1];
127+
return line === undefined ? undefined : Number(line);
128+
}
129+
120130
/** The name of a variable without its line tag, as it is written in code */
121131
export function withoutLineTag(name: string): string {
122132
return name.replace(new RegExp(`(?:${LINE_TAG})$`), "");
123133
}
124134

125-
function renderHighlightedContent(expression: string): string {
135+
function renderToken(content: string, scope: string): string {
136+
return `<span style="color:${colorForScope(scope)}">${escapeHtml(content)}</span>`;
137+
}
138+
139+
/**
140+
* Renders a name the verifier gives a value of the code, with the line tag after it if any
141+
* @returns the html and the length of the text it renders
142+
*/
143+
function renderGeneratedNameAt(expression: string, index: number, name: string, places?: TranslationTable): [string, number] {
144+
LINE_TAG_AT_INDEX.lastIndex = index + name.length;
145+
const tag = LINE_TAG_AT_INDEX.exec(expression)?.[0] ?? "";
146+
const content = renderToken(name, VARIABLE_SCOPE) + (tag ? renderLineTag(tag, name) : "");
147+
const html = renderGeneratedName(name, content, lineOfTag(tag), places?.[name + tag]);
148+
return [html, name.length + tag.length];
149+
}
150+
151+
/**
152+
* Renders an expression with its tokens colored
153+
* @param places where the variables of the expression come from, by name, such as the translation table of an error
154+
*/
155+
function renderHighlightedContent(expression: string, places?: TranslationTable): string {
126156
let html = "";
127157
let index = 0;
128158

@@ -140,25 +170,31 @@ function renderHighlightedContent(expression: string): string {
140170
const match = matchingRule.regex.exec(expression);
141171
const content = match?.[0] || expression[index] || "";
142172

173+
if (matchingRule.scope === VARIABLE_SCOPE && isGeneratedName(content)) {
174+
const [generated, length] = renderGeneratedNameAt(expression, index, content, places);
175+
html += generated;
176+
index += length;
177+
continue;
178+
}
143179
html += matchingRule.scope === LINE_TAG_SCOPE
144180
? renderLineTag(content, variableBefore(expression, index))
145-
: `<span style="color:${colorForScope(matchingRule.scope)}">${escapeHtml(content)}</span>`;
181+
: renderToken(content, matchingRule.scope);
146182
index += content.length;
147183
}
148184
return html;
149185
}
150186

151-
function renderExpression(expression: string, wrap: boolean): string {
187+
function renderExpression(expression: string, wrap: boolean, places?: TranslationTable): string {
152188
if (!expression) return "";
153189

154-
const content = renderHighlightedContent(expression);
190+
const content = renderHighlightedContent(expression, places);
155191
return wrap
156192
? `<pre class="lj-expression-block"><code class="lj-expression-code">${content}</code></pre>`
157193
: content;
158194
}
159195

160-
export function renderHighlightedExpression(expression: string): string {
161-
return renderExpression(expression, true);
196+
export function renderHighlightedExpression(expression: string, places?: TranslationTable): string {
197+
return renderExpression(expression, true, places);
162198
}
163199

164200
const WEAKER_THAN_AND = ["||", "-->", "?"];
@@ -239,7 +275,7 @@ export function getExpressionLines(expression: string): ExpressionLine[] {
239275
*/
240276
export function renderExpressionLines(
241277
lines: ExpressionLine[],
242-
renderText: (text: string, index: number) => string = renderHighlightedContent,
278+
renderText: (text: string, index: number) => string = text => renderHighlightedContent(text),
243279
trailing = "",
244280
): string {
245281
return lines.map((line, index) => {
@@ -250,14 +286,19 @@ export function renderExpressionLines(
250286

251287
/**
252288
* Renders an expression on the lines of getExpressionLines, in an expression block as renderHighlightedExpression does
289+
* @param places where the variables of the expression come from, by name
253290
*/
254-
export function renderHighlightedLines(expression: string): string {
291+
export function renderHighlightedLines(expression: string, places?: TranslationTable): string {
255292
if (!expression) return "";
256293

257-
const html = renderExpressionLines(getExpressionLines(expression));
294+
const html = renderExpressionLines(getExpressionLines(expression), text => renderHighlightedContent(text, places));
258295
return `<pre class="lj-expression-block"><code class="lj-expression-code">${html}</code></pre>`;
259296
}
260297

261-
export function renderHighlightedInlineExpression(expression: string): string {
262-
return renderExpression(expression, false);
298+
/**
299+
* Renders an expression inline, with its tokens colored
300+
* @param places where the variables of the expression come from, by name
301+
*/
302+
export function renderHighlightedInlineExpression(expression: string, places?: TranslationTable): string {
303+
return renderExpression(expression, false, places);
263304
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ function context(file = currentFile): LJContext {
4747
visibleVars: [variable],
4848
allVars: [variable],
4949
fileScopes: {},
50+
generatedPlaces: {},
5051
};
5152
}
5253

‎client/src/webview/styles.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,18 @@ export function getStyles(): string {
399399
color: var(--vscode-descriptionForeground);
400400
cursor: help;
401401
}
402+
/* a name LiquidJava gives a value of the code, explained on hover */
403+
.lj-generated-name {
404+
text-decoration: underline dotted var(--vscode-descriptionForeground);
405+
text-underline-offset: 2px;
406+
cursor: help;
407+
}
408+
.lj-generated-name.location-link {
409+
cursor: pointer;
410+
}
411+
.lj-generated-name.location-link:hover {
412+
text-decoration-style: solid;
413+
}
402414
.lj-expression,
403415
.lj-expression-code {
404416
font-family: var(--vscode-editor-font-family);

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ function ghost(name: string, file = currentFile, stateSet: string | null = null)
1515
function context(overrides: Partial<LJContext> = {}): LJContext {
1616
return {
1717
localVars: [], globalVars: [], ghosts: [], aliases: [], methods: [],
18-
visibleVars: [], allVars: [], fileScopes: {}, ...overrides,
18+
visibleVars: [], allVars: [], fileScopes: {}, generatedPlaces: {}, ...overrides,
1919
};
2020
}
2121

@@ -50,6 +50,21 @@ describe('context view', () => {
5050
expect(document.querySelectorAll('.context-section-content.collapsed')).toHaveLength(0);
5151
});
5252

53+
it('links the names LiquidJava gives values of the code in refinements to their lines', () => {
54+
document.body.innerHTML = renderContextView(context({
55+
allVars: [{
56+
name: 'a', displayName: 'a', internalName: 'a', type: 'int', refinement: 'a == #pos_result',
57+
mainRefinement: '', position: null, annotationPosition: null,
58+
}],
59+
generatedPlaces: { '#pos_result': { text: 'pos(x)', position: { file: '/w/A.java', lineStart: 6, colStart: 16, lineEnd: 6, colEnd: 22 } } },
60+
}), currentFile, sectionState);
61+
62+
const name = document.querySelector('#context-vars .lj-generated-name.location-link');
63+
expect(name?.getAttribute('title')).toBe(
64+
'The value returned by the call to pos() at line 7. LiquidJava gives it the name #pos_result. Click to go to it.');
65+
expect([name?.getAttribute('data-file'), name?.getAttribute('data-line')]).toEqual(['/w/A.java', '6']);
66+
});
67+
5368
it('honors each section collapse state independently', () => {
5469
document.body.innerHTML = renderContextView(context({ ghosts: [ghost('size')] }), currentFile, {
5570
aliases: false, ghosts: true, vars: false,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function renderContextView(context: LJContext | undefined, currentFile: s
2323
? 'No context information available at the cursor position'
2424
: `${renderContextAliases(aliases, sectionState.aliases)}
2525
${renderContextGhosts(ghosts, sectionState.ghosts)}
26-
${renderContextVariables(allVars, sectionState.vars, errorAtCursor)}
26+
${renderContextVariables(allVars, sectionState.vars, errorAtCursor, context?.generatedPlaces)}
2727
`}
2828
</div>
2929
`;

‎client/src/webview/views/context/variables.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { LJVariable } from "../../../types/context";
2-
import { RefinementMismatchError } from "../../../types/diagnostics";
2+
import { RefinementMismatchError, TranslationTable } from "../../../types/diagnostics";
33
import { renderHighlightedInlineExpression, withoutLineTag } from "../../highlighting";
44
import { escapeHtml, getSimpleName } from "../../utils";
55
import { renderToggleSection, renderVariableHighlightButton, renderDiagnosticRevealButton } from "../sections";
66

7-
export function renderContextVariables(variables: LJVariable[], isExpanded: boolean, errorAtCursor?: RefinementMismatchError): string {
7+
/**
8+
* @param generatedPlaces where each value the verifier introduces comes from, by name
9+
*/
10+
export function renderContextVariables(variables: LJVariable[], isExpanded: boolean, errorAtCursor?: RefinementMismatchError, generatedPlaces?: TranslationTable): string {
811
const relevantNames = new Set(Object.keys(errorAtCursor?.translationTable || {}).map(withoutLineTag));
912
return /*html*/`
1013
<div class="context-section">
@@ -28,7 +31,7 @@ export function renderContextVariables(variables: LJVariable[], isExpanded: bool
2831
return /*html*/`
2932
<tr class="${isRelevant ? 'context-variable-relevant' : ''}">
3033
<td>${renderVariableHighlightButton(variable)}</td>
31-
<td><code>${renderHighlightedInlineExpression(variable.refinement)}</code></td>
34+
<td><code>${renderHighlightedInlineExpression(variable.refinement, generatedPlaces)}</code></td>
3235
</tr>
3336
`;}).join('')}
3437
${errorAtCursor ? renderFailingRefinement(errorAtCursor) : ''}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { Counterexample } from "../../../types/diagnostics";
1+
import type { Counterexample, TranslationTable } from "../../../types/diagnostics";
22
import { renderHighlightedInlineExpression } from "../../highlighting";
33

4-
export function renderCounterexample(counterexample: Counterexample): string {
4+
export function renderCounterexample(counterexample: Counterexample, translationTable?: TranslationTable): string {
55
if (counterexample.assignments.length === 0) return "";
66

77
return /*html*/`
@@ -10,7 +10,7 @@ export function renderCounterexample(counterexample: Counterexample): string {
1010
${counterexample.assignments.map(assignment => /*html*/`
1111
<div class="counterexample-line">
1212
<span class="vc-node">${renderHighlightedInlineExpression(
13-
`${assignment.variable} == ${assignment.value}`
13+
`${assignment.variable} == ${assignment.value}`, translationTable
1414
)}</span>
1515
</div>
1616
`).join("")}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ type ErrorRendererMap = { [E in LJError as E['type']]: (error: E) => string };
3939

4040
const errorContentRenderers: ErrorRendererMap = {
4141
'refinement-error': (e: RefinementError) => /*html*/ `
42-
${renderCustomSection('Expected', renderHighlightedLines(e.expected))}
42+
${renderCustomSection('Expected', renderHighlightedLines(e.expected, e.translationTable))}
4343
${renderCustomSection('Found', renderVCImplication(e, e.found))}
4444
${e.counterexample.assignments.length > 0
45-
? renderCustomSection('Counterexample', renderCounterexample(e.counterexample))
45+
? renderCustomSection('Counterexample', renderCounterexample(e.counterexample, e.translationTable))
4646
: ''}
4747
`,
4848
'state-refinement-error': (e: StateRefinementError) => /*html*/ `
49-
${renderCustomSection('Expected', renderHighlightedLines(e.expected))}
49+
${renderCustomSection('Expected', renderHighlightedLines(e.expected, e.translationTable))}
5050
${renderCustomSection('Found', renderVCImplication(e, e.found))}
5151
`,
5252
'invalid-refinement-error': (e: InvalidRefinementError) => /*html*/ `

0 commit comments

Comments
 (0)