From bd48e62c527fdaac26937e3ddc8681db56c4ab6f Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sun, 10 May 2026 17:19:32 +0100 Subject: [PATCH 1/5] Add Method Hover --- client/src/services/hover.ts | 40 ++++++++++++- client/src/types/context.ts | 19 +++++++ .../java/dtos/context/ContextHistoryDTO.java | 1 + .../src/main/java/dtos/context/MethodDTO.java | 56 +++++++++++++++++++ .../dtos/diagnostics/SourcePositionDTO.java | 8 ++- .../java/utils/ContextHistoryConverter.java | 2 + 6 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 server/src/main/java/dtos/context/MethodDTO.java diff --git a/client/src/services/hover.ts b/client/src/services/hover.ts index 7b460eb..4b79f65 100644 --- a/client/src/services/hover.ts +++ b/client/src/services/hover.ts @@ -1,8 +1,8 @@ import * as vscode from 'vscode'; import { extension } from '../state'; -import type { Range, LJVariable } from '../types/context'; +import type { LJMethod, LJVariable } from '../types/context'; import { getSelectionContextVariables } from './context'; -import { getOriginalVariableName, normalizeFilePath, toRange } from '../utils/utils'; +import { getOriginalVariableName, normalizeFilePath } from '../utils/utils'; /** * Initializes hover provider for LiquidJava diagnostics @@ -15,7 +15,11 @@ export function registerHover() { const variable = getHoveredVariable(document, position); if (variable && variable.mainRefinement && variable.mainRefinement !== 'true') - hoverContent.appendCodeblock(`@Refinement("${variable.mainRefinement}")`, 'java'); + hoverContent.appendCodeblock(formatVariableHover(variable), 'java'); + else { + const method = getHoveredMethod(document, position); + if (method) hoverContent.appendCodeblock(formatMethodHover(method), 'java'); + } const diagnostics = vscode.languages.getDiagnostics(document.uri); const containsDiagnostic = !!diagnostics.find(d => d.range.contains(position) && d.source === 'liquidjava'); @@ -48,3 +52,33 @@ function getHoveredVariable(document: vscode.TextDocument, position: vscode.Posi const { allVars } = getSelectionContextVariables(file, positionAfterVariable); return allVars.find(variable => getOriginalVariableName(variable.name) === hoveredWord); } + +function getHoveredMethod(document: vscode.TextDocument, position: vscode.Position): LJMethod | null { + if (!extension.context) return null; + + const wordRange = document.getWordRangeAtPosition(position, /[A-Za-z_][A-Za-z0-9_]*/); + if (!wordRange) return null; + + const hoveredWord = document.getText(wordRange); + const file = normalizeFilePath(document.uri.fsPath); + return extension.context.methods.find(method => method.name === hoveredWord && (!method.position?.file || method.position.file === file)) + || extension.context.methods.find(method => method.name === hoveredWord) + || null; +} + +function formatVariableHover(variable: LJVariable): string { + return `@Refinement("${variable.mainRefinement}")`; +} + +function formatMethodHover(method: LJMethod): string { + return [ + method.signature, + method.returnRefinement && method.returnRefinement !== 'true' && `@Refinement("${method.returnRefinement}")`, + ...method.parameters + .filter(p => p.mainRefinement && p.mainRefinement !== 'true') + .map(p => `${p.type} ${p.name} @Refinement("${p.mainRefinement}")`), + ...method.stateRefinements + .filter(s => s.from || s.to) + .map(s => `@StateRefinement("${s.from ? `from=${s.from}, ` : ""}` +`${s.to ? `to=${s.to}` : ""}`) + ].filter(Boolean).join('\n'); +} diff --git a/client/src/types/context.ts b/client/src/types/context.ts index 2f2fc2d..e704a76 100644 --- a/client/src/types/context.ts +++ b/client/src/types/context.ts @@ -29,11 +29,30 @@ export type LJAlias = { predicate: string; } +export type LJMethodStateRefinement = { + from: string | null; + to: string | null; + message: string | null; +} + +export type LJMethod = { + name: string; + signature: string; + targetClass: string; + returnType: string; + returnRefinement: string; + parameters: LJVariable[]; + stateRefinements: LJMethodStateRefinement[]; + position: SourcePosition | null; + annotationPosition: SourcePosition | null; +} + export type LJContext = { localVars: LJVariable[]; globalVars: LJVariable[]; ghosts: LJGhost[]; aliases: LJAlias[]; + methods: LJMethod[]; visibleVars: LJVariable[]; // variables visible in the current selection allVars: LJVariable[]; // instance vars + global vars + vars in scope fileScopes: Record; // file -> scopes diff --git a/server/src/main/java/dtos/context/ContextHistoryDTO.java b/server/src/main/java/dtos/context/ContextHistoryDTO.java index 32c656b..19debbd 100644 --- a/server/src/main/java/dtos/context/ContextHistoryDTO.java +++ b/server/src/main/java/dtos/context/ContextHistoryDTO.java @@ -14,6 +14,7 @@ public record ContextHistoryDTO( List globalVars, List ghosts, List aliases, + List methods, Map> fileScopes ) { public static String stringifyType(CtTypeReference typeReference) { diff --git a/server/src/main/java/dtos/context/MethodDTO.java b/server/src/main/java/dtos/context/MethodDTO.java new file mode 100644 index 0000000..5dd114e --- /dev/null +++ b/server/src/main/java/dtos/context/MethodDTO.java @@ -0,0 +1,56 @@ +package dtos.context; + +import java.util.List; +import java.util.stream.Collectors; + +import dtos.diagnostics.SourcePositionDTO; +import liquidjava.processor.context.ObjectState; +import liquidjava.processor.context.PlacementInCode; +import liquidjava.processor.context.RefinedFunction; +import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.ast.formatter.ExpressionFormatter; + +/** + * DTO for serializing RefinedFunction instances to JSON. + */ +public record MethodDTO( + String name, + String signature, + String targetClass, + String returnType, + String returnRefinement, + List parameters, + List stateRefinements, + SourcePositionDTO position, + SourcePositionDTO annotationPosition +) { + public static MethodDTO from(RefinedFunction refinedFunction) { + PlacementInCode placement = refinedFunction.getPlacementInCode(); + if (placement == null) return null; + return new MethodDTO( + refinedFunction.getName(), + refinedFunction.getSignature(), + refinedFunction.getTargetClass(), + ContextHistoryDTO.stringifyType(refinedFunction.getType()), + format(refinedFunction.getRefReturn()), + refinedFunction.getArguments().stream().map(VariableDTO::from).filter(v -> v != null).collect(Collectors.toList()), + refinedFunction.getAllStates().stream().map(StateRefinementDTO::from).collect(Collectors.toList()), + SourcePositionDTO.from(placement.getPosition()), + SourcePositionDTO.from(placement.getAnnotationPosition()) + ); + } + + public record StateRefinementDTO(String from, String to, String message) { + public static StateRefinementDTO from(ObjectState state) { + return new StateRefinementDTO( + state.hasFrom() ? format(state.getFrom()) : null, + state.hasTo() ? format(state.getTo()) : null, + state.getMessage() + ); + } + } + + private static String format(Predicate predicate) { + return predicate == null ? "" : ExpressionFormatter.format(predicate); + } +} diff --git a/server/src/main/java/dtos/diagnostics/SourcePositionDTO.java b/server/src/main/java/dtos/diagnostics/SourcePositionDTO.java index d00595e..ca9e8b6 100644 --- a/server/src/main/java/dtos/diagnostics/SourcePositionDTO.java +++ b/server/src/main/java/dtos/diagnostics/SourcePositionDTO.java @@ -9,8 +9,12 @@ public record SourcePositionDTO(String file, int lineStart, int colStart, int li public static SourcePositionDTO from(SourcePosition pos) { if (pos == null) return null; - String file = pos.getFile() != null ? pos.getFile().getAbsolutePath() : null; - return new SourcePositionDTO(file, pos.getLine() - 1, pos.getColumn() - 1, pos.getEndLine() - 1, pos.getEndColumn()); + try { + String file = pos.getFile() != null ? pos.getFile().getAbsolutePath() : null; + return new SourcePositionDTO(file, pos.getLine() - 1, pos.getColumn() - 1, pos.getEndLine() - 1, pos.getEndColumn()); + } catch (UnsupportedOperationException e) { + return null; + } } public static SourcePositionDTO from(String pos) { diff --git a/server/src/main/java/utils/ContextHistoryConverter.java b/server/src/main/java/utils/ContextHistoryConverter.java index 10858fd..aab0e1b 100644 --- a/server/src/main/java/utils/ContextHistoryConverter.java +++ b/server/src/main/java/utils/ContextHistoryConverter.java @@ -8,6 +8,7 @@ import dtos.context.AliasDTO; import dtos.context.ContextHistoryDTO; +import dtos.context.MethodDTO; import dtos.context.GhostDTO; import dtos.context.VariableDTO; import dtos.diagnostics.SourcePositionDTO; @@ -29,6 +30,7 @@ public static ContextHistoryDTO convertToDTO(ContextHistory contextHistory) { contextHistory.getGlobalVars().stream().map(VariableDTO::from).filter(v -> v != null).collect(Collectors.toList()), contextHistory.getGhosts().stream().map(GhostDTO::from).collect(Collectors.toList()), contextHistory.getAliases().stream().map(AliasDTO::from).collect(Collectors.toList()), + contextHistory.getMethods().stream().map(MethodDTO::from).filter(f -> f != null).collect(Collectors.toList()), parseFileScopes(contextHistory.getFileScopes()) ); } From a4e2160d516b1f28d19db0f485aba8cda7d0eae1 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sun, 10 May 2026 18:09:19 +0100 Subject: [PATCH 2/5] Resolve Hover Methods using Definitions --- client/src/services/context.ts | 1 + client/src/services/definition.ts | 47 ++++++++++++++++++ client/src/services/hover.ts | 48 +++++++++++++++---- client/src/types/context.ts | 6 +-- .../src/main/java/dtos/context/MethodDTO.java | 15 ++---- 5 files changed, 93 insertions(+), 24 deletions(-) create mode 100644 client/src/services/definition.ts diff --git a/client/src/services/context.ts b/client/src/services/context.ts index 215ecb2..cd5d617 100644 --- a/client/src/services/context.ts +++ b/client/src/services/context.ts @@ -5,6 +5,7 @@ import { getOriginalVariableName } from "../utils/utils"; export function handleContext(context: LJContext) { extension.context = context; + extension.logger?.client.info(JSON.stringify(context.methods, null, 2)) if (!extension.file || !extension.currentSelection) return; // update variables based on new context in current selection diff --git a/client/src/services/definition.ts b/client/src/services/definition.ts new file mode 100644 index 0000000..fef7355 --- /dev/null +++ b/client/src/services/definition.ts @@ -0,0 +1,47 @@ +import * as vscode from 'vscode'; +import type { SourcePosition } from '../types/diagnostics'; +import { normalizeFilePath } from '../utils/utils'; + +type Definition = { + uri: vscode.Uri; + range: vscode.Range; +} + +export async function getDefinitions(document: vscode.TextDocument, position: vscode.Position): Promise { + try { + const definitions = await vscode.commands.executeCommand<(vscode.Location | vscode.LocationLink)[]>( + 'vscode.executeDefinitionProvider', + document.uri, + position + ) || []; + return definitions.map(definition => definition instanceof vscode.Location + ? { uri: definition.uri, range: definition.range } + : { uri: definition.targetUri, range: definition.targetSelectionRange || definition.targetRange } + ); + } catch { + return []; + } +} + +export function sourcePositionContains(position: SourcePosition, range: vscode.Range): boolean { + return toVSCodeRange(position).contains(range); +} + +export function definitionMatchesPosition(definition: Definition, position: SourcePosition | null): boolean { + return !!position?.file && + normalizeFilePath(definition.uri.fsPath) === position.file && + rangesOverlap(definition.range, toVSCodeRange(position)); +} + +export function definitionMatchesClass(definition: Definition, targetClass: string): boolean { + const uri = definition.uri.toString(); + return !!targetClass && (uri.includes(targetClass) || uri.includes(targetClass.replace(/\./g, '/'))); +} + +function rangesOverlap(left: vscode.Range, right: vscode.Range): boolean { + return left.contains(right.start) || left.contains(right.end) || right.contains(left.start) || right.contains(left.end); +} + +function toVSCodeRange(range: SourcePosition): vscode.Range { + return new vscode.Range(range.lineStart, range.colStart, range.lineEnd, range.colEnd); +} diff --git a/client/src/services/hover.ts b/client/src/services/hover.ts index 4b79f65..804cd98 100644 --- a/client/src/services/hover.ts +++ b/client/src/services/hover.ts @@ -3,13 +3,14 @@ import { extension } from '../state'; import type { LJMethod, LJVariable } from '../types/context'; import { getSelectionContextVariables } from './context'; import { getOriginalVariableName, normalizeFilePath } from '../utils/utils'; +import { definitionMatchesClass, definitionMatchesPosition, getDefinitions, sourcePositionContains } from './definition'; /** * Initializes hover provider for LiquidJava diagnostics */ export function registerHover() { vscode.languages.registerHoverProvider('java', { - provideHover(document, position) { + async provideHover(document, position) { const hoverContent = new vscode.MarkdownString(); hoverContent.isTrusted = true; @@ -17,7 +18,7 @@ export function registerHover() { if (variable && variable.mainRefinement && variable.mainRefinement !== 'true') hoverContent.appendCodeblock(formatVariableHover(variable), 'java'); else { - const method = getHoveredMethod(document, position); + const method = await getHoveredMethod(document, position); if (method) hoverContent.appendCodeblock(formatMethodHover(method), 'java'); } @@ -53,7 +54,7 @@ function getHoveredVariable(document: vscode.TextDocument, position: vscode.Posi return allVars.find(variable => getOriginalVariableName(variable.name) === hoveredWord); } -function getHoveredMethod(document: vscode.TextDocument, position: vscode.Position): LJMethod | null { +async function getHoveredMethod(document: vscode.TextDocument, position: vscode.Position): Promise { if (!extension.context) return null; const wordRange = document.getWordRangeAtPosition(position, /[A-Za-z_][A-Za-z0-9_]*/); @@ -61,9 +62,41 @@ function getHoveredMethod(document: vscode.TextDocument, position: vscode.Positi const hoveredWord = document.getText(wordRange); const file = normalizeFilePath(document.uri.fsPath); - return extension.context.methods.find(method => method.name === hoveredWord && (!method.position?.file || method.position.file === file)) - || extension.context.methods.find(method => method.name === hoveredWord) - || null; + const methods = extension.context.methods.filter(method => methodNameMatches(method, hoveredWord)); + const declaredMethod = methods.find(method => method.position?.file === file && sourcePositionContains(method.position, wordRange)); + if (declaredMethod) return declaredMethod; + + const definitions = await getDefinitions(document, position); + const resolvedMethod = methods.find(method => definitions.some(definition => + definitionMatchesPosition(definition, method.position) || + definitionMatchesClass(definition, method.targetClass) + )); + if (resolvedMethod) return resolvedMethod; + + const receiver = document.lineAt(wordRange.start.line).text + .slice(0, wordRange.start.character) + .match(/([A-Za-z_][A-Za-z0-9_]*)\s*\.\s*$/)?.[1]; + if (!receiver) return null; + const receiverVariable = [...(extension.context?.globalVars || []), ...(extension.context?.localVars || [])] + .find(variable => + getOriginalVariableName(variable.name) === receiver && + (!variable.position || variable.position.file === file && isBefore(variable.position, wordRange.start)) + ); + if (!receiverVariable) return null; + + return methods.find(method => typeMatchesTargetClass(receiverVariable.type, method.targetClass)) || null; +} + +function methodNameMatches(method: LJMethod, hoveredWord: string): boolean { + return method.name === hoveredWord || method.name.endsWith(`.${hoveredWord}`); +} + +function typeMatchesTargetClass(type: string, targetClass: string): boolean { + return type === targetClass || targetClass.endsWith(`.${type}`) || type.endsWith(`.${targetClass}`); +} + +function isBefore(range: { lineStart: number; colStart: number }, position: vscode.Position): boolean { + return range.lineStart < position.line || range.lineStart === position.line && range.colStart < position.character; } function formatVariableHover(variable: LJVariable): string { @@ -72,13 +105,12 @@ function formatVariableHover(variable: LJVariable): string { function formatMethodHover(method: LJMethod): string { return [ - method.signature, method.returnRefinement && method.returnRefinement !== 'true' && `@Refinement("${method.returnRefinement}")`, ...method.parameters .filter(p => p.mainRefinement && p.mainRefinement !== 'true') .map(p => `${p.type} ${p.name} @Refinement("${p.mainRefinement}")`), ...method.stateRefinements .filter(s => s.from || s.to) - .map(s => `@StateRefinement("${s.from ? `from=${s.from}, ` : ""}` +`${s.to ? `to=${s.to}` : ""}`) + .map(s => `@StateRefinement("${[s.from && `from=${s.from}`, s.to && `to=${s.to}`].filter(Boolean).join(', ')}")`) ].filter(Boolean).join('\n'); } diff --git a/client/src/types/context.ts b/client/src/types/context.ts index e704a76..3c5d4d2 100644 --- a/client/src/types/context.ts +++ b/client/src/types/context.ts @@ -32,19 +32,15 @@ export type LJAlias = { export type LJMethodStateRefinement = { from: string | null; to: string | null; - message: string | null; } export type LJMethod = { name: string; - signature: string; targetClass: string; - returnType: string; returnRefinement: string; parameters: LJVariable[]; stateRefinements: LJMethodStateRefinement[]; position: SourcePosition | null; - annotationPosition: SourcePosition | null; } export type LJContext = { @@ -63,4 +59,4 @@ export type Range = { colStart: number; lineEnd: number; colEnd: number; -} \ No newline at end of file +} diff --git a/server/src/main/java/dtos/context/MethodDTO.java b/server/src/main/java/dtos/context/MethodDTO.java index 5dd114e..bb11662 100644 --- a/server/src/main/java/dtos/context/MethodDTO.java +++ b/server/src/main/java/dtos/context/MethodDTO.java @@ -15,37 +15,30 @@ */ public record MethodDTO( String name, - String signature, String targetClass, - String returnType, String returnRefinement, List parameters, List stateRefinements, - SourcePositionDTO position, - SourcePositionDTO annotationPosition + SourcePositionDTO position ) { public static MethodDTO from(RefinedFunction refinedFunction) { PlacementInCode placement = refinedFunction.getPlacementInCode(); if (placement == null) return null; return new MethodDTO( refinedFunction.getName(), - refinedFunction.getSignature(), refinedFunction.getTargetClass(), - ContextHistoryDTO.stringifyType(refinedFunction.getType()), format(refinedFunction.getRefReturn()), refinedFunction.getArguments().stream().map(VariableDTO::from).filter(v -> v != null).collect(Collectors.toList()), refinedFunction.getAllStates().stream().map(StateRefinementDTO::from).collect(Collectors.toList()), - SourcePositionDTO.from(placement.getPosition()), - SourcePositionDTO.from(placement.getAnnotationPosition()) + SourcePositionDTO.from(placement.getPosition()) ); } - public record StateRefinementDTO(String from, String to, String message) { + public record StateRefinementDTO(String from, String to) { public static StateRefinementDTO from(ObjectState state) { return new StateRefinementDTO( state.hasFrom() ? format(state.getFrom()) : null, - state.hasTo() ? format(state.getTo()) : null, - state.getMessage() + state.hasTo() ? format(state.getTo()) : null ); } } From 640af7bb3ff35a8310a80ffe98403d1991c1fc26 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sun, 10 May 2026 18:25:52 +0100 Subject: [PATCH 3/5] Remove Positions --- client/src/services/definition.ts | 25 ++----------------- client/src/services/hover.ts | 25 +++++++++---------- client/src/types/context.ts | 1 - .../src/main/java/dtos/context/MethodDTO.java | 10 ++------ 4 files changed, 16 insertions(+), 45 deletions(-) diff --git a/client/src/services/definition.ts b/client/src/services/definition.ts index fef7355..e20a3b6 100644 --- a/client/src/services/definition.ts +++ b/client/src/services/definition.ts @@ -1,10 +1,7 @@ import * as vscode from 'vscode'; -import type { SourcePosition } from '../types/diagnostics'; -import { normalizeFilePath } from '../utils/utils'; type Definition = { uri: vscode.Uri; - range: vscode.Range; } export async function getDefinitions(document: vscode.TextDocument, position: vscode.Position): Promise { @@ -15,33 +12,15 @@ export async function getDefinitions(document: vscode.TextDocument, position: vs position ) || []; return definitions.map(definition => definition instanceof vscode.Location - ? { uri: definition.uri, range: definition.range } - : { uri: definition.targetUri, range: definition.targetSelectionRange || definition.targetRange } + ? { uri: definition.uri } + : { uri: definition.targetUri } ); } catch { return []; } } -export function sourcePositionContains(position: SourcePosition, range: vscode.Range): boolean { - return toVSCodeRange(position).contains(range); -} - -export function definitionMatchesPosition(definition: Definition, position: SourcePosition | null): boolean { - return !!position?.file && - normalizeFilePath(definition.uri.fsPath) === position.file && - rangesOverlap(definition.range, toVSCodeRange(position)); -} - export function definitionMatchesClass(definition: Definition, targetClass: string): boolean { const uri = definition.uri.toString(); return !!targetClass && (uri.includes(targetClass) || uri.includes(targetClass.replace(/\./g, '/'))); } - -function rangesOverlap(left: vscode.Range, right: vscode.Range): boolean { - return left.contains(right.start) || left.contains(right.end) || right.contains(left.start) || right.contains(left.end); -} - -function toVSCodeRange(range: SourcePosition): vscode.Range { - return new vscode.Range(range.lineStart, range.colStart, range.lineEnd, range.colEnd); -} diff --git a/client/src/services/hover.ts b/client/src/services/hover.ts index 804cd98..0fe35bb 100644 --- a/client/src/services/hover.ts +++ b/client/src/services/hover.ts @@ -3,7 +3,7 @@ import { extension } from '../state'; import type { LJMethod, LJVariable } from '../types/context'; import { getSelectionContextVariables } from './context'; import { getOriginalVariableName, normalizeFilePath } from '../utils/utils'; -import { definitionMatchesClass, definitionMatchesPosition, getDefinitions, sourcePositionContains } from './definition'; +import { definitionMatchesClass, getDefinitions } from './definition'; /** * Initializes hover provider for LiquidJava diagnostics @@ -16,7 +16,7 @@ export function registerHover() { const variable = getHoveredVariable(document, position); if (variable && variable.mainRefinement && variable.mainRefinement !== 'true') - hoverContent.appendCodeblock(formatVariableHover(variable), 'java'); + hoverContent.appendCodeblock(formatRefinement(variable.mainRefinement), 'java'); else { const method = await getHoveredMethod(document, position); if (method) hoverContent.appendCodeblock(formatMethodHover(method), 'java'); @@ -63,14 +63,9 @@ async function getHoveredMethod(document: vscode.TextDocument, position: vscode. const hoveredWord = document.getText(wordRange); const file = normalizeFilePath(document.uri.fsPath); const methods = extension.context.methods.filter(method => methodNameMatches(method, hoveredWord)); - const declaredMethod = methods.find(method => method.position?.file === file && sourcePositionContains(method.position, wordRange)); - if (declaredMethod) return declaredMethod; const definitions = await getDefinitions(document, position); - const resolvedMethod = methods.find(method => definitions.some(definition => - definitionMatchesPosition(definition, method.position) || - definitionMatchesClass(definition, method.targetClass) - )); + const resolvedMethod = methods.find(method => definitions.some(definition => definitionMatchesClass(definition, method.targetClass))); if (resolvedMethod) return resolvedMethod; const receiver = document.lineAt(wordRange.start.line).text @@ -99,18 +94,22 @@ function isBefore(range: { lineStart: number; colStart: number }, position: vsco return range.lineStart < position.line || range.lineStart === position.line && range.colStart < position.character; } -function formatVariableHover(variable: LJVariable): string { - return `@Refinement("${variable.mainRefinement}")`; +function formatRefinement(refinement: string): string { + return `@Refinement("${refinement}")`; +} + +function formatStateRefinement(from: string | null, to: string | null): string { + return `@StateRefinement("${[from && `from=${from}`, to && `to=${to}`].filter(Boolean).join(', ')}")`; } function formatMethodHover(method: LJMethod): string { return [ - method.returnRefinement && method.returnRefinement !== 'true' && `@Refinement("${method.returnRefinement}")`, + method.returnRefinement && method.returnRefinement !== 'true' && formatRefinement(method.returnRefinement), ...method.parameters .filter(p => p.mainRefinement && p.mainRefinement !== 'true') - .map(p => `${p.type} ${p.name} @Refinement("${p.mainRefinement}")`), + .map(p => `${p.type} ${p.name} ${formatRefinement(p.mainRefinement)}`), ...method.stateRefinements .filter(s => s.from || s.to) - .map(s => `@StateRefinement("${[s.from && `from=${s.from}`, s.to && `to=${s.to}`].filter(Boolean).join(', ')}")`) + .map(s => formatStateRefinement(s.from, s.to)) ].filter(Boolean).join('\n'); } diff --git a/client/src/types/context.ts b/client/src/types/context.ts index 3c5d4d2..5945c15 100644 --- a/client/src/types/context.ts +++ b/client/src/types/context.ts @@ -40,7 +40,6 @@ export type LJMethod = { returnRefinement: string; parameters: LJVariable[]; stateRefinements: LJMethodStateRefinement[]; - position: SourcePosition | null; } export type LJContext = { diff --git a/server/src/main/java/dtos/context/MethodDTO.java b/server/src/main/java/dtos/context/MethodDTO.java index bb11662..cb6d683 100644 --- a/server/src/main/java/dtos/context/MethodDTO.java +++ b/server/src/main/java/dtos/context/MethodDTO.java @@ -3,9 +3,7 @@ import java.util.List; import java.util.stream.Collectors; -import dtos.diagnostics.SourcePositionDTO; import liquidjava.processor.context.ObjectState; -import liquidjava.processor.context.PlacementInCode; import liquidjava.processor.context.RefinedFunction; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.formatter.ExpressionFormatter; @@ -18,19 +16,15 @@ public record MethodDTO( String targetClass, String returnRefinement, List parameters, - List stateRefinements, - SourcePositionDTO position + List stateRefinements ) { public static MethodDTO from(RefinedFunction refinedFunction) { - PlacementInCode placement = refinedFunction.getPlacementInCode(); - if (placement == null) return null; return new MethodDTO( refinedFunction.getName(), refinedFunction.getTargetClass(), format(refinedFunction.getRefReturn()), refinedFunction.getArguments().stream().map(VariableDTO::from).filter(v -> v != null).collect(Collectors.toList()), - refinedFunction.getAllStates().stream().map(StateRefinementDTO::from).collect(Collectors.toList()), - SourcePositionDTO.from(placement.getPosition()) + refinedFunction.getAllStates().stream().map(StateRefinementDTO::from).collect(Collectors.toList()) ); } From 1e476d071f11d9e4968157c3af92c7716b5b7de1 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sun, 10 May 2026 18:35:59 +0100 Subject: [PATCH 4/5] Minor Fix --- client/src/services/hover.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/services/hover.ts b/client/src/services/hover.ts index 0fe35bb..90cea7b 100644 --- a/client/src/services/hover.ts +++ b/client/src/services/hover.ts @@ -107,7 +107,7 @@ function formatMethodHover(method: LJMethod): string { method.returnRefinement && method.returnRefinement !== 'true' && formatRefinement(method.returnRefinement), ...method.parameters .filter(p => p.mainRefinement && p.mainRefinement !== 'true') - .map(p => `${p.type} ${p.name} ${formatRefinement(p.mainRefinement)}`), + .map(p => `${formatRefinement(p.mainRefinement)} ${p.type} ${p.name}`), ...method.stateRefinements .filter(s => s.from || s.to) .map(s => formatStateRefinement(s.from, s.to)) From dbaed9487e0aec0d8f5fd253444711fe01cbe697 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sun, 10 May 2026 18:43:36 +0100 Subject: [PATCH 5/5] Minor Fix --- client/src/services/hover.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/services/hover.ts b/client/src/services/hover.ts index 90cea7b..8137553 100644 --- a/client/src/services/hover.ts +++ b/client/src/services/hover.ts @@ -99,7 +99,7 @@ function formatRefinement(refinement: string): string { } function formatStateRefinement(from: string | null, to: string | null): string { - return `@StateRefinement("${[from && `from=${from}`, to && `to=${to}`].filter(Boolean).join(', ')}")`; + return `@StateRefinement(${[from && `from="${from}"`, to && `to="${to}"`].filter(Boolean).join(', ')})`; } function formatMethodHover(method: LJMethod): string {