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(); + } +} 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..9317e6869 --- /dev/null +++ b/Lara-JS/code/generate-ts-joinpoints.test.ts @@ -0,0 +1,49 @@ +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"); + + try { + const outputFile = fs.openSync(outputPath, "w"); + try { + generateEnums( + [ + { + 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 { + fs.closeSync(outputFile); + } + + 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 9d6dbe111..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.toLowerCase()}",\n`); + fs.writeSync(outputFile, ` ${entry.name}: "${entry.value}",\n`); }); fs.writeSync(outputFile, `} as const;\n`); fs.writeSync( 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);