diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 25d5f783..9d6da8a1 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":"open","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":"open","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":"open","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-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-0104-introduce-frontendprovider-registry-api.md b/discussion/workflow/plans/PLN-0104-introduce-frontendprovider-registry-api.md index 9f6b6265..c0a65a12 100644 --- a/discussion/workflow/plans/PLN-0104-introduce-frontendprovider-registry-api.md +++ b/discussion/workflow/plans/PLN-0104-introduce-frontendprovider-registry-api.md @@ -2,7 +2,7 @@ id: PLN-0104 ticket: multi-frontend-provider-contract title: Introduce FrontendProvider registry API -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-frontend-api/build.gradle.kts b/prometeu-compiler/prometeu-frontend-api/build.gradle.kts index 0bea0481..06c18c58 100644 --- a/prometeu-compiler/prometeu-frontend-api/build.gradle.kts +++ b/prometeu-compiler/prometeu-frontend-api/build.gradle.kts @@ -5,5 +5,5 @@ plugins { dependencies { api(project(":prometeu-infra")) - implementation(project(":prometeu-compiler:prometeu-compiler-core")) -} \ No newline at end of file + api(project(":prometeu-compiler:prometeu-compiler-core")) +} diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/services/FrontendLanguageService.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/services/FrontendLanguageService.java new file mode 100644 index 00000000..529d8cac --- /dev/null +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/services/FrontendLanguageService.java @@ -0,0 +1,7 @@ +package p.studio.compiler.services; + +/** + * Marker contract for optional frontend-owned editor services. + */ +public interface FrontendLanguageService { +} diff --git a/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/services/FrontendProvider.java b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/services/FrontendProvider.java new file mode 100644 index 00000000..339c96be --- /dev/null +++ b/prometeu-compiler/prometeu-frontend-api/src/main/java/p/studio/compiler/services/FrontendProvider.java @@ -0,0 +1,15 @@ +package p.studio.compiler.services; + +import p.studio.compiler.models.FrontendSpec; + +import java.util.Optional; + +public interface FrontendProvider { + FrontendSpec specification(); + + FrontendPhaseService compiler(); + + default Optional languageService() { + return Optional.empty(); + } +} 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 3c84d122..0c440e7b 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 @@ -2,6 +2,7 @@ package p.studio.compiler; import p.studio.compiler.models.FrontendSpec; import p.studio.compiler.services.FrontendPhaseService; +import p.studio.compiler.services.FrontendProvider; import p.studio.compiler.services.PBSFrontendPhaseService; import java.util.HashMap; @@ -10,29 +11,61 @@ import java.util.Map; import java.util.Optional; public class FrontendRegistryService { - private static final Map FRONTEND_SPECS = new HashMap<>(); - private static final Map FRONTEND_PHASE_SERVICES = new HashMap<>(); + private static final Map FRONTEND_PROVIDERS = new HashMap<>(); static { - FRONTEND_SPECS.put(PBSDefinitions.PBS.getLanguageId(), PBSDefinitions.PBS); - FRONTEND_PHASE_SERVICES.put(PBSDefinitions.PBS.getLanguageId(), new PBSFrontendPhaseService()); + register(new StaticFrontendProvider(PBSDefinitions.PBS, new PBSFrontendPhaseService())); } public static FrontendSpec getDefaultFrontendSpec() { - return FRONTEND_SPECS.get(PBSDefinitions.PBS.getLanguageId()); + return require(PBSDefinitions.PBS.getLanguageId()).specification(); } public static Optional getFrontendSpec(final String languageId) { - return Optional.ofNullable(FRONTEND_SPECS.get(languageId)); + return getProvider(languageId).map(FrontendProvider::specification); } public static Optional getFrontendPhaseService(final String languageId) { - return Optional.ofNullable(FRONTEND_PHASE_SERVICES.get(languageId)); + return getProvider(languageId).map(FrontendProvider::compiler); + } + + public static Optional getProvider(final String languageId) { + return Optional.ofNullable(FRONTEND_PROVIDERS.get(languageId)); + } + + public static FrontendProvider require(final String languageId) { + final var provider = FRONTEND_PROVIDERS.get(languageId); + if (provider == null) { + throw new IllegalArgumentException("Unknown frontend languageId: " + languageId); + } + + return provider; } public static List listFrontendSpecs() { - return FRONTEND_SPECS.values().stream() + return FRONTEND_PROVIDERS.values().stream() + .map(FrontendProvider::specification) .sorted((left, right) -> left.getLanguageId().compareToIgnoreCase(right.getLanguageId())) .toList(); } + + public static List listProviders() { + return FRONTEND_PROVIDERS.values().stream() + .sorted((left, right) -> left.specification().getLanguageId().compareToIgnoreCase(right.specification().getLanguageId())) + .toList(); + } + + static void register(final FrontendProvider provider) { + final var languageId = provider.specification().getLanguageId(); + if (FRONTEND_PROVIDERS.containsKey(languageId)) { + throw new IllegalArgumentException("Duplicate frontend languageId: " + languageId); + } + + FRONTEND_PROVIDERS.put(languageId, provider); + } + + private record StaticFrontendProvider( + FrontendSpec specification, + FrontendPhaseService compiler) implements FrontendProvider { + } } 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 new file mode 100644 index 00000000..6f2e7919 --- /dev/null +++ b/prometeu-compiler/prometeu-frontend-registry/src/test/java/p/studio/compiler/FrontendRegistryServiceTest.java @@ -0,0 +1,36 @@ +package p.studio.compiler; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class FrontendRegistryServiceTest { + + @Test + void requireShouldResolveProviderByLanguageId() { + final var provider = FrontendRegistryService.require(PBSDefinitions.PBS.getLanguageId()); + + assertEquals(PBSDefinitions.PBS.getLanguageId(), provider.specification().getLanguageId()); + assertSame(PBSDefinitions.PBS, provider.specification()); + } + + @Test + void requireShouldFailClearlyForUnknownLanguageId() { + final var thrown = assertThrows( + IllegalArgumentException.class, + () -> FrontendRegistryService.require("unknown-language")); + + assertTrue(thrown.getMessage().contains("unknown-language")); + } + + @Test + void specListingShouldBeProviderDerived() { + final var specs = FrontendRegistryService.listFrontendSpecs(); + + assertEquals(1, specs.size()); + assertEquals(PBSDefinitions.PBS.getLanguageId(), specs.getFirst().getLanguageId()); + } +}