Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions workspaces/adventure-pack/src/app/mergeCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { mergeJavaCode } from "./mergeJavaCode";
import type { JavaGoody } from "./zod-types/javaGoodyZodType";
import { sortTypeScriptModuleAndInterfaceDeclarations } from "./sortTypeScriptModuleAndInterfaceDeclarations";
import { stringifyTypeScriptModuleDeclarations } from "./stringifyTypeScriptModuleDeclarations";
import type { GoodyModuleDeclaration } from "../scripts/package-goodies/typescript/extractModuleDeclarations";

function topo({
equippedGoodies,
Expand Down Expand Up @@ -116,21 +117,29 @@ export function mergeCode({
return "";
}

const mergedDeclarations: Record<string, Record<string, string[]>> = {};
const mergedDeclarations: Record<string, GoodyModuleDeclaration> = {};
for (const goody of orderedGoodies) {
invariant(
goody.language === "typescript",
"Goody language must match language!",
);

for (const [moduleName, interfaceDeclarations] of Object.entries(
goody.moduleDeclarations,
)) {
for (const [
moduleName,
{ interfaces = {}, variables = [] },
] of Object.entries(goody.moduleDeclarations)) {
for (const [interfaceName, codeSections] of Object.entries(
interfaceDeclarations,
interfaces,
)) {
((mergedDeclarations[moduleName] ??= {})[interfaceName] ??=
[]).push(...codeSections);
(((mergedDeclarations[moduleName] ??= {}).interfaces ??= {})[
interfaceName
] ??= []).push(...codeSections);
}

if (variables.length > 0) {
((mergedDeclarations[moduleName] ??= {}).variables ??= []).push(
...variables,
);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,33 @@ import { compareStringsCaseInsensitive } from "@code-chronicles/util/compareStri
import { mapObjectValues } from "@code-chronicles/util/mapObjectValues";
import { sortObjectKeysRecursive } from "@code-chronicles/util/sortObjectKeysRecursive";

import type { GoodyModuleDeclaration } from "../scripts/package-goodies/typescript/extractModuleDeclarations";

export function sortTypeScriptModuleAndInterfaceDeclarations(
moduleDeclarations: ReadonlyDeep<Record<string, Record<string, string[]>>>,
): Record<string, Record<string, string[]>> {
moduleDeclarations: ReadonlyDeep<Record<string, GoodyModuleDeclaration>>,
): Record<string, GoodyModuleDeclaration> {
return sortObjectKeysRecursive(
mapObjectValues(moduleDeclarations, (interfaceDeclarations) =>
mapObjectValues(interfaceDeclarations, (codeSections) =>
[...codeSections].sort(compareStringsCaseInsensitive),
),
mapObjectValues(
moduleDeclarations,
(
moduleDeclaration: ReadonlyDeep<GoodyModuleDeclaration>,
): GoodyModuleDeclaration => ({
...(moduleDeclaration.interfaces && {
interfaces: mapObjectValues(
moduleDeclaration.interfaces,
(codeSections) =>
[...codeSections].sort(compareStringsCaseInsensitive),
),
}),

...(moduleDeclaration.variables && {
variables: [...moduleDeclaration.variables].sort(
compareStringsCaseInsensitive,
),
}),
}),
),
compareStringsCaseInsensitive,
2,
3,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,26 @@ import type { ReadonlyDeep } from "type-fest";

import { stringifyTypeScriptInterfaceDeclarations } from "./stringifyTypeScriptInterfaceDeclarations";

import type { GoodyModuleDeclaration } from "../scripts/package-goodies/typescript/extractModuleDeclarations";
import { isStringEmptyOrWhitespaceOnly } from "@code-chronicles/util/isStringEmptyOrWhitespaceOnly";

const INDENT = " ";

export function stringifyTypeScriptModuleDeclarations(
moduleDeclarations: ReadonlyDeep<Record<string, Record<string, string[]>>>,
moduleDeclarations: ReadonlyDeep<Record<string, GoodyModuleDeclaration>>,
): string {
return Object.entries(moduleDeclarations)
.map(
([moduleName, interfaceDeclarations]) =>
`declare ${moduleName} {\n${stringifyTypeScriptInterfaceDeclarations(interfaceDeclarations, { indent: " " })}\n}`,
)
.map(([moduleName, { interfaces = {}, variables = [] }]) => {
const body = [
stringifyTypeScriptInterfaceDeclarations(interfaces, {
indent: INDENT,
}),
...variables.map((variable) => INDENT + variable),
]
.filter((s) => !isStringEmptyOrWhitespaceOnly(s))
.join("\n\n");

return `declare ${moduleName} {\n${body}\n}`;
})
.join("\n\n");
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ export const typeScriptGoodyZodType = goodyBaseZodType
code: nonBlankStringZodType,
moduleDeclarations: z.record(
z.string(),
z.record(z.string(), z.array(nonBlankStringZodType)),
z.strictObject({
interfaces: z
.record(z.string(), z.array(nonBlankStringZodType))
.optional(),
variables: z.array(nonBlankStringZodType).optional(),
}),
),
language: z.literal("typescript"),
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,47 +1,68 @@
import { type SourceFile as TSSourceFile, SyntaxKind } from "ts-morph";

import { invariantViolation } from "@code-chronicles/util/invariantViolation";
import { sortTypeScriptModuleAndInterfaceDeclarations } from "../../../app/sortTypeScriptModuleAndInterfaceDeclarations";
import { removeNode } from "./removeNode";

export type GoodyModuleDeclaration = {
interfaces?: Record<string, string[]>;
variables?: string[];
};

export function extractModuleDeclarations(
sourceFile: TSSourceFile,
): Record<string, Record<string, string[]>> {
const res: Record<string, Record<string, string[]>> = {};
): Record<string, GoodyModuleDeclaration> {
const res: Record<string, GoodyModuleDeclaration> = {};

sourceFile.getModules().forEach((mod) => {
mod
.getBodyOrThrow()
.getChildSyntaxListOrThrow()
.getChildren()
.forEach((child) => {
if (child.getKind() === SyntaxKind.SingleLineCommentTrivia) {
return;
switch (child.getKind()) {
case SyntaxKind.SingleLineCommentTrivia: {
return;
}
case SyntaxKind.InterfaceDeclaration: {
const interfaceDecl = child.asKindOrThrow(
SyntaxKind.InterfaceDeclaration,
);

const interfaceName = interfaceDecl.getName();
const typeParameters = interfaceDecl
.getFirstChildByKind(SyntaxKind.TypeParameter)
?.getParentSyntaxListOrThrow()
.getFullText();

const interfaceKey =
typeParameters == null
? interfaceName
: `${interfaceName}<${typeParameters}>`;

(((res[mod.getName()] ??= {}).interfaces ??= {})[interfaceKey] ??=
[]).push(
interfaceDecl
.getChildSyntaxListOrThrow()
.getFullText()
.replace(/^\n+/, "")
.replace(/\n+$/, ""),
);

return;
}
case SyntaxKind.VariableStatement: {
((res[mod.getName()] ??= {}).variables ??= []).push(
child.getFullText().trim(),
);
return;
}
}

const interfaceDecl = child.asKindOrThrow(
SyntaxKind.InterfaceDeclaration,
"Only interface declarations are currently supported in module declarations, got: " +
invariantViolation(
"Only interface declarations or variable statements are currently supported in module declarations, got: " +
child.getKindName(),
);

const interfaceName = interfaceDecl.getName();
const typeParameters = interfaceDecl
.getFirstChildByKind(SyntaxKind.TypeParameter)
?.getParentSyntaxListOrThrow()
.getFullText();

const interfaceKey =
typeParameters == null
? interfaceName
: `${interfaceName}<${typeParameters}>`;

((res[mod.getName()] ??= {})[interfaceKey] ??= []).push(
interfaceDecl
.getChildSyntaxListOrThrow()
.getFullText()
.replace(/^\n+/, "")
.replace(/\n+$/, ""),
);
});

removeNode(mod);
Expand Down