From b297bebfe12abb2db32e90f051261945447684c4 Mon Sep 17 00:00:00 2001 From: Miorel-Lucian Palii Date: Wed, 10 Jul 2024 18:26:22 -0700 Subject: [PATCH] Add Java Pair goody Hypothetically we could use [`javafx.util.Pair`](https://docs.oracle.com/javase/8/javafx/api/javafx/util/Pair.html) but I don't love that the fields are named key and value. So let's reinvent the wheel! --- .../goodies/java/src/pair/Pair.java | 3 ++ .../goodies/java/src/pair/goody.json | 3 ++ .../__snapshots__/equip-test.ts.snap | 10 +++++++ .../__snapshots__/render-test.ts.snap | 6 ++++ .../src/app/__tests__/render-test.ts | 4 +-- .../src/app/components/GoodyCard.tsx | 4 +-- tools/adventure-pack/src/app/mergeCode.ts | 4 +-- tools/adventure-pack/src/app/mergeJavaCode.ts | 18 +++++++----- .../src/app/parsers/javaGoodyParser.ts | 2 +- ...ypeScriptModuleAndInterfaceDeclarations.ts | 4 +-- .../app/{goodyToText.ts => stringifyGoody.ts} | 2 +- ...tringifyTypeScriptInterfaceDeclarations.ts | 6 ++-- .../stringifyTypeScriptModuleDeclarations.ts | 2 +- .../java/splitCodeIntoClasses.ts | 29 ++++++++++++++----- 14 files changed, 68 insertions(+), 29 deletions(-) create mode 100644 tools/adventure-pack/goodies/java/src/pair/Pair.java create mode 100644 tools/adventure-pack/goodies/java/src/pair/goody.json rename tools/adventure-pack/src/app/{goodyToText.ts => stringifyGoody.ts} (95%) diff --git a/tools/adventure-pack/goodies/java/src/pair/Pair.java b/tools/adventure-pack/goodies/java/src/pair/Pair.java new file mode 100644 index 00000000..3fb94080 --- /dev/null +++ b/tools/adventure-pack/goodies/java/src/pair/Pair.java @@ -0,0 +1,3 @@ +package pair; + +public record Pair(TFirst first, TSecond second) {} diff --git a/tools/adventure-pack/goodies/java/src/pair/goody.json b/tools/adventure-pack/goodies/java/src/pair/goody.json new file mode 100644 index 00000000..7ab923f3 --- /dev/null +++ b/tools/adventure-pack/goodies/java/src/pair/goody.json @@ -0,0 +1,3 @@ +{ + "name": "Pair" +} diff --git a/tools/adventure-pack/src/app/__tests__/__snapshots__/equip-test.ts.snap b/tools/adventure-pack/src/app/__tests__/__snapshots__/equip-test.ts.snap index 02abc76f..d8db4b31 100644 --- a/tools/adventure-pack/src/app/__tests__/__snapshots__/equip-test.ts.snap +++ b/tools/adventure-pack/src/app/__tests__/__snapshots__/equip-test.ts.snap @@ -1,5 +1,15 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`App can equip single goody: Java Pair 1`] = ` +"////////////////////////// BEGIN ADVENTURE PACK CODE /////////////////////////// +// Adventure Pack commit fake-commit-hash +// Running at: https://example.com/ + +record Pair(TFirst first, TSecond second) {} + +/////////////////////////// END ADVENTURE PACK CODE ////////////////////////////" +`; + exports[`App can equip single goody: Java UnionFind 1`] = ` "////////////////////////// BEGIN ADVENTURE PACK CODE /////////////////////////// // Adventure Pack commit fake-commit-hash diff --git a/tools/adventure-pack/src/app/__tests__/__snapshots__/render-test.ts.snap b/tools/adventure-pack/src/app/__tests__/__snapshots__/render-test.ts.snap index dca3b7ef..38d83bf4 100644 --- a/tools/adventure-pack/src/app/__tests__/__snapshots__/render-test.ts.snap +++ b/tools/adventure-pack/src/app/__tests__/__snapshots__/render-test.ts.snap @@ -1,5 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`App can render goody: Java Pair 1`] = ` +"package pair; + +record Pair(TFirst first, TSecond second) {}" +`; + exports[`App can render goody: Java UnionFind 1`] = ` "package union_find; diff --git a/tools/adventure-pack/src/app/__tests__/render-test.ts b/tools/adventure-pack/src/app/__tests__/render-test.ts index 0383b042..14a233fb 100644 --- a/tools/adventure-pack/src/app/__tests__/render-test.ts +++ b/tools/adventure-pack/src/app/__tests__/render-test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from "@jest/globals"; import { LANGUAGE_NAMES } from "../constants"; import type { Language } from "../Language"; import { readAllGoodies } from "../../scripts/package-goodies/readAllGoodies"; -import { goodyToText } from "../goodyToText"; +import { stringifyGoody } from "../stringifyGoody"; describe("App", () => { it("can render goody", async () => { @@ -12,7 +12,7 @@ describe("App", () => { for (const language of Object.keys(goodiesByLanguage) as Language[]) { const goodies = goodiesByLanguage[language]; for (const goody of Object.values(goodies)) { - const code = goodyToText(goody); + const code = stringifyGoody(goody); expect(code).toMatchSnapshot( `${LANGUAGE_NAMES[language]} ${goody.name}`, diff --git a/tools/adventure-pack/src/app/components/GoodyCard.tsx b/tools/adventure-pack/src/app/components/GoodyCard.tsx index 0f5920d2..c9905442 100644 --- a/tools/adventure-pack/src/app/components/GoodyCard.tsx +++ b/tools/adventure-pack/src/app/components/GoodyCard.tsx @@ -1,7 +1,7 @@ import React from "react"; import type { Goody } from "../Goody"; -import { goodyToText } from "../goodyToText"; +import { stringifyGoody } from "../stringifyGoody"; import { HighlightedCode } from "./HighlightedCode"; type Props = { @@ -13,7 +13,7 @@ export function GoodyCard({ goody }: Props) {

{goody.name}

- {goodyToText(goody)} + {stringifyGoody(goody)}
); diff --git a/tools/adventure-pack/src/app/mergeCode.ts b/tools/adventure-pack/src/app/mergeCode.ts index 8903a2ff..59b8f20f 100644 --- a/tools/adventure-pack/src/app/mergeCode.ts +++ b/tools/adventure-pack/src/app/mergeCode.ts @@ -127,11 +127,11 @@ export function mergeCode({ for (const [moduleName, interfaceDeclarations] of Object.entries( goody.moduleDeclarations, )) { - for (const [interfaceName, codeGroups] of Object.entries( + for (const [interfaceName, codeSections] of Object.entries( interfaceDeclarations, )) { ((mergedDeclarations[moduleName] ??= {})[interfaceName] ??= - []).push(...codeGroups); + []).push(...codeSections); } } } diff --git a/tools/adventure-pack/src/app/mergeJavaCode.ts b/tools/adventure-pack/src/app/mergeJavaCode.ts index 86878b12..c93312b2 100644 --- a/tools/adventure-pack/src/app/mergeJavaCode.ts +++ b/tools/adventure-pack/src/app/mergeJavaCode.ts @@ -6,8 +6,7 @@ import type { JavaGoody } from "./parsers/javaGoodyParser"; const ADVENTURE_PACK_CLASS_NAME = "AP"; export function mergeJavaCode(goodies: Iterable>) { - const classes: Record }> = - {}; + const classes: Record = {}; for (const goody of goodies) { for (const className of Object.keys(goody.codeByClass)) { invariant( @@ -15,10 +14,10 @@ export function mergeJavaCode(goodies: Iterable>) { `Only the ${ADVENTURE_PACK_CLASS_NAME} class can exist in multiple goodies!`, ); - classes[className] ??= { code: [], modifiers: new Set() }; - for (const modifier of goody.codeByClass[className].modifiers) { - classes[className].modifiers.add(modifier); - } + classes[className] ??= { + code: [], + declaration: goody.codeByClass[className].declaration, + }; classes[className].code.push(goody.codeByClass[className].code); } } @@ -32,8 +31,13 @@ export function mergeJavaCode(goodies: Iterable>) { const res: string[] = []; for (const className of Object.keys(classes)) { + const classData = classes[className]; + const codeSections = classData.code.filter(Boolean); + res.push( - `${[...classes[className].modifiers, "class", className].join(" ")} {\n${classes[className].code.map((section) => section + "\n").join("\n")}}`, + codeSections.length > 0 + ? `${classData.declaration}\n${codeSections.map((codeSection) => codeSection + "\n").join("\n")}}` + : classData.declaration + "}", ); } diff --git a/tools/adventure-pack/src/app/parsers/javaGoodyParser.ts b/tools/adventure-pack/src/app/parsers/javaGoodyParser.ts index 6acdbf18..4705407f 100644 --- a/tools/adventure-pack/src/app/parsers/javaGoodyParser.ts +++ b/tools/adventure-pack/src/app/parsers/javaGoodyParser.ts @@ -11,7 +11,7 @@ export const javaGoodyParser = goodyBaseParser z .object({ code: z.string(), - modifiers: z.array(nonBlankStringParser), + declaration: nonBlankStringParser, }) .strict(), ), diff --git a/tools/adventure-pack/src/app/sortTypeScriptModuleAndInterfaceDeclarations.ts b/tools/adventure-pack/src/app/sortTypeScriptModuleAndInterfaceDeclarations.ts index 8a2f27eb..66a7cb7d 100644 --- a/tools/adventure-pack/src/app/sortTypeScriptModuleAndInterfaceDeclarations.ts +++ b/tools/adventure-pack/src/app/sortTypeScriptModuleAndInterfaceDeclarations.ts @@ -12,8 +12,8 @@ export function sortTypeScriptModuleAndInterfaceDeclarations( ): Record> { return sortObjectKeysRecursive( mapObjectValues(moduleDeclarations, (interfaceDeclarations) => - mapObjectValues(interfaceDeclarations, (codeGroups) => - [...codeGroups].sort(compareStringsCaseInsensitive), + mapObjectValues(interfaceDeclarations, (codeSections) => + [...codeSections].sort(compareStringsCaseInsensitive), ), ), compareStringsCaseInsensitive, diff --git a/tools/adventure-pack/src/app/goodyToText.ts b/tools/adventure-pack/src/app/stringifyGoody.ts similarity index 95% rename from tools/adventure-pack/src/app/goodyToText.ts rename to tools/adventure-pack/src/app/stringifyGoody.ts index 2e4c2815..9a02bbe5 100644 --- a/tools/adventure-pack/src/app/goodyToText.ts +++ b/tools/adventure-pack/src/app/stringifyGoody.ts @@ -2,7 +2,7 @@ import type { Goody } from "./Goody"; import { mergeJavaCode } from "./mergeJavaCode"; import { stringifyTypeScriptModuleDeclarations } from "./stringifyTypeScriptModuleDeclarations"; -export function goodyToText(goody: Goody): string { +export function stringifyGoody(goody: Goody): string { switch (goody.language) { case "java": { return ( diff --git a/tools/adventure-pack/src/app/stringifyTypeScriptInterfaceDeclarations.ts b/tools/adventure-pack/src/app/stringifyTypeScriptInterfaceDeclarations.ts index e2325a94..b1b95172 100644 --- a/tools/adventure-pack/src/app/stringifyTypeScriptInterfaceDeclarations.ts +++ b/tools/adventure-pack/src/app/stringifyTypeScriptInterfaceDeclarations.ts @@ -2,12 +2,12 @@ import type { ReadonlyDeep } from "type-fest"; export function stringifyTypeScriptInterfaceDeclarations( interfaceDeclarations: ReadonlyDeep>, - indent: string = "", + { indent = "" }: { indent?: string } = {}, ): string { return Object.entries(interfaceDeclarations) .map( - ([interfaceName, codeGroups]) => - `${indent}interface ${interfaceName} {\n${codeGroups.join("\n\n")}\n${indent}}`, + ([interfaceName, codeSections]) => + `${indent}interface ${interfaceName} {\n${codeSections.join("\n\n")}\n${indent}}`, ) .join("\n\n"); } diff --git a/tools/adventure-pack/src/app/stringifyTypeScriptModuleDeclarations.ts b/tools/adventure-pack/src/app/stringifyTypeScriptModuleDeclarations.ts index 477c34ef..a9edeb8c 100644 --- a/tools/adventure-pack/src/app/stringifyTypeScriptModuleDeclarations.ts +++ b/tools/adventure-pack/src/app/stringifyTypeScriptModuleDeclarations.ts @@ -8,7 +8,7 @@ export function stringifyTypeScriptModuleDeclarations( return Object.entries(moduleDeclarations) .map( ([moduleName, interfaceDeclarations]) => - `declare ${moduleName} {\n${stringifyTypeScriptInterfaceDeclarations(interfaceDeclarations, " ")}\n}`, + `declare ${moduleName} {\n${stringifyTypeScriptInterfaceDeclarations(interfaceDeclarations, { indent: " " })}\n}`, ) .join("\n\n"); } diff --git a/tools/adventure-pack/src/scripts/package-goodies/java/splitCodeIntoClasses.ts b/tools/adventure-pack/src/scripts/package-goodies/java/splitCodeIntoClasses.ts index c872ccde..43e1fb6e 100644 --- a/tools/adventure-pack/src/scripts/package-goodies/java/splitCodeIntoClasses.ts +++ b/tools/adventure-pack/src/scripts/package-goodies/java/splitCodeIntoClasses.ts @@ -1,6 +1,7 @@ import invariant from "invariant"; import { + compareStringsCaseInsensitive, getLines, isStringEmptyOrWhitespaceOnly, mapObjectValues, @@ -8,14 +9,13 @@ import { export function splitCodeIntoClasses( code: string, -): Record { - const classes: Record }> = - {}; +): Record { + const classes: Record = {}; let currentClassName: string | null = null; for (const line of getLines(code)) { const classMatch = line.match( - /^((?:(?:abstract|final|public)\s+)*)class\s+(\S+)\s*{/, + /^((?:(?:abstract|final|public)\s+)*)((?:class|enum|interface|record)\s+(\S+)\s*[{<].*)$/s, ); if (classMatch != null) { invariant(currentClassName == null, "Top-level class nesting?"); @@ -23,8 +23,21 @@ export function splitCodeIntoClasses( const modifiers = new Set(classMatch[1].trim().split(/\s+/)); modifiers.delete("public"); - currentClassName = classMatch[2]; - classes[currentClassName] = { code: [], modifiers }; + currentClassName = classMatch[3]; + classes[currentClassName] = { + code: [], + declaration: [ + ...Array.from(modifiers).sort(compareStringsCaseInsensitive), + classMatch[2].replace(/}?\n*$/, ""), + ] + .filter(Boolean) + .join(" "), + }; + + if (/}\n*$/.test(line)) { + currentClassName = null; + } + continue; } @@ -44,8 +57,8 @@ export function splitCodeIntoClasses( invariant(currentClassName == null, "Unfinished class?"); - return mapObjectValues(classes, ({ code, modifiers }) => ({ + return mapObjectValues(classes, ({ code, declaration }) => ({ code: code.join("").replace(/^\n+/, "").replace(/\n+$/, ""), - modifiers: Array.from(modifiers), + declaration, })); }