diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 9d7be19c..1560931e 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -9,7 +9,7 @@ {"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-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-0055","status":"in_progress","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":[{"id":"AGD-0058","file":"AGD-0058-multi-frontend-compiler-vs-language-services.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[{"id":"DEC-0041","file":"DEC-0041-separate-frontend-compilation-from-editorial-language-services.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15","ref_agenda":"AGD-0058"}],"plans":[{"id":"PLN-0107","file":"PLN-0107-specify-frontend-compiler-and-editorial-capability-boundary.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0041"]},{"id":"PLN-0108","file":"PLN-0108-move-pbs-editor-assistance-behind-optional-language-service.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0041"]},{"id":"PLN-0109","file":"PLN-0109-make-lsp-editorial-calls-capability-aware.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0041"]},{"id":"PLN-0110","file":"PLN-0110-add-compile-only-frontend-and-absent-capability-conformance-coverage.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0041"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0055","status":"in_progress","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":[{"id":"AGD-0058","file":"AGD-0058-multi-frontend-compiler-vs-language-services.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[{"id":"DEC-0041","file":"DEC-0041-separate-frontend-compilation-from-editorial-language-services.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15","ref_agenda":"AGD-0058"}],"plans":[{"id":"PLN-0107","file":"PLN-0107-specify-frontend-compiler-and-editorial-capability-boundary.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0041"]},{"id":"PLN-0108","file":"PLN-0108-move-pbs-editor-assistance-behind-optional-language-service.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0041"]},{"id":"PLN-0109","file":"PLN-0109-make-lsp-editorial-calls-capability-aware.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0041"]},{"id":"PLN-0110","file":"PLN-0110-add-compile-only-frontend-and-absent-capability-conformance-coverage.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0041"]}],"lessons":[]} {"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-0052","status":"open","ticket":"pbs-lsp-document-links","title":"PBS LSP Document Links","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","document-links","imports"],"agendas":[{"id":"AGD-0055","file":"AGD-0055-pbs-lsp-document-links.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]} diff --git a/discussion/workflow/plans/PLN-0110-add-compile-only-frontend-and-absent-capability-conformance-coverage.md b/discussion/workflow/plans/PLN-0110-add-compile-only-frontend-and-absent-capability-conformance-coverage.md index b0257383..cd4990b6 100644 --- a/discussion/workflow/plans/PLN-0110-add-compile-only-frontend-and-absent-capability-conformance-coverage.md +++ b/discussion/workflow/plans/PLN-0110-add-compile-only-frontend-and-absent-capability-conformance-coverage.md @@ -2,7 +2,7 @@ id: PLN-0110 ticket: multi-frontend-compiler-vs-language-services title: Add compile only frontend and absent capability conformance coverage -status: open +status: done created: 2026-07-15 ref_decisions: [DEC-0041] tags: [compiler, compiler-general, studio, lsp, editor, frontend, multi-frontend] diff --git a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/FrontendProviderBoundaryTest.java b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/FrontendProviderBoundaryTest.java index a69363d6..6ea8fef5 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/FrontendProviderBoundaryTest.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/FrontendProviderBoundaryTest.java @@ -1,12 +1,23 @@ package p.studio.compiler.specs; import org.junit.jupiter.api.Test; +import p.studio.compiler.messages.BuildingIssueSink; +import p.studio.compiler.messages.FrontendPhaseContext; +import p.studio.compiler.models.FrontendSpec; +import p.studio.compiler.models.IRBackend; +import p.studio.compiler.services.FrontendPhaseService; +import p.studio.compiler.services.FrontendProvider; +import p.studio.compiler.source.diagnostics.DiagnosticSink; +import p.studio.utilities.logs.LogAggregator; +import p.studio.utilities.structures.ReadOnlySet; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; class FrontendProviderBoundaryTest { @@ -37,6 +48,45 @@ class FrontendProviderBoundaryTest { } } + @Test + void commonModulesMustNotInstantiatePbsEditorialSupportServiceDirectly() throws IOException { + final var repoRoot = findRepoRoot(Path.of("").toAbsolutePath().normalize()); + final var sourceRoots = List.of( + repoRoot.resolve("prometeu-compiler/prometeu-build-pipeline/src/main/java"), + repoRoot.resolve("prometeu-compiler/prometeu-deps/src/main/java"), + repoRoot.resolve("prometeu-lsp/prometeu-lsp-v1/src/main/java"), + repoRoot.resolve("prometeu-studio/src/main/java"), + repoRoot.resolve("prometeu-app/src/main/java")); + + for (final var sourceRoot : sourceRoots) { + if (!Files.exists(sourceRoot)) { + continue; + } + + try (final var files = Files.walk(sourceRoot)) { + final var violations = files + .filter(path -> path.toString().endsWith(".java")) + .filter(path -> contains(path, "new PbsEditorialSupportService(")) + .toList(); + if (!violations.isEmpty()) { + fail("common modules must resolve PBS editor behavior through FrontendProvider.languageService(): " + violations); + } + } + } + } + + @Test + void compileOnlyFrontendProviderMayOmitLanguageService() { + final FrontendProvider provider = new CompileOnlyFrontendProvider(); + + assertTrue(provider.languageService().isEmpty()); + assertNotNull(provider.compiler().compile( + null, + DiagnosticSink.empty(), + LogAggregator.empty(), + BuildingIssueSink.empty())); + } + private boolean contains(final Path path, final String needle) { try { return Files.readString(path).contains(needle); @@ -56,4 +106,34 @@ class FrontendProviderBoundaryTest { fail("unable to locate repository root from " + start); return start; } + + private static final class CompileOnlyFrontendProvider implements FrontendProvider { + private final FrontendSpec specification = FrontendSpec.builder() + .languageId("compile-only") + .allowedExtensions(ReadOnlySet.from("co")) + .sourceRoots(ReadOnlySet.from("src")) + .build(); + private final FrontendPhaseService compiler = new CompileOnlyFrontendPhaseService(); + + @Override + public FrontendSpec specification() { + return specification; + } + + @Override + public FrontendPhaseService compiler() { + return compiler; + } + } + + private static final class CompileOnlyFrontendPhaseService implements FrontendPhaseService { + @Override + public IRBackend compile( + final FrontendPhaseContext request, + final DiagnosticSink diagnostics, + final LogAggregator logs, + final BuildingIssueSink issues) { + return IRBackend.builder().build(); + } + } } diff --git a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridge.java b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridge.java index 980ed8f4..37e27159 100644 --- a/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridge.java +++ b/prometeu-lsp/prometeu-lsp-v1/src/main/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridge.java @@ -6,6 +6,7 @@ import p.studio.compiler.PBSFrontendLanguageService; import p.studio.compiler.messages.*; import p.studio.compiler.models.*; import p.studio.compiler.pbs.semantics.*; +import p.studio.compiler.services.FrontendProvider; import p.studio.compiler.services.PBSFrontendPhaseService; import p.studio.compiler.source.identifiers.FileId; import p.studio.compiler.utilities.SourceProviderFactory; @@ -19,9 +20,19 @@ import p.studio.utilities.structures.ReadOnlyList; import java.net.URI; import java.nio.file.Path; import java.util.*; +import java.util.function.Function; public final class CompilerLanguageServiceBridge implements LanguageServiceBridge { private final AssetSurfaceContextLoader assetSurfaceContextLoader = new AssetSurfaceContextLoader(); + private final Function frontendProviderResolver; + + public CompilerLanguageServiceBridge() { + this(FrontendRegistryService::require); + } + + CompilerLanguageServiceBridge(final Function frontendProviderResolver) { + this.frontendProviderResolver = Objects.requireNonNull(frontendProviderResolver, "frontendProviderResolver"); + } @Override public BaselineServerDescription describeServer(final LspProjectContext context) { @@ -349,9 +360,8 @@ public final class CompilerLanguageServiceBridge implements LanguageServiceBridg return frontendProvider(context).specification(); } - private p.studio.compiler.services.FrontendProvider frontendProvider(final LspProjectContext context) { - return FrontendRegistryService.getProvider(context.languageId()) - .orElseThrow(() -> new IllegalArgumentException("no frontend registered for languageId: " + context.languageId())); + private FrontendProvider frontendProvider(final LspProjectContext context) { + return frontendProviderResolver.apply(context.languageId()); } private Optional pbsLanguageService(final LspProjectContext context) { diff --git a/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridgeTest.java b/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridgeTest.java index 12be85a8..7efb440d 100644 --- a/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridgeTest.java +++ b/prometeu-lsp/prometeu-lsp-v1/src/test/java/p/studio/lsp/services/compiler/CompilerLanguageServiceBridgeTest.java @@ -1,8 +1,17 @@ package p.studio.lsp.services.compiler; import org.junit.jupiter.api.Test; +import p.studio.compiler.messages.BuildingIssueSink; +import p.studio.compiler.messages.FrontendPhaseContext; +import p.studio.compiler.models.FrontendSpec; +import p.studio.compiler.models.IRBackend; +import p.studio.compiler.services.FrontendPhaseService; +import p.studio.compiler.services.FrontendProvider; +import p.studio.compiler.source.diagnostics.DiagnosticSink; import p.studio.lsp.messages.BaselineIssueSeverity; import p.studio.lsp.messages.LspProjectContext; +import p.studio.utilities.logs.LogAggregator; +import p.studio.utilities.structures.ReadOnlySet; import java.nio.file.Files; import java.nio.file.Path; @@ -12,6 +21,35 @@ import static org.junit.jupiter.api.Assertions.assertTrue; class CompilerLanguageServiceBridgeTest { + @Test + void editorialEndpointsReturnNeutralResponsesWhenLanguageServiceIsAbsent() { + final var provider = new CompileOnlyFrontendProvider(); + final CompilerLanguageServiceBridge bridge = new CompilerLanguageServiceBridge(languageId -> { + if (provider.specification().getLanguageId().equals(languageId)) { + return provider; + } + throw new IllegalArgumentException("no frontend registered for languageId: " + languageId); + }); + final LspProjectContext context = new LspProjectContext("main", provider.specification().getLanguageId(), Path.of(".")); + final String documentUri = Path.of("demo.co").toUri().toString(); + + final var completion = bridge.completion(context, documentUri, "fn main() {}", 0, 0); + assertFalse(completion.incomplete()); + assertTrue(completion.items().isEmpty()); + + final var hover = bridge.hover(context, documentUri, "fn main() {}", 0, 0); + assertEquals("No symbol information.", hover.markdown()); + + final var signatureHelp = bridge.signatureHelp(context, documentUri, "fn main() {}", 0, 0); + assertTrue(signatureHelp.signatures().isEmpty()); + assertEquals(0, signatureHelp.activeSignature()); + assertEquals(0, signatureHelp.activeParameter()); + + final var semanticTokens = bridge.semanticTokens(context, documentUri, "fn main() {}"); + assertTrue(semanticTokens.legend().isEmpty()); + assertTrue(semanticTokens.tokens().isEmpty()); + } + @Test void analyzeDocumentReturnsCompilerDiagnosticsForOverlayContent() { final Path projectRoot = findRepoRoot(Path.of("").toAbsolutePath().normalize()) @@ -305,4 +343,34 @@ class CompilerLanguageServiceBridgeTest { } throw new IllegalStateException("unable to locate repository root from " + start); } + + private static final class CompileOnlyFrontendProvider implements FrontendProvider { + private final FrontendSpec specification = FrontendSpec.builder() + .languageId("compile-only") + .allowedExtensions(ReadOnlySet.from("co")) + .sourceRoots(ReadOnlySet.from("src")) + .build(); + private final FrontendPhaseService compiler = new CompileOnlyFrontendPhaseService(); + + @Override + public FrontendSpec specification() { + return specification; + } + + @Override + public FrontendPhaseService compiler() { + return compiler; + } + } + + private static final class CompileOnlyFrontendPhaseService implements FrontendPhaseService { + @Override + public IRBackend compile( + final FrontendPhaseContext request, + final DiagnosticSink diagnostics, + final LogAggregator logs, + final BuildingIssueSink issues) { + return IRBackend.builder().build(); + } + } }