From 54e73045e1137540d91a631bdae4a3454f3e6f5e Mon Sep 17 00:00:00 2001 From: DaxServer Date: Sat, 6 Sep 2025 19:50:45 +0200 Subject: [PATCH 1/2] feat: Upgrade dependencies and refactor chunk creation - Upgrade `@eslint/js`, `@typescript-eslint/eslint-plugin`, `@typescript-eslint/parser`, and `eslint` to their latest versions - Refactor the `createChunkNodes` function in `local-type-collector.ts` to use the new `createChunkNodes` and `shouldChunkUnion` functions from the `chunk-large-types` module - This change improves the handling of large union types by breaking them into smaller chunks, making the generated code more manageable and easier to work with --- bun.lock | 38 +++---- package.json | 10 +- src/traverse/chunk-large-types.ts | 117 ++++++++++++++++++++++ src/traverse/dependency-extractor.ts | 3 + src/traverse/dependency-traversal.ts | 10 +- src/traverse/import-collector.ts | 57 +++++++++-- src/traverse/local-type-collector.ts | 82 +-------------- tests/handlers/typebox/as-const.test.ts | 126 ++++++++++++++++++++++++ 8 files changed, 329 insertions(+), 114 deletions(-) create mode 100644 src/traverse/chunk-large-types.ts create mode 100644 tests/handlers/typebox/as-const.test.ts diff --git a/bun.lock b/bun.lock index 6840a6f..59c290f 100644 --- a/bun.lock +++ b/bun.lock @@ -11,13 +11,13 @@ "ts-morph": "^26.0.0", }, "devDependencies": { - "@eslint/js": "^9.34.0", + "@eslint/js": "^9.35.0", "@prettier/sync": "^0.6.1", "@sinclair/typebox-codegen": "^0.11.1", "@types/bun": "^1.2.21", - "@typescript-eslint/eslint-plugin": "^8.41.0", - "@typescript-eslint/parser": "^8.41.0", - "eslint": "^9.34.0", + "@typescript-eslint/eslint-plugin": "^8.42.0", + "@typescript-eslint/parser": "^8.42.0", + "eslint": "^9.35.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-no-relative-import-paths": "^1.6.1", "eslint-plugin-prettier": "^5.5.4", @@ -27,7 +27,7 @@ "prettier": "^3.6.2", "prettier-plugin-organize-imports": "^4.2.0", "tsc-alias": "^1.8.16", - "typescript-eslint": "^8.41.0", + "typescript-eslint": "^8.42.0", }, "peerDependencies": { "typescript": "~5.9.2", @@ -35,7 +35,7 @@ }, }, "packages": { - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.8.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q=="], "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], @@ -47,7 +47,7 @@ "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], - "@eslint/js": ["@eslint/js@9.34.0", "", {}, "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw=="], + "@eslint/js": ["@eslint/js@9.35.0", "", {}, "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw=="], "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], @@ -91,25 +91,25 @@ "@types/react": ["@types/react@19.1.10", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.41.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.41.0", "@typescript-eslint/type-utils": "8.41.0", "@typescript-eslint/utils": "8.41.0", "@typescript-eslint/visitor-keys": "8.41.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.41.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-8fz6oa6wEKZrhXWro/S3n2eRJqlRcIa6SlDh59FXJ5Wp5XRZ8B9ixpJDcjadHq47hMx0u+HW6SNa6LjJQ6NLtw=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.42.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.42.0", "@typescript-eslint/type-utils": "8.42.0", "@typescript-eslint/utils": "8.42.0", "@typescript-eslint/visitor-keys": "8.42.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.42.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Aq2dPqsQkxHOLfb2OPv43RnIvfj05nw8v/6n3B2NABIPpHnjQnaLo9QGMTvml+tv4korl/Cjfrb/BYhoL8UUTQ=="], - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.41.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.41.0", "@typescript-eslint/types": "8.41.0", "@typescript-eslint/typescript-estree": "8.41.0", "@typescript-eslint/visitor-keys": "8.41.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg=="], + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.42.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.42.0", "@typescript-eslint/types": "8.42.0", "@typescript-eslint/typescript-estree": "8.42.0", "@typescript-eslint/visitor-keys": "8.42.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.41.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.41.0", "@typescript-eslint/types": "^8.41.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-b8V9SdGBQzQdjJ/IO3eDifGpDBJfvrNTp2QD9P2BeqWTGrRibgfgIlBSw6z3b6R7dPzg752tOs4u/7yCLxksSQ=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.42.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.42.0", "@typescript-eslint/types": "^8.42.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg=="], - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.41.0", "", { "dependencies": { "@typescript-eslint/types": "8.41.0", "@typescript-eslint/visitor-keys": "8.41.0" } }, "sha512-n6m05bXn/Cd6DZDGyrpXrELCPVaTnLdPToyhBoFkLIMznRUQUEQdSp96s/pcWSQdqOhrgR1mzJ+yItK7T+WPMQ=="], + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.42.0", "", { "dependencies": { "@typescript-eslint/types": "8.42.0", "@typescript-eslint/visitor-keys": "8.42.0" } }, "sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.41.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-TDhxYFPUYRFxFhuU5hTIJk+auzM/wKvWgoNYOPcOf6i4ReYlOoYN8q1dV5kOTjNQNJgzWN3TUUQMtlLOcUgdUw=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.42.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ=="], - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.41.0", "", { "dependencies": { "@typescript-eslint/types": "8.41.0", "@typescript-eslint/typescript-estree": "8.41.0", "@typescript-eslint/utils": "8.41.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-63qt1h91vg3KsjVVonFJWjgSK7pZHSQFKH6uwqxAH9bBrsyRhO6ONoKyXxyVBzG1lJnFAJcKAcxLS54N1ee1OQ=="], + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.42.0", "", { "dependencies": { "@typescript-eslint/types": "8.42.0", "@typescript-eslint/typescript-estree": "8.42.0", "@typescript-eslint/utils": "8.42.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-9KChw92sbPTYVFw3JLRH1ockhyR3zqqn9lQXol3/YbI6jVxzWoGcT3AsAW0mu1MY0gYtsXnUGV/AKpkAj5tVlQ=="], - "@typescript-eslint/types": ["@typescript-eslint/types@8.41.0", "", {}, "sha512-9EwxsWdVqh42afLbHP90n2VdHaWU/oWgbH2P0CfcNfdKL7CuKpwMQGjwev56vWu9cSKU7FWSu6r9zck6CVfnag=="], + "@typescript-eslint/types": ["@typescript-eslint/types@8.42.0", "", {}, "sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.41.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.41.0", "@typescript-eslint/tsconfig-utils": "8.41.0", "@typescript-eslint/types": "8.41.0", "@typescript-eslint/visitor-keys": "8.41.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-D43UwUYJmGhuwHfY7MtNKRZMmfd8+p/eNSfFe6tH5mbVDto+VQCayeAt35rOx3Cs6wxD16DQtIKw/YXxt5E0UQ=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.42.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.42.0", "@typescript-eslint/tsconfig-utils": "8.42.0", "@typescript-eslint/types": "8.42.0", "@typescript-eslint/visitor-keys": "8.42.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.41.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.41.0", "@typescript-eslint/types": "8.41.0", "@typescript-eslint/typescript-estree": "8.41.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-udbCVstxZ5jiPIXrdH+BZWnPatjlYwJuJkDA4Tbo3WyYLh8NvB+h/bKeSZHDOFKfphsZYJQqaFtLeXEqurQn1A=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.42.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.42.0", "@typescript-eslint/types": "8.42.0", "@typescript-eslint/typescript-estree": "8.42.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-JnIzu7H3RH5BrKC4NoZqRfmjqCIS1u3hGZltDYJgkVdqAezl4L9d1ZLw+36huCujtSBSAirGINF/S4UxOcR+/g=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.41.0", "", { "dependencies": { "@typescript-eslint/types": "8.41.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-+GeGMebMCy0elMNg67LRNoVnUFPIm37iu5CmHESVx56/9Jsfdpsvbv605DQ81Pi/x11IdKUsS5nzgTYbCQU9fg=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.42.0", "", { "dependencies": { "@typescript-eslint/types": "8.42.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ=="], "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], @@ -163,7 +163,7 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - "eslint": ["eslint@9.34.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.34.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg=="], + "eslint": ["eslint@9.35.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.35.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg=="], "eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="], @@ -361,7 +361,7 @@ "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], - "typescript-eslint": ["typescript-eslint@8.41.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.41.0", "@typescript-eslint/parser": "8.41.0", "@typescript-eslint/typescript-estree": "8.41.0", "@typescript-eslint/utils": "8.41.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-n66rzs5OBXW3SFSnZHr2T685q1i4ODm2nulFJhMZBotaTavsS8TrI3d7bDlRSs9yWo7HmyWrN9qDu14Qv7Y0Dw=="], + "typescript-eslint": ["typescript-eslint@8.42.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.42.0", "@typescript-eslint/parser": "8.42.0", "@typescript-eslint/typescript-estree": "8.42.0", "@typescript-eslint/utils": "8.42.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ozR/rQn+aQXQxh1YgbCzQWDFrsi9mcg+1PM3l/z5o1+20P7suOIaNg515bpr/OYt6FObz/NHcBstydDLHWeEKg=="], "undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], diff --git a/package.json b/package.json index d2dbd9f..869a036 100644 --- a/package.json +++ b/package.json @@ -38,13 +38,13 @@ "ts-morph": "^26.0.0" }, "devDependencies": { - "@eslint/js": "^9.34.0", + "@eslint/js": "^9.35.0", "@prettier/sync": "^0.6.1", "@sinclair/typebox-codegen": "^0.11.1", "@types/bun": "^1.2.21", - "@typescript-eslint/eslint-plugin": "^8.41.0", - "@typescript-eslint/parser": "^8.41.0", - "eslint": "^9.34.0", + "@typescript-eslint/eslint-plugin": "^8.42.0", + "@typescript-eslint/parser": "^8.42.0", + "eslint": "^9.35.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-no-relative-import-paths": "^1.6.1", "eslint-plugin-prettier": "^5.5.4", @@ -54,7 +54,7 @@ "prettier": "^3.6.2", "prettier-plugin-organize-imports": "^4.2.0", "tsc-alias": "^1.8.16", - "typescript-eslint": "^8.41.0" + "typescript-eslint": "^8.42.0" }, "peerDependencies": { "typescript": "~5.9.2" diff --git a/src/traverse/chunk-large-types.ts b/src/traverse/chunk-large-types.ts new file mode 100644 index 0000000..735b737 --- /dev/null +++ b/src/traverse/chunk-large-types.ts @@ -0,0 +1,117 @@ +import { NodeGraph } from '@daxserver/validation-schema-codegen/traverse/node-graph' +import type { TraversedNode } from '@daxserver/validation-schema-codegen/traverse/types' +import { resolverStore } from '@daxserver/validation-schema-codegen/utils/resolver-store' +import { Node, SourceFile } from 'ts-morph' + +const CHUNK_SIZE = 20 + +export const shouldChunkUnion = (node: Node): boolean => { + if (Node.isUnionTypeNode(node)) { + return node.getTypeNodes().length >= CHUNK_SIZE + } + + // Handle IndexedAccessType that resolves to large unions (e.g., typeof sites[number]) + if (Node.isIndexedAccessTypeNode(node)) { + const typeChecker = node.getProject().getTypeChecker() + const type = typeChecker.getTypeAtLocation(node) + if (type.isUnion()) { + return type.getUnionTypes().length >= CHUNK_SIZE + } + } + + return false +} + +export const createChunkNodes = ( + node: Node, + parentTypeName: string, + nodeGraph: NodeGraph, + maincodeNodeIds: Set, + requiredNodeIds: Set, + sourceFile: SourceFile, + addToRequired: boolean = true, +): string[] => { + let typeTexts: string[] = [] + + if (Node.isUnionTypeNode(node)) { + typeTexts = node.getTypeNodes().map((n) => n.getText()) + } else if (Node.isIndexedAccessTypeNode(node)) { + // Handle IndexedAccessType by resolving to union types + const typeChecker = node.getProject().getTypeChecker() + const type = typeChecker.getTypeAtLocation(node) + if (type.isUnion()) { + typeTexts = type.getUnionTypes().map((t) => t.getText()) + } + } + + if (typeTexts.length === 0) { + return [] + } + const chunks: string[][] = [] + + // Create chunks of 20 items each + for (let i = 0; i < typeTexts.length; i += CHUNK_SIZE) { + chunks.push(typeTexts.slice(i, i + CHUNK_SIZE)) + } + + const chunkReferences: string[] = [] + + // Create chunk nodes + for (let i = 0; i < chunks.length; i++) { + const chunkName = `${parentTypeName}_Chunk${i + 1}` + const chunkQualifiedName = resolverStore.generateQualifiedName(chunkName, sourceFile) + + chunkReferences.push(chunkQualifiedName) + maincodeNodeIds.add(chunkQualifiedName) + if (addToRequired) { + requiredNodeIds.add(chunkQualifiedName) + } + + // Create a new union node with only the chunk's type texts + const chunkTypeTexts = chunks[i]! + + // Create a synthetic union node for this chunk + const project = node.getProject() + const tempFileName = `__temp_chunk_${parentTypeName}_${i}_${Date.now()}.ts` + const tempSourceFile = project.createSourceFile( + tempFileName, + `type TempChunk = ${chunkTypeTexts.join(' | ')}`, + ) + const tempTypeAlias = tempSourceFile.getTypeAliases()[0]! + const chunkTypeNode = tempTypeAlias.getTypeNode()! + + const chunkTraversedNode: TraversedNode = { + node: chunkTypeNode, // Use the chunk-specific union node + type: 'chunk', + originalName: chunkName, + qualifiedName: chunkQualifiedName, + isImported: false, + isMainCode: true, + isChunk: true, + chunkReferences: [], // Chunk nodes don't need references to other chunks + } + + nodeGraph.addTypeNode(chunkQualifiedName, chunkTraversedNode) + + // Add to ResolverStore + resolverStore.addTypeMapping({ + originalName: chunkName, + sourceFile, + }) + } + + return chunkReferences +} + +export const markChunksAsRequired = ( + parentQualifiedName: string, + nodeGraph: NodeGraph, + requiredNodeIds: Set, +): void => { + const parentNode = nodeGraph.getNode(parentQualifiedName) + if (parentNode?.chunkReferences) { + for (const chunkRef of parentNode.chunkReferences) { + requiredNodeIds.add(chunkRef) + } + } +} diff --git a/src/traverse/dependency-extractor.ts b/src/traverse/dependency-extractor.ts index 21efbfb..36f918a 100644 --- a/src/traverse/dependency-extractor.ts +++ b/src/traverse/dependency-extractor.ts @@ -1,3 +1,4 @@ +import { markChunksAsRequired } from '@daxserver/validation-schema-codegen/traverse/chunk-large-types' import { NodeGraph } from '@daxserver/validation-schema-codegen/traverse/node-graph' import { TypeReferenceExtractor } from '@daxserver/validation-schema-codegen/traverse/type-reference-extractor' import { resolverStore } from '@daxserver/validation-schema-codegen/utils/resolver-store' @@ -50,6 +51,8 @@ export const extractDependencies = (nodeGraph: NodeGraph, requiredNodeIds: Set() private requiredNodeIds = new Set() - private importCollector: ImportCollector - constructor() { - this.importCollector = new ImportCollector(this.fileGraph, this.nodeGraph) - } + private importCollector = new ImportCollector( + this.fileGraph, + this.nodeGraph, + this.maincodeNodeIds, + this.requiredNodeIds, + ) startTraversal(sourceFile: SourceFile): TraversedNode[] { // Mark main source file nodes as main code diff --git a/src/traverse/import-collector.ts b/src/traverse/import-collector.ts index bb483b0..6742230 100644 --- a/src/traverse/import-collector.ts +++ b/src/traverse/import-collector.ts @@ -1,3 +1,7 @@ +import { + createChunkNodes, + shouldChunkUnion, +} from '@daxserver/validation-schema-codegen/traverse/chunk-large-types' import { FileGraph } from '@daxserver/validation-schema-codegen/traverse/file-graph' import { NodeGraph } from '@daxserver/validation-schema-codegen/traverse/node-graph' import { resolverStore } from '@daxserver/validation-schema-codegen/utils/resolver-store' @@ -7,6 +11,8 @@ export class ImportCollector { constructor( private fileGraph: FileGraph, private nodeGraph: NodeGraph, + private maincodeNodeIds: Set, + private requiredNodeIds: Set, ) {} collectFromImports(importDeclarations: ImportDeclaration[]): void { @@ -45,15 +51,48 @@ export class ImportCollector { typeAlias.getSourceFile(), ) const aliasName = aliasMap.get(typeName) - this.nodeGraph.addTypeNode(qualifiedName, { - node: typeAlias, - type: 'typeAlias', - originalName: typeName, - qualifiedName, - isImported: true, - isMainCode: false, - aliasName, - }) + + // Check if this type alias needs chunking + const typeNode = typeAlias.getTypeNode() + if (typeNode && shouldChunkUnion(typeNode)) { + // Create chunk nodes for large union + const chunkReferences = createChunkNodes( + typeNode, + typeName, + this.nodeGraph, + this.maincodeNodeIds, + this.requiredNodeIds, + typeAlias.getSourceFile(), + false, // Don't add chunks to required set initially + ) + + // Add the main type as an imported type alias with chunk references + this.nodeGraph.addTypeNode(qualifiedName, { + node: typeAlias, + type: 'typeAlias', + originalName: typeName, + qualifiedName, + isImported: true, + isMainCode: false, + aliasName, + chunkReferences: chunkReferences, + }) + + // Add dependencies from chunks to parent type for topological sorting + for (const chunkRef of chunkReferences) { + this.nodeGraph.addDependency(chunkRef, qualifiedName) + } + } else { + this.nodeGraph.addTypeNode(qualifiedName, { + node: typeAlias, + type: 'typeAlias', + originalName: typeName, + qualifiedName, + isImported: true, + isMainCode: false, + aliasName, + }) + } // Add to ResolverStore during traversal resolverStore.addTypeMapping({ diff --git a/src/traverse/local-type-collector.ts b/src/traverse/local-type-collector.ts index b880f53..ec8f3f5 100644 --- a/src/traverse/local-type-collector.ts +++ b/src/traverse/local-type-collector.ts @@ -1,82 +1,10 @@ +import { + createChunkNodes, + shouldChunkUnion, +} from '@daxserver/validation-schema-codegen/traverse/chunk-large-types' import { NodeGraph } from '@daxserver/validation-schema-codegen/traverse/node-graph' -import type { TraversedNode } from '@daxserver/validation-schema-codegen/traverse/types' import { resolverStore } from '@daxserver/validation-schema-codegen/utils/resolver-store' -import { Node, SourceFile } from 'ts-morph' - -const CHUNK_SIZE = 20 - -const shouldChunkUnion = (typeNode: Node): boolean => { - if (!Node.isUnionTypeNode(typeNode)) { - return false - } - return typeNode.getTypeNodes().length >= CHUNK_SIZE -} - -const createChunkNodes = ( - node: Node, - parentTypeName: string, - nodeGraph: NodeGraph, - maincodeNodeIds: Set, - requiredNodeIds: Set, - sourceFile: SourceFile, -): string[] => { - if (!Node.isUnionTypeNode(node)) { - return [] - } - - const typeNodes = node.getTypeNodes() - const chunks: Node[][] = [] - - // Create chunks of 20 items each - for (let i = 0; i < typeNodes.length; i += CHUNK_SIZE) { - chunks.push(typeNodes.slice(i, i + CHUNK_SIZE)) - } - - const chunkReferences: string[] = [] - - // Create chunk nodes - for (let i = 0; i < chunks.length; i++) { - const chunkName = `${parentTypeName}_Chunk${i + 1}` - const chunkQualifiedName = resolverStore.generateQualifiedName(chunkName, sourceFile) - - chunkReferences.push(chunkQualifiedName) - maincodeNodeIds.add(chunkQualifiedName) - requiredNodeIds.add(chunkQualifiedName) - - // Create a new union node with only the chunk's type nodes - const chunkTypeNodes = chunks[i]! - - // Create a synthetic union node for this chunk - const project = node.getProject() - const tempSourceFile = project.createSourceFile( - `__temp_chunk_${i}.ts`, - `type TempChunk = ${chunkTypeNodes.map((node) => node.getText()).join(' | ')}`, - ) - const tempTypeAlias = tempSourceFile.getTypeAliases()[0]! - const chunkTypeNode = tempTypeAlias.getTypeNode()! - - const chunkTraversedNode: TraversedNode = { - node: chunkTypeNode, // Use the chunk-specific union node - type: 'chunk', - originalName: chunkName, - qualifiedName: chunkQualifiedName, - isImported: false, - isMainCode: true, - isChunk: true, - chunkReferences: [], // Chunk nodes don't need references to other chunks - } - - nodeGraph.addTypeNode(chunkQualifiedName, chunkTraversedNode) - - // Add to ResolverStore - resolverStore.addTypeMapping({ - originalName: chunkName, - sourceFile, - }) - } - - return chunkReferences -} +import { SourceFile } from 'ts-morph' export const addLocalTypes = ( sourceFile: SourceFile, diff --git a/tests/handlers/typebox/as-const.test.ts b/tests/handlers/typebox/as-const.test.ts new file mode 100644 index 0000000..7b15371 --- /dev/null +++ b/tests/handlers/typebox/as-const.test.ts @@ -0,0 +1,126 @@ +import { createSourceFile, formatWithPrettier, generateFormattedCode } from '@test-fixtures/utils' +import { beforeEach, describe, expect, test } from 'bun:test' +import { Project } from 'ts-morph' + +describe('As const expressions', () => { + let project: Project + + beforeEach(() => { + project = new Project() + }) + + describe('array literals with as const', () => { + test('string literals array', () => { + const sourceFile = createSourceFile(project, 'type A = ["hello", "world"] as const') + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier(` + export const A = Type.Tuple([Type.Literal("hello"), Type.Literal("world")]); + + export type A = Static; + `), + ) + }) + + test('numeric literals array', () => { + const sourceFile = createSourceFile(project, 'type A = [1, 2, 3] as const') + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier(` + export const A = Type.Tuple([Type.Literal(1), Type.Literal(2), Type.Literal(3)]); + + export type A = Static; + `), + ) + }) + + test('boolean literals array', () => { + const sourceFile = createSourceFile(project, 'type A = [true, false] as const') + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier(` + export const A = Type.Tuple([Type.Literal(true), Type.Literal(false)]); + + export type A = Static; + `), + ) + }) + + test('mixed literals array', () => { + const sourceFile = createSourceFile(project, 'type A = ["hello", 42, true] as const') + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier(` + export const A = Type.Tuple([Type.Literal("hello"), Type.Literal(42), Type.Literal(true)]); + + export type A = Static; + `), + ) + }) + + test('empty array', () => { + const sourceFile = createSourceFile(project, 'type A = [] as const') + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier(` + export const A = Type.Tuple([]); + + export type A = Static; + `), + ) + }) + }) + + describe('non-array expressions with as const', () => { + test('string literal', () => { + const sourceFile = createSourceFile(project, 'type A = "hello" as const') + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier(` + export const A = Type.Literal("hello"); + + export type A = Static; + `), + ) + }) + + test('numeric literal', () => { + const sourceFile = createSourceFile(project, 'type A = 42 as const') + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier(` + export const A = Type.Literal(42); + + export type A = Static; + `), + ) + }) + + test('boolean literal', () => { + const sourceFile = createSourceFile(project, 'type A = true as const') + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier(` + export const A = Type.Literal(true); + + export type A = Static; + `), + ) + }) + + test('object literal', () => { + const sourceFile = createSourceFile(project, 'type A = { a: 1, b: 2 } as const') + + expect(generateFormattedCode(sourceFile)).toBe( + formatWithPrettier(` + export const A = Type.Object({ + a: Type.Literal(1), + b: Type.Literal(2), + }); + + export type A = Static; + `), + ) + }) + }) +}) From 9275d52b60e31270d54496dab11f2eee434de3d1 Mon Sep 17 00:00:00 2001 From: DaxServer Date: Sat, 6 Sep 2025 20:12:08 +0200 Subject: [PATCH 2/2] fix: reuse sourcefile in chunk creation --- src/traverse/chunk-large-types.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/traverse/chunk-large-types.ts b/src/traverse/chunk-large-types.ts index 735b737..6a17dda 100644 --- a/src/traverse/chunk-large-types.ts +++ b/src/traverse/chunk-large-types.ts @@ -56,6 +56,13 @@ export const createChunkNodes = ( const chunkReferences: string[] = [] + // scratch file for chunk nodes (once per parent) + const scratchSourceFile = node.getProject().createSourceFile( + `__chunks_${parentTypeName}_${Date.now()}.ts`, + '', + { overwrite: true }, + ) + // Create chunk nodes for (let i = 0; i < chunks.length; i++) { const chunkName = `${parentTypeName}_Chunk${i + 1}` @@ -71,14 +78,11 @@ export const createChunkNodes = ( const chunkTypeTexts = chunks[i]! // Create a synthetic union node for this chunk - const project = node.getProject() - const tempFileName = `__temp_chunk_${parentTypeName}_${i}_${Date.now()}.ts` - const tempSourceFile = project.createSourceFile( - tempFileName, - `type TempChunk = ${chunkTypeTexts.join(' | ')}`, - ) - const tempTypeAlias = tempSourceFile.getTypeAliases()[0]! - const chunkTypeNode = tempTypeAlias.getTypeNode()! + const chunkAlias = scratchSourceFile.addTypeAlias({ + name: chunkName, + type: chunkTypeTexts.join(' | '), + }) + const chunkTypeNode = chunkAlias.getTypeNode()! const chunkTraversedNode: TraversedNode = { node: chunkTypeNode, // Use the chunk-specific union node