Merge pull request 'dev/multi-frontend-remove-pbs-branches' (#16) from dev/multi-frontend-remove-pbs-branches into master
All checks were successful
JaCoCo Coverage #### Project Overview
No changes detected, that affect the code coverage.
* Line Coverage: 61.73% (17588/28493)
* Branch Coverage: 52.44% (6777/12924)
* Lines of Code: 28493
* Cyclomatic Complexity: 11423
#### Quality Gates Summary
Output truncated.
Test / Build skipped: 15, passed: 616
Intrepid/Prometeu/Studio/pipeline/head This commit looks good
All checks were successful
JaCoCo Coverage #### Project Overview
No changes detected, that affect the code coverage.
* Line Coverage: 61.73% (17588/28493)
* Branch Coverage: 52.44% (6777/12924)
* Lines of Code: 28493
* Cyclomatic Complexity: 11423
#### Quality Gates Summary
Output truncated.
Test / Build skipped: 15, passed: 616
Intrepid/Prometeu/Studio/pipeline/head This commit looks good
Reviewed-on: #16
This commit is contained in:
commit
ffd2975484
@ -1,4 +1,4 @@
|
|||||||
{"type":"meta","next_id":{"DSC":66,"AGD":69,"DEC":42,"PLN":111,"LSN":58,"CLSN":1}}
|
{"type":"meta","next_id":{"DSC":66,"AGD":69,"DEC":43,"PLN":117,"LSN":59,"CLSN":1}}
|
||||||
{"type":"discussion","id":"DSC-0065","status":"open","ticket":"multi-frontend-avoid-premature-abstractions","title":"Evitar abstracoes prematuras na preparacao multi-frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","frontend","architecture","multi-frontend","simplicity"],"agendas":[{"id":"AGD-0068","file":"AGD-0068-multi-frontend-avoid-premature-abstractions.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0065","status":"open","ticket":"multi-frontend-avoid-premature-abstractions","title":"Evitar abstracoes prematuras na preparacao multi-frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","frontend","architecture","multi-frontend","simplicity"],"agendas":[{"id":"AGD-0068","file":"AGD-0068-multi-frontend-avoid-premature-abstractions.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0064","status":"open","ticket":"multi-frontend-architectural-tests","title":"Testes arquiteturais para fronteiras multi-frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","frontend","architecture","tests","multi-frontend"],"agendas":[{"id":"AGD-0067","file":"AGD-0067-multi-frontend-architectural-tests.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0064","status":"open","ticket":"multi-frontend-architectural-tests","title":"Testes arquiteturais para fronteiras multi-frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","frontend","architecture","tests","multi-frontend"],"agendas":[{"id":"AGD-0067","file":"AGD-0067-multi-frontend-architectural-tests.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0063","status":"open","ticket":"multi-frontend-synthetic-test-frontend","title":"Frontend sintetico de teste para provar neutralidade do pipeline","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","frontend","tests","backend","multi-frontend"],"agendas":[{"id":"AGD-0066","file":"AGD-0066-multi-frontend-synthetic-test-frontend.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0063","status":"open","ticket":"multi-frontend-synthetic-test-frontend","title":"Frontend sintetico de teste para provar neutralidade do pipeline","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","frontend","tests","backend","multi-frontend"],"agendas":[{"id":"AGD-0066","file":"AGD-0066-multi-frontend-synthetic-test-frontend.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
@ -8,7 +8,7 @@
|
|||||||
{"type":"discussion","id":"DSC-0059","status":"open","ticket":"multi-frontend-common-lifecycle","title":"Extrair lifecycle comum das responsabilidades do frontend PBS","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","lifecycle","backend","multi-frontend"],"agendas":[{"id":"AGD-0062","file":"AGD-0062-multi-frontend-common-lifecycle.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0059","status":"open","ticket":"multi-frontend-common-lifecycle","title":"Extrair lifecycle comum das responsabilidades do frontend PBS","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","lifecycle","backend","multi-frontend"],"agendas":[{"id":"AGD-0062","file":"AGD-0062-multi-frontend-common-lifecycle.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0058","status":"open","ticket":"multi-frontend-serializable-ir","title":"Manter a IR comum serializavel por design","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","ir","backend","serialization","multi-frontend"],"agendas":[{"id":"AGD-0061","file":"AGD-0061-multi-frontend-serializable-ir.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0058","status":"open","ticket":"multi-frontend-serializable-ir","title":"Manter a IR comum serializavel por design","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","ir","backend","serialization","multi-frontend"],"agendas":[{"id":"AGD-0061","file":"AGD-0061-multi-frontend-serializable-ir.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0057","status":"open","ticket":"multi-frontend-frontend-backend-contract","title":"Estabilizar contrato entre frontend e backend comum","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","ir","backend","multi-frontend"],"agendas":[{"id":"AGD-0060","file":"AGD-0060-multi-frontend-frontend-backend-contract.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0057","status":"open","ticket":"multi-frontend-frontend-backend-contract","title":"Estabilizar contrato entre frontend e backend comum","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","ir","backend","multi-frontend"],"agendas":[{"id":"AGD-0060","file":"AGD-0060-multi-frontend-frontend-backend-contract.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
{"type":"discussion","id":"DSC-0056","status":"open","ticket":"multi-frontend-remove-pbs-branches","title":"Remover verificacoes explicitas de PBS do codigo comum","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","studio","frontend","coupling","multi-frontend"],"agendas":[{"id":"AGD-0059","file":"AGD-0059-multi-frontend-remove-pbs-branches.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0056","status":"done","ticket":"multi-frontend-remove-pbs-branches","title":"Generalizar o contrato LSP/editorial para frontends","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","compiler-pbs","studio","frontend","coupling","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0058","file":"discussion/lessons/DSC-0056-multi-frontend-remove-pbs-branches/LSN-0058-generic-frontend-editorial-contract-for-lsp.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}
|
||||||
{"type":"discussion","id":"DSC-0055","status":"done","ticket":"multi-frontend-compiler-vs-language-services","title":"Separar compilacao de servicos editoriais de frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","lsp","editor","frontend","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0056","file":"discussion/lessons/DSC-0055-multi-frontend-compiler-vs-language-services/LSN-0056-compile-first-frontends-with-optional-editorial-capabilities.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}
|
{"type":"discussion","id":"DSC-0055","status":"done","ticket":"multi-frontend-compiler-vs-language-services","title":"Separar compilacao de servicos editoriais de frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","lsp","editor","frontend","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0056","file":"discussion/lessons/DSC-0055-multi-frontend-compiler-vs-language-services/LSN-0056-compile-first-frontends-with-optional-editorial-capabilities.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}
|
||||||
{"type":"discussion","id":"DSC-0054","status":"done","ticket":"multi-frontend-provider-contract","title":"Introduzir provider completo de frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","frontend","registry","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0055","file":"discussion/lessons/DSC-0054-multi-frontend-provider-contract/LSN-0055-static-frontend-providers-before-plugin-architecture.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}
|
{"type":"discussion","id":"DSC-0054","status":"done","ticket":"multi-frontend-provider-contract","title":"Introduzir provider completo de frontend","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["compiler","compiler-general","studio","frontend","registry","multi-frontend"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0055","file":"discussion/lessons/DSC-0054-multi-frontend-provider-contract/LSN-0055-static-frontend-providers-before-plugin-architecture.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15"}]}
|
||||||
{"type":"discussion","id":"DSC-0053","status":"open","ticket":"pbs-lsp-call-and-type-hierarchy","title":"PBS LSP Call and Type Hierarchy","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","call-hierarchy","type-hierarchy"],"agendas":[{"id":"AGD-0056","file":"AGD-0056-pbs-lsp-call-and-type-hierarchy.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
{"type":"discussion","id":"DSC-0053","status":"open","ticket":"pbs-lsp-call-and-type-hierarchy","title":"PBS LSP Call and Type Hierarchy","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","call-hierarchy","type-hierarchy"],"agendas":[{"id":"AGD-0056","file":"AGD-0056-pbs-lsp-call-and-type-hierarchy.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]}
|
||||||
|
|||||||
@ -0,0 +1,92 @@
|
|||||||
|
---
|
||||||
|
id: LSN-0058
|
||||||
|
ticket: multi-frontend-remove-pbs-branches
|
||||||
|
title: Generic Frontend Editorial Contract for LSP
|
||||||
|
created: 2026-07-15
|
||||||
|
tags: [compiler, compiler-general, compiler-pbs, studio, frontend, lsp, coupling, multi-frontend]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Original Problem
|
||||||
|
|
||||||
|
Prometeu was preparing for multiple compiler frontends while the LSP bridge still knew too much about PBS internals. The first framing was broad: remove explicit PBS checks from common code. After inspection, that was too blunt.
|
||||||
|
|
||||||
|
PBS was already mostly centered in `prometeu-frontend-pbs`. Defaults, templates, `.pbs` fixtures, VS Code language registration, semantic keys, and composition-root bootstrap were legitimate PBS references. The real architectural problem was narrower: common LSP code imported and called PBS-specific editorial types and services for semantic tokens, completion, hover, signature help, and semantic read preparation.
|
||||||
|
|
||||||
|
## Consolidated Decision
|
||||||
|
|
||||||
|
The common LSP/editorial path must consume frontend-provided editorial capabilities through generic contracts exposed by `prometeu-frontend-api`.
|
||||||
|
|
||||||
|
The provider contract remains stable:
|
||||||
|
|
||||||
|
1. `FrontendProvider.compiler()` is mandatory.
|
||||||
|
2. `FrontendProvider.languageService()` is optional.
|
||||||
|
3. Compile-only frontends are valid.
|
||||||
|
4. Unknown `languageId` values fail explicitly.
|
||||||
|
5. PBS may be the product default only when the user has not chosen a language.
|
||||||
|
|
||||||
|
PBS is not the shared editor model. PBS implements the generic contract from inside its frontend boundary.
|
||||||
|
|
||||||
|
## Final Implementation Shape
|
||||||
|
|
||||||
|
The implementation introduced generic frontend editorial DTOs and default methods in `FrontendLanguageService`:
|
||||||
|
|
||||||
|
1. `FrontendDocumentRequest`
|
||||||
|
2. `FrontendEditorialContext`
|
||||||
|
3. `FrontendCompletionCandidate`
|
||||||
|
4. `FrontendHover`
|
||||||
|
5. `FrontendSignatureHelp`
|
||||||
|
6. `FrontendSignature`
|
||||||
|
7. `FrontendSymbolKind`
|
||||||
|
8. `FrontendDocumentation`
|
||||||
|
|
||||||
|
`CompilerLanguageServiceBridge` now resolves the selected provider and calls generic `FrontendLanguageService` methods for semantic tokens, completion, hover, and signature help.
|
||||||
|
|
||||||
|
PBS-specific analysis setup moved behind `PBSFrontendLanguageService.prepareEditorialContext(...)`. The bridge no longer imports `p.studio.compiler.pbs.*`, no longer knows `PbsAst.File`, and no longer calls `PBSFrontendPhaseService.semanticReadSurface(...)` directly.
|
||||||
|
|
||||||
|
An architectural test now protects the common LSP code from reintroducing PBS implementation imports.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
A frontend with no language service remains valid:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public final class CompileOnlyFrontendProvider implements FrontendProvider {
|
||||||
|
@Override
|
||||||
|
public FrontendSpec specification() {
|
||||||
|
return SPEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FrontendPhaseService compiler() {
|
||||||
|
return compiler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
An LSP consumer should resolve the selected provider, ask for `languageService()`, and fall back deterministically when editorial capability is absent.
|
||||||
|
|
||||||
|
PBS can still keep rich internal models such as `PbsEditorialResolvedSymbol`, but those models must be mapped to generic `FrontendHover`, `FrontendCompletionCandidate`, or `FrontendSignatureHelp` before common LSP consumes them.
|
||||||
|
|
||||||
|
## Pitfalls
|
||||||
|
|
||||||
|
Do not remove legitimate PBS defaults just because they mention PBS. PBS remains the product default when the user has not chosen another language.
|
||||||
|
|
||||||
|
Do not use PBS as a fallback for unknown `languageId`. Unknown language identity is a configuration error and must fail explicitly.
|
||||||
|
|
||||||
|
Do not make editor assistance mandatory. Compilation and editor support are separate frontend capabilities.
|
||||||
|
|
||||||
|
Do not move frontend semantic ownership into LSP or Studio. The LSP bridge transports frontend-owned editorial results; it does not author language semantics.
|
||||||
|
|
||||||
|
Do not reintroduce PBS imports in common LSP code for convenience. If a capability needs PBS-specific state, create or extend a frontend-owned generic boundary and keep the PBS mapping inside `prometeu-frontend-pbs`.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- `DEC-0042` locked the generic frontend editorial language-service contract.
|
||||||
|
- `PLN-0111` introduced generic frontend editorial DTOs and defaults.
|
||||||
|
- `PLN-0112` migrated semantic tokens.
|
||||||
|
- `PLN-0113` migrated completion.
|
||||||
|
- `PLN-0114` migrated hover and documentation.
|
||||||
|
- `PLN-0115` migrated signature help.
|
||||||
|
- `PLN-0116` removed PBS imports from common LSP and added boundary coverage.
|
||||||
|
- Related lesson: `LSN-0055` Static frontend providers before plugin architecture.
|
||||||
|
- Related lesson: `LSN-0056` Compile-first frontends with optional editorial capabilities.
|
||||||
@ -1,78 +0,0 @@
|
|||||||
---
|
|
||||||
id: AGD-0059
|
|
||||||
ticket: multi-frontend-remove-pbs-branches
|
|
||||||
title: Remover verificacoes explicitas de PBS do codigo comum
|
|
||||||
status: open
|
|
||||||
created: 2026-07-15
|
|
||||||
resolved:
|
|
||||||
decision:
|
|
||||||
tags: [compiler, compiler-general, compiler-pbs, studio, frontend, coupling, multi-frontend]
|
|
||||||
---
|
|
||||||
|
|
||||||
# Agenda - Remover verificacoes explicitas de PBS do codigo comum
|
|
||||||
|
|
||||||
## Objetivo
|
|
||||||
|
|
||||||
Domain owner: `compiler/general`, com impacto em `studio` e `compiler/pbs`.
|
|
||||||
|
|
||||||
Definir uma auditoria e remocao incremental de branches, instanciacoes e chamadas estaticas PBS em codigo comum.
|
|
||||||
|
|
||||||
## Contexto atual
|
|
||||||
|
|
||||||
O documento de alinhamento aponta ocorrencias como `"pbs"`, `PBSDefinitions`, `PBSFrontendPhaseService`, `PBSFrontend`, `PbsParser`, `PbsSemantic` e `languageId`. A primeira busca no repo confirma ocorrencias espalhadas em docs, testes, frontend PBS, build pipeline e Studio.
|
|
||||||
|
|
||||||
## Escopo
|
|
||||||
|
|
||||||
- Classificar ocorrencias como internas ao PBS, composition root, testes PBS ou referencias indevidas.
|
|
||||||
- Substituir referencias indevidas por provider, spec, capability ou servico registrado.
|
|
||||||
- Manter excecoes explicitas documentadas.
|
|
||||||
|
|
||||||
## Fora de escopo
|
|
||||||
|
|
||||||
- Remover constantes PBS internas do proprio frontend.
|
|
||||||
- Alterar templates explicitamente PBS.
|
|
||||||
- Renomear packages.
|
|
||||||
|
|
||||||
## Arquivos e componentes a inspecionar
|
|
||||||
|
|
||||||
- `prometeu-app/src/main/java/p/studio/AppContainer.java`
|
|
||||||
- `prometeu-studio/src/main/java/p/studio/window/NewProjectWizard.java`
|
|
||||||
- `prometeu-studio/src/main/java/p/studio/projects/...`
|
|
||||||
- `prometeu-compiler/prometeu-build-pipeline/...`
|
|
||||||
- `prometeu-compiler/frontends/prometeu-frontend-pbs/...`
|
|
||||||
- testes que importam `p.studio.compiler.pbs...`
|
|
||||||
|
|
||||||
## Alteracoes propostas
|
|
||||||
|
|
||||||
Opcao A: auditoria textual primeiro, seguida de PRs pequenos por area.
|
|
||||||
|
|
||||||
Opcao B: criar teste arquitetural antes e usar falhas para guiar remocao.
|
|
||||||
|
|
||||||
Recomendacao inicial: fazer auditoria textual e classificar em tabela curta antes de alterar; em seguida adicionar teste arquitetural para impedir regressao.
|
|
||||||
|
|
||||||
## Estrategia de implementacao
|
|
||||||
|
|
||||||
Usar `rg` para localizar termos, confirmar nomes reais, registrar classificacao no plano derivado e migrar apenas uma fronteira por vez.
|
|
||||||
|
|
||||||
## Testes necessarios
|
|
||||||
|
|
||||||
- Teste de dependencia impedindo imports PBS em packages comuns.
|
|
||||||
- Testes de build existentes do PBS.
|
|
||||||
- Testes de Studio/project wizard para garantir templates PBS ainda funcionam.
|
|
||||||
|
|
||||||
## Criterios de aceitacao
|
|
||||||
|
|
||||||
- Codigo comum nao decide comportamento por `"pbs"`.
|
|
||||||
- Excecoes ficam restritas a composition root, testes PBS, templates PBS e frontend PBS.
|
|
||||||
|
|
||||||
## Riscos
|
|
||||||
|
|
||||||
- Remover uma referencia valida do PBS por classificacao apressada.
|
|
||||||
- Quebrar setup de projeto existente ao mover extensoes e templates para provider.
|
|
||||||
|
|
||||||
## Decisoes que devem ser registradas
|
|
||||||
|
|
||||||
- Lista de excecoes permitidas.
|
|
||||||
- Criterio de ownership para cada pacote comum.
|
|
||||||
- Ordem de migracao por area.
|
|
||||||
|
|
||||||
@ -1,13 +1,27 @@
|
|||||||
package p.studio.compiler;
|
package p.studio.compiler;
|
||||||
|
|
||||||
import p.studio.compiler.messages.FESurfaceContext;
|
import p.studio.compiler.messages.FESurfaceContext;
|
||||||
|
import p.studio.compiler.messages.BuildingIssueSink;
|
||||||
|
import p.studio.compiler.messages.FrontendPhaseContext;
|
||||||
import p.studio.compiler.models.FrontendSemanticToken;
|
import p.studio.compiler.models.FrontendSemanticToken;
|
||||||
import p.studio.compiler.pbs.ast.PbsAst;
|
import p.studio.compiler.pbs.ast.PbsAst;
|
||||||
import p.studio.compiler.pbs.semantics.PbsEditorialCompletionCandidate;
|
import p.studio.compiler.pbs.semantics.PbsEditorialCompletionCandidate;
|
||||||
import p.studio.compiler.pbs.semantics.PbsEditorialResolvedSymbol;
|
import p.studio.compiler.pbs.semantics.PbsEditorialResolvedSymbol;
|
||||||
import p.studio.compiler.pbs.semantics.PbsEditorialSignatureHelp;
|
import p.studio.compiler.pbs.semantics.PbsEditorialSignatureHelp;
|
||||||
|
import p.studio.compiler.pbs.semantics.PbsEditorialSymbolKind;
|
||||||
import p.studio.compiler.pbs.semantics.PbsEditorialSupportService;
|
import p.studio.compiler.pbs.semantics.PbsEditorialSupportService;
|
||||||
|
import p.studio.compiler.services.FrontendCompletionCandidate;
|
||||||
|
import p.studio.compiler.services.FrontendDocumentRequest;
|
||||||
|
import p.studio.compiler.services.FrontendDocumentation;
|
||||||
|
import p.studio.compiler.services.FrontendEditorialContext;
|
||||||
|
import p.studio.compiler.services.FrontendHover;
|
||||||
import p.studio.compiler.services.FrontendLanguageService;
|
import p.studio.compiler.services.FrontendLanguageService;
|
||||||
|
import p.studio.compiler.services.FrontendSignature;
|
||||||
|
import p.studio.compiler.services.FrontendSignatureHelp;
|
||||||
|
import p.studio.compiler.services.FrontendSymbolKind;
|
||||||
|
import p.studio.compiler.services.PBSFrontendPhaseService;
|
||||||
|
import p.studio.compiler.source.diagnostics.DiagnosticSink;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
import p.studio.utilities.structures.ReadOnlyList;
|
import p.studio.utilities.structures.ReadOnlyList;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -29,6 +43,27 @@ public final class PBSFrontendLanguageService implements FrontendLanguageService
|
|||||||
this.semanticTokenProvider = Objects.requireNonNull(semanticTokenProvider, "semanticTokenProvider");
|
this.semanticTokenProvider = Objects.requireNonNull(semanticTokenProvider, "semanticTokenProvider");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<FrontendEditorialContext> prepareEditorialContext(
|
||||||
|
final FrontendPhaseContext phaseContext,
|
||||||
|
final FileId fileId) {
|
||||||
|
if (phaseContext == null || fileId == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
final var semanticReadSurface = PBSFrontendPhaseService.semanticReadSurface(
|
||||||
|
phaseContext,
|
||||||
|
DiagnosticSink.empty(),
|
||||||
|
BuildingIssueSink.empty());
|
||||||
|
final var ast = semanticReadSurface.astByFile().get(fileId);
|
||||||
|
if (ast == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(new FrontendEditorialContext(
|
||||||
|
ast,
|
||||||
|
semanticReadSurface.supplementalTopDeclsByFile().getOrDefault(fileId, ReadOnlyList.empty()),
|
||||||
|
phaseContext.feSurfaceContext()));
|
||||||
|
}
|
||||||
|
|
||||||
public List<PbsEditorialCompletionCandidate> completion(
|
public List<PbsEditorialCompletionCandidate> completion(
|
||||||
final String sourceText,
|
final String sourceText,
|
||||||
final PbsAst.File ast,
|
final PbsAst.File ast,
|
||||||
@ -38,6 +73,32 @@ public final class PBSFrontendLanguageService implements FrontendLanguageService
|
|||||||
return editorialSupportService.completion(sourceText, ast, supplementalTopDecls, feSurfaceContext, offset);
|
return editorialSupportService.completion(sourceText, ast, supplementalTopDecls, feSurfaceContext, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<FrontendCompletionCandidate> completion(
|
||||||
|
final FrontendDocumentRequest request,
|
||||||
|
final FrontendEditorialContext editorialContext,
|
||||||
|
final int offset) {
|
||||||
|
if (request == null
|
||||||
|
|| editorialContext == null
|
||||||
|
|| !(editorialContext.syntaxTree() instanceof PbsAst.File ast)
|
||||||
|
|| !(editorialContext.supplementalDeclarations() instanceof ReadOnlyList<?> supplementalTopDecls)
|
||||||
|
|| !(editorialContext.semanticContext() instanceof FESurfaceContext feSurfaceContext)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final ReadOnlyList<PbsAst.TopDecl> typedSupplementalTopDecls =
|
||||||
|
(ReadOnlyList<PbsAst.TopDecl>) supplementalTopDecls;
|
||||||
|
return completion(
|
||||||
|
request.documentText(),
|
||||||
|
ast,
|
||||||
|
typedSupplementalTopDecls,
|
||||||
|
feSurfaceContext,
|
||||||
|
offset).stream()
|
||||||
|
.map(this::toFrontendCompletionCandidate)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<PbsEditorialResolvedSymbol> hover(
|
public Optional<PbsEditorialResolvedSymbol> hover(
|
||||||
final String sourceText,
|
final String sourceText,
|
||||||
final PbsAst.File ast,
|
final PbsAst.File ast,
|
||||||
@ -47,6 +108,30 @@ public final class PBSFrontendLanguageService implements FrontendLanguageService
|
|||||||
return editorialSupportService.hover(sourceText, ast, supplementalTopDecls, feSurfaceContext, offset);
|
return editorialSupportService.hover(sourceText, ast, supplementalTopDecls, feSurfaceContext, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<FrontendHover> hover(
|
||||||
|
final FrontendDocumentRequest request,
|
||||||
|
final FrontendEditorialContext editorialContext,
|
||||||
|
final int offset) {
|
||||||
|
if (request == null
|
||||||
|
|| editorialContext == null
|
||||||
|
|| !(editorialContext.syntaxTree() instanceof PbsAst.File ast)
|
||||||
|
|| !(editorialContext.supplementalDeclarations() instanceof ReadOnlyList<?> supplementalTopDecls)
|
||||||
|
|| !(editorialContext.semanticContext() instanceof FESurfaceContext feSurfaceContext)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final ReadOnlyList<PbsAst.TopDecl> typedSupplementalTopDecls =
|
||||||
|
(ReadOnlyList<PbsAst.TopDecl>) supplementalTopDecls;
|
||||||
|
return hover(
|
||||||
|
request.documentText(),
|
||||||
|
ast,
|
||||||
|
typedSupplementalTopDecls,
|
||||||
|
feSurfaceContext,
|
||||||
|
offset).map(this::toFrontendHover);
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<PbsEditorialSignatureHelp> signatureHelp(
|
public Optional<PbsEditorialSignatureHelp> signatureHelp(
|
||||||
final String sourceText,
|
final String sourceText,
|
||||||
final PbsAst.File ast,
|
final PbsAst.File ast,
|
||||||
@ -56,7 +141,76 @@ public final class PBSFrontendLanguageService implements FrontendLanguageService
|
|||||||
return editorialSupportService.signatureHelp(sourceText, ast, supplementalTopDecls, feSurfaceContext, offset);
|
return editorialSupportService.signatureHelp(sourceText, ast, supplementalTopDecls, feSurfaceContext, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FrontendSignatureHelp signatureHelp(
|
||||||
|
final FrontendDocumentRequest request,
|
||||||
|
final FrontendEditorialContext editorialContext,
|
||||||
|
final int offset) {
|
||||||
|
if (request == null
|
||||||
|
|| editorialContext == null
|
||||||
|
|| !(editorialContext.syntaxTree() instanceof PbsAst.File ast)
|
||||||
|
|| !(editorialContext.supplementalDeclarations() instanceof ReadOnlyList<?> supplementalTopDecls)
|
||||||
|
|| !(editorialContext.semanticContext() instanceof FESurfaceContext feSurfaceContext)) {
|
||||||
|
return FrontendSignatureHelp.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final ReadOnlyList<PbsAst.TopDecl> typedSupplementalTopDecls =
|
||||||
|
(ReadOnlyList<PbsAst.TopDecl>) supplementalTopDecls;
|
||||||
|
return signatureHelp(
|
||||||
|
request.documentText(),
|
||||||
|
ast,
|
||||||
|
typedSupplementalTopDecls,
|
||||||
|
feSurfaceContext,
|
||||||
|
offset)
|
||||||
|
.map(this::toFrontendSignatureHelp)
|
||||||
|
.orElseGet(FrontendSignatureHelp::empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<FrontendSemanticToken> semanticTokens(final FrontendDocumentRequest request) {
|
||||||
|
return semanticTokens(request == null ? "" : request.documentText());
|
||||||
|
}
|
||||||
|
|
||||||
public List<FrontendSemanticToken> semanticTokens(final String documentText) {
|
public List<FrontendSemanticToken> semanticTokens(final String documentText) {
|
||||||
return semanticTokenProvider.tokenize(documentText == null ? "" : documentText);
|
return semanticTokenProvider.tokenize(documentText == null ? "" : documentText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private FrontendCompletionCandidate toFrontendCompletionCandidate(final PbsEditorialCompletionCandidate candidate) {
|
||||||
|
return new FrontendCompletionCandidate(
|
||||||
|
candidate.label(),
|
||||||
|
toFrontendSymbolKind(candidate.kind()),
|
||||||
|
candidate.detail(),
|
||||||
|
candidate.documentation(),
|
||||||
|
candidate.origin());
|
||||||
|
}
|
||||||
|
|
||||||
|
private FrontendHover toFrontendHover(final PbsEditorialResolvedSymbol symbol) {
|
||||||
|
return new FrontendHover(
|
||||||
|
symbol.displayName(),
|
||||||
|
toFrontendSymbolKind(symbol.kind()),
|
||||||
|
symbol.detail(),
|
||||||
|
symbol.origin(),
|
||||||
|
symbol.signatures().stream()
|
||||||
|
.map(this::toFrontendSignature)
|
||||||
|
.toList(),
|
||||||
|
FrontendDocumentation.markdown(symbol.documentation()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private FrontendSignatureHelp toFrontendSignatureHelp(final PbsEditorialSignatureHelp signatureHelp) {
|
||||||
|
return new FrontendSignatureHelp(
|
||||||
|
signatureHelp.signatures().stream()
|
||||||
|
.map(this::toFrontendSignature)
|
||||||
|
.toList(),
|
||||||
|
signatureHelp.activeSignature(),
|
||||||
|
signatureHelp.activeParameter());
|
||||||
|
}
|
||||||
|
|
||||||
|
private FrontendSignature toFrontendSignature(final p.studio.compiler.pbs.semantics.PbsEditorialSignature signature) {
|
||||||
|
return new FrontendSignature(signature.label(), signature.parameterLabels(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private FrontendSymbolKind toFrontendSymbolKind(final PbsEditorialSymbolKind kind) {
|
||||||
|
return FrontendSymbolKind.valueOf(kind.name());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,6 +75,29 @@ class FrontendProviderBoundaryTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void commonLspMustNotImportPbsImplementationPackages() throws IOException {
|
||||||
|
final var repoRoot = findRepoRoot(Path.of("").toAbsolutePath().normalize());
|
||||||
|
final var sourceRoot = repoRoot.resolve("prometeu-lsp/prometeu-lsp-v1/src/main/java");
|
||||||
|
final var prohibitedNeedles = List.of(
|
||||||
|
"p.studio.compiler.PBSFrontendLanguageService",
|
||||||
|
"p.studio.compiler.services.PBSFrontendPhaseService",
|
||||||
|
"p.studio.compiler.pbs",
|
||||||
|
"PbsAst",
|
||||||
|
"PbsEditorial");
|
||||||
|
|
||||||
|
try (final var files = Files.walk(sourceRoot)) {
|
||||||
|
final var violations = files
|
||||||
|
.filter(path -> path.toString().endsWith(".java"))
|
||||||
|
.filter(path -> prohibitedNeedles.stream().anyMatch(needle -> contains(path, needle)))
|
||||||
|
.toList();
|
||||||
|
if (!violations.isEmpty()) {
|
||||||
|
fail("common LSP code must consume generic frontend language-service contracts instead of PBS implementation packages: "
|
||||||
|
+ violations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void compileOnlyFrontendProviderMayOmitLanguageService() {
|
void compileOnlyFrontendProviderMayOmitLanguageService() {
|
||||||
final FrontendProvider provider = new CompileOnlyFrontendProvider();
|
final FrontendProvider provider = new CompileOnlyFrontendProvider();
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
package p.studio.compiler.services;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record FrontendCompletionCandidate(
|
||||||
|
String label,
|
||||||
|
FrontendSymbolKind kind,
|
||||||
|
String detail,
|
||||||
|
String documentation,
|
||||||
|
String origin) {
|
||||||
|
|
||||||
|
public FrontendCompletionCandidate {
|
||||||
|
label = requireText(label, "label");
|
||||||
|
kind = Objects.requireNonNull(kind, "kind");
|
||||||
|
detail = normalize(detail);
|
||||||
|
documentation = normalize(documentation);
|
||||||
|
origin = normalize(origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalize(final String value) {
|
||||||
|
return value == null ? "" : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String requireText(
|
||||||
|
final String value,
|
||||||
|
final String field) {
|
||||||
|
final String candidate = Objects.requireNonNull(value, field).trim();
|
||||||
|
if (candidate.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException(field + " must not be blank");
|
||||||
|
}
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package p.studio.compiler.services;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record FrontendDocumentRequest(
|
||||||
|
Path projectRoot,
|
||||||
|
Path documentPath,
|
||||||
|
String documentText) {
|
||||||
|
|
||||||
|
public FrontendDocumentRequest {
|
||||||
|
projectRoot = normalizePath(projectRoot, "projectRoot");
|
||||||
|
documentPath = normalizePath(documentPath, "documentPath");
|
||||||
|
documentText = documentText == null ? "" : documentText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path normalizePath(
|
||||||
|
final Path path,
|
||||||
|
final String field) {
|
||||||
|
return Objects.requireNonNull(path, field).toAbsolutePath().normalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package p.studio.compiler.services;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record FrontendDocumentation(
|
||||||
|
FrontendDocumentationFormat format,
|
||||||
|
String text) {
|
||||||
|
|
||||||
|
public FrontendDocumentation {
|
||||||
|
format = Objects.requireNonNull(format, "format");
|
||||||
|
text = text == null ? "" : text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBlank() {
|
||||||
|
return text.isBlank();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrontendDocumentation markdown(final String text) {
|
||||||
|
return new FrontendDocumentation(FrontendDocumentationFormat.MARKDOWN, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
package p.studio.compiler.services;
|
||||||
|
|
||||||
|
public enum FrontendDocumentationFormat {
|
||||||
|
MARKDOWN,
|
||||||
|
PLAINTEXT
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
package p.studio.compiler.services;
|
||||||
|
|
||||||
|
public record FrontendEditorialContext(
|
||||||
|
Object syntaxTree,
|
||||||
|
Object supplementalDeclarations,
|
||||||
|
Object semanticContext) {
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package p.studio.compiler.services;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record FrontendHover(
|
||||||
|
String displayName,
|
||||||
|
FrontendSymbolKind kind,
|
||||||
|
String detail,
|
||||||
|
String origin,
|
||||||
|
List<FrontendSignature> signatures,
|
||||||
|
FrontendDocumentation documentation) {
|
||||||
|
|
||||||
|
public FrontendHover {
|
||||||
|
displayName = requireText(displayName, "displayName");
|
||||||
|
kind = Objects.requireNonNull(kind, "kind");
|
||||||
|
detail = normalize(detail);
|
||||||
|
origin = normalize(origin);
|
||||||
|
signatures = signatures == null ? List.of() : List.copyOf(signatures);
|
||||||
|
documentation = documentation == null
|
||||||
|
? FrontendDocumentation.markdown("")
|
||||||
|
: documentation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalize(final String value) {
|
||||||
|
return value == null ? "" : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String requireText(
|
||||||
|
final String value,
|
||||||
|
final String field) {
|
||||||
|
final String candidate = Objects.requireNonNull(value, field).trim();
|
||||||
|
if (candidate.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException(field + " must not be blank");
|
||||||
|
}
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,62 @@
|
|||||||
package p.studio.compiler.services;
|
package p.studio.compiler.services;
|
||||||
|
|
||||||
|
import p.studio.compiler.models.FrontendSemanticToken;
|
||||||
|
import p.studio.compiler.messages.FrontendPhaseContext;
|
||||||
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marker contract for optional frontend-owned editor services.
|
* Optional frontend-owned editor services.
|
||||||
*/
|
*/
|
||||||
public interface FrontendLanguageService {
|
public interface FrontendLanguageService {
|
||||||
|
default Optional<FrontendEditorialContext> prepareEditorialContext(
|
||||||
|
final FrontendPhaseContext phaseContext,
|
||||||
|
final FileId fileId) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
default List<FrontendSemanticToken> semanticTokens(final FrontendDocumentRequest request) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
default List<FrontendCompletionCandidate> completion(
|
||||||
|
final FrontendDocumentRequest request,
|
||||||
|
final int offset) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
default List<FrontendCompletionCandidate> completion(
|
||||||
|
final FrontendDocumentRequest request,
|
||||||
|
final FrontendEditorialContext editorialContext,
|
||||||
|
final int offset) {
|
||||||
|
return completion(request, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
default Optional<FrontendHover> hover(
|
||||||
|
final FrontendDocumentRequest request,
|
||||||
|
final int offset) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
default Optional<FrontendHover> hover(
|
||||||
|
final FrontendDocumentRequest request,
|
||||||
|
final FrontendEditorialContext editorialContext,
|
||||||
|
final int offset) {
|
||||||
|
return hover(request, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
default FrontendSignatureHelp signatureHelp(
|
||||||
|
final FrontendDocumentRequest request,
|
||||||
|
final int offset) {
|
||||||
|
return FrontendSignatureHelp.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
default FrontendSignatureHelp signatureHelp(
|
||||||
|
final FrontendDocumentRequest request,
|
||||||
|
final FrontendEditorialContext editorialContext,
|
||||||
|
final int offset) {
|
||||||
|
return signatureHelp(request, offset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
package p.studio.compiler.services;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record FrontendSignature(
|
||||||
|
String label,
|
||||||
|
List<String> parameterLabels,
|
||||||
|
String documentation) {
|
||||||
|
|
||||||
|
public FrontendSignature {
|
||||||
|
label = requireText(label, "label");
|
||||||
|
parameterLabels = parameterLabels == null ? List.of() : parameterLabels.stream()
|
||||||
|
.map(parameterLabel -> requireText(parameterLabel, "parameterLabel"))
|
||||||
|
.toList();
|
||||||
|
documentation = documentation == null ? "" : documentation.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String requireText(
|
||||||
|
final String value,
|
||||||
|
final String field) {
|
||||||
|
final String candidate = Objects.requireNonNull(value, field).trim();
|
||||||
|
if (candidate.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException(field + " must not be blank");
|
||||||
|
}
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package p.studio.compiler.services;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record FrontendSignatureHelp(
|
||||||
|
List<FrontendSignature> signatures,
|
||||||
|
int activeSignature,
|
||||||
|
int activeParameter) {
|
||||||
|
|
||||||
|
public FrontendSignatureHelp {
|
||||||
|
signatures = signatures == null ? List.of() : List.copyOf(signatures);
|
||||||
|
if (activeSignature < 0) {
|
||||||
|
throw new IllegalArgumentException("activeSignature must be >= 0");
|
||||||
|
}
|
||||||
|
if (activeParameter < 0) {
|
||||||
|
throw new IllegalArgumentException("activeParameter must be >= 0");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FrontendSignatureHelp empty() {
|
||||||
|
return new FrontendSignatureHelp(List.of(), 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package p.studio.compiler.services;
|
||||||
|
|
||||||
|
public enum FrontendSymbolKind {
|
||||||
|
KEYWORD,
|
||||||
|
LOCAL,
|
||||||
|
PARAMETER,
|
||||||
|
FIELD,
|
||||||
|
FUNCTION,
|
||||||
|
METHOD,
|
||||||
|
CONSTRUCTOR,
|
||||||
|
STRUCT,
|
||||||
|
BUILTIN_TYPE,
|
||||||
|
SERVICE,
|
||||||
|
HOST,
|
||||||
|
CONTRACT,
|
||||||
|
CALLBACK,
|
||||||
|
ENUM,
|
||||||
|
ERROR,
|
||||||
|
GLOBAL,
|
||||||
|
CONST
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
package p.studio.compiler.services;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class FrontendLanguageServiceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void defaultLanguageServiceCapabilitiesAreEmpty() {
|
||||||
|
final FrontendLanguageService service = new FrontendLanguageService() {
|
||||||
|
};
|
||||||
|
final FrontendDocumentRequest request = new FrontendDocumentRequest(
|
||||||
|
Path.of("."),
|
||||||
|
Path.of("src/main.pbs"),
|
||||||
|
"fun main() {}");
|
||||||
|
|
||||||
|
assertTrue(service.semanticTokens(request).isEmpty());
|
||||||
|
assertTrue(service.completion(request, 0).isEmpty());
|
||||||
|
assertTrue(service.hover(request, 0).isEmpty());
|
||||||
|
assertEquals(FrontendSignatureHelp.empty(), service.signatureHelp(request, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void completionCandidateNormalizesOptionalText() {
|
||||||
|
final var candidate = new FrontendCompletionCandidate(
|
||||||
|
" draw ",
|
||||||
|
FrontendSymbolKind.FUNCTION,
|
||||||
|
" callable ",
|
||||||
|
" docs ",
|
||||||
|
" stdlib ");
|
||||||
|
|
||||||
|
assertEquals("draw", candidate.label());
|
||||||
|
assertEquals("callable", candidate.detail());
|
||||||
|
assertEquals("docs", candidate.documentation());
|
||||||
|
assertEquals("stdlib", candidate.origin());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void hoverDefaultsToEmptyMarkdownDocumentation() {
|
||||||
|
final var hover = new FrontendHover(
|
||||||
|
"draw",
|
||||||
|
FrontendSymbolKind.FUNCTION,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
List.of(new FrontendSignature("draw(x)", List.of("x"), null)),
|
||||||
|
null);
|
||||||
|
|
||||||
|
assertEquals("draw", hover.displayName());
|
||||||
|
assertTrue(hover.documentation().isBlank());
|
||||||
|
assertEquals(FrontendDocumentationFormat.MARKDOWN, hover.documentation().format());
|
||||||
|
assertEquals(1, hover.signatures().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void signatureHelpRejectsNegativeActiveIndexes() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new FrontendSignatureHelp(List.of(), -1, 0));
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> new FrontendSignatureHelp(List.of(), 0, -1));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,12 +2,16 @@ package p.studio.lsp.services.compiler;
|
|||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import p.studio.compiler.FrontendRegistryService;
|
import p.studio.compiler.FrontendRegistryService;
|
||||||
import p.studio.compiler.PBSFrontendLanguageService;
|
|
||||||
import p.studio.compiler.messages.*;
|
import p.studio.compiler.messages.*;
|
||||||
import p.studio.compiler.models.*;
|
import p.studio.compiler.models.*;
|
||||||
import p.studio.compiler.pbs.semantics.*;
|
import p.studio.compiler.services.FrontendCompletionCandidate;
|
||||||
|
import p.studio.compiler.services.FrontendDocumentRequest;
|
||||||
|
import p.studio.compiler.services.FrontendEditorialContext;
|
||||||
|
import p.studio.compiler.services.FrontendHover;
|
||||||
import p.studio.compiler.services.FrontendProvider;
|
import p.studio.compiler.services.FrontendProvider;
|
||||||
import p.studio.compiler.services.PBSFrontendPhaseService;
|
import p.studio.compiler.services.FrontendSignature;
|
||||||
|
import p.studio.compiler.services.FrontendSignatureHelp;
|
||||||
|
import p.studio.compiler.services.FrontendSymbolKind;
|
||||||
import p.studio.compiler.source.identifiers.FileId;
|
import p.studio.compiler.source.identifiers.FileId;
|
||||||
import p.studio.compiler.utilities.SourceProviderFactory;
|
import p.studio.compiler.utilities.SourceProviderFactory;
|
||||||
import p.studio.compiler.workspaces.AssetSurfaceContextLoader;
|
import p.studio.compiler.workspaces.AssetSurfaceContextLoader;
|
||||||
@ -15,7 +19,6 @@ import p.studio.compiler.workspaces.BuilderPipelineService;
|
|||||||
import p.studio.lsp.messages.*;
|
import p.studio.lsp.messages.*;
|
||||||
import p.studio.lsp.services.LanguageServiceBridge;
|
import p.studio.lsp.services.LanguageServiceBridge;
|
||||||
import p.studio.utilities.logs.LogAggregator;
|
import p.studio.utilities.logs.LogAggregator;
|
||||||
import p.studio.utilities.structures.ReadOnlyList;
|
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -72,18 +75,25 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
final int line,
|
final int line,
|
||||||
final int character) {
|
final int character) {
|
||||||
Objects.requireNonNull(context, "context");
|
Objects.requireNonNull(context, "context");
|
||||||
final Optional<PBSFrontendLanguageService> languageService = pbsLanguageService(context);
|
final var provider = frontendProvider(context);
|
||||||
|
final var languageService = provider.languageService();
|
||||||
if (languageService.isEmpty()) {
|
if (languageService.isEmpty()) {
|
||||||
return new BaselineCompletion(false, List.of());
|
return new BaselineCompletion(false, List.of());
|
||||||
}
|
}
|
||||||
return editorialDocument(context, documentUri, text)
|
return editorialDocument(context, documentUri, text)
|
||||||
.map(document -> {
|
.map(document -> {
|
||||||
final int offset = new DocumentPositionMapper(document.text()).byteOffsetOf(line, character);
|
final int offset = new DocumentPositionMapper(document.text()).byteOffsetOf(line, character);
|
||||||
final List<PbsEditorialCompletionCandidate> candidates = languageService.orElseThrow().completion(
|
final FrontendDocumentRequest request = new FrontendDocumentRequest(
|
||||||
document.text(),
|
context.projectRoot(),
|
||||||
document.ast(),
|
normalizeDocumentPath(documentUri),
|
||||||
document.supplementalTopDecls(),
|
document.text());
|
||||||
document.feSurfaceContext(),
|
final FrontendEditorialContext editorialContext = new FrontendEditorialContext(
|
||||||
|
document.editorialContext().syntaxTree(),
|
||||||
|
document.editorialContext().supplementalDeclarations(),
|
||||||
|
document.editorialContext().semanticContext());
|
||||||
|
final List<FrontendCompletionCandidate> candidates = languageService.orElseThrow().completion(
|
||||||
|
request,
|
||||||
|
editorialContext,
|
||||||
offset);
|
offset);
|
||||||
return new BaselineCompletion(
|
return new BaselineCompletion(
|
||||||
false,
|
false,
|
||||||
@ -100,18 +110,25 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
final int line,
|
final int line,
|
||||||
final int character) {
|
final int character) {
|
||||||
Objects.requireNonNull(context, "context");
|
Objects.requireNonNull(context, "context");
|
||||||
final Optional<PBSFrontendLanguageService> languageService = pbsLanguageService(context);
|
final var provider = frontendProvider(context);
|
||||||
|
final var languageService = provider.languageService();
|
||||||
if (languageService.isEmpty()) {
|
if (languageService.isEmpty()) {
|
||||||
return new BaselineHover("No symbol information.");
|
return new BaselineHover("No symbol information.");
|
||||||
}
|
}
|
||||||
return editorialDocument(context, documentUri, text)
|
return editorialDocument(context, documentUri, text)
|
||||||
.flatMap(document -> {
|
.flatMap(document -> {
|
||||||
final int offset = new DocumentPositionMapper(document.text()).byteOffsetOf(line, character);
|
final int offset = new DocumentPositionMapper(document.text()).byteOffsetOf(line, character);
|
||||||
|
final FrontendDocumentRequest request = new FrontendDocumentRequest(
|
||||||
|
context.projectRoot(),
|
||||||
|
normalizeDocumentPath(documentUri),
|
||||||
|
document.text());
|
||||||
|
final FrontendEditorialContext editorialContext = new FrontendEditorialContext(
|
||||||
|
document.editorialContext().syntaxTree(),
|
||||||
|
document.editorialContext().supplementalDeclarations(),
|
||||||
|
document.editorialContext().semanticContext());
|
||||||
return languageService.orElseThrow().hover(
|
return languageService.orElseThrow().hover(
|
||||||
document.text(),
|
request,
|
||||||
document.ast(),
|
editorialContext,
|
||||||
document.supplementalTopDecls(),
|
|
||||||
document.feSurfaceContext(),
|
|
||||||
offset);
|
offset);
|
||||||
})
|
})
|
||||||
.map(symbol -> new BaselineHover(formatHoverMarkdown(symbol)))
|
.map(symbol -> new BaselineHover(formatHoverMarkdown(symbol)))
|
||||||
@ -126,26 +143,28 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
final int line,
|
final int line,
|
||||||
final int character) {
|
final int character) {
|
||||||
Objects.requireNonNull(context, "context");
|
Objects.requireNonNull(context, "context");
|
||||||
final Optional<PBSFrontendLanguageService> languageService = pbsLanguageService(context);
|
final var provider = frontendProvider(context);
|
||||||
|
final var languageService = provider.languageService();
|
||||||
if (languageService.isEmpty()) {
|
if (languageService.isEmpty()) {
|
||||||
return new BaselineSignatureHelp(List.of(), 0, 0);
|
return new BaselineSignatureHelp(List.of(), 0, 0);
|
||||||
}
|
}
|
||||||
return editorialDocument(context, documentUri, text)
|
return editorialDocument(context, documentUri, text)
|
||||||
.flatMap(document -> {
|
.map(document -> {
|
||||||
final int offset = new DocumentPositionMapper(document.text()).byteOffsetOf(line, character);
|
final int offset = new DocumentPositionMapper(document.text()).byteOffsetOf(line, character);
|
||||||
|
final FrontendDocumentRequest request = new FrontendDocumentRequest(
|
||||||
|
context.projectRoot(),
|
||||||
|
normalizeDocumentPath(documentUri),
|
||||||
|
document.text());
|
||||||
|
final FrontendEditorialContext editorialContext = new FrontendEditorialContext(
|
||||||
|
document.editorialContext().syntaxTree(),
|
||||||
|
document.editorialContext().supplementalDeclarations(),
|
||||||
|
document.editorialContext().semanticContext());
|
||||||
return languageService.orElseThrow().signatureHelp(
|
return languageService.orElseThrow().signatureHelp(
|
||||||
document.text(),
|
request,
|
||||||
document.ast(),
|
editorialContext,
|
||||||
document.supplementalTopDecls(),
|
|
||||||
document.feSurfaceContext(),
|
|
||||||
offset);
|
offset);
|
||||||
})
|
})
|
||||||
.map(signatureHelp -> new BaselineSignatureHelp(
|
.map(this::mapSignatureHelp)
|
||||||
signatureHelp.signatures().stream()
|
|
||||||
.map(signature -> new BaselineSignature(signature.label(), signature.parameterLabels(), ""))
|
|
||||||
.toList(),
|
|
||||||
signatureHelp.activeSignature(),
|
|
||||||
signatureHelp.activeParameter()))
|
|
||||||
.orElseGet(() -> new BaselineSignatureHelp(List.of(), 0, 0));
|
.orElseGet(() -> new BaselineSignatureHelp(List.of(), 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,10 +175,16 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
final String text) {
|
final String text) {
|
||||||
Objects.requireNonNull(context, "context");
|
Objects.requireNonNull(context, "context");
|
||||||
final String effectiveText = text == null ? "" : text;
|
final String effectiveText = text == null ? "" : text;
|
||||||
final var frontend = frontendProvider(context).specification();
|
final Path documentPath = normalizeDocumentPath(documentUri);
|
||||||
|
final var provider = frontendProvider(context);
|
||||||
|
final var frontend = provider.specification();
|
||||||
final FrontendSemanticPresentationSpec presentation = frontend.getSemanticPresentation();
|
final FrontendSemanticPresentationSpec presentation = frontend.getSemanticPresentation();
|
||||||
final List<FrontendSemanticToken> tokens = pbsLanguageService(context)
|
final FrontendDocumentRequest request = new FrontendDocumentRequest(
|
||||||
.map(languageService -> languageService.semanticTokens(effectiveText))
|
context.projectRoot(),
|
||||||
|
documentPath,
|
||||||
|
effectiveText);
|
||||||
|
final List<FrontendSemanticToken> tokens = provider.languageService()
|
||||||
|
.map(languageService -> languageService.semanticTokens(request))
|
||||||
.orElseGet(List::of);
|
.orElseGet(List::of);
|
||||||
final var positionMapper = new DocumentPositionMapper(effectiveText);
|
final var positionMapper = new DocumentPositionMapper(effectiveText);
|
||||||
final var semanticTokens = new ArrayList<BaselineSemanticToken>();
|
final var semanticTokens = new ArrayList<BaselineSemanticToken>();
|
||||||
@ -202,6 +227,10 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
final LspProjectContext context,
|
final LspProjectContext context,
|
||||||
final String documentUri,
|
final String documentUri,
|
||||||
final String text) {
|
final String text) {
|
||||||
|
final var languageService = frontendProvider(context).languageService();
|
||||||
|
if (languageService.isEmpty()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
final Path documentPath = normalizeDocumentPath(documentUri);
|
final Path documentPath = normalizeDocumentPath(documentUri);
|
||||||
final BuilderPipelineContext pipelineContext = BuilderPipelineContext.fromConfig(
|
final BuilderPipelineContext pipelineContext = BuilderPipelineContext.fromConfig(
|
||||||
new BuilderPipelineConfig(
|
new BuilderPipelineConfig(
|
||||||
@ -222,19 +251,11 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
pipelineContext.resolvedWorkspace.target(),
|
pipelineContext.resolvedWorkspace.target(),
|
||||||
HostAdmissionContext.permissiveDefault(),
|
HostAdmissionContext.permissiveDefault(),
|
||||||
assetSurfaceContextLoader.load(pipelineContext.resolvedWorkspace.mainProject().getRootPath()));
|
assetSurfaceContextLoader.load(pipelineContext.resolvedWorkspace.mainProject().getRootPath()));
|
||||||
final var semanticReadSurface = PBSFrontendPhaseService.semanticReadSurface(
|
return languageService.orElseThrow()
|
||||||
frontendPhaseContext,
|
.prepareEditorialContext(frontendPhaseContext, fileId)
|
||||||
p.studio.compiler.source.diagnostics.DiagnosticSink.empty(),
|
.map(editorialContext -> new EditorialDocument(
|
||||||
BuildingIssueSink.empty());
|
text == null ? readCurrentText(pipelineContext, fileId) : text,
|
||||||
final var ast = semanticReadSurface.astByFile().get(fileId);
|
editorialContext));
|
||||||
if (ast == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
return Optional.of(new EditorialDocument(
|
|
||||||
text == null ? readCurrentText(pipelineContext, fileId) : text,
|
|
||||||
ast,
|
|
||||||
semanticReadSurface.supplementalTopDeclsByFile().getOrDefault(fileId, ReadOnlyList.empty()),
|
|
||||||
frontendPhaseContext.feSurfaceContext()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private FileId findFileId(
|
private FileId findFileId(
|
||||||
@ -305,7 +326,7 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
return Path.of(URI.create(Objects.requireNonNull(documentUri, "documentUri"))).toAbsolutePath().normalize();
|
return Path.of(URI.create(Objects.requireNonNull(documentUri, "documentUri"))).toAbsolutePath().normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private BaselineCompletionItem mapCompletionItem(final PbsEditorialCompletionCandidate candidate) {
|
private BaselineCompletionItem mapCompletionItem(final FrontendCompletionCandidate candidate) {
|
||||||
return new BaselineCompletionItem(
|
return new BaselineCompletionItem(
|
||||||
candidate.label(),
|
candidate.label(),
|
||||||
mapCompletionKind(candidate.kind()),
|
mapCompletionKind(candidate.kind()),
|
||||||
@ -356,6 +377,18 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
tokenStyle.underline());
|
tokenStyle.underline());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BaselineSignatureHelp mapSignatureHelp(final FrontendSignatureHelp signatureHelp) {
|
||||||
|
return new BaselineSignatureHelp(
|
||||||
|
signatureHelp.signatures().stream()
|
||||||
|
.map(signature -> new BaselineSignature(
|
||||||
|
signature.label(),
|
||||||
|
signature.parameterLabels(),
|
||||||
|
signature.documentation()))
|
||||||
|
.toList(),
|
||||||
|
signatureHelp.activeSignature(),
|
||||||
|
signatureHelp.activeParameter());
|
||||||
|
}
|
||||||
|
|
||||||
private p.studio.compiler.models.FrontendSpec frontendSpec(final LspProjectContext context) {
|
private p.studio.compiler.models.FrontendSpec frontendSpec(final LspProjectContext context) {
|
||||||
return frontendProvider(context).specification();
|
return frontendProvider(context).specification();
|
||||||
}
|
}
|
||||||
@ -364,13 +397,7 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
return frontendProviderResolver.apply(context.languageId());
|
return frontendProviderResolver.apply(context.languageId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<PBSFrontendLanguageService> pbsLanguageService(final LspProjectContext context) {
|
private BaselineCompletionItemKind mapCompletionKind(final FrontendSymbolKind kind) {
|
||||||
return frontendProvider(context).languageService()
|
|
||||||
.filter(PBSFrontendLanguageService.class::isInstance)
|
|
||||||
.map(PBSFrontendLanguageService.class::cast);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BaselineCompletionItemKind mapCompletionKind(final PbsEditorialSymbolKind kind) {
|
|
||||||
return switch (kind) {
|
return switch (kind) {
|
||||||
case KEYWORD -> BaselineCompletionItemKind.KEYWORD;
|
case KEYWORD -> BaselineCompletionItemKind.KEYWORD;
|
||||||
case LOCAL, PARAMETER -> BaselineCompletionItemKind.VARIABLE;
|
case LOCAL, PARAMETER -> BaselineCompletionItemKind.VARIABLE;
|
||||||
@ -391,9 +418,9 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private String formatHoverMarkdown(final PbsEditorialResolvedSymbol symbol) {
|
private String formatHoverMarkdown(final FrontendHover symbol) {
|
||||||
final StringBuilder markdown = new StringBuilder();
|
final StringBuilder markdown = new StringBuilder();
|
||||||
final List<PbsEditorialSignature> signatures = symbol.signatures();
|
final List<FrontendSignature> signatures = symbol.signatures();
|
||||||
if (!signatures.isEmpty()) {
|
if (!signatures.isEmpty()) {
|
||||||
markdown.append("```pbs\n");
|
markdown.append("```pbs\n");
|
||||||
for (int index = 0; index < signatures.size(); index += 1) {
|
for (int index = 0; index < signatures.size(); index += 1) {
|
||||||
@ -419,15 +446,13 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg
|
|||||||
markdown.append(" from `").append(symbol.origin()).append('`');
|
markdown.append(" from `").append(symbol.origin()).append('`');
|
||||||
}
|
}
|
||||||
if (!symbol.documentation().isBlank()) {
|
if (!symbol.documentation().isBlank()) {
|
||||||
markdown.append("\n\n").append(symbol.documentation());
|
markdown.append("\n\n").append(symbol.documentation().text());
|
||||||
}
|
}
|
||||||
return markdown.toString();
|
return markdown.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private record EditorialDocument(
|
private record EditorialDocument(
|
||||||
String text,
|
String text,
|
||||||
p.studio.compiler.pbs.ast.PbsAst.File ast,
|
FrontendEditorialContext editorialContext) {
|
||||||
ReadOnlyList<p.studio.compiler.pbs.ast.PbsAst.TopDecl> supplementalTopDecls,
|
|
||||||
p.studio.compiler.messages.FESurfaceContext feSurfaceContext) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user