From 7d613ed5e5adeb1074e0d5700279fdc00dd8327c Mon Sep 17 00:00:00 2001 From: "kamil.lydka" Date: Sat, 21 Jun 2025 23:15:10 +0200 Subject: [PATCH 01/14] feat: generate type mirror script --- package-lock.json | 19 ++-- package.json | 4 +- src/classses/generated/TypeMirror/index.ts | 92 +++++++++++++++++++ src/regexes/index.ts | 3 + .../generateTypeMirrorClass/codegen.ts | 32 +++++++ .../generateTypeMirrorClass/fileUtils.ts | 23 +++++ src/scripts/generateTypeMirrorClass/index.ts | 24 +++++ .../typeProcessing/clean.ts | 4 + .../typeProcessing/guards.ts | 6 ++ .../typeProcessing/processing.ts | 64 +++++++++++++ .../typeProcessing/types.ts | 17 ++++ 11 files changed, 276 insertions(+), 12 deletions(-) create mode 100644 src/classses/generated/TypeMirror/index.ts create mode 100644 src/scripts/generateTypeMirrorClass/codegen.ts create mode 100644 src/scripts/generateTypeMirrorClass/fileUtils.ts create mode 100644 src/scripts/generateTypeMirrorClass/index.ts create mode 100644 src/scripts/generateTypeMirrorClass/typeProcessing/clean.ts create mode 100644 src/scripts/generateTypeMirrorClass/typeProcessing/guards.ts create mode 100644 src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts create mode 100644 src/scripts/generateTypeMirrorClass/typeProcessing/types.ts diff --git a/package-lock.json b/package-lock.json index 7b4914c..22f49b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "@eslint/js": "^9.25.1", + "@types/node": "^24.0.3", "@types/ramda": "^0.30.2", "eslint-config-prettier": "^10.1.2", "globals": "^16.0.0", @@ -992,13 +993,11 @@ "peer": true }, "node_modules/@types/node": { - "version": "22.14.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", - "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", - "dev": true, - "peer": true, + "version": "24.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz", + "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.8.0" } }, "node_modules/@types/ramda": { @@ -3961,11 +3960,9 @@ "dev": true }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "peer": true + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==" }, "node_modules/union": { "version": "0.5.0", diff --git a/package.json b/package.json index a55a322..745fa0e 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "graph:build": "depcruise src --include-only src --output-type dot | dot -T svg | depcruise-wrap-stream-in-html > index.html", "graph:test": "depcruise src --include-only src", "graph:watch": "nodemon --watch src --ext ts --exec \"npm run graph:build\"", - "graph:serve": "concurrently \"npm run graph:watch\" \"vite\"" + "graph:serve": "concurrently \"npm run graph:watch\" \"vite\"", + "generate-type-mirror": "npx tsx src/scripts/generateTypeMirrorClass" }, "keywords": [], "author": "", @@ -27,6 +28,7 @@ }, "dependencies": { "@eslint/js": "^9.25.1", + "@types/node": "^24.0.3", "@types/ramda": "^0.30.2", "eslint-config-prettier": "^10.1.2", "globals": "^16.0.0", diff --git a/src/classses/generated/TypeMirror/index.ts b/src/classses/generated/TypeMirror/index.ts new file mode 100644 index 0000000..6085141 --- /dev/null +++ b/src/classses/generated/TypeMirror/index.ts @@ -0,0 +1,92 @@ +export class TypeUtils { + + ReTypeError(_ErrorType: keyof ErrorsLookup, Context: string, Value, Constraint = unknown): string { + return `ReTypeError<${_ErrorType}, ${Context}, ${Value}, ${Constraint}>` + } + + EmptyStringError(CX: string, T, Constraint = unknown): string { + return `EmptyStringError<${CX}, ${T}, ${Constraint}>` + } + + AnyMatchError$(T): string { + return `AnyMatchError$<${T}>` + } + + FilterError$(T): string { + return `FilterError$<${T}>` + } + + Trace(Context: string, Name: string, Next: string = ""): string { + return `Trace<${Context}, ${Name}, ${Next}>` + } + + _FlatValidate$(T, CX: string, BypassMode: BYPASS_MODES = BypassModes["off"]): string { + return `_FlatValidate$<${T}, ${CX}, ${BypassMode}>` + } + + Validate$(T, CX: string = ""): string { + return `Validate$<${T}, ${CX}>` + } + + EitherValidate(T, CX: string = ""): string { + return `EitherValidate<${T}, ${CX}>` + } + + ValidateArr$(Args: unknown[], Context: string, Index: any[] = []): string { + return `ValidateArr$<${Args}, ${Context}, ${Index}>` + } + + ValidateEmptyString$(T): string { + return `ValidateEmptyString$<${T}>` + } + + EitherValidate_EmptyString$(T): string { + return `EitherValidate_EmptyString$<${T}>` + } + + ValidateLiteral$(Mode: BYPASS_MODES, T, Match): string { + return `ValidateLiteral$<${Mode}, ${T}, ${Match}>` + } + + Validate_StringLiteral(T): string { + return `Validate_StringLiteral<${T}>` + } + + Validate_NumberLiteral(T): string { + return `Validate_NumberLiteral<${T}>` + } + + Validate_BooleanLiteral(T): string { + return `Validate_BooleanLiteral<${T}>` + } + + EitherValidate_StringLiteral(T): string { + return `EitherValidate_StringLiteral<${T}>` + } + + EitherValidate_NumberLiteral(T): string { + return `EitherValidate_NumberLiteral<${T}>` + } + + EitherValidate_BooleanLiteral(T): string { + return `EitherValidate_BooleanLiteral<${T}>` + } + + ValidateType$(CX: string, T$, Match): string { + return `ValidateType$<${CX}, ${T$}, ${Match}>` + } + + EitherValidate_Type$(T$, Match): string { + return `EitherValidate_Type$<${T$}, ${Match}>` + } + + ValidateUsableSting$(T): string { + return `ValidateUsableSting$<${T}>` + } + + CH_ValidateUsableSting$(T): string { + return `CH_ValidateUsableSting$<${T}>` + } + + } + \ No newline at end of file diff --git a/src/regexes/index.ts b/src/regexes/index.ts index 1443bff..63f922f 100644 --- a/src/regexes/index.ts +++ b/src/regexes/index.ts @@ -1,3 +1,6 @@ export const regexes = { extractTypesAndValidations: /type ([\w$]*)<([^<>]*)>\s*=\s*(.*)/, + exportType: /\bexport\s+type\b/, + blockComment: /\/\*\*[\s\S]*?\*\//g, + lineComment: /\/\/.*/g, } diff --git a/src/scripts/generateTypeMirrorClass/codegen.ts b/src/scripts/generateTypeMirrorClass/codegen.ts new file mode 100644 index 0000000..20116af --- /dev/null +++ b/src/scripts/generateTypeMirrorClass/codegen.ts @@ -0,0 +1,32 @@ +import type { METHOD } from "./typeProcessing/types" + +export const generateParamsDeclaration = ( + generics: { name: string; constraint?: string; defaultValue?: string }[], +): string => + generics + .map( + ({ name, constraint, defaultValue }) => + `${name}${constraint ? `: ${constraint}` : ""}${defaultValue ? ` = ${defaultValue}` : ""}`, + ) + .join(", ") + +export const generateParamsInterpolation = (generics: { name: string }[]): string => + generics.map((g) => `\${${g.name}}`).join(", ") + +export const generateMethod = ({ name, generics }: METHOD): string => { + const paramsDecl = generateParamsDeclaration(generics) + const paramsInterp = generateParamsInterpolation(generics) + return ` + ${name}(${paramsDecl}): string { + return \`${name}<${paramsInterp}>\` + }` +} + +export const generateClassBody = (methods: METHOD[]): string => methods.map(generateMethod).join("\n") + +export const generateOutput = (methods: METHOD[]): string => + `export class TypeUtils { + ${generateClassBody(methods)} + + } + ` diff --git a/src/scripts/generateTypeMirrorClass/fileUtils.ts b/src/scripts/generateTypeMirrorClass/fileUtils.ts new file mode 100644 index 0000000..93602ce --- /dev/null +++ b/src/scripts/generateTypeMirrorClass/fileUtils.ts @@ -0,0 +1,23 @@ +import * as fs from "fs" +import * as path from "path" + +export const isTypeScriptFile = (fileName: string): boolean => { + return fileName.endsWith(".ts") && !fileName.endsWith(".d.ts") +} + +export const collectTypeFiles = (dir: string): string[] => { + const files: string[] = [] + const entries = fs.readdirSync(dir, { withFileTypes: true }) + + for (const entry of entries) { + const fullPath = path.join(dir, entry.name) + + if (entry.isDirectory()) { + files.push(...collectTypeFiles(fullPath)) + } else if (entry.isFile() && isTypeScriptFile(entry.name)) { + files.push(fullPath) + } + } + + return files +} diff --git a/src/scripts/generateTypeMirrorClass/index.ts b/src/scripts/generateTypeMirrorClass/index.ts new file mode 100644 index 0000000..88288d9 --- /dev/null +++ b/src/scripts/generateTypeMirrorClass/index.ts @@ -0,0 +1,24 @@ +import * as ts from "typescript" +import * as fs from "fs" +import * as path from "path" +import { collectTypeFiles } from "./fileUtils" +import { processAllFiles } from "./typeProcessing/processing" +import { generateOutput } from "./codegen" + +const SCRIPT_TARGET = ts.ScriptTarget.ES2020 + +const typesDir = path.resolve(process.cwd(), "src/coreTypes") +const outputFilePath = path.resolve(process.cwd(), "src/classses/generated/TypeMirror/index.ts") // TODO: Remove typo 'classSes' when will be fixed in the codebase + +const main = (): void => { + const allFiles = collectTypeFiles(typesDir) + const { parsedTypes, failedTypes } = processAllFiles(allFiles, SCRIPT_TARGET) + const output = generateOutput(parsedTypes) + + fs.writeFileSync(outputFilePath, output) + + console.log("Broken types:", failedTypes.length) + console.log("Broken types names:", failedTypes.map((type) => type.name).join("\n")) +} + +main() diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/clean.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/clean.ts new file mode 100644 index 0000000..936a21e --- /dev/null +++ b/src/scripts/generateTypeMirrorClass/typeProcessing/clean.ts @@ -0,0 +1,4 @@ +import { regexes } from "../../../regexes" + +export const cleanTypeDef = (raw: string): string => + raw.replace(regexes.exportType, "type").replace(regexes.blockComment, "").replace(regexes.lineComment, "") diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/guards.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/guards.ts new file mode 100644 index 0000000..7de36bc --- /dev/null +++ b/src/scripts/generateTypeMirrorClass/typeProcessing/guards.ts @@ -0,0 +1,6 @@ +import * as ts from "typescript" + +export const isTypeAlias = (node: ts.Node): node is ts.TypeAliasDeclaration => ts.isTypeAliasDeclaration(node) + +export const isExported = (node: ts.TypeAliasDeclaration): boolean => + node.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword) ?? false diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts new file mode 100644 index 0000000..8ee733c --- /dev/null +++ b/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts @@ -0,0 +1,64 @@ +import * as ts from "typescript" +import * as fs from "fs" +import { parseTypeDeclaration } from "../../../utils/parseTypeDeclarations" +import { cleanTypeDef } from "./clean" +import { isTypeAlias, isExported } from "./guards" +import type { METHOD, BROKEN_TYPE, PARSE_RESULT } from "./types" + +const CHARACTER_ENCODING = "utf-8" + +export function processExportedTypeAlias( + node: ts.TypeAliasDeclaration, + sourceText: string, +): { parsed?: METHOD; failed?: BROKEN_TYPE } { + const name = node.name.text + + const rawText = sourceText.slice(node.pos, node.end).trim() + const cleaned = cleanTypeDef(rawText) + + try { + const { name, generics } = parseTypeDeclaration(cleaned) + return { parsed: { name, generics } } + } catch { + return { failed: { name } } + } +} + +export function visitSourceFile(filePath: string, scriptTarget: ts.ScriptTarget): PARSE_RESULT { + const sourceText = fs.readFileSync(filePath, CHARACTER_ENCODING) + + const file = ts.createSourceFile(filePath, sourceText, scriptTarget, true) + + const parsedTypes: METHOD[] = [] + const failedTypes: BROKEN_TYPE[] = [] + + file.forEachChild((node) => { + if (isTypeAlias(node) && isExported(node)) { + const result = processExportedTypeAlias(node, sourceText) + + if (result.parsed) { + parsedTypes.push(result.parsed) + } + + if (result.failed) { + failedTypes.push(result.failed) + } + } + }) + + return { parsedTypes, failedTypes } +} + +export function processAllFiles(filePaths: string[], scriptTarget: ts.ScriptTarget): PARSE_RESULT { + return filePaths.reduce( + (acc, filePath) => { + const { parsedTypes, failedTypes } = visitSourceFile(filePath, scriptTarget) + + acc.parsedTypes.push(...parsedTypes) + acc.failedTypes.push(...failedTypes) + + return acc + }, + { parsedTypes: [], failedTypes: [] }, + ) +} diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/types.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/types.ts new file mode 100644 index 0000000..2f603ef --- /dev/null +++ b/src/scripts/generateTypeMirrorClass/typeProcessing/types.ts @@ -0,0 +1,17 @@ +import { GENERIC } from "../../../utils/parseTypeDeclarations" + +type METHOD = { + name: string + generics: GENERIC[] +} + +type BROKEN_TYPE = { + name: string +} + +type PARSE_RESULT = { + parsedTypes: METHOD[] + failedTypes: BROKEN_TYPE[] +} + +export type { GENERIC, METHOD, BROKEN_TYPE, PARSE_RESULT } From 12afff0cf1fb1dacd6adf9d7b6993b887c4fc4fb Mon Sep 17 00:00:00 2001 From: "kamil.lydka" Date: Thu, 26 Jun 2025 20:00:01 +0200 Subject: [PATCH 02/14] feat: generate type mirror script - improvements --- src/classses/generated/TypeMirror/index.ts | 19 +++++++++--- .../generateTypeMirrorClass/codegen.ts | 23 +++++++++----- src/scripts/generateTypeMirrorClass/index.ts | 10 +++--- .../typeProcessing/processing.ts | 31 +++++++++---------- .../typeProcessing/{clean.ts => sanitize.ts} | 2 +- .../typeProcessing/types.ts | 20 +++++++----- .../{fileUtils.ts => utils/file.ts} | 4 +-- .../generateTypeMirrorClass/utils/import.ts | 28 +++++++++++++++++ 8 files changed, 93 insertions(+), 44 deletions(-) rename src/scripts/generateTypeMirrorClass/typeProcessing/{clean.ts => sanitize.ts} (74%) rename src/scripts/generateTypeMirrorClass/{fileUtils.ts => utils/file.ts} (78%) create mode 100644 src/scripts/generateTypeMirrorClass/utils/import.ts diff --git a/src/classses/generated/TypeMirror/index.ts b/src/classses/generated/TypeMirror/index.ts index 6085141..773f8f0 100644 --- a/src/classses/generated/TypeMirror/index.ts +++ b/src/classses/generated/TypeMirror/index.ts @@ -1,3 +1,8 @@ + + import type { ErrorsLookup } from "coreTypes/errors" // TODO: Hardcoded path, should be replaced with a dynamic import + import type { BYPASS_MODES } from "coreTypes/validators" + + export class TypeUtils { ReTypeError(_ErrorType: keyof ErrorsLookup, Context: string, Value, Constraint = unknown): string { @@ -16,14 +21,18 @@ export class TypeUtils { return `FilterError$<${T}>` } - Trace(Context: string, Name: string, Next: string = ""): string { - return `Trace<${Context}, ${Name}, ${Next}>` + Trace(Context: string, ParentName: string): string { + return `Trace<${Context}, ${ParentName}>` } _FlatValidate$(T, CX: string, BypassMode: BYPASS_MODES = BypassModes["off"]): string { return `_FlatValidate$<${T}, ${CX}, ${BypassMode}>` } + ValidateArr(Data: unknown[], Acc, Context: string, Index: any[] = []): string { + return `ValidateArr<${Data}, ${Acc}, ${Context}, ${Index}>` + } + Validate$(T, CX: string = ""): string { return `Validate$<${T}, ${CX}>` } @@ -32,8 +41,8 @@ export class TypeUtils { return `EitherValidate<${T}, ${CX}>` } - ValidateArr$(Args: unknown[], Context: string, Index: any[] = []): string { - return `ValidateArr$<${Args}, ${Context}, ${Index}>` + ValidateFlatTuple$(Args: unknown[], Context: string, Index: any[] = []): string { + return `ValidateFlatTuple$<${Args}, ${Context}, ${Index}>` } ValidateEmptyString$(T): string { @@ -88,5 +97,5 @@ export class TypeUtils { return `CH_ValidateUsableSting$<${T}>` } - } +} \ No newline at end of file diff --git a/src/scripts/generateTypeMirrorClass/codegen.ts b/src/scripts/generateTypeMirrorClass/codegen.ts index 20116af..063970a 100644 --- a/src/scripts/generateTypeMirrorClass/codegen.ts +++ b/src/scripts/generateTypeMirrorClass/codegen.ts @@ -1,8 +1,7 @@ -import type { METHOD } from "./typeProcessing/types" +import type { GENERIC, METHOD } from "./typeProcessing/types" +import { maybeRegisterConstraintImport } from "./utils/import" -export const generateParamsDeclaration = ( - generics: { name: string; constraint?: string; defaultValue?: string }[], -): string => +export const generateParamsDeclaration = (generics: GENERIC[]): string => generics .map( ({ name, constraint, defaultValue }) => @@ -10,12 +9,17 @@ export const generateParamsDeclaration = ( ) .join(", ") -export const generateParamsInterpolation = (generics: { name: string }[]): string => +export const generateParamsInterpolation = (generics: Pick[]): string => generics.map((g) => `\${${g.name}}`).join(", ") export const generateMethod = ({ name, generics }: METHOD): string => { const paramsDecl = generateParamsDeclaration(generics) const paramsInterp = generateParamsInterpolation(generics) + + generics.forEach(({ constraint }) => { + maybeRegisterConstraintImport(constraint) + }) + return ` ${name}(${paramsDecl}): string { return \`${name}<${paramsInterp}>\` @@ -25,8 +29,13 @@ export const generateMethod = ({ name, generics }: METHOD): string => { export const generateClassBody = (methods: METHOD[]): string => methods.map(generateMethod).join("\n") export const generateOutput = (methods: METHOD[]): string => - `export class TypeUtils { + ` + import type { ErrorsLookup } from "coreTypes/errors" // TODO: Hardcoded path, should be replaced with a dynamic import + import type { BYPASS_MODES } from "coreTypes/validators" + + +export class TypeUtils { ${generateClassBody(methods)} - } +} ` diff --git a/src/scripts/generateTypeMirrorClass/index.ts b/src/scripts/generateTypeMirrorClass/index.ts index 88288d9..8cd4aa9 100644 --- a/src/scripts/generateTypeMirrorClass/index.ts +++ b/src/scripts/generateTypeMirrorClass/index.ts @@ -1,7 +1,7 @@ import * as ts from "typescript" import * as fs from "fs" import * as path from "path" -import { collectTypeFiles } from "./fileUtils" +import { collectTypeFiles } from "./utils/file" import { processAllFiles } from "./typeProcessing/processing" import { generateOutput } from "./codegen" @@ -12,13 +12,13 @@ const outputFilePath = path.resolve(process.cwd(), "src/classses/generated/TypeM const main = (): void => { const allFiles = collectTypeFiles(typesDir) - const { parsedTypes, failedTypes } = processAllFiles(allFiles, SCRIPT_TARGET) - const output = generateOutput(parsedTypes) + const { parsed, failed } = processAllFiles(allFiles, SCRIPT_TARGET) + const output = generateOutput(parsed) fs.writeFileSync(outputFilePath, output) - console.log("Broken types:", failedTypes.length) - console.log("Broken types names:", failedTypes.map((type) => type.name).join("\n")) + console.log("Broken types:", failed.length) + console.log("Broken types names:", failed.map((type) => type.name).join("\n")) } main() diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts index 8ee733c..6c164aa 100644 --- a/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts +++ b/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts @@ -1,24 +1,23 @@ import * as ts from "typescript" import * as fs from "fs" import { parseTypeDeclaration } from "../../../utils/parseTypeDeclarations" -import { cleanTypeDef } from "./clean" -import { isTypeAlias, isExported } from "./guards" -import type { METHOD, BROKEN_TYPE, PARSE_RESULT } from "./types" +import { sanitize } from "./sanitize" +import { isExported, isTypeAlias } from "./guards" +import type { BROKEN_TYPE, METHOD, PARSE_RESULT, SINGLE_PARSE_RESULT } from "./types" const CHARACTER_ENCODING = "utf-8" export function processExportedTypeAlias( node: ts.TypeAliasDeclaration, sourceText: string, -): { parsed?: METHOD; failed?: BROKEN_TYPE } { +): Partial { const name = node.name.text const rawText = sourceText.slice(node.pos, node.end).trim() - const cleaned = cleanTypeDef(rawText) + const sanitizedTypeDef = sanitize(rawText) try { - const { name, generics } = parseTypeDeclaration(cleaned) - return { parsed: { name, generics } } + return { parsed: parseTypeDeclaration(sanitizedTypeDef) } } catch { return { failed: { name } } } @@ -29,36 +28,36 @@ export function visitSourceFile(filePath: string, scriptTarget: ts.ScriptTarget) const file = ts.createSourceFile(filePath, sourceText, scriptTarget, true) - const parsedTypes: METHOD[] = [] - const failedTypes: BROKEN_TYPE[] = [] + const parsed: METHOD[] = [] + const failed: BROKEN_TYPE[] = [] file.forEachChild((node) => { if (isTypeAlias(node) && isExported(node)) { const result = processExportedTypeAlias(node, sourceText) if (result.parsed) { - parsedTypes.push(result.parsed) + parsed.push(result.parsed) } if (result.failed) { - failedTypes.push(result.failed) + failed.push(result.failed) } } }) - return { parsedTypes, failedTypes } + return { parsed, failed } } export function processAllFiles(filePaths: string[], scriptTarget: ts.ScriptTarget): PARSE_RESULT { return filePaths.reduce( (acc, filePath) => { - const { parsedTypes, failedTypes } = visitSourceFile(filePath, scriptTarget) + const { parsed, failed } = visitSourceFile(filePath, scriptTarget) - acc.parsedTypes.push(...parsedTypes) - acc.failedTypes.push(...failedTypes) + acc.parsed.push(...parsed) + acc.failed.push(...failed) return acc }, - { parsedTypes: [], failedTypes: [] }, + { parsed: [], failed: [] }, ) } diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/clean.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/sanitize.ts similarity index 74% rename from src/scripts/generateTypeMirrorClass/typeProcessing/clean.ts rename to src/scripts/generateTypeMirrorClass/typeProcessing/sanitize.ts index 936a21e..9443010 100644 --- a/src/scripts/generateTypeMirrorClass/typeProcessing/clean.ts +++ b/src/scripts/generateTypeMirrorClass/typeProcessing/sanitize.ts @@ -1,4 +1,4 @@ import { regexes } from "../../../regexes" -export const cleanTypeDef = (raw: string): string => +export const sanitize = (raw: string): string => raw.replace(regexes.exportType, "type").replace(regexes.blockComment, "").replace(regexes.lineComment, "") diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/types.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/types.ts index 2f603ef..d08adb1 100644 --- a/src/scripts/generateTypeMirrorClass/typeProcessing/types.ts +++ b/src/scripts/generateTypeMirrorClass/typeProcessing/types.ts @@ -1,17 +1,23 @@ -import { GENERIC } from "../../../utils/parseTypeDeclarations" +import type { GENERIC } from "../../../utils/parseTypeDeclarations" -type METHOD = { +export type METHOD = { name: string generics: GENERIC[] } -type BROKEN_TYPE = { +export type BROKEN_TYPE = { name: string } -type PARSE_RESULT = { - parsedTypes: METHOD[] - failedTypes: BROKEN_TYPE[] +export type SINGLE_PARSE_RESULT = { + parsed: METHOD + failed: BROKEN_TYPE } -export type { GENERIC, METHOD, BROKEN_TYPE, PARSE_RESULT } +type ConvertValuesToArray = { + [K in keyof T]: T[K][] +} + +export type PARSE_RESULT = ConvertValuesToArray + +export type { GENERIC } diff --git a/src/scripts/generateTypeMirrorClass/fileUtils.ts b/src/scripts/generateTypeMirrorClass/utils/file.ts similarity index 78% rename from src/scripts/generateTypeMirrorClass/fileUtils.ts rename to src/scripts/generateTypeMirrorClass/utils/file.ts index 93602ce..319fc1b 100644 --- a/src/scripts/generateTypeMirrorClass/fileUtils.ts +++ b/src/scripts/generateTypeMirrorClass/utils/file.ts @@ -1,9 +1,7 @@ import * as fs from "fs" import * as path from "path" -export const isTypeScriptFile = (fileName: string): boolean => { - return fileName.endsWith(".ts") && !fileName.endsWith(".d.ts") -} +export const isTypeScriptFile = (fileName: string): boolean => fileName.endsWith(".ts") && !fileName.endsWith(".d.ts") export const collectTypeFiles = (dir: string): string[] => { const files: string[] = [] diff --git a/src/scripts/generateTypeMirrorClass/utils/import.ts b/src/scripts/generateTypeMirrorClass/utils/import.ts new file mode 100644 index 0000000..6e66e84 --- /dev/null +++ b/src/scripts/generateTypeMirrorClass/utils/import.ts @@ -0,0 +1,28 @@ +import { ImportRegistry } from "../../../services/ImportRegistry" + +export function getPrimitiveConstraints(): string[] { + return ["string", "number", "boolean", "null", "undefined", "symbol", "void", "never", "any", "unknown"] +} + +export function isPrimitiveArray(constraint: string): boolean { + return constraint.endsWith("[]") && getPrimitiveConstraints().includes(constraint.replace(/\[\]$/, "").trim()) +} + +export function isPrimitiveConstraint(constraint?: string): boolean { + if (!constraint) return true + if (getPrimitiveConstraints().includes(constraint.trim())) return true + if (isPrimitiveArray(constraint)) return true + return false +} + +export function stripKeyof(constraint?: string): string | undefined { + if (!constraint) return constraint + return constraint.replace(/^keyof\s+/, "").trim() +} + +export function maybeRegisterConstraintImport(constraint?: string): void { + const sanitized = stripKeyof(constraint) + if (sanitized && !isPrimitiveConstraint(sanitized)) { + ImportRegistry.addImport(sanitized) + } +} From e77db38b50b52cda1997ae10fa2fde777f4f8a66 Mon Sep 17 00:00:00 2001 From: Wojciech Morawski Date: Wed, 2 Jul 2025 12:30:03 +0200 Subject: [PATCH 03/14] WIP --- index.html | 1318 ++++++++++------- package.json | 2 +- src/regexes/index.ts | 2 - src/scripts/generateTypeMirrorClass/index.ts | 27 +- .../typeProcessing/sanitize.ts | 3 +- .../typeProcessing/types.ts | 23 - 6 files changed, 769 insertions(+), 606 deletions(-) delete mode 100644 src/scripts/generateTypeMirrorClass/typeProcessing/types.ts diff --git a/index.html b/index.html index 85bf6f0..51fb571 100644 --- a/index.html +++ b/index.html @@ -125,177 +125,207 @@ - - + + dependency-cruiser output - + cluster_src - -src + +src cluster_src/classses - -classses + +classses cluster_src/classses/Lax - -Lax + +Lax cluster_src/classses/Strict - -Strict + +Strict -cluster_src/classses/types - -types +cluster_src/classses/generated + +generated -cluster_src/classses/utils - -utils +cluster_src/classses/generated/TypeMirror + +TypeMirror -cluster_src/coreTypes - -coreTypes +cluster_src/classses/types + +types -cluster_src/coreTypes/errors - -errors +cluster_src/classses/utils + +utils -cluster_src/coreTypes/trace - -trace +cluster_src/coreTypes + +coreTypes -cluster_src/coreTypes/validators - -validators +cluster_src/coreTypes/errors + +errors -cluster_src/coreTypes/validators/flatValidate - -flatValidate +cluster_src/coreTypes/trace + +trace -cluster_src/coreTypes/validators/validate - -validate +cluster_src/coreTypes/validators + +validators -cluster_src/coreTypes/validators/validateArr - -validateArr +cluster_src/coreTypes/validators/flatValidate + +flatValidate -cluster_src/coreTypes/validators/validateComputedGenerics - -validateComputedGenerics +cluster_src/coreTypes/validators/validate + +validate -cluster_src/coreTypes/validators/validateEmtpyString - -validateEmtpyString +cluster_src/coreTypes/validators/validateArr + +validateArr -cluster_src/coreTypes/validators/validateLiteral - -validateLiteral +cluster_src/coreTypes/validators/validateComputedGenerics + +validateComputedGenerics -cluster_src/coreTypes/validators/validateType - -validateType +cluster_src/coreTypes/validators/validateEmtpyString + +validateEmtpyString -cluster_src/coreTypes/validators/validateUsableSting - -validateUsableSting +cluster_src/coreTypes/validators/validateLiteral + +validateLiteral -cluster_src/regexes - -regexes +cluster_src/coreTypes/validators/validateType + +validateType -cluster_src/services - -services +cluster_src/coreTypes/validators/validateUsableSting + +validateUsableSting -cluster_src/services/ImportRegistry - -ImportRegistry +cluster_src/regexes + +regexes -cluster_src/testData - -testData +cluster_src/scripts + +scripts -cluster_src/tsc - -tsc +cluster_src/scripts/generateTypeMirrorClass + +generateTypeMirrorClass -cluster_src/utilTypes - -utilTypes +cluster_src/scripts/generateTypeMirrorClass/typeProcessing + +typeProcessing -cluster_src/utils - -utils +cluster_src/scripts/generateTypeMirrorClass/utils + +utils -cluster_src/utils/consts - -consts +cluster_src/services + +services -cluster_src/utils/createJsDocs - -createJsDocs +cluster_src/services/ImportRegistry + +ImportRegistry -cluster_src/utils/parseTypeDeclarations - -parseTypeDeclarations +cluster_src/testData + +testData -cluster_src/utils/reTypeError - -reTypeError +cluster_src/tsc + +tsc -cluster_src/utils/resolveGenerics - -resolveGenerics +cluster_src/utilTypes + +utilTypes -cluster_src/utils/typeBuilder - -typeBuilder +cluster_src/utils + +utils +cluster_src/utils/consts + +consts + + +cluster_src/utils/createJsDocs + +createJsDocs + + +cluster_src/utils/parseTypeDeclarations + +parseTypeDeclarations + + +cluster_src/utils/reTypeError + +reTypeError + + +cluster_src/utils/resolveGenerics + +resolveGenerics + + +cluster_src/utils/typeBuilder + +typeBuilder + + cluster_src/utils/typeBuilder/utils - -utils + +utils src/buildTypes.ts - -buildTypes.ts + +buildTypes.ts @@ -303,946 +333,1102 @@ src/classses/Lax/index.ts - -index.ts + +index.ts src/buildTypes.ts->src/classses/Lax/index.ts - - + + src/classses/Strict/index.ts - -index.ts + +index.ts src/buildTypes.ts->src/classses/Strict/index.ts - - + + src/services/ImportRegistry/index.ts - -index.ts + +index.ts src/buildTypes.ts->src/services/ImportRegistry/index.ts - - + + src/classses/types/index.ts - -index.ts + +index.ts src/classses/Lax/index.ts->src/classses/types/index.ts - - + + src/classses/utils/index.ts - -index.ts + +index.ts src/classses/Lax/index.ts->src/classses/utils/index.ts - - + + src/utils/createJsDocs/index.ts - -index.ts + +index.ts src/classses/Lax/index.ts->src/utils/createJsDocs/index.ts - - + + src/utils/parseTypeDeclarations/index.ts - -index.ts + +index.ts src/classses/Lax/index.ts->src/utils/parseTypeDeclarations/index.ts - - + + src/utils/resolveGenerics/index.ts - -index.ts + +index.ts src/classses/Lax/index.ts->src/utils/resolveGenerics/index.ts - - + + src/utils/typeBuilder/index.ts - -index.ts + +index.ts src/classses/Lax/index.ts->src/utils/typeBuilder/index.ts - - + + src/classses/Strict/index.ts->src/services/ImportRegistry/index.ts - - + + src/classses/Strict/index.ts->src/classses/utils/index.ts - - + + src/classses/Strict/index.ts->src/utils/createJsDocs/index.ts - - + + src/classses/Strict/index.ts->src/utils/parseTypeDeclarations/index.ts - - + + src/classses/Strict/index.ts->src/utils/resolveGenerics/index.ts - - + + src/classses/Strict/index.ts->src/utils/typeBuilder/index.ts - - + + src/utils/reTypeError/trace.ts - -trace.ts + +trace.ts src/classses/Strict/index.ts->src/utils/reTypeError/trace.ts - - + + src/classses/Lax/index.test.ts - -index.test.ts + +index.test.ts src/classses/Lax/index.test.ts->src/classses/Lax/index.ts - - + + - + src/classses/utils/index.ts->src/utils/parseTypeDeclarations/index.ts - - + + - + src/utils/consts/index.ts - - -index.ts + + +index.ts - + src/classses/utils/index.ts->src/utils/consts/index.ts - - + + - + src/utils/createJsDocs/index.ts->src/utils/parseTypeDeclarations/index.ts - - + + - + src/utilTypes/index.ts - - -index.ts + + +index.ts - + src/utils/createJsDocs/index.ts->src/utilTypes/index.ts - - + + - + src/regexes/index.ts - - -index.ts + + +index.ts - + src/utils/parseTypeDeclarations/index.ts->src/regexes/index.ts - - + + - + src/utils/resolveGenerics/index.ts->src/utils/parseTypeDeclarations/index.ts - - + + - + src/utils/resolveGenerics/index.ts->src/utils/consts/index.ts - - + + - + src/utils/resolveGenerics/index.ts->src/utilTypes/index.ts - - + + - + src/utils/typeBuilder/genericArgs.ts - - -genericArgs.ts + + +genericArgs.ts - + src/utils/typeBuilder/index.ts->src/utils/typeBuilder/genericArgs.ts - - + + - + src/utils/typeBuilder/relaxConstraints.ts - - -relaxConstraints.ts + + +relaxConstraints.ts - + src/utils/typeBuilder/index.ts->src/utils/typeBuilder/relaxConstraints.ts - - + + - + src/utils/typeBuilder/typeDefinitions.ts - - -typeDefinitions.ts + + +typeDefinitions.ts - + src/utils/typeBuilder/index.ts->src/utils/typeBuilder/typeDefinitions.ts - - + + src/classses/Strict/index.test.ts - -index.test.ts + +index.test.ts src/classses/Strict/index.test.ts->src/classses/Strict/index.ts - - + + - + src/utils/reTypeError/trace.ts->src/utils/consts/index.ts - - + + - + src/utils/reTypeError/uuid.ts - - -uuid.ts + + +uuid.ts - + src/utils/reTypeError/trace.ts->src/utils/reTypeError/uuid.ts - - + + - - -src/utils/consts/index.ts->src/utils/parseTypeDeclarations/index.ts - - - - - -src/coreTypes/errors/errors.ts - - -errors.ts + + +src/classses/generated/TypeMirror/index.ts + + +index.ts - - -src/coreTypes/errors/errors.ts->src/utilTypes/index.ts - - - - - -src/coreTypes/trace/index.ts - - -index.ts + + +src/coreTypes/errors/index.ts + + +index.ts - - -src/coreTypes/errors/errors.ts->src/coreTypes/trace/index.ts - - + + +src/classses/generated/TypeMirror/index.ts->src/coreTypes/errors/index.ts + + - - -src/utilTypes/unionToArray.ts - - -unionToArray.ts + + +src/coreTypes/validators/index.ts + + +index.ts - - -src/utilTypes/index.ts->src/utilTypes/unionToArray.ts - - + + +src/classses/generated/TypeMirror/index.ts->src/coreTypes/validators/index.ts + + - + -src/coreTypes/errors/index.ts - - -index.ts +src/coreTypes/errors/errors.ts + + +errors.ts - + src/coreTypes/errors/index.ts->src/coreTypes/errors/errors.ts - - + + - + src/coreTypes/errors/utils.ts - - -utils.ts + + +utils.ts - + src/coreTypes/errors/index.ts->src/coreTypes/errors/utils.ts - - + + + + + +src/coreTypes/validators/consts.ts + + +consts.ts + + + + + +src/coreTypes/validators/index.ts->src/coreTypes/validators/consts.ts + + + + + +src/coreTypes/validators/validate/index.ts + + +index.ts + + + + + +src/coreTypes/validators/index.ts->src/coreTypes/validators/validate/index.ts + + + + + +src/utils/consts/index.ts->src/utils/parseTypeDeclarations/index.ts + + + + + +src/coreTypes/errors/errors.ts->src/utilTypes/index.ts + + + + + +src/coreTypes/trace/index.ts + + +index.ts + + + + + +src/coreTypes/errors/errors.ts->src/coreTypes/trace/index.ts + + + + + +src/utilTypes/unionToArray.ts + + +unionToArray.ts + + + + + +src/utilTypes/index.ts->src/utilTypes/unionToArray.ts + + - + src/coreTypes/errors/utils.ts->src/coreTypes/errors/errors.ts - - + + - + src/coreTypes/index.ts - - -index.ts + + +index.ts - - -src/coreTypes/index.ts->src/coreTypes/trace/index.ts - - - - + src/coreTypes/index.ts->src/coreTypes/errors/index.ts - - + + + + + +src/coreTypes/index.ts->src/coreTypes/trace/index.ts + + - + src/coreTypes/validators/validateArr/index.ts - - -index.ts + + +index.ts - + src/coreTypes/index.ts->src/coreTypes/validators/validateArr/index.ts - - + + - + src/coreTypes/validators/validateArr/index.ts->src/coreTypes/trace/index.ts - - + + - + src/coreTypes/validators/flatValidate/index.ts - - -index.ts + + +index.ts - + src/coreTypes/validators/validateArr/index.ts->src/coreTypes/validators/flatValidate/index.ts - - - - - -src/coreTypes/validators/consts.ts - - -consts.ts - - + + - -src/coreTypes/validators/consts.ts->src/utilTypes/index.ts - - - - -src/coreTypes/validators/flatValidate/index.ts->src/coreTypes/trace/index.ts - - +src/coreTypes/validators/consts.ts->src/utilTypes/index.ts + + - -src/coreTypes/validators/flatValidate/index.ts->src/coreTypes/errors/index.ts - - - - -src/coreTypes/validators/flatValidate/index.ts->src/coreTypes/validators/consts.ts - - - - - -src/coreTypes/validators/index.ts - - -index.ts - - +src/coreTypes/validators/flatValidate/index.ts->src/coreTypes/errors/index.ts + + - + -src/coreTypes/validators/index.ts->src/coreTypes/validators/consts.ts - - - - - -src/coreTypes/validators/validate/index.ts - - -index.ts - - +src/coreTypes/validators/flatValidate/index.ts->src/coreTypes/trace/index.ts + + - + -src/coreTypes/validators/index.ts->src/coreTypes/validators/validate/index.ts - - +src/coreTypes/validators/flatValidate/index.ts->src/coreTypes/validators/consts.ts + + - + src/coreTypes/validators/validate/index.ts->src/coreTypes/trace/index.ts - - + + - + src/coreTypes/validators/validate/index.ts->src/coreTypes/validators/consts.ts - - + + - + src/coreTypes/validators/validate/index.ts->src/coreTypes/validators/flatValidate/index.ts - - + + - + src/coreTypes/validators/validateComputedGenerics/index.ts - - -index.ts + + +index.ts - + src/coreTypes/validators/validate/index.ts->src/coreTypes/validators/validateComputedGenerics/index.ts - - + + - + src/coreTypes/validators/validateComputedGenerics/index.ts->src/utilTypes/index.ts - - + + - + src/coreTypes/validators/validateComputedGenerics/index.ts->src/coreTypes/trace/index.ts - - + + - + src/coreTypes/validators/validateComputedGenerics/index.ts->src/coreTypes/validators/flatValidate/index.ts - - + + - + src/coreTypes/validators/validateEmtpyString/index.ts - - -index.ts + + +index.ts - + src/coreTypes/validators/validateEmtpyString/index.ts->src/coreTypes/errors/index.ts - - - - - -src/coreTypes/validators/validateEmtpyString/index.ts->src/coreTypes/errors/utils.ts - - + + - + src/coreTypes/validators/validateEmtpyString/index.ts->src/coreTypes/validators/index.ts - - + + + + + +src/coreTypes/validators/validateEmtpyString/index.ts->src/coreTypes/errors/utils.ts + + - + src/coreTypes/validators/validateLiteral/index.ts - - -index.ts + + +index.ts - + src/coreTypes/validators/validateLiteral/index.ts->src/coreTypes/errors/index.ts - - - - - -src/coreTypes/validators/validateLiteral/index.ts->src/coreTypes/errors/utils.ts - - + + - + src/coreTypes/validators/validateLiteral/index.ts->src/coreTypes/validators/index.ts - - + + + + + +src/coreTypes/validators/validateLiteral/index.ts->src/coreTypes/errors/utils.ts + + - + src/coreTypes/validators/validateType/index.ts - - -index.ts + + +index.ts - - -src/coreTypes/validators/validateType/index.ts->src/coreTypes/trace/index.ts - - - - + src/coreTypes/validators/validateType/index.ts->src/coreTypes/errors/index.ts - - - - - -src/coreTypes/validators/validateType/index.ts->src/coreTypes/errors/utils.ts - - + + - + src/coreTypes/validators/validateType/index.ts->src/coreTypes/validators/index.ts - - + + + + + +src/coreTypes/validators/validateType/index.ts->src/coreTypes/trace/index.ts + + + + + +src/coreTypes/validators/validateType/index.ts->src/coreTypes/errors/utils.ts + + - + src/coreTypes/validators/validateUsableSting/index.ts - - -index.ts + + +index.ts - + src/coreTypes/validators/validateUsableSting/index.ts->src/coreTypes/errors/utils.ts - - + + - + src/coreTypes/validators/validateUsableSting/index.ts->src/coreTypes/validators/validate/index.ts - - + + - + src/coreTypes/validators/validateUsableSting/index.ts->src/coreTypes/validators/validateEmtpyString/index.ts - - + + - + src/coreTypes/validators/validateUsableSting/index.ts->src/coreTypes/validators/validateLiteral/index.ts - - + + - + src/run.ts - - -run.ts + + +run.ts - + src/run.ts->src/services/ImportRegistry/index.ts - - + + - - -src/testData/index.ts - - -index.ts + + +src/scripts/generateTypeMirrorClass/codegen.ts + + +codegen.ts + + + + + +src/scripts/generateTypeMirrorClass/typeProcessing/types.ts + + +types.ts + + + + + +src/scripts/generateTypeMirrorClass/codegen.ts->src/scripts/generateTypeMirrorClass/typeProcessing/types.ts + + + + + +src/scripts/generateTypeMirrorClass/utils/import.ts + + +import.ts + + + + + +src/scripts/generateTypeMirrorClass/codegen.ts->src/scripts/generateTypeMirrorClass/utils/import.ts + + + + + +src/scripts/generateTypeMirrorClass/typeProcessing/types.ts->src/utils/parseTypeDeclarations/index.ts + + + + + +src/scripts/generateTypeMirrorClass/utils/import.ts->src/services/ImportRegistry/index.ts + + + + + +src/scripts/generateTypeMirrorClass/index.ts + + +index.ts + + + + + +src/scripts/generateTypeMirrorClass/index.ts->src/scripts/generateTypeMirrorClass/codegen.ts + + + + + +src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts + + +processing.ts + + +src/scripts/generateTypeMirrorClass/index.ts->src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts + + + - + src/tsc/collectTsFilePaths.ts - - -collectTsFilePaths.ts + + +collectTsFilePaths.ts + + + + + +src/scripts/generateTypeMirrorClass/index.ts->src/tsc/collectTsFilePaths.ts + + + + + +src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts->src/utils/parseTypeDeclarations/index.ts + + + + + +src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts->src/scripts/generateTypeMirrorClass/typeProcessing/types.ts + + + + + +src/scripts/generateTypeMirrorClass/typeProcessing/guards.ts + + +guards.ts + + + + + +src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts->src/scripts/generateTypeMirrorClass/typeProcessing/guards.ts + + + + + +src/scripts/generateTypeMirrorClass/typeProcessing/sanitize.ts + + +sanitize.ts + + + + + +src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts->src/scripts/generateTypeMirrorClass/typeProcessing/sanitize.ts + + + + + +src/scripts/generateTypeMirrorClass/typeProcessing/sanitize.ts->src/regexes/index.ts + + + + + +src/testData/index.ts + + +index.ts - + src/tsc/findTypeDeclarations.ts - - -findTypeDeclarations.ts + + +findTypeDeclarations.ts - + src/tsc/findTypeDeclarations.ts->src/tsc/collectTsFilePaths.ts - - + + - + src/tsc/index.ts - - -index.ts + + +index.ts - + src/tsc/index.ts->src/tsc/findTypeDeclarations.ts - - + + - + src/utils/parseTypeDeclarations/index.test.ts - - -index.test.ts + + +index.test.ts - + src/utils/parseTypeDeclarations/index.test.ts->src/utils/parseTypeDeclarations/index.ts - - + + - + src/utils/reTypeError/index.ts - - -index.ts + + +index.ts - + src/utils/reTypeError/index.ts->src/utils/parseTypeDeclarations/index.ts - - + + - + src/utils/reTypeError/index.ts->src/utils/reTypeError/trace.ts - - - - - -src/utils/reTypeError/index.ts->src/utilTypes/index.ts - - + + - + src/utils/reTypeError/index.ts->src/coreTypes/errors/index.ts - - + + + + + +src/utils/reTypeError/index.ts->src/utilTypes/index.ts + + - + src/utils/typeBuilder/genericArgs.ts->src/utils/parseTypeDeclarations/index.ts - - + + - + src/utils/typeBuilder/genericArgs.ts->src/utils/reTypeError/trace.ts - - + + - + src/utils/typeBuilder/genericArgs.ts->src/utils/consts/index.ts - - + + - + src/utils/typeBuilder/utils/index.ts - - -index.ts + + +index.ts - + src/utils/typeBuilder/genericArgs.ts->src/utils/typeBuilder/utils/index.ts - - + + - + src/utils/typeBuilder/utils/index.ts->src/utils/parseTypeDeclarations/index.ts - - + + - + src/utils/typeBuilder/utils/index.ts->src/utils/reTypeError/trace.ts - - + + - + src/utils/typeBuilder/utils/index.ts->src/utils/consts/index.ts - - + + - + src/utils/typeBuilder/relaxConstraints.ts->src/services/ImportRegistry/index.ts - - + + - + src/utils/typeBuilder/relaxConstraints.ts->src/utils/parseTypeDeclarations/index.ts - - + + - + src/utils/typeBuilder/relaxConstraints.ts->src/utils/reTypeError/trace.ts - - + + - + src/utils/typeBuilder/relaxConstraints.ts->src/utils/reTypeError/index.ts - - + + - + src/utils/typeBuilder/typeDefinitions.ts->src/utils/parseTypeDeclarations/index.ts - - + + - + src/utils/typeBuilder/typeDefinitions.ts->src/utils/reTypeError/trace.ts - - + + - + src/utils/typeBuilder/typeDefinitions.ts->src/utilTypes/index.ts - - + + - + src/utils/typeBuilder/typeDefinitions.ts->src/utils/typeBuilder/genericArgs.ts - - + + diff --git a/package.json b/package.json index e72ba3d..183f75d 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,11 @@ "scripts": { "build:types": "nodemon --watch src --ext ts --exec \"tsx ./src/buildTypes.ts\"", "build:findTypeDeclarations": "nodemon --watch src --ext ts --exec \"tsx ./scripts/findTypeDeclarations.ts\"", + "build:typeMirror": "nodemon --watch src --ext ts --exec \"npx tsx src/scripts/generateTypeMirrorClass\"", "graph:build": "depcruise src --output-type dot | dot -T svg | depcruise-wrap-stream-in-html > index.html", "graph:test": "depcruise src", "graph:watch": "nodemon --watch src --ext ts --exec \"npm run graph:build\"", "graph:serve": "concurrently \"npm run graph:watch\" \"vite\"", - "generate-type-mirror": "npx tsx src/scripts/generateTypeMirrorClass", "test": "concurrently \"npm run test:vitest\" \"npm run test:tsc\" \"npm run graph:test\"", "test:vitest": "vitest run", "test:tsc": "tsc --noEmit", diff --git a/src/regexes/index.ts b/src/regexes/index.ts index 63f922f..c8b4773 100644 --- a/src/regexes/index.ts +++ b/src/regexes/index.ts @@ -1,6 +1,4 @@ export const regexes = { - extractTypesAndValidations: /type ([\w$]*)<([^<>]*)>\s*=\s*(.*)/, - exportType: /\bexport\s+type\b/, blockComment: /\/\*\*[\s\S]*?\*\//g, lineComment: /\/\/.*/g, } diff --git a/src/scripts/generateTypeMirrorClass/index.ts b/src/scripts/generateTypeMirrorClass/index.ts index 8cd4aa9..778eec3 100644 --- a/src/scripts/generateTypeMirrorClass/index.ts +++ b/src/scripts/generateTypeMirrorClass/index.ts @@ -1,24 +1,27 @@ -import * as ts from "typescript" import * as fs from "fs" import * as path from "path" -import { collectTypeFiles } from "./utils/file" -import { processAllFiles } from "./typeProcessing/processing" +import * as ts from "typescript" + +import { collectTsFilePaths } from "tsc/collectTsFilePaths" import { generateOutput } from "./codegen" +import { processAllFiles } from "./typeProcessing/processing" const SCRIPT_TARGET = ts.ScriptTarget.ES2020 -const typesDir = path.resolve(process.cwd(), "src/coreTypes") -const outputFilePath = path.resolve(process.cwd(), "src/classses/generated/TypeMirror/index.ts") // TODO: Remove typo 'classSes' when will be fixed in the codebase +const main = (dirToScan: string, outputFilePath: string): void => { + const filePaths = collectTsFilePaths(dirToScan) + + const parsed = processAllFiles(filePaths, SCRIPT_TARGET) -const main = (): void => { - const allFiles = collectTypeFiles(typesDir) - const { parsed, failed } = processAllFiles(allFiles, SCRIPT_TARGET) + console.log(parsed) const output = generateOutput(parsed) fs.writeFileSync(outputFilePath, output) - - console.log("Broken types:", failed.length) - console.log("Broken types names:", failed.map((type) => type.name).join("\n")) } -main() +main( + // TODO: Remove typo 'classSes' when will be fixed in the codebase + path.resolve(process.cwd(), "src/coreTypes"), + // path.resolve(process.cwd(), "src/classses/generated/TypeMirror/index.ts"), + path.resolve(process.cwd(), "dist/TypeMirror.ts"), +) diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/sanitize.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/sanitize.ts index 9443010..18cb617 100644 --- a/src/scripts/generateTypeMirrorClass/typeProcessing/sanitize.ts +++ b/src/scripts/generateTypeMirrorClass/typeProcessing/sanitize.ts @@ -1,4 +1,3 @@ import { regexes } from "../../../regexes" -export const sanitize = (raw: string): string => - raw.replace(regexes.exportType, "type").replace(regexes.blockComment, "").replace(regexes.lineComment, "") +export const sanitize = (raw: string): string => raw.replace(regexes.blockComment, "").replace(regexes.lineComment, "") diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/types.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/types.ts deleted file mode 100644 index d08adb1..0000000 --- a/src/scripts/generateTypeMirrorClass/typeProcessing/types.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { GENERIC } from "../../../utils/parseTypeDeclarations" - -export type METHOD = { - name: string - generics: GENERIC[] -} - -export type BROKEN_TYPE = { - name: string -} - -export type SINGLE_PARSE_RESULT = { - parsed: METHOD - failed: BROKEN_TYPE -} - -type ConvertValuesToArray = { - [K in keyof T]: T[K][] -} - -export type PARSE_RESULT = ConvertValuesToArray - -export type { GENERIC } From c023ce6eb4a6466f895f25e284293622a88e48eb Mon Sep 17 00:00:00 2001 From: Wojciech Morawski Date: Wed, 2 Jul 2025 19:49:05 +0200 Subject: [PATCH 04/14] WIP --- src/coreTypes/errors/errors.ts | 18 ++--- .../typeProcessing/processing.ts | 68 +++++++------------ .../generateTypeMirrorClass/utils/file.ts | 21 ------ src/tsc/utils/index.ts | 17 +++++ 4 files changed, 49 insertions(+), 75 deletions(-) delete mode 100644 src/scripts/generateTypeMirrorClass/utils/file.ts create mode 100644 src/tsc/utils/index.ts diff --git a/src/coreTypes/errors/errors.ts b/src/coreTypes/errors/errors.ts index b4502b0..b781b9a 100644 --- a/src/coreTypes/errors/errors.ts +++ b/src/coreTypes/errors/errors.ts @@ -67,29 +67,29 @@ export type ReTypeError< _ErrorType extends keyof ErrorsLookup, Context extends string, Value, - Constraint = unknown + _Constraint = unknown > = { __type: _ErrorType __message: ErrorsLookup[_ErrorType]["msg"] __context: Context __value: Value & {} // TODO: pretty - __constraint?: Constraint & {} // TODO: pretty + __constraint?: _Constraint & {} // TODO: pretty __url: ErrorsLookup[_ErrorType]["url"] } // ----------------------- // prettier-ignore -export type NeverError = ReTypeError<"NeverError", Trace, T, Constraint> +export type NeverError = ReTypeError<"NeverError", Trace, T, _Constraint> // prettier-ignore -export type AnyError = ReTypeError<"AnyError", Trace, T, Constraint> +export type AnyError = ReTypeError<"AnyError", Trace, T, _Constraint> // prettier-ignore -export type UnknownError = ReTypeError<"UnknownError", Trace, T, Constraint> +export type UnknownError = ReTypeError<"UnknownError", Trace, T, _Constraint> // prettier-ignore -export type MismatchError = ReTypeError<"MismatchError", Trace, T, Constraint> +export type MismatchError = ReTypeError<"MismatchError", Trace, T, _Constraint> // prettier-ignore -export type NonLiteralError = ReTypeError<"NonLiteralError", Trace, T, Constraint> +export type NonLiteralError = ReTypeError<"NonLiteralError", Trace, T, _Constraint> // prettier-ignore -export type EmptyStringError = ReTypeError<"EmptyStringError", Trace, T, Constraint> +export type EmptyStringError = ReTypeError<"EmptyStringError", Trace, T, _Constraint> // prettier-ignore -export type OpenTypeError = ReTypeError<"OpenTypeError", Trace, T, Constraint> +export type OpenTypeError = ReTypeError<"OpenTypeError", Trace, T, _Constraint> diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts index 6c164aa..bf4f8bb 100644 --- a/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts +++ b/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts @@ -1,63 +1,41 @@ -import * as ts from "typescript" import * as fs from "fs" -import { parseTypeDeclaration } from "../../../utils/parseTypeDeclarations" -import { sanitize } from "./sanitize" -import { isExported, isTypeAlias } from "./guards" -import type { BROKEN_TYPE, METHOD, PARSE_RESULT, SINGLE_PARSE_RESULT } from "./types" +import { getTypeNodeName } from "tsc/utils" +import * as ts from "typescript" +import { type PARSED_TYPE_DECLARATION, parseTypeDeclaration } from "../../../utils/parseTypeDeclarations" +import { isExportedType, isTypeAlias, isTypeFunction } from "./guards" const CHARACTER_ENCODING = "utf-8" -export function processExportedTypeAlias( - node: ts.TypeAliasDeclaration, - sourceText: string, -): Partial { - const name = node.name.text - - const rawText = sourceText.slice(node.pos, node.end).trim() - const sanitizedTypeDef = sanitize(rawText) - - try { - return { parsed: parseTypeDeclaration(sanitizedTypeDef) } - } catch { - return { failed: { name } } - } -} - -export function visitSourceFile(filePath: string, scriptTarget: ts.ScriptTarget): PARSE_RESULT { +export function visitSourceFile(filePath: string, scriptTarget: ts.ScriptTarget): PARSED_TYPE_DECLARATION[] { const sourceText = fs.readFileSync(filePath, CHARACTER_ENCODING) - const file = ts.createSourceFile(filePath, sourceText, scriptTarget, true) - const parsed: METHOD[] = [] - const failed: BROKEN_TYPE[] = [] + const parsed: PARSED_TYPE_DECLARATION[] = [] file.forEachChild((node) => { - if (isTypeAlias(node) && isExported(node)) { - const result = processExportedTypeAlias(node, sourceText) + if (isTypeAlias(node) && isTypeFunction(node) && isExportedType(node)) { + // Get type parameter information including defaults + // const typeParams = getTypeParameterInfo(node, sourceText) + // console.warn(`Type ${node.name.text} has parameters:`, typeParams) + // console.warn(`Type ${node.name.text} has body:`, typeBody) - if (result.parsed) { - parsed.push(result.parsed) - } + // const result = processExportedTypeAlias(node, sourceText, filePath) - if (result.failed) { - failed.push(result.failed) - } + // TODO: convert to pipe + const nodeText = getTypeNodeName(sourceText)(node) + const result = parseTypeDeclaration(nodeText) + + parsed.push(result) } }) - return { parsed, failed } + return parsed } -export function processAllFiles(filePaths: string[], scriptTarget: ts.ScriptTarget): PARSE_RESULT { - return filePaths.reduce( - (acc, filePath) => { - const { parsed, failed } = visitSourceFile(filePath, scriptTarget) - - acc.parsed.push(...parsed) - acc.failed.push(...failed) +export function processAllFiles(filePaths: string[], scriptTarget: ts.ScriptTarget): PARSED_TYPE_DECLARATION[] { + return filePaths.reduce((acc, filePath) => { + const parsed = visitSourceFile(filePath, scriptTarget) - return acc - }, - { parsed: [], failed: [] }, - ) + return [...acc, ...parsed] + }, []) } diff --git a/src/scripts/generateTypeMirrorClass/utils/file.ts b/src/scripts/generateTypeMirrorClass/utils/file.ts deleted file mode 100644 index 319fc1b..0000000 --- a/src/scripts/generateTypeMirrorClass/utils/file.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as fs from "fs" -import * as path from "path" - -export const isTypeScriptFile = (fileName: string): boolean => fileName.endsWith(".ts") && !fileName.endsWith(".d.ts") - -export const collectTypeFiles = (dir: string): string[] => { - const files: string[] = [] - const entries = fs.readdirSync(dir, { withFileTypes: true }) - - for (const entry of entries) { - const fullPath = path.join(dir, entry.name) - - if (entry.isDirectory()) { - files.push(...collectTypeFiles(fullPath)) - } else if (entry.isFile() && isTypeScriptFile(entry.name)) { - files.push(fullPath) - } - } - - return files -} diff --git a/src/tsc/utils/index.ts b/src/tsc/utils/index.ts new file mode 100644 index 0000000..184db89 --- /dev/null +++ b/src/tsc/utils/index.ts @@ -0,0 +1,17 @@ +import { regexes } from "regexes" +import type ts from "typescript" + +const sanitize = (nodeText: string): string => + nodeText + // + .replace(regexes.blockComment, "") + .replace(regexes.lineComment, "") + .trim() + +export const getTypeNodeName = + (sourceText: string) => + (node: ts.Node): string => + sanitize(sourceText.slice(node.pos, node.end)) + +export const getTypeNodeBody = (node: ts.TypeAliasDeclaration, sourceText: string): string => + getTypeNodeName(sourceText)(node.type) From 739308bc29c97c4d7d6774593c1952b4df4f8ef7 Mon Sep 17 00:00:00 2001 From: Wojciech Morawski Date: Wed, 2 Jul 2025 20:17:15 +0200 Subject: [PATCH 05/14] Stable --- src/classses/generated/TypeMirror/index.ts | 101 ------------------ .../generateTypeMirrorClass/codegen.ts | 28 ++--- src/scripts/generateTypeMirrorClass/index.ts | 7 +- .../typeProcessing/guards.ts | 7 +- .../typeProcessing/processing.ts | 21 ++-- .../typeProcessing/sanitize.ts | 3 - .../typeProcessing/test-type-params.ts | 60 +++++++++++ src/tsc/collectTsFilePaths.ts | 34 ++---- src/tsc/findTypeDeclarations.ts | 9 +- src/tsc/utils/index.ts | 36 ++++++- src/utils/parseTypeDeclarations/index.ts | 67 ++++++------ 11 files changed, 169 insertions(+), 204 deletions(-) delete mode 100644 src/classses/generated/TypeMirror/index.ts delete mode 100644 src/scripts/generateTypeMirrorClass/typeProcessing/sanitize.ts create mode 100644 src/scripts/generateTypeMirrorClass/typeProcessing/test-type-params.ts diff --git a/src/classses/generated/TypeMirror/index.ts b/src/classses/generated/TypeMirror/index.ts deleted file mode 100644 index 773f8f0..0000000 --- a/src/classses/generated/TypeMirror/index.ts +++ /dev/null @@ -1,101 +0,0 @@ - - import type { ErrorsLookup } from "coreTypes/errors" // TODO: Hardcoded path, should be replaced with a dynamic import - import type { BYPASS_MODES } from "coreTypes/validators" - - -export class TypeUtils { - - ReTypeError(_ErrorType: keyof ErrorsLookup, Context: string, Value, Constraint = unknown): string { - return `ReTypeError<${_ErrorType}, ${Context}, ${Value}, ${Constraint}>` - } - - EmptyStringError(CX: string, T, Constraint = unknown): string { - return `EmptyStringError<${CX}, ${T}, ${Constraint}>` - } - - AnyMatchError$(T): string { - return `AnyMatchError$<${T}>` - } - - FilterError$(T): string { - return `FilterError$<${T}>` - } - - Trace(Context: string, ParentName: string): string { - return `Trace<${Context}, ${ParentName}>` - } - - _FlatValidate$(T, CX: string, BypassMode: BYPASS_MODES = BypassModes["off"]): string { - return `_FlatValidate$<${T}, ${CX}, ${BypassMode}>` - } - - ValidateArr(Data: unknown[], Acc, Context: string, Index: any[] = []): string { - return `ValidateArr<${Data}, ${Acc}, ${Context}, ${Index}>` - } - - Validate$(T, CX: string = ""): string { - return `Validate$<${T}, ${CX}>` - } - - EitherValidate(T, CX: string = ""): string { - return `EitherValidate<${T}, ${CX}>` - } - - ValidateFlatTuple$(Args: unknown[], Context: string, Index: any[] = []): string { - return `ValidateFlatTuple$<${Args}, ${Context}, ${Index}>` - } - - ValidateEmptyString$(T): string { - return `ValidateEmptyString$<${T}>` - } - - EitherValidate_EmptyString$(T): string { - return `EitherValidate_EmptyString$<${T}>` - } - - ValidateLiteral$(Mode: BYPASS_MODES, T, Match): string { - return `ValidateLiteral$<${Mode}, ${T}, ${Match}>` - } - - Validate_StringLiteral(T): string { - return `Validate_StringLiteral<${T}>` - } - - Validate_NumberLiteral(T): string { - return `Validate_NumberLiteral<${T}>` - } - - Validate_BooleanLiteral(T): string { - return `Validate_BooleanLiteral<${T}>` - } - - EitherValidate_StringLiteral(T): string { - return `EitherValidate_StringLiteral<${T}>` - } - - EitherValidate_NumberLiteral(T): string { - return `EitherValidate_NumberLiteral<${T}>` - } - - EitherValidate_BooleanLiteral(T): string { - return `EitherValidate_BooleanLiteral<${T}>` - } - - ValidateType$(CX: string, T$, Match): string { - return `ValidateType$<${CX}, ${T$}, ${Match}>` - } - - EitherValidate_Type$(T$, Match): string { - return `EitherValidate_Type$<${T$}, ${Match}>` - } - - ValidateUsableSting$(T): string { - return `ValidateUsableSting$<${T}>` - } - - CH_ValidateUsableSting$(T): string { - return `CH_ValidateUsableSting$<${T}>` - } - -} - \ No newline at end of file diff --git a/src/scripts/generateTypeMirrorClass/codegen.ts b/src/scripts/generateTypeMirrorClass/codegen.ts index 063970a..8da3db8 100644 --- a/src/scripts/generateTypeMirrorClass/codegen.ts +++ b/src/scripts/generateTypeMirrorClass/codegen.ts @@ -1,18 +1,16 @@ -import type { GENERIC, METHOD } from "./typeProcessing/types" +import type { GENERIC, PARSED_TYPE_DECLARATION } from "utils/parseTypeDeclarations" import { maybeRegisterConstraintImport } from "./utils/import" export const generateParamsDeclaration = (generics: GENERIC[]): string => generics - .map( - ({ name, constraint, defaultValue }) => - `${name}${constraint ? `: ${constraint}` : ""}${defaultValue ? ` = ${defaultValue}` : ""}`, - ) + .map((g) => `${g.name}${g.constraint ? `: ${g.constraint}` : ""}${g.defaultValue ? ` = ${g.defaultValue}` : ""}`) + // .map((g) => `${g.name}`) // ${g.constraint}`) .join(", ") -export const generateParamsInterpolation = (generics: Pick[]): string => +export const generateParamsInterpolation = (generics: GENERIC[]): string => generics.map((g) => `\${${g.name}}`).join(", ") -export const generateMethod = ({ name, generics }: METHOD): string => { +export const generateMethod = ({ typeName, generics }: PARSED_TYPE_DECLARATION): string => { const paramsDecl = generateParamsDeclaration(generics) const paramsInterp = generateParamsInterpolation(generics) @@ -20,22 +18,24 @@ export const generateMethod = ({ name, generics }: METHOD): string => { maybeRegisterConstraintImport(constraint) }) + console.log(generics) + return ` - ${name}(${paramsDecl}): string { - return \`${name}<${paramsInterp}>\` + ${typeName}(${paramsDecl}): string { + return \`${typeName}<${paramsInterp}>\` }` } -export const generateClassBody = (methods: METHOD[]): string => methods.map(generateMethod).join("\n") +export const generateClassBody = (methods: PARSED_TYPE_DECLARATION[]): string => methods.map(generateMethod).join("\n") -export const generateOutput = (methods: METHOD[]): string => +export const generateOutput = (methods: PARSED_TYPE_DECLARATION[]): string => ` import type { ErrorsLookup } from "coreTypes/errors" // TODO: Hardcoded path, should be replaced with a dynamic import - import type { BYPASS_MODES } from "coreTypes/validators" - + import type { BYPASS_MODES } from "coreTypes/validators" + export class TypeUtils { ${generateClassBody(methods)} - + } ` diff --git a/src/scripts/generateTypeMirrorClass/index.ts b/src/scripts/generateTypeMirrorClass/index.ts index 778eec3..b2e37cc 100644 --- a/src/scripts/generateTypeMirrorClass/index.ts +++ b/src/scripts/generateTypeMirrorClass/index.ts @@ -1,17 +1,14 @@ import * as fs from "fs" import * as path from "path" -import * as ts from "typescript" import { collectTsFilePaths } from "tsc/collectTsFilePaths" import { generateOutput } from "./codegen" import { processAllFiles } from "./typeProcessing/processing" -const SCRIPT_TARGET = ts.ScriptTarget.ES2020 - const main = (dirToScan: string, outputFilePath: string): void => { + // TODO: convert to functional pattern const filePaths = collectTsFilePaths(dirToScan) - - const parsed = processAllFiles(filePaths, SCRIPT_TARGET) + const parsed = processAllFiles(filePaths) console.log(parsed) const output = generateOutput(parsed) diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/guards.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/guards.ts index 7de36bc..4903d64 100644 --- a/src/scripts/generateTypeMirrorClass/typeProcessing/guards.ts +++ b/src/scripts/generateTypeMirrorClass/typeProcessing/guards.ts @@ -1,6 +1,9 @@ import * as ts from "typescript" +export const isTypeFunction = (node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration): boolean => + !!node.typeParameters && node.typeParameters.length > 0 + export const isTypeAlias = (node: ts.Node): node is ts.TypeAliasDeclaration => ts.isTypeAliasDeclaration(node) -export const isExported = (node: ts.TypeAliasDeclaration): boolean => - node.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword) ?? false +export const isExportedType = (node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration): boolean => + !!node.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword) diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts index bf4f8bb..63b8fd2 100644 --- a/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts +++ b/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts @@ -1,26 +1,18 @@ import * as fs from "fs" -import { getTypeNodeName } from "tsc/utils" -import * as ts from "typescript" +import { createSourceFile, getTypeNodeName } from "tsc/utils" import { type PARSED_TYPE_DECLARATION, parseTypeDeclaration } from "../../../utils/parseTypeDeclarations" import { isExportedType, isTypeAlias, isTypeFunction } from "./guards" const CHARACTER_ENCODING = "utf-8" -export function visitSourceFile(filePath: string, scriptTarget: ts.ScriptTarget): PARSED_TYPE_DECLARATION[] { - const sourceText = fs.readFileSync(filePath, CHARACTER_ENCODING) - const file = ts.createSourceFile(filePath, sourceText, scriptTarget, true) +export function TODO_parseSth_rename_it_later(sourceText: string): PARSED_TYPE_DECLARATION[] { + const file = createSourceFile(sourceText) + // TODO: convert to SET const parsed: PARSED_TYPE_DECLARATION[] = [] file.forEachChild((node) => { if (isTypeAlias(node) && isTypeFunction(node) && isExportedType(node)) { - // Get type parameter information including defaults - // const typeParams = getTypeParameterInfo(node, sourceText) - // console.warn(`Type ${node.name.text} has parameters:`, typeParams) - // console.warn(`Type ${node.name.text} has body:`, typeBody) - - // const result = processExportedTypeAlias(node, sourceText, filePath) - // TODO: convert to pipe const nodeText = getTypeNodeName(sourceText)(node) const result = parseTypeDeclaration(nodeText) @@ -32,9 +24,10 @@ export function visitSourceFile(filePath: string, scriptTarget: ts.ScriptTarget) return parsed } -export function processAllFiles(filePaths: string[], scriptTarget: ts.ScriptTarget): PARSED_TYPE_DECLARATION[] { +export function processAllFiles(filePaths: string[]): PARSED_TYPE_DECLARATION[] { return filePaths.reduce((acc, filePath) => { - const parsed = visitSourceFile(filePath, scriptTarget) + const sourceText = fs.readFileSync(filePath, CHARACTER_ENCODING) + const parsed = TODO_parseSth_rename_it_later(sourceText) return [...acc, ...parsed] }, []) diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/sanitize.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/sanitize.ts deleted file mode 100644 index 18cb617..0000000 --- a/src/scripts/generateTypeMirrorClass/typeProcessing/sanitize.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { regexes } from "../../../regexes" - -export const sanitize = (raw: string): string => raw.replace(regexes.blockComment, "").replace(regexes.lineComment, "") diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/test-type-params.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/test-type-params.ts new file mode 100644 index 0000000..f9b1d93 --- /dev/null +++ b/src/scripts/generateTypeMirrorClass/typeProcessing/test-type-params.ts @@ -0,0 +1,60 @@ +import * as fs from "fs" +import { getGenericsFromNode } from "tsc/utils" +import * as ts from "typescript" + +// Example type declarations with different parameter patterns +const exampleCode = ` +// Type with no defaults +export type SimpleGeneric = T[]; + +// Type with constraint but no default +export type ConstrainedGeneric = Record; + +// Type with default +export type DefaultGeneric = T | null; + +// Type with constraint and default +export type ConstrainedDefaultGeneric = T | undefined; + +// Type with multiple parameters +export type MultiParamGeneric = { + prop1: T; + prop2: U; + prop3: V; +}; +` + +// Function to parse and analyze the example code +function analyzeTypeParameters() { + // Write the example code to a temporary file + const tempFilePath = "/tmp/type-params-test.ts" + fs.writeFileSync(tempFilePath, exampleCode) + + // Create a SourceFile object + const program = ts.createProgram([tempFilePath], {}) + const sourceFile = program.getSourceFile(tempFilePath) + + if (!sourceFile) { + console.error("Failed to parse source file") + return + } + + // Get the text content of the source file + const sourceText = sourceFile.getFullText() + + // Process each type alias declaration + sourceFile.forEachChild((node) => { + if (ts.isTypeAliasDeclaration(node)) { + const typeName = node.name.text + const typeParams = getGenericsFromNode(node, sourceText) + + console.warn(`Type '${typeName}' parameters:`, JSON.stringify(typeParams, null, 2)) + } + }) + + // Clean up the temporary file + fs.unlinkSync(tempFilePath) +} + +// Run the analysis +analyzeTypeParameters() diff --git a/src/tsc/collectTsFilePaths.ts b/src/tsc/collectTsFilePaths.ts index 5f2796b..d5377e2 100644 --- a/src/tsc/collectTsFilePaths.ts +++ b/src/tsc/collectTsFilePaths.ts @@ -1,33 +1,21 @@ import * as fs from "fs" import * as path from "path" -export const collectTsFilePaths = (dirsToScan: string[]): string[] => { - const tsFileAbsolutePaths: string[] = [] +const isTypeScriptFile = (fileName: string): boolean => fileName.endsWith(".ts") && !fileName.endsWith(".d.ts") - function addFilesFromDirectory(_path: string): void { - try { - const entries = fs.readdirSync(_path, { withFileTypes: true }) +export const collectTsFilePaths = (dirPath: string): string[] => { + const filePaths: string[] = [] + const entries = fs.readdirSync(dirPath, { withFileTypes: true }) - for (const entry of entries) { - const srcPath = path.join(_path, entry.name) + for (const entry of entries) { + const fullPath = path.join(dirPath, entry.name) - console.log("src", _path) - - if (entry.isDirectory() && !["node_modules", ".git"].includes(entry.name)) { - addFilesFromDirectory(srcPath) - } else if (entry.isFile() && /\.tsx?$/.test(entry.name)) { - tsFileAbsolutePaths.push(path.resolve(srcPath)) - } - } - } catch (error) { - console.error(`Cannot scan directory ${_path}:`, error) + if (entry.isDirectory()) { + filePaths.push(...collectTsFilePaths(fullPath)) + } else if (entry.isFile() && isTypeScriptFile(entry.name)) { + filePaths.push(fullPath) } } - dirsToScan - // - .filter(fs.existsSync) - .forEach(addFilesFromDirectory) - - return tsFileAbsolutePaths + return filePaths } diff --git a/src/tsc/findTypeDeclarations.ts b/src/tsc/findTypeDeclarations.ts index d96d9c3..1849d19 100644 --- a/src/tsc/findTypeDeclarations.ts +++ b/src/tsc/findTypeDeclarations.ts @@ -1,8 +1,10 @@ import * as fs from "fs" import * as ts from "typescript" import { collectTsFilePaths } from "./collectTsFilePaths" +import { createSourceFile } from "./utils" export const findTypeDeclarations = (rootDir: string, dirsToScan: string[], myType: string): string[] => { + // TODO: BROKEN const filePaths = collectTsFilePaths(dirsToScan) // console.log(`Total TypeScript files found: ${filePaths.length}`) @@ -11,12 +13,7 @@ export const findTypeDeclarations = (rootDir: string, dirsToScan: string[], myTy const defPaths: string[] = [] for (const filePath of filePaths) { - const sourceFile = ts.createSourceFile(filePath, fs.readFileSync(filePath, "utf8"), ts.ScriptTarget.Latest) - - // print source file - // console.log( - // printer.printNode(ts.EmitHint.Unspecified, sourceFile, ts.createSourceFile("", "", ts.ScriptTarget.Latest)), - // ) + const sourceFile = createSourceFile(fs.readFileSync(filePath, "utf8")) ts.forEachChild(sourceFile, (node) => { if (ts.isTypeAliasDeclaration(node) && node.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword)) { diff --git a/src/tsc/utils/index.ts b/src/tsc/utils/index.ts index 184db89..5338b23 100644 --- a/src/tsc/utils/index.ts +++ b/src/tsc/utils/index.ts @@ -1,5 +1,6 @@ import { regexes } from "regexes" -import type ts from "typescript" +import ts from "typescript" +import type { GENERIC } from "utils/parseTypeDeclarations" const sanitize = (nodeText: string): string => nodeText @@ -15,3 +16,36 @@ export const getTypeNodeName = export const getTypeNodeBody = (node: ts.TypeAliasDeclaration, sourceText: string): string => getTypeNodeName(sourceText)(node.type) + +export const createSourceFile = (sourceText: string, filePath: string = "any.ts"): ts.SourceFile => + // TODO: do we need true below? + ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.ESNext, true) + +export const getGenericsFromNode = ( + node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration, + sourceText: string, +): GENERIC[] => { + if (!node.typeParameters || node.typeParameters.length === 0) { + return [] + } + + return node.typeParameters.map((tp) => { + // TODO: fix with functional patterns + const result: GENERIC = { + name: tp.name.text, + } + + const _getNodeText = getTypeNodeName(sourceText) + + if (tp.constraint) { + // console.log("constraint----------------", _getNodeText(tp.constraint)) + result.constraint = _getNodeText(tp.constraint) + } + + if (tp.default) { + result.defaultValue = _getNodeText(tp.default) + } + + return result + }) +} diff --git a/src/utils/parseTypeDeclarations/index.ts b/src/utils/parseTypeDeclarations/index.ts index c0d6bf4..3e3c94a 100644 --- a/src/utils/parseTypeDeclarations/index.ts +++ b/src/utils/parseTypeDeclarations/index.ts @@ -1,4 +1,5 @@ -import { regexes } from "regexes" +import { isTypeAlias, isTypeFunction } from "scripts/generateTypeMirrorClass/typeProcessing/guards" +import { createSourceFile, getGenericsFromNode, getTypeNodeBody } from "tsc/utils" export type GENERIC = { name: string @@ -12,46 +13,42 @@ export type PARSED_TYPE_DECLARATION = { body: string } -export const parseTypeDeclaration = (_typeFunc: string): PARSED_TYPE_DECLARATION => { - const typeFunc = _typeFunc.trim() - const match = typeFunc.match(regexes.extractTypesAndValidations) +export const parseTypeDeclaration = (typeFunc: string): PARSED_TYPE_DECLARATION => { + // TODO: use SET instead of array + const _parsed: PARSED_TYPE_DECLARATION[] = [] + + createSourceFile(typeFunc) + // + .forEachChild((node) => { + // console.log("typeParameters", node.kind) // , "typeParameters" in node && node.typeParameters?.length) + + if (isTypeAlias(node) && isTypeFunction(node)) { + _parsed.push({ + // TODO: convert to parseNodeType() + typeName: node.name.text, + generics: getGenericsFromNode(node, typeFunc), + body: getTypeNodeBody(node, typeFunc), + }) + + // console.warn(`Type ${node.name.text} has body:`, typeBody) + } + }) + + const parsed = _parsed[0] - if (!match || !match[1] || !match[2] || !match[3]) { + if (!parsed) { throw new Error(`parseTypeDeclarations: Type function not found in type definition: ${typeFunc}`) } - const typeName = match[1] - const rawArgs = match[2].split(",") - const body = match[3] - - if (!typeName) { - throw new Error(`parseTypeDeclarations: Type function name not found in type definition: ${typeFunc}`) + if (!parsed.typeName) { + throw new Error(`parseTypeDeclarations: Type function NAME not found in type definition: ${typeFunc}`) } - if (!rawArgs) { - throw new Error(`parseTypeDeclarations: Type function args not found in type definition: ${typeFunc}`) + if (!parsed.generics) { + throw new Error(`parseTypeDeclarations: Type function ARGS not found in type definition: ${typeFunc}`) } - if (!body) { - throw new Error(`parseTypeDeclarations: Type function body not found in type definition: ${typeFunc}`) + if (!parsed.body) { + throw new Error(`parseTypeDeclarations: Type function BODY not found in type definition: ${typeFunc}`) } - const generics: GENERIC[] = [] - - for (let i = 0; i < rawArgs.length; i++) { - const arg = rawArgs[i] - - generics.push({ - // @ts-expect-error arg is not undefined - name: arg.split("extends")[0].split("=")[0].trim(), - // @ts-expect-error arg is not undefined - constraint: arg.split("extends")[1]?.split("=")[0].trim(), - // @ts-expect-error arg is not undefined - defaultValue: arg.split("=")[1]?.trim(), - }) - } - - return { - typeName, - generics, - body, - } + return parsed } From b447947e4e013dab7081d73722ea4c8c4f6d7237 Mon Sep 17 00:00:00 2001 From: Wojciech Morawski Date: Thu, 3 Jul 2025 05:59:01 +0200 Subject: [PATCH 06/14] feat(utils): add maybe func --- src/tsc/utils/index.ts | 23 +++++++---------------- src/utils/funcProg/index.ts | 13 +++++++++++++ 2 files changed, 20 insertions(+), 16 deletions(-) create mode 100644 src/utils/funcProg/index.ts diff --git a/src/tsc/utils/index.ts b/src/tsc/utils/index.ts index 5338b23..babc29b 100644 --- a/src/tsc/utils/index.ts +++ b/src/tsc/utils/index.ts @@ -1,5 +1,6 @@ import { regexes } from "regexes" import ts from "typescript" +import { maybe } from "utils/funcProg" import type { GENERIC } from "utils/parseTypeDeclarations" const sanitize = (nodeText: string): string => @@ -29,23 +30,13 @@ export const getGenericsFromNode = ( return [] } - return node.typeParameters.map((tp) => { - // TODO: fix with functional patterns - const result: GENERIC = { - name: tp.name.text, - } + return node.typeParameters.map((tp): GENERIC => { + const maybeGetNodeText = maybe(getTypeNodeName(sourceText)) - const _getNodeText = getTypeNodeName(sourceText) - - if (tp.constraint) { - // console.log("constraint----------------", _getNodeText(tp.constraint)) - result.constraint = _getNodeText(tp.constraint) - } - - if (tp.default) { - result.defaultValue = _getNodeText(tp.default) + return { + name: tp.name.text, + constraint: maybeGetNodeText(tp.constraint), + defaultValue: maybeGetNodeText(tp.default), } - - return result }) } diff --git a/src/utils/funcProg/index.ts b/src/utils/funcProg/index.ts new file mode 100644 index 0000000..4fc02a4 --- /dev/null +++ b/src/utils/funcProg/index.ts @@ -0,0 +1,13 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +export const maybe = + any>( + func: Func, + ): ((...args: Partial>) => ReturnType | undefined) => + (...args) => { + if (args.some((arg) => typeof arg === "undefined")) { + return + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return func(...args) + } From f045e2bfff640d6a2cd89fbfa535128b53df680c Mon Sep 17 00:00:00 2001 From: Wojciech Morawski Date: Thu, 3 Jul 2025 06:19:13 +0200 Subject: [PATCH 07/14] fix: rebuild main with pipe func --- src/scripts/generateTypeMirrorClass/index.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/scripts/generateTypeMirrorClass/index.ts b/src/scripts/generateTypeMirrorClass/index.ts index b2e37cc..1f24d85 100644 --- a/src/scripts/generateTypeMirrorClass/index.ts +++ b/src/scripts/generateTypeMirrorClass/index.ts @@ -1,19 +1,19 @@ import * as fs from "fs" import * as path from "path" +import { curry, pipe } from "ramda" import { collectTsFilePaths } from "tsc/collectTsFilePaths" import { generateOutput } from "./codegen" import { processAllFiles } from "./typeProcessing/processing" const main = (dirToScan: string, outputFilePath: string): void => { - // TODO: convert to functional pattern - const filePaths = collectTsFilePaths(dirToScan) - const parsed = processAllFiles(filePaths) - - console.log(parsed) - const output = generateOutput(parsed) - - fs.writeFileSync(outputFilePath, output) + pipe( + // + collectTsFilePaths, + processAllFiles, + generateOutput, + curry(fs.writeFileSync)(outputFilePath), + )(dirToScan) } main( From 379342794fc3bd996b90cc211f23577a1e791d1f Mon Sep 17 00:00:00 2001 From: Wojciech Morawski Date: Thu, 3 Jul 2025 06:24:59 +0200 Subject: [PATCH 08/14] fix: claSSSes typo --- .dependency-cruiser.js | 8 ++++---- scripts/findTypeDeclarations.ts | 2 +- src/buildTypes.ts | 4 ++-- src/{classses => classes}/Lax/index.test.ts | 0 src/{classses => classes}/Lax/index.ts | 2 +- src/{classses => classes}/Strict/index.test.ts | 0 src/{classses => classes}/Strict/index.ts | 2 +- src/{classses => classes}/types/index.ts | 0 src/{classses => classes}/utils/index.ts | 0 src/run.ts | 2 +- src/scripts/generateTypeMirrorClass/index.ts | 3 +-- src/tsc/findTypeDeclarations.ts | 6 +----- 12 files changed, 12 insertions(+), 17 deletions(-) rename src/{classses => classes}/Lax/index.test.ts (100%) rename src/{classses => classes}/Lax/index.ts (97%) rename src/{classses => classes}/Strict/index.test.ts (100%) rename src/{classses => classes}/Strict/index.ts (98%) rename src/{classses => classes}/types/index.ts (100%) rename src/{classses => classes}/utils/index.ts (100%) diff --git a/.dependency-cruiser.js b/.dependency-cruiser.js index 319f06f..5c8320e 100644 --- a/.dependency-cruiser.js +++ b/.dependency-cruiser.js @@ -9,7 +9,7 @@ module.exports = { path: "^src/utils/", }, to: { - path: "^src/classses/", + path: "^src/classes/", }, }, // @@ -153,7 +153,7 @@ module.exports = { "section of your package.json. If this module is development only - add it to the " + "from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration", from: { - path: "^(src/classses)", + path: "^(src/classes)", pathNot: "[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$", }, to: { @@ -384,7 +384,7 @@ module.exports = { attributes: { fillcolor: "#FFA726", fontcolor: "black" }, }, { - criteria: { source: "classses" }, + criteria: { source: "classes" }, attributes: { fillcolor: "#EF5350", fontcolor: "black" }, }, { @@ -402,7 +402,7 @@ module.exports = { ], dependencies: [ { - criteria: { resolved: "classses" }, + criteria: { resolved: "classes" }, attributes: { color: "#EF5350" }, }, { diff --git a/scripts/findTypeDeclarations.ts b/scripts/findTypeDeclarations.ts index 5dc041b..ecbb34e 100644 --- a/scripts/findTypeDeclarations.ts +++ b/scripts/findTypeDeclarations.ts @@ -16,7 +16,7 @@ import { findTypeDeclarations } from "../src/tsc" // ) const rootDir = "./src" -const dirsToScan = ["./src", "./tests", "./lib"] +const dirsToScan = "./src" const myType = "ValidateFlatTuple$" const declarationPaths = findTypeDeclarations(rootDir, dirsToScan, myType) diff --git a/src/buildTypes.ts b/src/buildTypes.ts index f90e693..b262208 100644 --- a/src/buildTypes.ts +++ b/src/buildTypes.ts @@ -1,5 +1,5 @@ -import { Lax } from "classses/Lax" -import { Strict } from "classses/Strict" +import { Lax } from "classes/Lax" +import { Strict } from "classes/Strict" import fs from "node:fs" import { toPairs } from "ramda" import { ImportRegistry } from "services/ImportRegistry" diff --git a/src/classses/Lax/index.test.ts b/src/classes/Lax/index.test.ts similarity index 100% rename from src/classses/Lax/index.test.ts rename to src/classes/Lax/index.test.ts diff --git a/src/classses/Lax/index.ts b/src/classes/Lax/index.ts similarity index 97% rename from src/classses/Lax/index.ts rename to src/classes/Lax/index.ts index c26c4fb..18a92c5 100644 --- a/src/classses/Lax/index.ts +++ b/src/classes/Lax/index.ts @@ -1,4 +1,4 @@ -import { resolveEitherName, resolveLaxName } from "classses/utils" +import { resolveEitherName, resolveLaxName } from "classes/utils" import { prop } from "ramda" import { createJsDocs } from "utils/createJsDocs" import type { PARSED_TYPE_DECLARATION } from "utils/parseTypeDeclarations" diff --git a/src/classses/Strict/index.test.ts b/src/classes/Strict/index.test.ts similarity index 100% rename from src/classses/Strict/index.test.ts rename to src/classes/Strict/index.test.ts diff --git a/src/classses/Strict/index.ts b/src/classes/Strict/index.ts similarity index 98% rename from src/classses/Strict/index.ts rename to src/classes/Strict/index.ts index dd25074..ef84c23 100644 --- a/src/classses/Strict/index.ts +++ b/src/classes/Strict/index.ts @@ -1,4 +1,4 @@ -import { rejectContext, resolveEitherLaxName, resolveStrictLaxName } from "classses/utils" +import { rejectContext, resolveEitherLaxName, resolveStrictLaxName } from "classes/utils" import { ImportRegistry } from "services/ImportRegistry" import { createJsDocs } from "utils/createJsDocs" import type { PARSED_TYPE_DECLARATION } from "utils/parseTypeDeclarations" diff --git a/src/classses/types/index.ts b/src/classes/types/index.ts similarity index 100% rename from src/classses/types/index.ts rename to src/classes/types/index.ts diff --git a/src/classses/utils/index.ts b/src/classes/utils/index.ts similarity index 100% rename from src/classses/utils/index.ts rename to src/classes/utils/index.ts diff --git a/src/run.ts b/src/run.ts index c1efca6..9d411a3 100644 --- a/src/run.ts +++ b/src/run.ts @@ -45,7 +45,7 @@ async function analyzeTypeScriptFiles(): Promise { console.log("🔍 Analyzing TypeScript files in the project...") // Find all TypeScript files in the classes directory - const classesDir = path.resolve(process.cwd(), "src", "classses") + const classesDir = path.resolve(process.cwd(), "src", "classes") const tsFiles = await findAllTsFiles(classesDir) console.log(`Found ${tsFiles.length} TypeScript files`) diff --git a/src/scripts/generateTypeMirrorClass/index.ts b/src/scripts/generateTypeMirrorClass/index.ts index 1f24d85..6dd8f6e 100644 --- a/src/scripts/generateTypeMirrorClass/index.ts +++ b/src/scripts/generateTypeMirrorClass/index.ts @@ -17,8 +17,7 @@ const main = (dirToScan: string, outputFilePath: string): void => { } main( - // TODO: Remove typo 'classSes' when will be fixed in the codebase + // path.resolve(process.cwd(), "src/coreTypes"), - // path.resolve(process.cwd(), "src/classses/generated/TypeMirror/index.ts"), path.resolve(process.cwd(), "dist/TypeMirror.ts"), ) diff --git a/src/tsc/findTypeDeclarations.ts b/src/tsc/findTypeDeclarations.ts index 1849d19..dd17dc1 100644 --- a/src/tsc/findTypeDeclarations.ts +++ b/src/tsc/findTypeDeclarations.ts @@ -3,13 +3,9 @@ import * as ts from "typescript" import { collectTsFilePaths } from "./collectTsFilePaths" import { createSourceFile } from "./utils" -export const findTypeDeclarations = (rootDir: string, dirsToScan: string[], myType: string): string[] => { - // TODO: BROKEN +export const findTypeDeclarations = (rootDir: string, dirsToScan: string, myType: string): string[] => { const filePaths = collectTsFilePaths(dirsToScan) - // console.log(`Total TypeScript files found: ${filePaths.length}`) - // filePaths.forEach((file) => console.log(` ${file}`)) - const defPaths: string[] = [] for (const filePath of filePaths) { From ccf66f8d0195b554639b92a135d3fe98a1558a46 Mon Sep 17 00:00:00 2001 From: Wojciech Morawski Date: Thu, 3 Jul 2025 09:16:16 +0200 Subject: [PATCH 09/14] fix: cleanup --- .../typeProcessing/processing.ts | 19 +++++++------------ src/tsc/findTypeDeclarations.ts | 8 +++----- src/tsc/utils/index.ts | 9 ++++++++- src/utils/funcProg/index.ts | 6 ++++++ 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts index 63b8fd2..79d1ed2 100644 --- a/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts +++ b/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts @@ -1,17 +1,14 @@ -import * as fs from "fs" -import { createSourceFile, getTypeNodeName } from "tsc/utils" +import { createSourceFileFromPath, getTypeNodeName } from "tsc/utils" import { type PARSED_TYPE_DECLARATION, parseTypeDeclaration } from "../../../utils/parseTypeDeclarations" import { isExportedType, isTypeAlias, isTypeFunction } from "./guards" -const CHARACTER_ENCODING = "utf-8" - -export function TODO_parseSth_rename_it_later(sourceText: string): PARSED_TYPE_DECLARATION[] { - const file = createSourceFile(sourceText) +const processSingleFile = (filePath: string): PARSED_TYPE_DECLARATION[] => { + const { sourceText, sourceFile } = createSourceFileFromPath(filePath) // TODO: convert to SET const parsed: PARSED_TYPE_DECLARATION[] = [] - file.forEachChild((node) => { + sourceFile.forEachChild((node) => { if (isTypeAlias(node) && isTypeFunction(node) && isExportedType(node)) { // TODO: convert to pipe const nodeText = getTypeNodeName(sourceText)(node) @@ -24,11 +21,9 @@ export function TODO_parseSth_rename_it_later(sourceText: string): PARSED_TYPE_D return parsed } -export function processAllFiles(filePaths: string[]): PARSED_TYPE_DECLARATION[] { - return filePaths.reduce((acc, filePath) => { - const sourceText = fs.readFileSync(filePath, CHARACTER_ENCODING) - const parsed = TODO_parseSth_rename_it_later(sourceText) +export const processAllFiles = (filePaths: string[]): PARSED_TYPE_DECLARATION[] => + filePaths.reduce((acc, filePath) => { + const parsed = processSingleFile(filePath) return [...acc, ...parsed] }, []) -} diff --git a/src/tsc/findTypeDeclarations.ts b/src/tsc/findTypeDeclarations.ts index dd17dc1..d9d346c 100644 --- a/src/tsc/findTypeDeclarations.ts +++ b/src/tsc/findTypeDeclarations.ts @@ -1,7 +1,6 @@ -import * as fs from "fs" import * as ts from "typescript" import { collectTsFilePaths } from "./collectTsFilePaths" -import { createSourceFile } from "./utils" +import { createSourceFileFromPath } from "./utils" export const findTypeDeclarations = (rootDir: string, dirsToScan: string, myType: string): string[] => { const filePaths = collectTsFilePaths(dirsToScan) @@ -9,7 +8,7 @@ export const findTypeDeclarations = (rootDir: string, dirsToScan: string, myType const defPaths: string[] = [] for (const filePath of filePaths) { - const sourceFile = createSourceFile(fs.readFileSync(filePath, "utf8")) + const { sourceFile } = createSourceFileFromPath(filePath) ts.forEachChild(sourceFile, (node) => { if (ts.isTypeAliasDeclaration(node) && node.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword)) { @@ -22,8 +21,7 @@ export const findTypeDeclarations = (rootDir: string, dirsToScan: string, myType // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison if (typeName === myType) { - // defPaths.push(filePath.replace(rootDir, ".").replace(rootDir.replace("./", ""), ".")) - defPaths.push(filePath) ///.replace(rootDir, ".").replace(rootDir.replace("./", ""), ".")) + defPaths.push(filePath) } } }) diff --git a/src/tsc/utils/index.ts b/src/tsc/utils/index.ts index babc29b..c74b2d4 100644 --- a/src/tsc/utils/index.ts +++ b/src/tsc/utils/index.ts @@ -1,6 +1,6 @@ import { regexes } from "regexes" import ts from "typescript" -import { maybe } from "utils/funcProg" +import { maybe, readFileSync } from "utils/funcProg" import type { GENERIC } from "utils/parseTypeDeclarations" const sanitize = (nodeText: string): string => @@ -22,6 +22,13 @@ export const createSourceFile = (sourceText: string, filePath: string = "any.ts" // TODO: do we need true below? ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.ESNext, true) +export const createSourceFileFromPath = (filePath: string): { sourceText: string; sourceFile: ts.SourceFile } => { + const sourceText = readFileSync(filePath) + const sourceFile = createSourceFile(sourceText) + + return { sourceText, sourceFile } +} + export const getGenericsFromNode = ( node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration, sourceText: string, diff --git a/src/utils/funcProg/index.ts b/src/utils/funcProg/index.ts index 4fc02a4..eb3e56b 100644 --- a/src/utils/funcProg/index.ts +++ b/src/utils/funcProg/index.ts @@ -1,3 +1,5 @@ +import { readFileSync as _readFileSync } from "fs" + /* eslint-disable @typescript-eslint/no-explicit-any */ export const maybe = any>( @@ -11,3 +13,7 @@ export const maybe = // eslint-disable-next-line @typescript-eslint/no-unsafe-return return func(...args) } + +// + +export const readFileSync = (filePath: string): string => _readFileSync(filePath, "utf-8") From a7d271336e485571ac7ae2e1c20d4ab39e70e03a Mon Sep 17 00:00:00 2001 From: Wojciech Morawski Date: Thu, 3 Jul 2025 09:44:08 +0200 Subject: [PATCH 10/14] fix: tsc guards --- .../typeProcessing/guards.ts | 20 ++++++++++++++----- .../typeProcessing/processing.ts | 8 ++++---- src/tsc/findTypeDeclarations.ts | 5 ++++- src/tsc/utils/index.ts | 8 ++++---- src/utils/parseTypeDeclarations/index.ts | 13 +++++------- 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/guards.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/guards.ts index 4903d64..9b6ddfd 100644 --- a/src/scripts/generateTypeMirrorClass/typeProcessing/guards.ts +++ b/src/scripts/generateTypeMirrorClass/typeProcessing/guards.ts @@ -1,9 +1,19 @@ import * as ts from "typescript" -export const isTypeFunction = (node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration): boolean => - !!node.typeParameters && node.typeParameters.length > 0 +const isTypeAlias = (node: ts.Node): node is ts.TypeAliasDeclaration => ts.isTypeAliasDeclaration(node) -export const isTypeAlias = (node: ts.Node): node is ts.TypeAliasDeclaration => ts.isTypeAliasDeclaration(node) +export const isTypeFunction = (node: ts.Node): node is ts.TypeAliasDeclaration => { + if (isTypeAlias(node)) { + return !!node.typeParameters && node.typeParameters.length > 0 + } -export const isExportedType = (node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration): boolean => - !!node.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword) + return false +} + +export const isExportedTypeFunction = (node: ts.Node): node is ts.TypeAliasDeclaration => { + if (isTypeAlias(node) && isTypeFunction(node)) { + return !!node.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword) + } + + return false +} diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts index 79d1ed2..5a0a1d4 100644 --- a/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts +++ b/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts @@ -1,6 +1,6 @@ -import { createSourceFileFromPath, getTypeNodeName } from "tsc/utils" +import { createSourceFileFromPath, getNodeText } from "tsc/utils" import { type PARSED_TYPE_DECLARATION, parseTypeDeclaration } from "../../../utils/parseTypeDeclarations" -import { isExportedType, isTypeAlias, isTypeFunction } from "./guards" +import { isExportedTypeFunction } from "./guards" const processSingleFile = (filePath: string): PARSED_TYPE_DECLARATION[] => { const { sourceText, sourceFile } = createSourceFileFromPath(filePath) @@ -9,9 +9,9 @@ const processSingleFile = (filePath: string): PARSED_TYPE_DECLARATION[] => { const parsed: PARSED_TYPE_DECLARATION[] = [] sourceFile.forEachChild((node) => { - if (isTypeAlias(node) && isTypeFunction(node) && isExportedType(node)) { + if (isExportedTypeFunction(node)) { // TODO: convert to pipe - const nodeText = getTypeNodeName(sourceText)(node) + const nodeText = getNodeText(sourceText)(node) const result = parseTypeDeclaration(nodeText) parsed.push(result) diff --git a/src/tsc/findTypeDeclarations.ts b/src/tsc/findTypeDeclarations.ts index d9d346c..a143fc9 100644 --- a/src/tsc/findTypeDeclarations.ts +++ b/src/tsc/findTypeDeclarations.ts @@ -1,3 +1,4 @@ +import { isExportedTypeFunction } from "scripts/generateTypeMirrorClass/typeProcessing/guards" import * as ts from "typescript" import { collectTsFilePaths } from "./collectTsFilePaths" import { createSourceFileFromPath } from "./utils" @@ -11,8 +12,10 @@ export const findTypeDeclarations = (rootDir: string, dirsToScan: string, myType const { sourceFile } = createSourceFileFromPath(filePath) ts.forEachChild(sourceFile, (node) => { - if (ts.isTypeAliasDeclaration(node) && node.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword)) { + if (isExportedTypeFunction(node)) { const typeName = node.name.escapedText + + // TODO: remove console.log( typeName, rootDir, diff --git a/src/tsc/utils/index.ts b/src/tsc/utils/index.ts index c74b2d4..2d13f80 100644 --- a/src/tsc/utils/index.ts +++ b/src/tsc/utils/index.ts @@ -10,13 +10,13 @@ const sanitize = (nodeText: string): string => .replace(regexes.lineComment, "") .trim() -export const getTypeNodeName = +export const getNodeText = (sourceText: string) => (node: ts.Node): string => sanitize(sourceText.slice(node.pos, node.end)) -export const getTypeNodeBody = (node: ts.TypeAliasDeclaration, sourceText: string): string => - getTypeNodeName(sourceText)(node.type) +export const getNodeBody = (node: ts.TypeAliasDeclaration, sourceText: string): string => + getNodeText(sourceText)(node.type) export const createSourceFile = (sourceText: string, filePath: string = "any.ts"): ts.SourceFile => // TODO: do we need true below? @@ -38,7 +38,7 @@ export const getGenericsFromNode = ( } return node.typeParameters.map((tp): GENERIC => { - const maybeGetNodeText = maybe(getTypeNodeName(sourceText)) + const maybeGetNodeText = maybe(getNodeText(sourceText)) return { name: tp.name.text, diff --git a/src/utils/parseTypeDeclarations/index.ts b/src/utils/parseTypeDeclarations/index.ts index 3e3c94a..bfbb236 100644 --- a/src/utils/parseTypeDeclarations/index.ts +++ b/src/utils/parseTypeDeclarations/index.ts @@ -1,5 +1,5 @@ -import { isTypeAlias, isTypeFunction } from "scripts/generateTypeMirrorClass/typeProcessing/guards" -import { createSourceFile, getGenericsFromNode, getTypeNodeBody } from "tsc/utils" +import { isTypeFunction } from "scripts/generateTypeMirrorClass/typeProcessing/guards" +import { createSourceFile, getGenericsFromNode, getNodeBody } from "tsc/utils" export type GENERIC = { name: string @@ -20,17 +20,14 @@ export const parseTypeDeclaration = (typeFunc: string): PARSED_TYPE_DECLARATION createSourceFile(typeFunc) // .forEachChild((node) => { - // console.log("typeParameters", node.kind) // , "typeParameters" in node && node.typeParameters?.length) - - if (isTypeAlias(node) && isTypeFunction(node)) { + // TODO: isExportedTypeFunction instead? + if (isTypeFunction(node)) { _parsed.push({ // TODO: convert to parseNodeType() typeName: node.name.text, generics: getGenericsFromNode(node, typeFunc), - body: getTypeNodeBody(node, typeFunc), + body: getNodeBody(node, typeFunc), }) - - // console.warn(`Type ${node.name.text} has body:`, typeBody) } }) From be957333904c74e1062c6b98fe52c9ba55f41c60 Mon Sep 17 00:00:00 2001 From: Wojciech Morawski Date: Thu, 3 Jul 2025 10:00:32 +0200 Subject: [PATCH 11/14] some more --- .../typeProcessing/processing.ts | 9 +++-- src/utils/parseTypeDeclarations/index.ts | 35 ++++++++++++------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts b/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts index 5a0a1d4..78d5d9e 100644 --- a/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts +++ b/src/scripts/generateTypeMirrorClass/typeProcessing/processing.ts @@ -1,5 +1,5 @@ -import { createSourceFileFromPath, getNodeText } from "tsc/utils" -import { type PARSED_TYPE_DECLARATION, parseTypeDeclaration } from "../../../utils/parseTypeDeclarations" +import { createSourceFileFromPath } from "tsc/utils" +import { type PARSED_TYPE_DECLARATION, parseNode } from "utils/parseTypeDeclarations" import { isExportedTypeFunction } from "./guards" const processSingleFile = (filePath: string): PARSED_TYPE_DECLARATION[] => { @@ -10,14 +10,13 @@ const processSingleFile = (filePath: string): PARSED_TYPE_DECLARATION[] => { sourceFile.forEachChild((node) => { if (isExportedTypeFunction(node)) { - // TODO: convert to pipe - const nodeText = getNodeText(sourceText)(node) - const result = parseTypeDeclaration(nodeText) + const result = parseNode(sourceText, node) parsed.push(result) } }) + // TODO: check lengths return parsed } diff --git a/src/utils/parseTypeDeclarations/index.ts b/src/utils/parseTypeDeclarations/index.ts index bfbb236..2e903cb 100644 --- a/src/utils/parseTypeDeclarations/index.ts +++ b/src/utils/parseTypeDeclarations/index.ts @@ -1,5 +1,6 @@ import { isTypeFunction } from "scripts/generateTypeMirrorClass/typeProcessing/guards" -import { createSourceFile, getGenericsFromNode, getNodeBody } from "tsc/utils" +import { createSourceFile, getGenericsFromNode, getNodeBody, getNodeText } from "tsc/utils" +import type ts from "typescript" export type GENERIC = { name: string @@ -13,39 +14,47 @@ export type PARSED_TYPE_DECLARATION = { body: string } -export const parseTypeDeclaration = (typeFunc: string): PARSED_TYPE_DECLARATION => { +const _parseNode = (node: ts.TypeAliasDeclaration, typeFuncDef: string): PARSED_TYPE_DECLARATION => ({ + typeName: node.name.text, + generics: getGenericsFromNode(node, typeFuncDef), + body: getNodeBody(node, typeFuncDef), +}) + +export const parseTypeDeclaration = (typeFuncDef: string): PARSED_TYPE_DECLARATION => { // TODO: use SET instead of array const _parsed: PARSED_TYPE_DECLARATION[] = [] - createSourceFile(typeFunc) + createSourceFile(typeFuncDef) // .forEachChild((node) => { // TODO: isExportedTypeFunction instead? if (isTypeFunction(node)) { - _parsed.push({ - // TODO: convert to parseNodeType() - typeName: node.name.text, - generics: getGenericsFromNode(node, typeFunc), - body: getNodeBody(node, typeFunc), - }) + _parsed.push(_parseNode(node, typeFuncDef)) } }) const parsed = _parsed[0] if (!parsed) { - throw new Error(`parseTypeDeclarations: Type function not found in type definition: ${typeFunc}`) + throw new Error(`parseTypeDeclarations: Type function not found in type definition: ${typeFuncDef}`) } if (!parsed.typeName) { - throw new Error(`parseTypeDeclarations: Type function NAME not found in type definition: ${typeFunc}`) + throw new Error(`parseTypeDeclarations: Type function NAME not found in type definition: ${typeFuncDef}`) } if (!parsed.generics) { - throw new Error(`parseTypeDeclarations: Type function ARGS not found in type definition: ${typeFunc}`) + throw new Error(`parseTypeDeclarations: Type function ARGS not found in type definition: ${typeFuncDef}`) } if (!parsed.body) { - throw new Error(`parseTypeDeclarations: Type function BODY not found in type definition: ${typeFunc}`) + throw new Error(`parseTypeDeclarations: Type function BODY not found in type definition: ${typeFuncDef}`) } return parsed } + +// TODO: cumbersome logic: node -> text -> node -> _parseNode +export const parseNode = (sourceText: string, node: ts.Node): PARSED_TYPE_DECLARATION => { + const typeFuncDef = getNodeText(sourceText)(node) + + return parseTypeDeclaration(typeFuncDef) +} From 2ae1893418e85a6ee0355138e24ab03775315cb4 Mon Sep 17 00:00:00 2001 From: Wojciech Morawski Date: Thu, 10 Jul 2025 12:23:28 +0200 Subject: [PATCH 12/14] fix: maybe --- src/utils/funcProg/index.ts | 58 ++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/src/utils/funcProg/index.ts b/src/utils/funcProg/index.ts index eb3e56b..0408237 100644 --- a/src/utils/funcProg/index.ts +++ b/src/utils/funcProg/index.ts @@ -1,18 +1,60 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { readFileSync as _readFileSync } from "fs" -/* eslint-disable @typescript-eslint/no-explicit-any */ -export const maybe = - any>( - func: Func, - ): ((...args: Partial>) => ReturnType | undefined) => - (...args) => { - if (args.some((arg) => typeof arg === "undefined")) { - return +type PartialTuple = T extends [infer Head, ...infer Tail] + ? // + [Head?, ...PartialTuple] + : [] + +function maybe any>(func: Func) { + return >>( + ...args: MaybeArgs + ): MaybeArgs extends Parameters + ? ReturnType + : MaybeArgs["length"] extends Parameters["length"] + ? ReturnType | undefined + : undefined => { + if (args.some((arg) => arg === undefined)) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return undefined as any // X } // eslint-disable-next-line @typescript-eslint/no-unsafe-return return func(...args) } +} + +// + +// GOOD +const id = (_a: T, _b: U): T => _a +// BAD +// const id = (_a: string = '01', _b: number = 10) => _a; + +// --------------------------------------------- + +const test = maybe(id) + +// 1. no args +const a = test() +// ^? + +// 2. no enough args +const b = test("1") +// ^? + +// 3. correct args +const c = test("1", 1) +// ^? + +// 4. optional args +const d = test(Math.random() > 0.5 ? "" : undefined, Math.random() > 0.5 ? 1 : undefined) +const _d = d +// ^? + +// 5. to many args +const e = test("1", 1, "") +// ^? // From 943b641385797e92854e42737befb0aaa5e1060a Mon Sep 17 00:00:00 2001 From: Wojciech Morawski Date: Thu, 10 Jul 2025 13:09:50 +0200 Subject: [PATCH 13/14] fix: findTypeDeclarations --- scripts/findTypeDeclarations.ts | 5 ++--- src/coreTypes/errors/errors.ts | 16 +++++++------- src/scripts/generateTypeMirrorClass/index.ts | 4 ++-- src/tsc/findTypeDeclarations.ts | 22 +++++++------------- src/utils/funcProg/index.ts | 5 +++-- 5 files changed, 22 insertions(+), 30 deletions(-) diff --git a/scripts/findTypeDeclarations.ts b/scripts/findTypeDeclarations.ts index ecbb34e..56b1886 100644 --- a/scripts/findTypeDeclarations.ts +++ b/scripts/findTypeDeclarations.ts @@ -15,9 +15,8 @@ import { findTypeDeclarations } from "../src/tsc" // printer.printNode(ts.EmitHint.Unspecified, typeStringAlias, ts.createSourceFile("", "", ts.ScriptTarget.Latest)), // ) -const rootDir = "./src" const dirsToScan = "./src" -const myType = "ValidateFlatTuple$" -const declarationPaths = findTypeDeclarations(rootDir, dirsToScan, myType) +const typesToFind = ["ValidateFlatTuple$", "ReTypeError"] +const declarationPaths = findTypeDeclarations(dirsToScan, typesToFind) console.log("declarationPaths", declarationPaths) diff --git a/src/coreTypes/errors/errors.ts b/src/coreTypes/errors/errors.ts index b781b9a..7e9a937 100644 --- a/src/coreTypes/errors/errors.ts +++ b/src/coreTypes/errors/errors.ts @@ -67,7 +67,7 @@ export type ReTypeError< _ErrorType extends keyof ErrorsLookup, Context extends string, Value, - _Constraint = unknown + _Constraint = "TODO:unknown" > = { __type: _ErrorType __message: ErrorsLookup[_ErrorType]["msg"] @@ -80,16 +80,16 @@ export type ReTypeError< // ----------------------- // prettier-ignore -export type NeverError = ReTypeError<"NeverError", Trace, T, _Constraint> +export type NeverError = ReTypeError<"NeverError", Trace, T, _Constraint> // prettier-ignore -export type AnyError = ReTypeError<"AnyError", Trace, T, _Constraint> +export type AnyError = ReTypeError<"AnyError", Trace, T, _Constraint> // prettier-ignore -export type UnknownError = ReTypeError<"UnknownError", Trace, T, _Constraint> +export type UnknownError = ReTypeError<"UnknownError", Trace, T, _Constraint> // prettier-ignore -export type MismatchError = ReTypeError<"MismatchError", Trace, T, _Constraint> +export type MismatchError = ReTypeError<"MismatchError", Trace, T, _Constraint> // prettier-ignore -export type NonLiteralError = ReTypeError<"NonLiteralError", Trace, T, _Constraint> +export type NonLiteralError = ReTypeError<"NonLiteralError", Trace, T, _Constraint> // prettier-ignore -export type EmptyStringError = ReTypeError<"EmptyStringError", Trace, T, _Constraint> +export type EmptyStringError = ReTypeError<"EmptyStringError", Trace, T, _Constraint> // prettier-ignore -export type OpenTypeError = ReTypeError<"OpenTypeError", Trace, T, _Constraint> +export type OpenTypeError = ReTypeError<"OpenTypeError", Trace, T, _Constraint> diff --git a/src/scripts/generateTypeMirrorClass/index.ts b/src/scripts/generateTypeMirrorClass/index.ts index 6dd8f6e..1748f34 100644 --- a/src/scripts/generateTypeMirrorClass/index.ts +++ b/src/scripts/generateTypeMirrorClass/index.ts @@ -1,7 +1,7 @@ import * as fs from "fs" import * as path from "path" -import { curry, pipe } from "ramda" +import { pipe } from "ramda" import { collectTsFilePaths } from "tsc/collectTsFilePaths" import { generateOutput } from "./codegen" import { processAllFiles } from "./typeProcessing/processing" @@ -12,7 +12,7 @@ const main = (dirToScan: string, outputFilePath: string): void => { collectTsFilePaths, processAllFiles, generateOutput, - curry(fs.writeFileSync)(outputFilePath), + (code: string) => fs.writeFileSync(outputFilePath, code), )(dirToScan) } diff --git a/src/tsc/findTypeDeclarations.ts b/src/tsc/findTypeDeclarations.ts index a143fc9..ecfcc15 100644 --- a/src/tsc/findTypeDeclarations.ts +++ b/src/tsc/findTypeDeclarations.ts @@ -3,32 +3,24 @@ import * as ts from "typescript" import { collectTsFilePaths } from "./collectTsFilePaths" import { createSourceFileFromPath } from "./utils" -export const findTypeDeclarations = (rootDir: string, dirsToScan: string, myType: string): string[] => { - const filePaths = collectTsFilePaths(dirsToScan) +export const findTypeDeclarations = (dirsToScan: string, providedTypeDeclaration: string[]): string[] => { + const importPaths: string[] = [] - const defPaths: string[] = [] + const filePaths = collectTsFilePaths(dirsToScan) for (const filePath of filePaths) { const { sourceFile } = createSourceFileFromPath(filePath) ts.forEachChild(sourceFile, (node) => { if (isExportedTypeFunction(node)) { - const typeName = node.name.escapedText - - // TODO: remove - console.log( - typeName, - rootDir, - node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword), - ) + const currentTypeName = node.name.escapedText.toString() - // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison - if (typeName === myType) { - defPaths.push(filePath) + if (providedTypeDeclaration.includes(currentTypeName)) { + importPaths.push(`import { ${currentTypeName} } from "${filePath}"`) } } }) } - return defPaths + return importPaths } diff --git a/src/utils/funcProg/index.ts b/src/utils/funcProg/index.ts index 0408237..051b4eb 100644 --- a/src/utils/funcProg/index.ts +++ b/src/utils/funcProg/index.ts @@ -6,7 +6,7 @@ type PartialTuple = T extends [infer Head, ...infer Tail] [Head?, ...PartialTuple] : [] -function maybe any>(func: Func) { +export function maybe any>(func: Func) { return >>( ...args: MaybeArgs ): MaybeArgs extends Parameters @@ -16,7 +16,7 @@ function maybe any>(func: Func) { : undefined => { if (args.some((arg) => arg === undefined)) { // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return undefined as any // X + return undefined as any } // eslint-disable-next-line @typescript-eslint/no-unsafe-return @@ -53,6 +53,7 @@ const _d = d // ^? // 5. to many args +// @ts-expect-error too many args const e = test("1", 1, "") // ^? From 8a8d7b9a9d83ad519ccb077c9139764020e0470c Mon Sep 17 00:00:00 2001 From: Wojciech Morawski Date: Fri, 18 Jul 2025 09:34:03 +0200 Subject: [PATCH 14/14] WIP --- src/classes/Lax/index.ts | 14 +++-- src/classes/Strict/index.ts | 7 ++- src/coreTypes/errors/errors.ts | 18 +++--- src/coreTypes/errors/utils.ts | 12 ++-- .../generateTypeMirrorClass/codegen.ts | 39 ++++++++---- .../generateTypeMirrorClass/utils/import.ts | 49 +++++++-------- src/tsc/findTypeDeclarations.ts | 61 +++++++++++++++++-- src/utilTypes/index.ts | 3 + src/utils/consts/index.ts | 2 +- src/utils/createJsDocs/index.ts | 8 ++- src/utils/reTypeError/index.ts | 10 +-- src/utils/typeBuilder/eitherBody.ts | 10 +++ src/utils/typeBuilder/genericArgs.ts | 23 +++++-- src/utils/typeBuilder/typeDefinitions.ts | 20 +++--- 14 files changed, 192 insertions(+), 84 deletions(-) create mode 100644 src/utils/typeBuilder/eitherBody.ts diff --git a/src/classes/Lax/index.ts b/src/classes/Lax/index.ts index 18a92c5..aa73f02 100644 --- a/src/classes/Lax/index.ts +++ b/src/classes/Lax/index.ts @@ -6,8 +6,12 @@ import { parseTypeDeclaration } from "utils/parseTypeDeclarations" import type { WITH_CONTEXT } from "utils/resolveGenerics" import { resolveGenerics } from "utils/resolveGenerics" import { typeBuilder } from "utils/typeBuilder" +import { eitherBody } from "utils/typeBuilder/eitherBody" +import type { Brand } from "utilTypes" import type { WITH_COMMENTS } from "../types" +export type LAX_BODY = Brand + export class Lax { protected parsedType: PARSED_TYPE_DECLARATION @@ -48,9 +52,7 @@ export class Lax { currentTypeName: typeName, }) - const body = `[_Error] extends [never] - ? ${typeInvocation} - : _Error` + const body = eitherBody(typeInvocation) return typeBuilder.typeDeclaration({ docs, @@ -62,7 +64,7 @@ export class Lax { // --- PROTECTED ---------------------------------------------------------------------- - protected makeLaxBody({ withContext, withComments }: WITH_COMMENTS & WITH_CONTEXT): string { + protected makeLaxBody({ withContext, withComments }: WITH_COMMENTS & WITH_CONTEXT): LAX_BODY { // TODO: mismatch error could be more detailed and reuse validation (what it should be) // TODO: inside validation missing (before return) @@ -82,7 +84,7 @@ export class Lax { generics: resolveGenerics({ withContext, generics }), // currentTypeName: "laxName", }), - ) + ) as LAX_BODY if (withComments) { return ( @@ -90,7 +92,7 @@ export class Lax { ` // --- ${laxName} START --- ${conditionalTypeBody} - // --- ${laxName} END ---` + // --- ${laxName} END ---` as LAX_BODY ) } diff --git a/src/classes/Strict/index.ts b/src/classes/Strict/index.ts index ef84c23..77017e0 100644 --- a/src/classes/Strict/index.ts +++ b/src/classes/Strict/index.ts @@ -8,6 +8,9 @@ import { resolveGenerics } from "utils/resolveGenerics" import type { CurrentTypeName } from "utils/reTypeError/trace" import { trace } from "utils/reTypeError/trace" import { typeBuilder } from "utils/typeBuilder" +import type { Brand } from "utilTypes" + +export type STRICT_LAX_BODY = Brand export class Strict { protected parsedType: PARSED_TYPE_DECLARATION @@ -34,7 +37,7 @@ export class Strict { } // TODO: import validation modules keys - protected makeStrictLaxBody({ currentTypeName }: { currentTypeName: CurrentTypeName }): string { + protected makeStrictLaxBody({ currentTypeName }: { currentTypeName: CurrentTypeName }): STRICT_LAX_BODY { // TODO: mismatch error could be more detailed and reuse validation (what it should be) // TODO: Kamils class const ValidationType = "ValidateFlatTuple$" @@ -57,7 +60,7 @@ export class Strict { >, // Pass original generics ${genericsInvocationWithoutContext} ->` +>` as STRICT_LAX_BODY return typeDef } diff --git a/src/coreTypes/errors/errors.ts b/src/coreTypes/errors/errors.ts index 7e9a937..9864bf4 100644 --- a/src/coreTypes/errors/errors.ts +++ b/src/coreTypes/errors/errors.ts @@ -20,7 +20,7 @@ export const ERROR_TYPE = { export type ErrorType = ValueOf // TODO: do we need js here? -export const ERRORS_LOOKUP = { +export const ErrorsLookup = { // input -------------------------------- OpenTypeError: { msg: "input: is open types (any, unknown, never)", @@ -60,21 +60,21 @@ export const ERRORS_LOOKUP = { { msg: string; url: string } > -export type ErrorsLookup = typeof ERRORS_LOOKUP +export type ERRORS_LOOKUP = typeof ErrorsLookup // TODO: js docs export type ReTypeError< - _ErrorType extends keyof ErrorsLookup, + _ErrorType extends keyof ERRORS_LOOKUP, Context extends string, Value, _Constraint = "TODO:unknown" > = { - __type: _ErrorType - __message: ErrorsLookup[_ErrorType]["msg"] - __context: Context - __value: Value & {} // TODO: pretty - __constraint?: _Constraint & {} // TODO: pretty - __url: ErrorsLookup[_ErrorType]["url"] + readonly __type: _ErrorType + readonly __message: ERRORS_LOOKUP[_ErrorType]["msg"] + readonly __context: Context + readonly __value: Value & {} // TODO: pretty + readonly __constraint?: _Constraint & {} // TODO: pretty + readonly __url: ERRORS_LOOKUP[_ErrorType]["url"] } // ----------------------- diff --git a/src/coreTypes/errors/utils.ts b/src/coreTypes/errors/utils.ts index 15f4622..acadc66 100644 --- a/src/coreTypes/errors/utils.ts +++ b/src/coreTypes/errors/utils.ts @@ -1,15 +1,15 @@ import type { - ErrorsLookup, + ERRORS_LOOKUP, NeverError, } from "./errors" export type GENERIC_ERROR = { - __type: keyof ErrorsLookup - __message: ErrorsLookup[keyof ErrorsLookup]["msg"] - __url: ErrorsLookup[keyof ErrorsLookup]["url"] - __context: string + readonly __type: keyof ERRORS_LOOKUP + readonly __message: ERRORS_LOOKUP[keyof ERRORS_LOOKUP]["msg"] + readonly __url: ERRORS_LOOKUP[keyof ERRORS_LOOKUP]["url"] + readonly __context: string // eslint-disable-next-line @typescript-eslint/no-explicit-any - __value: any + readonly __value: any } // ----------------------------------------------------------------- diff --git a/src/scripts/generateTypeMirrorClass/codegen.ts b/src/scripts/generateTypeMirrorClass/codegen.ts index 8da3db8..5f32135 100644 --- a/src/scripts/generateTypeMirrorClass/codegen.ts +++ b/src/scripts/generateTypeMirrorClass/codegen.ts @@ -1,5 +1,10 @@ +import { ImportRegistry } from "services/ImportRegistry" +import { findTypeDeclarations } from "tsc" import type { GENERIC, PARSED_TYPE_DECLARATION } from "utils/parseTypeDeclarations" -import { maybeRegisterConstraintImport } from "./utils/import" +import { deconstructConstraint, isPrimitiveConstraint } from "./utils/import" + +// import type { ErrorsLookup } from "coreTypes/errors" // TODO: Hardcoded path, should be replaced with a dynamic import +// import type { BYPASS_MODES } from "coreTypes/validators" export const generateParamsDeclaration = (generics: GENERIC[]): string => generics @@ -14,11 +19,15 @@ export const generateMethod = ({ typeName, generics }: PARSED_TYPE_DECLARATION): const paramsDecl = generateParamsDeclaration(generics) const paramsInterp = generateParamsInterpolation(generics) - generics.forEach(({ constraint }) => { - maybeRegisterConstraintImport(constraint) - }) + generics.forEach(({ constraint: _constraint }) => { + if (_constraint) { + const constraint = deconstructConstraint(_constraint) - console.log(generics) + if (!isPrimitiveConstraint(constraint)) { + ImportRegistry.addImport(constraint) + } + } + }) return ` ${typeName}(${paramsDecl}): string { @@ -28,14 +37,20 @@ export const generateMethod = ({ typeName, generics }: PARSED_TYPE_DECLARATION): export const generateClassBody = (methods: PARSED_TYPE_DECLARATION[]): string => methods.map(generateMethod).join("\n") -export const generateOutput = (methods: PARSED_TYPE_DECLARATION[]): string => - ` - import type { ErrorsLookup } from "coreTypes/errors" // TODO: Hardcoded path, should be replaced with a dynamic import - import type { BYPASS_MODES } from "coreTypes/validators" +export const generateOutput = (methods: PARSED_TYPE_DECLARATION[]): string => { + const utilClass = `export class TypeUtils { + ${generateClassBody(methods)} +}` + const imports = ImportRegistry.getImports() + console.log("imports --->", imports) -export class TypeUtils { - ${generateClassBody(methods)} + const declarations = findTypeDeclarations("./src", imports) -} + console.log("declarations --->", declarations) + + return ` + ${declarations.join("\n")} + ${utilClass} ` +} diff --git a/src/scripts/generateTypeMirrorClass/utils/import.ts b/src/scripts/generateTypeMirrorClass/utils/import.ts index 6e66e84..d295ebe 100644 --- a/src/scripts/generateTypeMirrorClass/utils/import.ts +++ b/src/scripts/generateTypeMirrorClass/utils/import.ts @@ -1,28 +1,29 @@ -import { ImportRegistry } from "../../../services/ImportRegistry" +import { flip, includes, pipe, trim } from "ramda" -export function getPrimitiveConstraints(): string[] { - return ["string", "number", "boolean", "null", "undefined", "symbol", "void", "never", "any", "unknown"] -} +const primitiveConstraints: string[] = [ + "string", + "number", + "boolean", + "null", + "undefined", + "symbol", + "void", + "never", + "any", + "unknown", +] -export function isPrimitiveArray(constraint: string): boolean { - return constraint.endsWith("[]") && getPrimitiveConstraints().includes(constraint.replace(/\[\]$/, "").trim()) -} +const stripKeyof = (constraint: string): string => constraint.replace(/^keyof\s+/, "") +const stripArray = (constraint: string): string => constraint.replace(/\[\]$/, "") -export function isPrimitiveConstraint(constraint?: string): boolean { - if (!constraint) return true - if (getPrimitiveConstraints().includes(constraint.trim())) return true - if (isPrimitiveArray(constraint)) return true - return false -} +export const deconstructConstraint: (constraint: string) => string = pipe( + // + stripKeyof, + stripArray, + trim, +) -export function stripKeyof(constraint?: string): string | undefined { - if (!constraint) return constraint - return constraint.replace(/^keyof\s+/, "").trim() -} - -export function maybeRegisterConstraintImport(constraint?: string): void { - const sanitized = stripKeyof(constraint) - if (sanitized && !isPrimitiveConstraint(sanitized)) { - ImportRegistry.addImport(sanitized) - } -} +export const isPrimitiveConstraint: (constraint: string) => boolean = pipe( + deconstructConstraint, + flip(includes)(primitiveConstraints), +) diff --git a/src/tsc/findTypeDeclarations.ts b/src/tsc/findTypeDeclarations.ts index ecfcc15..359875d 100644 --- a/src/tsc/findTypeDeclarations.ts +++ b/src/tsc/findTypeDeclarations.ts @@ -1,26 +1,77 @@ -import { isExportedTypeFunction } from "scripts/generateTypeMirrorClass/typeProcessing/guards" import * as ts from "typescript" import { collectTsFilePaths } from "./collectTsFilePaths" -import { createSourceFileFromPath } from "./utils" +import { createSourceFileFromPath, getNodeText } from "./utils" + +// Helper functions to identify static types +const isTypeAlias = (node: ts.Node): node is ts.TypeAliasDeclaration => ts.isTypeAliasDeclaration(node) +const isInterface = (node: ts.Node): node is ts.InterfaceDeclaration => ts.isInterfaceDeclaration(node) +const isExported = (node: ts.Node): boolean => { + if (ts.canHaveModifiers(node) && node.modifiers) { + return node.modifiers.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword) + } + return false +} + +// Check if node is an exported static type (type alias without type parameters) +const isExportedStaticTypeAlias = (node: ts.Node): node is ts.TypeAliasDeclaration => { + if (isTypeAlias(node) && isExported(node)) { + // Static types have no type parameters or empty type parameters + return !node.typeParameters || node.typeParameters.length === 0 + } + return false +} + +// Check if node is an exported interface +const isExportedInterface = (node: ts.Node): node is ts.InterfaceDeclaration => isInterface(node) && isExported(node) export const findTypeDeclarations = (dirsToScan: string, providedTypeDeclaration: string[]): string[] => { const importPaths: string[] = [] + const allFoundTypes: { name: string; path: string }[] = [] const filePaths = collectTsFilePaths(dirsToScan) + console.warn("Scanning files:", filePaths.length) for (const filePath of filePaths) { - const { sourceFile } = createSourceFileFromPath(filePath) + const { sourceFile, sourceText } = createSourceFileFromPath(filePath) + const relativePath = filePath.replace(/\.ts$/, "") ts.forEachChild(sourceFile, (node) => { - if (isExportedTypeFunction(node)) { + // Check for exported static type aliases + if (isExportedStaticTypeAlias(node)) { + const currentTypeName = node.name.escapedText.toString() + + // if (node.name.parent.parent) { + // const x = node.name.parent.parent?.text + // } + + console.log("node.name", getNodeText(sourceText)(node.name)) + + // try { + // console.log("node.name", getNodeBody(sourceFile as unknown as any, node)) + // } catch (error) {} + + allFoundTypes.push({ name: currentTypeName, path: relativePath }) + + if (providedTypeDeclaration.includes(currentTypeName)) { + importPaths.push(`import { ${currentTypeName} } from "${relativePath}"`) + } + } + // Check for exported interfaces + else if (isExportedInterface(node)) { + console.log("DEAD") + const currentTypeName = node.name.escapedText.toString() + allFoundTypes.push({ name: currentTypeName, path: relativePath }) if (providedTypeDeclaration.includes(currentTypeName)) { - importPaths.push(`import { ${currentTypeName} } from "${filePath}"`) + importPaths.push(`import { ${currentTypeName} } from "${relativePath}"`) } } }) } + console.warn("Found exported static types:", allFoundTypes.length) + allFoundTypes.forEach((type) => console.warn(`${type.name} - ${type.path}`)) + return importPaths } diff --git a/src/utilTypes/index.ts b/src/utilTypes/index.ts index c046dbd..22bfc95 100644 --- a/src/utilTypes/index.ts +++ b/src/utilTypes/index.ts @@ -5,3 +5,6 @@ export type DEAD_BRANCH = never export type ValueOf = T[keyof T] export type SafeOmit = Omit export type SafePick = Pick + +declare const __brand: unique symbol +export type Brand = BrandType & { readonly [__brand]: BrandName } diff --git a/src/utils/consts/index.ts b/src/utils/consts/index.ts index e03d89d..7bec9ef 100644 --- a/src/utils/consts/index.ts +++ b/src/utils/consts/index.ts @@ -8,7 +8,7 @@ export const CONTEXT_GENERIC = { constraint: "string", } as const satisfies GENERIC -const ERROR = "_Error" as const +export const ERROR = "_Error" as const // const ERROR_DECLARATION = `${ERROR} extends GENERIC_ERROR` as const export const ERROR_GENERIC = { name: ERROR, diff --git a/src/utils/createJsDocs/index.ts b/src/utils/createJsDocs/index.ts index c43c227..691050a 100644 --- a/src/utils/createJsDocs/index.ts +++ b/src/utils/createJsDocs/index.ts @@ -1,7 +1,9 @@ -import type { SafeOmit } from "utilTypes" +import type { Brand, SafeOmit } from "utilTypes" import type { PARSED_TYPE_DECLARATION } from "../parseTypeDeclarations" -export const createJsDocs = ({ typeName: name, generics }: SafeOmit): string => { +export type JS_DOCS = Brand + +export const createJsDocs = ({ typeName: name, generics }: SafeOmit): JS_DOCS => { const params = generics .map((generic) => { const defaultValue = generic.defaultValue ? `any, fallbacks to ${generic.defaultValue}` : "any" @@ -15,5 +17,5 @@ export const createJsDocs = ({ typeName: name, generics }: SafeOmit( @@ -27,8 +27,8 @@ const buildReTypeError = < ): string => { const e: ReTypeError<_ErrorType, Context, _Generic["name"], _Generic["constraint"]> = { __type, - __message: ERRORS_LOOKUP[__type]["msg"], - __url: ERRORS_LOOKUP[__type]["url"], + __message: ErrorsLookup[__type]["msg"], + __url: ErrorsLookup[__type]["url"], __context, __value: generic.name, __constraint: generic.constraint, diff --git a/src/utils/typeBuilder/eitherBody.ts b/src/utils/typeBuilder/eitherBody.ts new file mode 100644 index 0000000..759c735 --- /dev/null +++ b/src/utils/typeBuilder/eitherBody.ts @@ -0,0 +1,10 @@ +import { ERROR } from "utils/consts" +import type { Brand } from "utilTypes" +import type { TYPE_INVOCATION } from "./typeDefinitions" + +export type EITHER_BODY = Brand + +export const eitherBody = (typeInvocation: TYPE_INVOCATION): EITHER_BODY => + `[${ERROR}] extends [never] +? ${typeInvocation} +: ${ERROR}` as EITHER_BODY diff --git a/src/utils/typeBuilder/genericArgs.ts b/src/utils/typeBuilder/genericArgs.ts index cf98c30..670c915 100644 --- a/src/utils/typeBuilder/genericArgs.ts +++ b/src/utils/typeBuilder/genericArgs.ts @@ -2,13 +2,23 @@ import { P, match } from "ts-pattern" import { CONTEXT, CONTEXT_DECLARATION } from "utils/consts" import type { GENERIC } from "utils/parseTypeDeclarations" import type { CurrentTypeName } from "utils/reTypeError/trace" +import type { Brand } from "utilTypes" import { supportContextTracing } from "./utils" -export const genericArgsDeclaration = ({ generics, lax }: { generics: GENERIC[]; lax?: boolean }): string => +export type GENERIC_ARGS_DECLARATION = Brand + +export const genericArgsDeclaration = ({ + generics, + lax, +}: { + generics: GENERIC[] + lax?: boolean +}): GENERIC_ARGS_DECLARATION => generics .map((generic) => // TODO: dirty, support context appropriately match([lax || false, generic]) + .returnType() // LAX TRUE .with([true, { name: P.string, defaultValue: P.string }], ([_, g]) => `${g.name} = ${g.defaultValue}`) // .with([true, { name: P.string, constraint: P.string }], ([_, g]) => `${g.name} extends ${g.constraint}`) @@ -23,10 +33,15 @@ export const genericArgsDeclaration = ({ generics, lax }: { generics: GENERIC[]; .with([false, { name: P.string }], ([_, g]) => g.name) .exhaustive(), ) - .join(", ") + .join(", ") as GENERIC_ARGS_DECLARATION + +export type GENERIC_ARGS_INVOCATION = Brand -export const genericArgsInvocation = (generics: GENERIC[], currentTypeName?: CurrentTypeName): string => +export const genericArgsInvocation = ( + generics: GENERIC[], + currentTypeName?: CurrentTypeName, +): GENERIC_ARGS_INVOCATION => generics // .map(supportContextTracing(currentTypeName)) - .join(", ") + .join(", ") as GENERIC_ARGS_INVOCATION diff --git a/src/utils/typeBuilder/typeDefinitions.ts b/src/utils/typeBuilder/typeDefinitions.ts index 173d940..37dea2a 100644 --- a/src/utils/typeBuilder/typeDefinitions.ts +++ b/src/utils/typeBuilder/typeDefinitions.ts @@ -1,7 +1,11 @@ -import type { SafeOmit } from "utilTypes" +import type { LAX_BODY } from "classes/Lax" +import type { STRICT_LAX_BODY } from "classes/Strict" +import type { Brand, SafeOmit } from "utilTypes" +import type { JS_DOCS } from "utils/createJsDocs" import type { PARSED_TYPE_DECLARATION } from "utils/parseTypeDeclarations" import type { CurrentTypeName } from "utils/reTypeError/trace" -import { genericArgsInvocation } from "./genericArgs" +import type { EITHER_BODY } from "./eitherBody" +import { type GENERIC_ARGS_DECLARATION, genericArgsInvocation } from "./genericArgs" export const typeDeclaration = ({ docs, @@ -9,14 +13,16 @@ export const typeDeclaration = ({ genericsDeclarations, body, }: { - docs: string + docs: JS_DOCS typeName: string - genericsDeclarations: string - body: string + genericsDeclarations: GENERIC_ARGS_DECLARATION + body: EITHER_BODY | LAX_BODY | STRICT_LAX_BODY }): string => `${docs} type ${typeName}<${genericsDeclarations}> = ${body}` type Props = SafeOmit & { currentTypeName?: CurrentTypeName } -export const typeInvocation = ({ typeName, generics, currentTypeName }: Props): string => - `${typeName}<${genericArgsInvocation(generics, currentTypeName)}>` +export type TYPE_INVOCATION = Brand + +export const typeInvocation = ({ typeName, generics, currentTypeName }: Props): TYPE_INVOCATION => + `${typeName}<${genericArgsInvocation(generics, currentTypeName)}>` as TYPE_INVOCATION