implements PLN-0034 lsp inline hint transport contract
This commit is contained in:
parent
08f28b4a7d
commit
3e8f53dc16
@ -15,4 +15,4 @@
|
||||
{"type":"discussion","id":"DSC-0014","status":"done","ticket":"studio-frontend-owned-semantic-editor-presentation","title":"Definir ownership do schema visual semantico do editor por frontend","created_at":"2026-04-02","updated_at":"2026-04-02","tags":["studio","editor","frontend","presentation","semantic-highlighting","compiler","pbs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0029","file":"discussion/lessons/DSC-0014-studio-frontend-owned-semantic-editor-presentation/LSN-0029-frontend-owned-semantic-presentation-descriptor-and-host-consumption.md","status":"done","created_at":"2026-04-02","updated_at":"2026-04-02"}]}
|
||||
{"type":"discussion","id":"DSC-0015","status":"done","ticket":"pbs-service-facade-reserved-metadata","title":"SDK Service Bodies Calling Builtin/Intrinsic Proxies as Ordinary PBS Code","created_at":"2026-04-03","updated_at":"2026-04-03","tags":["compiler","pbs","sdk","stdlib","lowering","service","intrinsic","sdk-interface"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0030","file":"discussion/lessons/DSC-0015-pbs-service-facade-reserved-metadata/LSN-0030-sdk-service-bodies-over-private-reserved-proxies.md","status":"done","created_at":"2026-04-03","updated_at":"2026-04-03"}]}
|
||||
{"type":"discussion","id":"DSC-0016","status":"open","ticket":"studio-editor-scope-guides-and-brace-anchoring","title":"Scope Guides do Code Editor com ancoragem exata em braces e destaque do escopo ativo","created_at":"2026-04-03","updated_at":"2026-04-03","tags":["studio","editor","scope-guides","braces","semantic-read","frontend-contract"],"agendas":[{"id":"AGD-0017","file":"AGD-0017-studio-editor-scope-guides-and-brace-anchoring.md","status":"accepted","created_at":"2026-04-03","updated_at":"2026-04-03"}],"decisions":[{"id":"DEC-0014","file":"DEC-0014-studio-editor-active-scope-and-structural-anchors.md","status":"accepted","created_at":"2026-04-03","updated_at":"2026-04-03"}],"plans":[{"id":"PLN-0030","file":"PLN-0030-studio-active-container-and-active-scope-gutter-wave-1.md","status":"done","created_at":"2026-04-03","updated_at":"2026-04-03"},{"id":"PLN-0031","file":"PLN-0031-studio-structural-anchor-semantic-surface-specification.md","status":"done","created_at":"2026-04-03","updated_at":"2026-04-03"},{"id":"PLN-0032","file":"PLN-0032-frontend-structural-anchor-payloads-and-anchor-aware-tests.md","status":"done","created_at":"2026-04-03","updated_at":"2026-04-03"}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0017","status":"open","ticket":"studio-editor-inline-type-hints-for-let-bindings","title":"Inline Type Hints for Let Bindings in the Studio Editor","created_at":"2026-04-03","updated_at":"2026-04-03","tags":["studio","editor","inline-hints","inlay-hints","lsp","pbs","type-inference"],"agendas":[{"id":"AGD-0018","file":"AGD-0018-studio-editor-inline-type-hints-for-let-bindings.md","status":"accepted","created_at":"2026-04-03","updated_at":"2026-04-03"}],"decisions":[{"id":"DEC-0015","file":"DEC-0015-studio-editor-inline-type-hints-contract-and-rendering-model.md","status":"accepted","created_at":"2026-04-03","updated_at":"2026-04-03","ref_agenda":"AGD-0018"}],"plans":[{"id":"PLN-0033","file":"PLN-0033-inline-hint-spec-and-contract-propagation.md","status":"done","created_at":"2026-04-03","updated_at":"2026-04-03","ref_decisions":["DEC-0015"]},{"id":"PLN-0034","file":"PLN-0034-lsp-inline-hint-transport-contract.md","status":"open","created_at":"2026-04-03","updated_at":"2026-04-03","ref_decisions":["DEC-0015"]},{"id":"PLN-0035","file":"PLN-0035-pbs-inline-type-hint-payload-production.md","status":"open","created_at":"2026-04-03","updated_at":"2026-04-03","ref_decisions":["DEC-0015"]},{"id":"PLN-0036","file":"PLN-0036-studio-inline-hint-rendering-and-rollout.md","status":"open","created_at":"2026-04-03","updated_at":"2026-04-03","ref_decisions":["DEC-0015"]}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0017","status":"open","ticket":"studio-editor-inline-type-hints-for-let-bindings","title":"Inline Type Hints for Let Bindings in the Studio Editor","created_at":"2026-04-03","updated_at":"2026-04-03","tags":["studio","editor","inline-hints","inlay-hints","lsp","pbs","type-inference"],"agendas":[{"id":"AGD-0018","file":"AGD-0018-studio-editor-inline-type-hints-for-let-bindings.md","status":"accepted","created_at":"2026-04-03","updated_at":"2026-04-03"}],"decisions":[{"id":"DEC-0015","file":"DEC-0015-studio-editor-inline-type-hints-contract-and-rendering-model.md","status":"accepted","created_at":"2026-04-03","updated_at":"2026-04-03","ref_agenda":"AGD-0018"}],"plans":[{"id":"PLN-0033","file":"PLN-0033-inline-hint-spec-and-contract-propagation.md","status":"done","created_at":"2026-04-03","updated_at":"2026-04-03","ref_decisions":["DEC-0015"]},{"id":"PLN-0034","file":"PLN-0034-lsp-inline-hint-transport-contract.md","status":"done","created_at":"2026-04-03","updated_at":"2026-04-03","ref_decisions":["DEC-0015"]},{"id":"PLN-0035","file":"PLN-0035-pbs-inline-type-hint-payload-production.md","status":"open","created_at":"2026-04-03","updated_at":"2026-04-03","ref_decisions":["DEC-0015"]},{"id":"PLN-0036","file":"PLN-0036-studio-inline-hint-rendering-and-rollout.md","status":"open","created_at":"2026-04-03","updated_at":"2026-04-03","ref_decisions":["DEC-0015"]}],"lessons":[]}
|
||||
|
||||
@ -0,0 +1,107 @@
|
||||
---
|
||||
id: PLN-0034
|
||||
ticket: studio-editor-inline-type-hints-for-let-bindings
|
||||
title: LSP inline hint transport contract
|
||||
status: done
|
||||
created: 2026-04-03
|
||||
completed: 2026-04-03
|
||||
tags: [lsp, studio, editor, inline-hints, dto, transport]
|
||||
---
|
||||
|
||||
## Objective
|
||||
|
||||
Implement the LSP transport contract that enforces and delivers frontend-owned inline hint payloads to Studio consumers.
|
||||
|
||||
## Background
|
||||
|
||||
DEC-0015 requires the LSP to fix and transport the inline hint contract while preserving frontend ownership of hint content and anchor semantics. The Studio must consume transported hints mechanically, and valid spans must survive partial degradation.
|
||||
|
||||
Current LSP surfaces carry diagnostics, symbols, structural anchors, and semantic highlighting, but no dedicated inline hint payload.
|
||||
|
||||
## Scope
|
||||
|
||||
### Included
|
||||
- Add a dedicated inline hint DTO/message surface to `prometeu-lsp-api`.
|
||||
- Extend semantic session and analyze-result transport to include inline hint payloads.
|
||||
- Ensure partial valid hints survive transport even when analysis is partially degraded.
|
||||
- Add LSP tests for contract shape and partial preservation.
|
||||
|
||||
### Excluded
|
||||
- Frontend production of hint payloads.
|
||||
- Studio rendering.
|
||||
- Final editor substrate changes.
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1 - Define public DTOs for inline hint payloads
|
||||
|
||||
**What:** Add public API DTOs/messages for inline hint transport.
|
||||
|
||||
**How:** Introduce explicit payload shapes in `prometeu-lsp-api` that cover at minimum:
|
||||
- anchor location or anchor span;
|
||||
- display text;
|
||||
- optional semantic category/styling fields if required by the contract;
|
||||
- enough information for decorative-only host rendering.
|
||||
|
||||
The contract must remain frontend-content-preserving and must not encode Studio-owned hint policy.
|
||||
|
||||
**File(s):**
|
||||
- `prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/dtos/`
|
||||
- `prometeu-lsp/prometeu-lsp-api/src/main/java/p/studio/lsp/messages/`
|
||||
|
||||
### Step 2 - Extend semantic session construction and analyze responses
|
||||
|
||||
**What:** Make `prometeu-lsp-v1` collect and return inline hints in the semantic-read result.
|
||||
|
||||
**How:** Update semantic session building and analyze-result assembly so inline hints are returned alongside diagnostics, symbols, structural anchors, and semantic highlights.
|
||||
|
||||
Transport must preserve valid hint payloads even if some hint production fails locally elsewhere in the same document.
|
||||
|
||||
**File(s):**
|
||||
- `prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspSemanticReadPhase.java`
|
||||
- `prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/LspSemanticAnalyseService.java`
|
||||
- `prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/messages/`
|
||||
|
||||
### Step 3 - Add transport contract tests
|
||||
|
||||
**What:** Lock the LSP contract behavior with focused tests.
|
||||
|
||||
**How:** Add tests that verify:
|
||||
- analyze responses can carry inline hints;
|
||||
- hint payloads preserve anchors and text deterministically;
|
||||
- valid hints survive partial degradation;
|
||||
- no host-owned inference is inserted by the LSP when the frontend does not publish hints.
|
||||
|
||||
**File(s):**
|
||||
- `prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/`
|
||||
- `prometeu-lsp/prometeu-lsp-api/src/test/java/` if DTO/API conformance tests are needed
|
||||
|
||||
## Test Requirements
|
||||
|
||||
### Unit Tests
|
||||
- DTO invariants for inline hint payloads.
|
||||
- Session/result assembly tests for non-empty and empty hint payloads.
|
||||
|
||||
### Integration Tests
|
||||
- Analyze-document tests covering partial valid hints under degraded analysis.
|
||||
|
||||
### Manual Verification
|
||||
- Inspect serialized/analyzed LSP results for a PBS document with inferred `let` types.
|
||||
- Confirm no hint appears when the frontend publishes none.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [x] `prometeu-lsp-api` exposes a dedicated inline hint transport contract.
|
||||
- [x] `prometeu-lsp-v1` returns inline hints through analyze results without inventing content.
|
||||
- [x] Partial valid hints survive transport under degraded analysis.
|
||||
- [x] Tests lock the contract shape and preservation rules.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Accepted decision `DEC-0015-studio-editor-inline-type-hints-contract-and-rendering-model.md`
|
||||
- `PLN-0033-inline-hint-spec-and-contract-propagation.md`
|
||||
|
||||
## Risks
|
||||
|
||||
- DTO under-design may force churn once Studio rendering starts.
|
||||
- Folding inline hints into an existing message surface without clear boundaries may create avoidable API ambiguity.
|
||||
@ -0,0 +1,18 @@
|
||||
package p.studio.lsp.dtos;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record LspInlineHintDTO(
|
||||
LspRangeDTO anchor,
|
||||
String label,
|
||||
String category) {
|
||||
|
||||
public LspInlineHintDTO {
|
||||
anchor = Objects.requireNonNull(anchor, "anchor");
|
||||
label = Objects.requireNonNull(label, "label").trim();
|
||||
category = Objects.requireNonNull(category, "category").trim();
|
||||
if (label.isBlank()) {
|
||||
throw new IllegalArgumentException("label cannot be blank");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ package p.studio.lsp.messages;
|
||||
|
||||
import p.studio.lsp.dtos.LspDiagnosticDTO;
|
||||
import p.studio.lsp.dtos.LspHighlightSpanDTO;
|
||||
import p.studio.lsp.dtos.LspInlineHintDTO;
|
||||
import p.studio.lsp.dtos.LspSemanticPresentationDTO;
|
||||
import p.studio.lsp.dtos.LspSessionStateDTO;
|
||||
import p.studio.lsp.dtos.LspStructuralAnchorDTO;
|
||||
@ -15,6 +16,7 @@ public record LspAnalyzeDocumentResult(
|
||||
LspSemanticPresentationDTO semanticPresentation,
|
||||
List<LspDiagnosticDTO> diagnostics,
|
||||
List<LspHighlightSpanDTO> semanticHighlights,
|
||||
List<LspInlineHintDTO> inlineHints,
|
||||
List<LspSymbolDTO> documentSymbols,
|
||||
List<LspStructuralAnchorDTO> structuralAnchors,
|
||||
List<LspSymbolDTO> workspaceSymbols) {
|
||||
@ -24,6 +26,7 @@ public record LspAnalyzeDocumentResult(
|
||||
Objects.requireNonNull(semanticPresentation, "semanticPresentation");
|
||||
diagnostics = List.copyOf(Objects.requireNonNull(diagnostics, "diagnostics"));
|
||||
semanticHighlights = List.copyOf(Objects.requireNonNull(semanticHighlights, "semanticHighlights"));
|
||||
inlineHints = List.copyOf(Objects.requireNonNull(inlineHints, "inlineHints"));
|
||||
documentSymbols = List.copyOf(Objects.requireNonNull(documentSymbols, "documentSymbols"));
|
||||
structuralAnchors = List.copyOf(Objects.requireNonNull(structuralAnchors, "structuralAnchors"));
|
||||
workspaceSymbols = List.copyOf(Objects.requireNonNull(workspaceSymbols, "workspaceSymbols"));
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
package p.studio.lsp.dtos;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
final class LspInlineHintDTOTest {
|
||||
@Test
|
||||
void trimsLabelAndCategory() {
|
||||
final var hint = new LspInlineHintDTO(new LspRangeDTO(3, 3), " int ", " type ");
|
||||
|
||||
assertEquals("int", hint.label());
|
||||
assertEquals("type", hint.category());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsBlankLabels() {
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
new LspInlineHintDTO(new LspRangeDTO(1, 1), " ", "type"));
|
||||
}
|
||||
}
|
||||
@ -19,6 +19,7 @@ class LspSemanticAnalyseService {
|
||||
session.semanticPresentation(),
|
||||
session.diagnosticsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
||||
session.semanticHighlightsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
||||
session.inlineHintsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
||||
session.documentSymbolsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
||||
session.structuralAnchorsByDocument().getOrDefault(normalizedRequestedDocument, List.of()),
|
||||
session.workspaceSymbols());
|
||||
|
||||
@ -146,6 +146,7 @@ final class LspSemanticReadPhase {
|
||||
Map.of(),
|
||||
Map.of(),
|
||||
Map.of(),
|
||||
Map.of(),
|
||||
List.of(),
|
||||
Map.of(),
|
||||
Map.of());
|
||||
@ -180,6 +181,7 @@ final class LspSemanticReadPhase {
|
||||
semanticPresentation(snapshot.frontendSpec()),
|
||||
diagnosticsByDocument,
|
||||
semanticIndex.semanticHighlightsByDocument(),
|
||||
Map.of(),
|
||||
semanticIndex.documentSymbolsByDocument(),
|
||||
semanticIndex.structuralAnchorsByDocument(),
|
||||
semanticIndex.workspaceSymbols(),
|
||||
|
||||
@ -3,6 +3,7 @@ package p.studio.lsp.messages;
|
||||
import p.studio.compiler.pbs.lexer.PbsToken;
|
||||
import p.studio.lsp.dtos.LspDiagnosticDTO;
|
||||
import p.studio.lsp.dtos.LspHighlightSpanDTO;
|
||||
import p.studio.lsp.dtos.LspInlineHintDTO;
|
||||
import p.studio.lsp.dtos.LspSemanticPresentationDTO;
|
||||
import p.studio.lsp.dtos.LspStructuralAnchorDTO;
|
||||
import p.studio.lsp.dtos.LspSymbolDTO;
|
||||
@ -16,6 +17,7 @@ public record SemanticSession(
|
||||
LspSemanticPresentationDTO semanticPresentation,
|
||||
Map<Path, List<LspDiagnosticDTO>> diagnosticsByDocument,
|
||||
Map<Path, List<LspHighlightSpanDTO>> semanticHighlightsByDocument,
|
||||
Map<Path, List<LspInlineHintDTO>> inlineHintsByDocument,
|
||||
Map<Path, List<LspSymbolDTO>> documentSymbolsByDocument,
|
||||
Map<Path, List<LspStructuralAnchorDTO>> structuralAnchorsByDocument,
|
||||
List<LspSymbolDTO> workspaceSymbols,
|
||||
|
||||
@ -89,6 +89,7 @@ final class LspServiceImplTest {
|
||||
assertEquals("pbs-function", semanticKeyForLexeme(analysis, OVERLAY_SOURCE, "helper"));
|
||||
assertEquals(List.of("/themes/pbs/semantic-highlighting.css"), analysis.semanticPresentation().resources());
|
||||
assertTrue(analysis.semanticPresentation().semanticKeys().contains("pbs-function"));
|
||||
assertTrue(analysis.inlineHints().isEmpty());
|
||||
|
||||
assertTrue(
|
||||
analysis.documentSymbols().stream().anyMatch(symbol -> symbol.name().equals("helper_call")),
|
||||
@ -204,6 +205,22 @@ final class LspServiceImplTest {
|
||||
assertEquals(List.of("pbs-struct", "pbs-struct", "pbs-struct"), semanticKeysForLexeme(analysis, PROJECT_IMPORT_SOURCE, "Color"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void analyzeDocumentReturnsDedicatedInlineHintTransportSurface() throws Exception {
|
||||
final Path projectRoot = createProject();
|
||||
final Path mainFile = projectRoot.resolve("src/main.pbs");
|
||||
Files.writeString(mainFile, "fn main() -> void { let value = 1; }\n");
|
||||
|
||||
final VfsProjectDocument vfs = new FilesystemProjectDocumentVfsFactory().open(projectContext(projectRoot));
|
||||
final LspService service = new LspServiceImpl(
|
||||
new LspProjectContext("Example", "pbs", projectRoot),
|
||||
vfs);
|
||||
|
||||
final var analysis = service.analyzeDocument(new LspAnalyzeDocumentRequest(mainFile));
|
||||
|
||||
assertTrue(analysis.inlineHints().isEmpty(), analysis.toString());
|
||||
}
|
||||
|
||||
private Path createProject() throws Exception {
|
||||
final Path src = Files.createDirectories(tempDir.resolve("src"));
|
||||
Files.writeString(tempDir.resolve("prometeu.json"), """
|
||||
|
||||
@ -38,6 +38,7 @@ final class EditorDocumentHighlightingRouterTest {
|
||||
List.of(new LspHighlightSpanDTO(new LspRangeDTO(0, 2), "pbs-keyword")),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of());
|
||||
|
||||
final EditorDocumentHighlightingResult result = EditorDocumentHighlightingRouter.route(
|
||||
@ -70,6 +71,7 @@ final class EditorDocumentHighlightingRouterTest {
|
||||
List.of(new LspHighlightSpanDTO(new LspRangeDTO(0, 1), "fe-punctuation")),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of());
|
||||
|
||||
final EditorDocumentHighlightingResult result = EditorDocumentHighlightingRouter.route(
|
||||
@ -102,6 +104,7 @@ final class EditorDocumentHighlightingRouterTest {
|
||||
List.of(new LspHighlightSpanDTO(new LspRangeDTO(0, 2), "pbs-keyword")),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of());
|
||||
|
||||
final EditorDocumentHighlightingResult result = EditorDocumentHighlightingRouter.route(
|
||||
@ -136,6 +139,7 @@ final class EditorDocumentHighlightingRouterTest {
|
||||
List.of(new LspHighlightSpanDTO(new LspRangeDTO(16, 20), "pbs-service")),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of());
|
||||
|
||||
final EditorDocumentHighlightingResult result = EditorDocumentHighlightingRouter.route(
|
||||
@ -169,6 +173,7 @@ final class EditorDocumentHighlightingRouterTest {
|
||||
List.of(new LspHighlightSpanDTO(new LspRangeDTO(19, 23), "pbs-service")),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of(),
|
||||
List.of());
|
||||
|
||||
final EditorDocumentHighlightingResult result = EditorDocumentHighlightingRouter.route(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user