diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 49dfec69..3494e7d9 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -10,7 +10,7 @@ {"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":"open","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":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]} -{"type":"discussion","id":"DSC-0054","status":"in_progress","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":[{"id":"AGD-0057","file":"AGD-0057-multi-frontend-provider-contract.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[{"id":"DEC-0040","file":"DEC-0040-frontendprovider-explicito-como-contrato-de-frontend.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15","ref_agenda":"AGD-0057"}],"plans":[{"id":"PLN-0103","file":"PLN-0103-specify-frontend-provider-and-registry-contract.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0040"]},{"id":"PLN-0104","file":"PLN-0104-introduce-frontendprovider-registry-api.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0040"]},{"id":"PLN-0105","file":"PLN-0105-wrap-pbs-frontend-as-explicit-provider.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0040"]},{"id":"PLN-0106","file":"PLN-0106-migrate-common-consumers-to-provider-lookup.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0040"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0054","status":"in_progress","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":[{"id":"AGD-0057","file":"AGD-0057-multi-frontend-provider-contract.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[{"id":"DEC-0040","file":"DEC-0040-frontendprovider-explicito-como-contrato-de-frontend.md","status":"accepted","created_at":"2026-07-15","updated_at":"2026-07-15","ref_agenda":"AGD-0057"}],"plans":[{"id":"PLN-0103","file":"PLN-0103-specify-frontend-provider-and-registry-contract.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0040"]},{"id":"PLN-0104","file":"PLN-0104-introduce-frontendprovider-registry-api.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0040"]},{"id":"PLN-0105","file":"PLN-0105-wrap-pbs-frontend-as-explicit-provider.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0040"]},{"id":"PLN-0106","file":"PLN-0106-migrate-common-consumers-to-provider-lookup.md","status":"done","created_at":"2026-07-15","updated_at":"2026-07-15","ref_decisions":["DEC-0040"]}],"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":[]} {"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":[]} {"type":"discussion","id":"DSC-0051","status":"open","ticket":"pbs-lsp-diagnostics-ux","title":"PBS LSP Diagnostics UX","created_at":"2026-07-15","updated_at":"2026-07-15","tags":["studio","lsp","vscode","compiler-pbs","editor","diagnostics","ux"],"agendas":[{"id":"AGD-0054","file":"AGD-0054-pbs-lsp-diagnostics-ux.md","status":"open","created_at":"2026-07-15","updated_at":"2026-07-15"}],"decisions":[],"plans":[],"lessons":[]} diff --git a/discussion/workflow/plans/PLN-0106-migrate-common-consumers-to-provider-lookup.md b/discussion/workflow/plans/PLN-0106-migrate-common-consumers-to-provider-lookup.md index 1dd6090f..a1400a7a 100644 --- a/discussion/workflow/plans/PLN-0106-migrate-common-consumers-to-provider-lookup.md +++ b/discussion/workflow/plans/PLN-0106-migrate-common-consumers-to-provider-lookup.md @@ -2,7 +2,7 @@ id: PLN-0106 ticket: multi-frontend-provider-contract title: Migrate common consumers to provider lookup -status: open +status: done created: 2026-07-15 ref_decisions: [DEC-0040] tags: [compiler, compiler-general, studio, frontend, registry, multi-frontend] diff --git a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/FrontendPhasePipelineStage.java b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/FrontendPhasePipelineStage.java index 7fb1a1a9..68c7f41e 100644 --- a/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/FrontendPhasePipelineStage.java +++ b/prometeu-compiler/prometeu-build-pipeline/src/main/java/p/studio/compiler/workspaces/stages/FrontendPhasePipelineStage.java @@ -17,13 +17,7 @@ public class FrontendPhasePipelineStage implements PipelineStage { @Override public BuildingIssueSink run(final BuilderPipelineContext ctx, final LogAggregator logs) { final var frontedSpec = ctx.resolvedWorkspace.frontendSpec(); - final var service = FrontendRegistryService.getFrontendPhaseService(frontedSpec.getLanguageId()); - if (service.isEmpty()) { - return BuildingIssueSink.empty() - .report(builder -> builder - .error(true) - .message("[BUILD]: unable to find a service for frontend phase: " + frontedSpec.getLanguageId())); - } + final var provider = FrontendRegistryService.require(frontedSpec.getLanguageId()); final var projectTable = ctx.resolvedWorkspace.graph().projectTable(); final var fileTable = ctx.fileTable; final var frontendPhaseContext = new FrontendPhaseContext( @@ -36,7 +30,7 @@ public class FrontendPhasePipelineStage implements PipelineStage { assetSurfaceContextLoader.load(ctx.resolvedWorkspace.mainProject().getRootPath())); final var diagnostics = DiagnosticSink.empty(); final var issues = BuildingIssueSink.empty(); - ctx.irBackend = service.get().compile(frontendPhaseContext, diagnostics, logs, issues); + ctx.irBackend = provider.compiler().compile(frontendPhaseContext, diagnostics, logs, issues); logs.using(log).debug("Frontend phase completed successfully!"); adaptDiagnostics(diagnostics, issues); return issues; 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 new file mode 100644 index 00000000..a69363d6 --- /dev/null +++ b/prometeu-compiler/prometeu-build-pipeline/src/test/java/p/studio/compiler/specs/FrontendProviderBoundaryTest.java @@ -0,0 +1,59 @@ +package p.studio.compiler.specs; + +import org.junit.jupiter.api.Test; + +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.fail; + +class FrontendProviderBoundaryTest { + + @Test + void commonModulesMustNotInstantiatePbsFrontendPhaseServiceDirectly() throws IOException { + final var repoRoot = findRepoRoot(Path.of("").toAbsolutePath().normalize()); + final var commonSourceRoots = List.of( + repoRoot.resolve("prometeu-compiler/prometeu-build-pipeline/src/main/java"), + repoRoot.resolve("prometeu-compiler/prometeu-deps/src/main/java"), + repoRoot.resolve("prometeu-studio/src/main/java"), + repoRoot.resolve("prometeu-app/src/main/java")); + + for (final var sourceRoot : commonSourceRoots) { + 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 PBSFrontendPhaseService(")) + .toList(); + if (!violations.isEmpty()) { + fail("common modules must resolve frontend compiler behavior through FrontendProvider: " + violations); + } + } + } + } + + private boolean contains(final Path path, final String needle) { + try { + return Files.readString(path).contains(needle); + } catch (IOException e) { + throw new IllegalStateException("failed to read " + path, e); + } + } + + private Path findRepoRoot(final Path start) { + var current = start; + while (current != null) { + if (Files.exists(current.resolve("settings.gradle.kts"))) { + return current; + } + current = current.getParent(); + } + fail("unable to locate repository root from " + start); + return start; + } +} diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/utilities/PrometeuManifestUtils.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/utilities/PrometeuManifestUtils.java index 8455d5c9..985d5f00 100644 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/utilities/PrometeuManifestUtils.java +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/utilities/PrometeuManifestUtils.java @@ -60,7 +60,7 @@ public final class PrometeuManifestUtils { .message("[DEPS]: invalid 'target' \"" + dto.target() + "\": " + manifestPathCanon)); } final var language = StringUtils.isBlank(dto.language()) - ? FrontendRegistryService.getDefaultFrontendSpec().getLanguageId() + ? FrontendRegistryService.getDefaultProvider().specification().getLanguageId() : dto.language(); final var dependencies = DependencyResolver.resolveDependencies(mainProjectRootPathCanon, dto.dependencies(), issuesLocal); if (ReadOnlyCollection.isNotEmpty(issuesLocal)) { diff --git a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/DiscoverPhase.java b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/DiscoverPhase.java index d4daea74..4b204b68 100644 --- a/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/DiscoverPhase.java +++ b/prometeu-compiler/prometeu-deps/src/main/java/p/studio/compiler/workspaces/phases/DiscoverPhase.java @@ -55,23 +55,24 @@ public class DiscoverPhase implements DependencyPhase { ctx.rootStdlib = manifest.get().stdlib(); ctx.target = manifest.get().target(); } - final var frontendSpec = FrontendRegistryService.getFrontendSpec(manifest.get().language()); + final var frontendProvider = FrontendRegistryService.getProvider(manifest.get().language()); // Returns empty when language is unknown - if (frontendSpec.isEmpty()) { + if (frontendProvider.isEmpty()) { issues.report(builder -> builder .error(true) .message("[DEPS]: unknown language " + manifest.get().language() + " for project " + manifest.get().name())); return Optional.empty(); } + final var frontendSpec = frontendProvider.get().specification(); // Enforces consistent language across projects if (Objects.isNull(ctx.frontendSpec)) { - ctx.frontendSpec = frontendSpec.get(); - } else if (!ctx.frontendSpec.getLanguageId().equals(frontendSpec.get().getLanguageId())) { + ctx.frontendSpec = frontendSpec; + } else if (!ctx.frontendSpec.getLanguageId().equals(frontendSpec.getLanguageId())) { // Reports language inconsistency across projects issues.report(builder -> builder .error(true) .message(String.format("[DEPS]: inconsistent language: [ %s ] has \"%s\" but should be \"%s\"", - manifest.get().name(), frontendSpec.get().getLanguageId(), ctx.frontendSpec.getLanguageId()))); + manifest.get().name(), frontendSpec.getLanguageId(), ctx.frontendSpec.getLanguageId()))); return Optional.empty(); } final var projectInfo = ProjectInfo diff --git a/prometeu-compiler/prometeu-frontend-registry/build.gradle.kts b/prometeu-compiler/prometeu-frontend-registry/build.gradle.kts index 7eff67cf..2caa4e92 100644 --- a/prometeu-compiler/prometeu-frontend-registry/build.gradle.kts +++ b/prometeu-compiler/prometeu-frontend-registry/build.gradle.kts @@ -6,7 +6,7 @@ dependencies { implementation(project(":prometeu-infra")) implementation(project(":prometeu-compiler:prometeu-compiler-core")) - implementation(project(":prometeu-compiler:prometeu-frontend-api")) + api(project(":prometeu-compiler:prometeu-frontend-api")) implementation(project(":prometeu-compiler:frontends:prometeu-frontend-pbs")) -} \ No newline at end of file +} diff --git a/prometeu-compiler/prometeu-frontend-registry/src/main/java/p/studio/compiler/FrontendRegistryService.java b/prometeu-compiler/prometeu-frontend-registry/src/main/java/p/studio/compiler/FrontendRegistryService.java index 43190fef..bf30b6b3 100644 --- a/prometeu-compiler/prometeu-frontend-registry/src/main/java/p/studio/compiler/FrontendRegistryService.java +++ b/prometeu-compiler/prometeu-frontend-registry/src/main/java/p/studio/compiler/FrontendRegistryService.java @@ -21,7 +21,11 @@ public class FrontendRegistryService { } public static FrontendSpec getDefaultFrontendSpec() { - return require(PBSDefinitions.PBS.getLanguageId()).specification(); + return getDefaultProvider().specification(); + } + + public static FrontendProvider getDefaultProvider() { + return require(PBSDefinitions.PBS.getLanguageId()); } public static Optional getFrontendSpec(final String languageId) { diff --git a/prometeu-compiler/prometeu-frontend-registry/src/test/java/p/studio/compiler/FrontendRegistryServiceTest.java b/prometeu-compiler/prometeu-frontend-registry/src/test/java/p/studio/compiler/FrontendRegistryServiceTest.java index 6f2e7919..71e5c32e 100644 --- a/prometeu-compiler/prometeu-frontend-registry/src/test/java/p/studio/compiler/FrontendRegistryServiceTest.java +++ b/prometeu-compiler/prometeu-frontend-registry/src/test/java/p/studio/compiler/FrontendRegistryServiceTest.java @@ -33,4 +33,12 @@ class FrontendRegistryServiceTest { assertEquals(1, specs.size()); assertEquals(PBSDefinitions.PBS.getLanguageId(), specs.getFirst().getLanguageId()); } + + @Test + void providerListingShouldExposeOnlyPbsByDefault() { + final var providers = FrontendRegistryService.listProviders(); + + assertEquals(1, providers.size()); + assertEquals(PBSDefinitions.PBS.getLanguageId(), providers.getFirst().specification().getLanguageId()); + } } diff --git a/prometeu-studio/src/main/java/p/studio/projects/ProjectLanguageCatalogService.java b/prometeu-studio/src/main/java/p/studio/projects/ProjectLanguageCatalogService.java index 394e9cbb..fea4319d 100644 --- a/prometeu-studio/src/main/java/p/studio/projects/ProjectLanguageCatalogService.java +++ b/prometeu-studio/src/main/java/p/studio/projects/ProjectLanguageCatalogService.java @@ -9,8 +9,8 @@ import java.util.Objects; public final class ProjectLanguageCatalogService { public List listLanguageTemplates() { - return FrontendRegistryService.listFrontendSpecs().stream() - .map(this::toTemplate) + return FrontendRegistryService.listProviders().stream() + .map(provider -> toTemplate(provider.specification())) .sorted(Comparator.comparing(ProjectLanguageTemplate::languageId, String.CASE_INSENSITIVE_ORDER)) .toList(); } diff --git a/prometeu-studio/src/test/java/p/studio/projects/ProjectLanguageCatalogServiceTest.java b/prometeu-studio/src/test/java/p/studio/projects/ProjectLanguageCatalogServiceTest.java new file mode 100644 index 00000000..a02306af --- /dev/null +++ b/prometeu-studio/src/test/java/p/studio/projects/ProjectLanguageCatalogServiceTest.java @@ -0,0 +1,18 @@ +package p.studio.projects; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ProjectLanguageCatalogServiceTest { + + @Test + void shouldListLanguagesFromRegisteredProviders() { + final var service = new ProjectLanguageCatalogService(); + + final var templates = service.listLanguageTemplates(); + + assertEquals(1, templates.size()); + assertEquals("pbs", templates.getFirst().languageId()); + } +}