From 29c18bf47c19901f572954b110d6941f855bac94 Mon Sep 17 00:00:00 2001 From: "L. Sousa" Date: Tue, 28 Jul 2026 18:23:58 +0100 Subject: [PATCH 1/5] Restore test --- LaraUtils/test/pt/up/fe/specs/lara/LaraSystemToolsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LaraUtils/test/pt/up/fe/specs/lara/LaraSystemToolsTest.java b/LaraUtils/test/pt/up/fe/specs/lara/LaraSystemToolsTest.java index b637071dd..80f81ca4f 100644 --- a/LaraUtils/test/pt/up/fe/specs/lara/LaraSystemToolsTest.java +++ b/LaraUtils/test/pt/up/fe/specs/lara/LaraSystemToolsTest.java @@ -126,7 +126,7 @@ void testRunCommand_withNullTimeout_handlesGracefully() { // Given String command = "echo hello"; boolean printToConsole = false; - long timeoutNanos = 2000000000; // 2 seconds + Long timeoutNanos = null; // When ProcessOutputAsString result = LaraSystemTools.runCommand(command, workingDirectory, printToConsole, timeoutNanos); From 2d0f2b0ff001ab17273de93439e0410c3773f178 Mon Sep 17 00:00:00 2001 From: "L. Sousa" Date: Tue, 28 Jul 2026 19:05:10 +0100 Subject: [PATCH 2/5] Preserve enum values in TypeScript interfaces --- Lara-JS/code/generate-ts-joinpoints.test.ts | 34 +++++++++++++++++++++ Lara-JS/scripts/generate-ts-joinpoints.ts | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 Lara-JS/code/generate-ts-joinpoints.test.ts diff --git a/Lara-JS/code/generate-ts-joinpoints.test.ts b/Lara-JS/code/generate-ts-joinpoints.test.ts new file mode 100644 index 000000000..3d0710016 --- /dev/null +++ b/Lara-JS/code/generate-ts-joinpoints.test.ts @@ -0,0 +1,34 @@ +import fs from "fs"; +import os from "os"; +import path from "path"; +import { generateEnums } from "../scripts/generate-ts-joinpoints.ts"; + +describe("generateEnums", () => { + it("preserves the enum values from the language specification", () => { + const outputDirectory = fs.mkdtempSync( + path.join(os.tmpdir(), "lara-build-interfaces-"), + ); + const outputPath = path.join(outputDirectory, "Joinpoints.ts"); + const outputFile = fs.openSync(outputPath, "w"); + + try { + generateEnums( + [{ name: "StorageClass", entries: ["NONE", "PRIVATE_EXTERN", "STATIC"] }], + outputFile, + ); + } finally { + fs.closeSync(outputFile); + } + + try { + expect(fs.readFileSync(outputPath, "utf8")).toContain( + ' STATIC: "STATIC",', + ); + expect(fs.readFileSync(outputPath, "utf8")).toContain( + ' PRIVATE_EXTERN: "PRIVATE_EXTERN",', + ); + } finally { + fs.rmSync(outputDirectory, { recursive: true, force: true }); + } + }); +}); diff --git a/Lara-JS/scripts/generate-ts-joinpoints.ts b/Lara-JS/scripts/generate-ts-joinpoints.ts index 9d6dbe111..e65555c3f 100644 --- a/Lara-JS/scripts/generate-ts-joinpoints.ts +++ b/Lara-JS/scripts/generate-ts-joinpoints.ts @@ -207,7 +207,7 @@ function generateEnum(e: ConvertedEnum, outputFile: number) { */\n`); fs.writeSync(outputFile, `export const ${e.name} = {\n`); e.entries.forEach((entry) => { - fs.writeSync(outputFile, ` ${entry}: "${entry.toLowerCase()}",\n`); + fs.writeSync(outputFile, ` ${entry}: "${entry}",\n`); }); fs.writeSync(outputFile, `} as const;\n`); fs.writeSync( From be75685c0ddaa9f80cde3f200d85ce2dc954be51 Mon Sep 17 00:00:00 2001 From: "L. Sousa" Date: Tue, 4 Aug 2026 14:57:41 +0100 Subject: [PATCH 3/5] refactor: simplify enum generator test --- Lara-JS/code/generate-ts-joinpoints.test.ts | 27 +++++++++------------ 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/Lara-JS/code/generate-ts-joinpoints.test.ts b/Lara-JS/code/generate-ts-joinpoints.test.ts index 3d0710016..9976c1152 100644 --- a/Lara-JS/code/generate-ts-joinpoints.test.ts +++ b/Lara-JS/code/generate-ts-joinpoints.test.ts @@ -9,24 +9,21 @@ describe("generateEnums", () => { path.join(os.tmpdir(), "lara-build-interfaces-"), ); const outputPath = path.join(outputDirectory, "Joinpoints.ts"); - const outputFile = fs.openSync(outputPath, "w"); try { - generateEnums( - [{ name: "StorageClass", entries: ["NONE", "PRIVATE_EXTERN", "STATIC"] }], - outputFile, - ); - } finally { - fs.closeSync(outputFile); - } + const outputFile = fs.openSync(outputPath, "w"); + try { + generateEnums( + [{ name: "StorageClass", entries: ["NONE", "PRIVATE_EXTERN", "STATIC"] }], + outputFile, + ); + } finally { + fs.closeSync(outputFile); + } - try { - expect(fs.readFileSync(outputPath, "utf8")).toContain( - ' STATIC: "STATIC",', - ); - expect(fs.readFileSync(outputPath, "utf8")).toContain( - ' PRIVATE_EXTERN: "PRIVATE_EXTERN",', - ); + const output = fs.readFileSync(outputPath, "utf8"); + expect(output).toContain(' STATIC: "STATIC",'); + expect(output).toContain(' PRIVATE_EXTERN: "PRIVATE_EXTERN",'); } finally { fs.rmSync(outputDirectory, { recursive: true, force: true }); } From f7fa270d0e727e2edc80b67fb76ba21a8e732d00 Mon Sep 17 00:00:00 2001 From: "L. Sousa" Date: Sat, 5 Sep 2026 22:14:41 +0100 Subject: [PATCH 4/5] fix(Lara-JS): honor enum display strings in generated TS values Mirror the Java side's effective display (display ?? value) when generating TS enum const values, so the generated string literal type matches what the bridge returns via getDisplay() for specs using the value(value, display) form. Programmatic enums (no display) are unchanged. --- Lara-JS/code/generate-ts-joinpoints.test.ts | 20 ++++++++++++++++++- .../convert-joinpoint-specification.ts | 6 +++--- Lara-JS/scripts/generate-ts-joinpoints.ts | 2 +- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Lara-JS/code/generate-ts-joinpoints.test.ts b/Lara-JS/code/generate-ts-joinpoints.test.ts index 9976c1152..9317e6869 100644 --- a/Lara-JS/code/generate-ts-joinpoints.test.ts +++ b/Lara-JS/code/generate-ts-joinpoints.test.ts @@ -14,7 +14,23 @@ describe("generateEnums", () => { const outputFile = fs.openSync(outputPath, "w"); try { generateEnums( - [{ name: "StorageClass", entries: ["NONE", "PRIVATE_EXTERN", "STATIC"] }], + [ + { + name: "StorageClass", + entries: [ + { name: "NONE", value: "NONE" }, + { name: "PRIVATE_EXTERN", value: "PRIVATE_EXTERN" }, + { name: "STATIC", value: "STATIC" }, + ], + }, + { + name: "AccessSpecifier", + entries: [ + { name: "DEFAULT", value: "DEFAULT" }, + { name: "STATIC", value: "static" }, + ], + }, + ], outputFile, ); } finally { @@ -24,6 +40,8 @@ describe("generateEnums", () => { const output = fs.readFileSync(outputPath, "utf8"); expect(output).toContain(' STATIC: "STATIC",'); expect(output).toContain(' PRIVATE_EXTERN: "PRIVATE_EXTERN",'); + expect(output).toContain('export const AccessSpecifier = {'); + expect(output).toContain(' STATIC: "static",'); } finally { fs.rmSync(outputDirectory, { recursive: true, force: true }); } diff --git a/Lara-JS/scripts/convert-joinpoint-specification.ts b/Lara-JS/scripts/convert-joinpoint-specification.ts index 2195a25ac..ac10536f6 100644 --- a/Lara-JS/scripts/convert-joinpoint-specification.ts +++ b/Lara-JS/scripts/convert-joinpoint-specification.ts @@ -32,7 +32,7 @@ type JSON_EnumSpecification = { type: "enum"; name: string; extends?: string; - children: { value: string }[]; + children: { value: string; display?: string }[]; }; export type ConvertedSpecification = { @@ -74,7 +74,7 @@ export type ConvertedParameter = { export type ConvertedEnum = { name: string; extends?: string; - entries: string[]; + entries: { name: string; value: string }[]; }; export function convertSpecification(input: JSON_LanguageSpecification, baseJoinPointSpec?: ConvertedSpecification | undefined): ConvertedSpecification { @@ -367,7 +367,7 @@ function convertEnum(e: JSON_EnumSpecification): ConvertedEnum { name: e.name, extends: e.extends, entries: e.children.map((child) => { - return child.value; + return { name: child.value, value: child.display ?? child.value }; }), }; } diff --git a/Lara-JS/scripts/generate-ts-joinpoints.ts b/Lara-JS/scripts/generate-ts-joinpoints.ts index e65555c3f..8428b9718 100644 --- a/Lara-JS/scripts/generate-ts-joinpoints.ts +++ b/Lara-JS/scripts/generate-ts-joinpoints.ts @@ -207,7 +207,7 @@ function generateEnum(e: ConvertedEnum, outputFile: number) { */\n`); fs.writeSync(outputFile, `export const ${e.name} = {\n`); e.entries.forEach((entry) => { - fs.writeSync(outputFile, ` ${entry}: "${entry}",\n`); + fs.writeSync(outputFile, ` ${entry.name}: "${entry.value}",\n`); }); fs.writeSync(outputFile, `} as const;\n`); fs.writeSync( From fcf374f94ed7eee82d9d23a3e14b23a5d0801749 Mon Sep 17 00:00:00 2001 From: "L. Sousa" Date: Sat, 5 Sep 2026 22:15:49 +0100 Subject: [PATCH 5/5] feat(LangSpec2): reject duplicate effective displays in enum definitions Duplicate display strings (display ?? value) within an enum make the colliding values unreachable through fromDisplay(), which returns the first match, and collapse the generated TS literal type. Report them in SpecValidator since both the Java emitter and the TS generator derive from the validated model. --- .../langspec2/validation/SpecValidator.java | 14 ++++++ .../validation/SpecValidatorTest.java | 44 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 LangSpec2/test/org/lara/langspec2/validation/SpecValidatorTest.java diff --git a/LangSpec2/src/org/lara/langspec2/validation/SpecValidator.java b/LangSpec2/src/org/lara/langspec2/validation/SpecValidator.java index 6cf0f8f3f..92a530ee8 100644 --- a/LangSpec2/src/org/lara/langspec2/validation/SpecValidator.java +++ b/LangSpec2/src/org/lara/langspec2/validation/SpecValidator.java @@ -35,6 +35,7 @@ public static List collectErrors(WeaverModel model) { checkNoSelfTypeInTypeDefs(model, errors); checkReservedKeywords(model, errors); checkDefaultAttributes(model, errors); + checkEnumDisplays(model, errors); return errors; } @@ -209,5 +210,18 @@ private static void checkDefaultAttributes(WeaverModel model, List error } } + private static void checkEnumDisplays(WeaverModel model, List errors) { + for (var ed : model.getEnumDefs().values()) { + var displays = new HashSet(); + for (var value : ed.values()) { + var display = value.display() != null ? value.display() : value.value(); + if (!displays.add(display)) { + errors.add("Duplicate display '" + display + "' in enum '" + ed.name() + + "': fromDisplay() resolves to the first constant, making the other one unreachable"); + } + } + } + } + private record MemberSignature(String name, List paramTypes) {} } diff --git a/LangSpec2/test/org/lara/langspec2/validation/SpecValidatorTest.java b/LangSpec2/test/org/lara/langspec2/validation/SpecValidatorTest.java new file mode 100644 index 000000000..9c07f3be0 --- /dev/null +++ b/LangSpec2/test/org/lara/langspec2/validation/SpecValidatorTest.java @@ -0,0 +1,44 @@ +package org.lara.langspec2.validation; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.lara.langspec2.model.EnumDef; +import org.lara.langspec2.model.EnumValue; +import org.lara.langspec2.model.JpClass; +import org.lara.langspec2.model.WeaverModel; + +class SpecValidatorTest { + + private static WeaverModel newModel() { + return new WeaverModel("Prefix", "example.pkg", new JpClass("global")); + } + + @Test + void duplicateDisplaysInEnumAreReported() { + var model = newModel(); + model.addEnumDef(new EnumDef("Kind", List.of( + new EnumValue("A"), + new EnumValue("B"), + new EnumValue("C", "A")))); + + var errors = SpecValidator.collectErrors(model); + + assertThat(errors).anyMatch(error -> error.contains("Duplicate display 'A' in enum 'Kind'")); + } + + @Test + void distinctDisplaysInEnumAreAccepted() { + var model = newModel(); + model.addEnumDef(new EnumDef("Kind", List.of( + new EnumValue("A"), + new EnumValue("B"), + new EnumValue("C", "c")))); + + var errors = SpecValidator.collectErrors(model); + + assertThat(errors).isEmpty(); + } +}