diff --git a/examples/rpc-websockets-esm/tsconfig.json b/examples/rpc-websockets-esm/tsconfig.json index 755dbd57e..51119804d 100644 --- a/examples/rpc-websockets-esm/tsconfig.json +++ b/examples/rpc-websockets-esm/tsconfig.json @@ -7,5 +7,7 @@ "esModuleInterop": true, "module": "esnext" }, - "reflection": true + "deepkitTypeCompilerOptions": { + "reflection": true + } } diff --git a/examples/rpc-websockets/tsconfig.json b/examples/rpc-websockets/tsconfig.json index 7fabbf2dd..b12cbe291 100644 --- a/examples/rpc-websockets/tsconfig.json +++ b/examples/rpc-websockets/tsconfig.json @@ -7,5 +7,7 @@ "esModuleInterop": true, "module": "CommonJS" }, - "reflection": true + "deepkitTypeCompilerOptions": { + "reflection": true + }, } diff --git a/packages/angular-universal/tsconfig.json b/packages/angular-universal/tsconfig.json index 02ce0e3b2..11eb6682c 100644 --- a/packages/angular-universal/tsconfig.json +++ b/packages/angular-universal/tsconfig.json @@ -17,7 +17,9 @@ "composite": true, "types": [] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "tests", diff --git a/packages/api-console-api/tsconfig.json b/packages/api-console-api/tsconfig.json index be0b6352c..1d273c302 100644 --- a/packages/api-console-api/tsconfig.json +++ b/packages/api-console-api/tsconfig.json @@ -18,7 +18,9 @@ "composite": true, "types": [] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "index.ts", "src" diff --git a/packages/api-console-gui/tsconfig.json b/packages/api-console-gui/tsconfig.json index ed08a84b6..8dd5e249e 100644 --- a/packages/api-console-gui/tsconfig.json +++ b/packages/api-console-gui/tsconfig.json @@ -26,9 +26,11 @@ "marked" ] }, - "reflection": [ - "src/app/store.ts" - ], + "deepkitTypeCompilerOptions": { + "reflection": [ + "src/app/store.ts" + ] + }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, diff --git a/packages/api-console-module/tsconfig.json b/packages/api-console-module/tsconfig.json index b03146bdc..0e09d2b51 100644 --- a/packages/api-console-module/tsconfig.json +++ b/packages/api-console-module/tsconfig.json @@ -16,7 +16,9 @@ "composite": true, "types": [] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "index.ts", diff --git a/packages/app/tsconfig.json b/packages/app/tsconfig.json index d8c94fe3d..89952a07e 100644 --- a/packages/app/tsconfig.json +++ b/packages/app/tsconfig.json @@ -17,7 +17,9 @@ "composite": true, "types": [] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "index.ts" diff --git a/packages/broker/tsconfig.json b/packages/broker/tsconfig.json index 1d3e3a6a6..d30078849 100644 --- a/packages/broker/tsconfig.json +++ b/packages/broker/tsconfig.json @@ -16,7 +16,9 @@ "composite": true, "types": [] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "index.ts" diff --git a/packages/bson/tsconfig.json b/packages/bson/tsconfig.json index 75f03d7a9..ffb4e77a9 100644 --- a/packages/bson/tsconfig.json +++ b/packages/bson/tsconfig.json @@ -20,7 +20,9 @@ "node" ] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "benchmarks", "src", diff --git a/packages/api-console-gui/dist/.gitkeep b/packages/core/dist/.gitkeep similarity index 100% rename from packages/api-console-gui/dist/.gitkeep rename to packages/core/dist/.gitkeep diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index ffdf72510..15ba0e29e 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -20,7 +20,9 @@ "node" ] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "index.ts" diff --git a/packages/create-app/files/tsconfig.json b/packages/create-app/files/tsconfig.json index 3bd236193..1805c27cf 100644 --- a/packages/create-app/files/tsconfig.json +++ b/packages/create-app/files/tsconfig.json @@ -8,7 +8,9 @@ "module": "CommonJS", "moduleResolution": "node" }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "files": [ "client.rpc.ts", "app.ts" diff --git a/packages/create-app/tsconfig.json b/packages/create-app/tsconfig.json index 7b3432dc9..5b31b6f51 100644 --- a/packages/create-app/tsconfig.json +++ b/packages/create-app/tsconfig.json @@ -22,7 +22,9 @@ "main.ts", "index.ts" ], - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "references": [ { "path": "../core/tsconfig.json" diff --git a/packages/framework-examples/dist/.gitkeep b/packages/desktop-ui/dist/.gitkeep similarity index 100% rename from packages/framework-examples/dist/.gitkeep rename to packages/desktop-ui/dist/.gitkeep diff --git a/packages/event/tsconfig.json b/packages/event/tsconfig.json index eee4d7731..a834710ce 100644 --- a/packages/event/tsconfig.json +++ b/packages/event/tsconfig.json @@ -20,10 +20,12 @@ "src", "index.ts" ], + "deepkitTypeCompilerOptions": { + "reflection": true + }, "exclude": [ "tests" ], - "reflection": true, "references": [ { "path": "../core/tsconfig.json" diff --git a/packages/example-app/tsconfig.json b/packages/example-app/tsconfig.json index 0bd71a2fe..39607ca72 100644 --- a/packages/example-app/tsconfig.json +++ b/packages/example-app/tsconfig.json @@ -21,7 +21,9 @@ "ts-node": { "experimentalResolver": true }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "includes": [ "*.ts", "*.tsx" diff --git a/packages/filesystem/tsconfig.json b/packages/filesystem/tsconfig.json index 1d1eb0366..6fc0f52dd 100644 --- a/packages/filesystem/tsconfig.json +++ b/packages/filesystem/tsconfig.json @@ -20,10 +20,12 @@ "src", "index.ts" ], + "deepkitTypeCompilerOptions": { + "reflection": true + }, "exclude": [ "tests" ], - "reflection": true, "references": [ { "path": "../core/tsconfig.json" diff --git a/packages/framework-debug-api/tsconfig.json b/packages/framework-debug-api/tsconfig.json index 745d02376..c4a3e40bb 100644 --- a/packages/framework-debug-api/tsconfig.json +++ b/packages/framework-debug-api/tsconfig.json @@ -18,7 +18,9 @@ "composite": true, "types": [] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "jsx", diff --git a/packages/framework-debug-gui/tsconfig.json b/packages/framework-debug-gui/tsconfig.json index 77cfbd360..0545e5cdf 100644 --- a/packages/framework-debug-gui/tsconfig.json +++ b/packages/framework-debug-gui/tsconfig.json @@ -27,10 +27,12 @@ "marked" ] }, - "reflection": [ - "./src/app/state.ts", - "node_modules/@deepkit/api-console-gui/src/app/store.ts" - ], + "deepkitTypeCompilerOptions": { + "reflection": [ + "./src/app/state.ts", + "node_modules/@deepkit/api-console-gui/src/app/store.ts" + ] + }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false, "strictInjectionParameters": true, diff --git a/packages/framework-examples/tsconfig.json b/packages/framework-examples/tsconfig.json index eedcc9507..7ad2b937b 100644 --- a/packages/framework-examples/tsconfig.json +++ b/packages/framework-examples/tsconfig.json @@ -19,7 +19,9 @@ "jsxImportSource": "@deepkit/framework", "types": [] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "./src/**/*.ts" ], diff --git a/packages/framework-integration/tsconfig.json b/packages/framework-integration/tsconfig.json index b8f2340b7..eacbdb152 100644 --- a/packages/framework-integration/tsconfig.json +++ b/packages/framework-integration/tsconfig.json @@ -21,7 +21,9 @@ "ws" ] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "files": [ "tests/util.ts", "tests/controller-basic.spec.ts", diff --git a/packages/framework/dist/esm/package.json b/packages/framework/dist/esm/package.json deleted file mode 100644 index 6990891ff..000000000 --- a/packages/framework/dist/esm/package.json +++ /dev/null @@ -1 +0,0 @@ -{"type": "module"} diff --git a/packages/framework/tsconfig.json b/packages/framework/tsconfig.json index 102ce1c55..5123ad5f5 100644 --- a/packages/framework/tsconfig.json +++ b/packages/framework/tsconfig.json @@ -27,7 +27,9 @@ "node" ] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "jsx", diff --git a/packages/http/tsconfig.json b/packages/http/tsconfig.json index 354f7c3e5..56b2f7d98 100644 --- a/packages/http/tsconfig.json +++ b/packages/http/tsconfig.json @@ -19,7 +19,9 @@ "send" ] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "index.ts" diff --git a/packages/framework-integration/dist/.gitkeep b/packages/injector/dist/.gitkeep similarity index 100% rename from packages/framework-integration/dist/.gitkeep rename to packages/injector/dist/.gitkeep diff --git a/packages/injector/src/injector.ts b/packages/injector/src/injector.ts index 66cbb5074..fe358e3ff 100644 --- a/packages/injector/src/injector.ts +++ b/packages/injector/src/injector.ts @@ -794,7 +794,7 @@ export class Injector implements InjectorInterface { args.push(entry.module.injector!.resolver!(entry.tagProvider.provider.provide, scope)); } - return new type.classType(args); + return new (type.classType)(args); } if (type.kind === ReflectionKind.function && type.typeName === 'PartialFactory') { diff --git a/packages/injector/tsconfig.json b/packages/injector/tsconfig.json index a8259a593..3110427fb 100644 --- a/packages/injector/tsconfig.json +++ b/packages/injector/tsconfig.json @@ -17,7 +17,9 @@ "composite": true, "types": [] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "index.ts" diff --git a/packages/logger/tsconfig.json b/packages/logger/tsconfig.json index 7eeaa58eb..8c51642b9 100644 --- a/packages/logger/tsconfig.json +++ b/packages/logger/tsconfig.json @@ -18,7 +18,9 @@ "format-util" ] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "index.ts" diff --git a/packages/mongo/tsconfig.json b/packages/mongo/tsconfig.json index 93409b948..79dd6d64e 100644 --- a/packages/mongo/tsconfig.json +++ b/packages/mongo/tsconfig.json @@ -21,7 +21,9 @@ "node" ] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "index.ts" diff --git a/packages/mysql/tsconfig.json b/packages/mysql/tsconfig.json index 872c886a2..7d7c3deb0 100644 --- a/packages/mysql/tsconfig.json +++ b/packages/mysql/tsconfig.json @@ -17,9 +17,11 @@ "composite": true, "types": [] }, - "reflection": [ - "tests/**/*.ts" - ], + "deepkitTypeCompilerOptions": { + "reflection": [ + "tests/**/*.ts" + ] + }, "include": [ "benchmarks", "src", diff --git a/packages/orm-browser-api/tsconfig.json b/packages/orm-browser-api/tsconfig.json index 80b68585c..4d0dafa60 100644 --- a/packages/orm-browser-api/tsconfig.json +++ b/packages/orm-browser-api/tsconfig.json @@ -17,7 +17,9 @@ "composite": true, "types": [] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "jsx", diff --git a/packages/orm-browser-example/dist/.gitkeep b/packages/orm-browser-example/dist/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/orm-browser-example/tsconfig.json b/packages/orm-browser-example/tsconfig.json index c7c18c9e5..0aa922920 100644 --- a/packages/orm-browser-example/tsconfig.json +++ b/packages/orm-browser-example/tsconfig.json @@ -16,7 +16,9 @@ "composite": true, "types": [] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "database.ts", "./bookstore/*ts" diff --git a/packages/orm-browser/tsconfig.json b/packages/orm-browser/tsconfig.json index 189879367..9c2a49cf2 100644 --- a/packages/orm-browser/tsconfig.json +++ b/packages/orm-browser/tsconfig.json @@ -16,7 +16,9 @@ "composite": true, "types": [] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "index.ts" diff --git a/packages/orm-integration/tsconfig.json b/packages/orm-integration/tsconfig.json index dd78b96f3..06a38bd51 100644 --- a/packages/orm-integration/tsconfig.json +++ b/packages/orm-integration/tsconfig.json @@ -17,7 +17,9 @@ "composite": true, "types": [] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "index.ts" diff --git a/packages/orm/tsconfig.json b/packages/orm/tsconfig.json index 3fef63a2c..726d36917 100644 --- a/packages/orm/tsconfig.json +++ b/packages/orm/tsconfig.json @@ -18,7 +18,9 @@ "sqlstring" ] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "browser.ts", diff --git a/packages/postgres/tsconfig.json b/packages/postgres/tsconfig.json index 4cac532d8..043080642 100644 --- a/packages/postgres/tsconfig.json +++ b/packages/postgres/tsconfig.json @@ -20,9 +20,11 @@ "sqlstring" ] }, - "reflection": [ - "tests/**/*.ts" - ], + "deepkitTypeCompilerOptions": { + "reflection": [ + "tests/**/*.ts" + ] + }, "include": [ "src", "index.ts" diff --git a/packages/rpc/tsconfig.json b/packages/rpc/tsconfig.json index 0b7173976..dfd506095 100644 --- a/packages/rpc/tsconfig.json +++ b/packages/rpc/tsconfig.json @@ -20,7 +20,9 @@ "node" ] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "index.ts" diff --git a/packages/sql/tsconfig.json b/packages/sql/tsconfig.json index ed9944dc6..051a26c94 100644 --- a/packages/sql/tsconfig.json +++ b/packages/sql/tsconfig.json @@ -19,7 +19,9 @@ "sqlstring" ] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "benchmarks", "src", diff --git a/packages/sqlite/tsconfig.json b/packages/sqlite/tsconfig.json index 9de48930b..a805eff61 100644 --- a/packages/sqlite/tsconfig.json +++ b/packages/sqlite/tsconfig.json @@ -19,9 +19,11 @@ "better-sqlite3" ] }, - "reflection": [ - "tests/**/*.ts" - ], + "deepkitTypeCompilerOptions": { + "reflection": [ + "tests/**/*.ts" + ] + }, "include": [ "benchmarks", "src", diff --git a/packages/stopwatch/tsconfig.json b/packages/stopwatch/tsconfig.json index e7dfd66ba..3e69eb007 100644 --- a/packages/stopwatch/tsconfig.json +++ b/packages/stopwatch/tsconfig.json @@ -16,7 +16,9 @@ "composite": true, "types": [] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "index.ts" diff --git a/packages/template/tsconfig.json b/packages/template/tsconfig.json index 99eaffe4c..6d4cffbd4 100644 --- a/packages/template/tsconfig.json +++ b/packages/template/tsconfig.json @@ -18,7 +18,9 @@ "jsxImportSource": "..", "types": ["estree"] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "index.ts", diff --git a/packages/type-compiler/src/compiler.ts b/packages/type-compiler/src/compiler.ts index 81722f720..cd4cbc0dc 100644 --- a/packages/type-compiler/src/compiler.ts +++ b/packages/type-compiler/src/compiler.ts @@ -77,11 +77,15 @@ import ts from 'typescript'; import { ensureImportIsEmitted, extractJSDocAttribute, + getEntityName, + getExternalRuntimeTypeName, getGlobalsOfSourceFile, getIdentifierName, getNameAsString, getPropertyName, - hasModifier, + getRuntimeTypeName, + hasModifier, hasSourceFile, + isBuiltType, isNodeWithLocals, NodeConverter, PackExpression, @@ -94,6 +98,7 @@ import stripJsonComments from 'strip-json-comments'; import { MappedModifier, ReflectionOp, TypeNumberBrand } from '@deepkit/type-spec'; import { patternMatch, ReflectionMode, reflectionModeMatcher, reflectionModes, Resolver } from './resolver.js'; import { knownLibFilesForCompilerOptions } from '@typescript/vfs'; +import { External } from './external.js'; // don't use from @deepkit/core since we don't want to have a dependency to @deepkit/core export function isObject(obj: any): obj is { [key: string]: any } { @@ -189,15 +194,19 @@ const serverEnv = 'undefined' !== typeof process; */ export const packSize: number = 2 ** packSizeByte; //64 -interface ReflectionOptions { +export interface ReflectionOptions { /** * Allows to exclude type definitions/TS files from being included in the type compilation step. * When a global .d.ts is matched, their types won't be embedded (useful to exclude DOM for example) */ exclude?: string[]; + /** + * External imports to reflect + */ + inlineExternalLibraryImports?: true | Record; } -interface ReflectionConfig { +export interface ReflectionConfig { mode: typeof reflectionModes[number]; options: ReflectionOptions; /** @@ -498,6 +507,16 @@ function getReceiveTypeParameter(type: TypeNode): TypeReferenceNode | undefined return; } +interface DeepkitTypeCompilerOptions extends ReflectionOptions { + reflection?: string | string[]; +} + +export interface ReflectionTransformerConfig { + compilerOptions: ts.CompilerOptions; + extends?: string; + deepkitTypeCompilerOptions?: DeepkitTypeCompilerOptions; +} + /** * Read the TypeScript AST and generate pack struct (instructions + pre-defined stack). * @@ -530,6 +549,8 @@ export class ReflectionTransformer implements CustomTransformer { { name: EntityName, sourceFile: SourceFile, compiled?: Statement[] } >(); + protected external: External; + /** * Types added to this map will get a type program at the top root level of the program. * This is for imported types, which need to be inlined into the current file, as we do not emit type imports (TS will omit them). @@ -537,7 +558,7 @@ export class ReflectionTransformer implements CustomTransformer { protected embedDeclarations = new Map(); /** - * When a node was embedded or compiled (from the maps above), we store it here to know to not add it again. + * When a node was embeddedLibraryVariables or compiled (from the maps above), we store it here to know to not add it again. */ protected compiledDeclarations = new Set(); @@ -558,17 +579,18 @@ export class ReflectionTransformer implements CustomTransformer { protected tempResultIdentifier?: Identifier; protected parseConfigHost?: ParseConfigHost; - protected config: { compilerOptions: ts.CompilerOptions, extends?: string, reflectionOptions?: ReflectionOptions, reflection?: string | string[] } = { compilerOptions: {} }; + protected config: ReflectionTransformerConfig = { compilerOptions: {}}; constructor( protected context: TransformationContext, ) { this.f = context.factory; - this.nodeConverter = new NodeConverter(this.f); //it is important to not have undefined values like {paths: undefined} because it would override the read tsconfig.json this.compilerOptions = filterUndefined(context.getCompilerOptions()); this.host = createCompilerHost(this.compilerOptions); this.resolver = new Resolver(this.compilerOptions, this.host); + this.external = new External(this.resolver); + this.nodeConverter = new NodeConverter(this.f, this.external); } forHost(host: CompilerHost): this { @@ -634,9 +656,13 @@ export class ReflectionTransformer implements CustomTransformer { return Object.assign(configFile.config, { compilerOptions: parsed.options }); } - transformSourceFile(sourceFile: SourceFile): SourceFile { + protected setSourceFile(sourceFile: SourceFile) { this.sourceFile = sourceFile; + this.external.sourceFile = sourceFile; + } + transformSourceFile(sourceFile: SourceFile): SourceFile { + this.setSourceFile(sourceFile); //if it's not a TS/TSX file, we do not transform it if (sourceFile.scriptKind !== ScriptKind.TS && sourceFile.scriptKind !== ScriptKind.TSX) return sourceFile; @@ -679,17 +705,17 @@ export class ReflectionTransformer implements CustomTransformer { let basePath = this.config.compilerOptions.configFilePath as string; if (basePath) { basePath = dirname(basePath); - if (!this.reflectionMode && currentConfig.reflection !== undefined) this.reflectionMode = this.parseReflectionMode(currentConfig.reflection, basePath); - if (!this.compilerOptions && currentConfig.reflectionOptions !== undefined) { - this.reflectionOptions = this.parseReflectionOptionsDefaults(currentConfig.reflectionOptions, basePath); + if (!this.reflectionMode && currentConfig.deepkitTypeCompilerOptions !== undefined) this.reflectionMode = this.parseReflectionMode(currentConfig.deepkitTypeCompilerOptions.reflection, basePath); + if (!this.compilerOptions && currentConfig.deepkitTypeCompilerOptions !== undefined) { + this.reflectionOptions = this.parseReflectionOptionsDefaults(currentConfig.deepkitTypeCompilerOptions, basePath); } while ((this.reflectionMode === undefined || this.compilerOptions === undefined) && 'string' === typeof basePath && currentConfig.extends) { const path = join(basePath, currentConfig.extends); - const nextConfig = ts.readConfigFile(path, (path: string) => this.host.readFile(path)); + const nextConfig = ts.readConfigFile(path, (path: string) => this.host.readFile(path)) as { config: ReflectionTransformerConfig }; if (!nextConfig) break; - if (!this.reflectionMode && nextConfig.config.reflection !== undefined) this.reflectionMode = this.parseReflectionMode(nextConfig.config.reflection, basePath); - if (!this.reflectionOptions && nextConfig.config.reflectionOptions !== undefined) { - this.reflectionOptions = this.parseReflectionOptionsDefaults(nextConfig.config.reflectionOptions, basePath); + if (!this.reflectionMode && nextConfig.config.deepkitTypeCompilerOptions !== undefined) this.reflectionMode = this.parseReflectionMode(nextConfig.config.deepkitTypeCompilerOptions.reflection, basePath); + if (!this.reflectionOptions && nextConfig.config.deepkitTypeCompilerOptions !== undefined) { + this.reflectionOptions = this.parseReflectionOptionsDefaults(nextConfig.config.deepkitTypeCompilerOptions, basePath); } currentConfig = Object.assign({}, nextConfig.config); basePath = dirname(path); @@ -798,7 +824,6 @@ export class ReflectionTransformer implements CustomTransformer { } else if (isArrowFunction(node)) { return this.decorateArrowFunction(this.injectResetΩ(node)); } else if ((isNewExpression(node) || isCallExpression(node)) && node.typeArguments && node.typeArguments.length > 0) { - if (isCallExpression(node)) { const autoTypeFunctions = ['valuesOf', 'propertiesOf', 'typeOf']; if (isIdentifier(node.expression) && autoTypeFunctions.includes(getIdentifierName(node.expression))) { @@ -943,7 +968,7 @@ export class ReflectionTransformer implements CustomTransformer { return node; }; - this.sourceFile = visitNode(this.sourceFile, visitor); + this.setSourceFile(visitNode(this.sourceFile, visitor)); const newTopStatements: Statement[] = []; @@ -955,7 +980,7 @@ export class ReflectionTransformer implements CustomTransformer { break; } - if (this.embedDeclarations.size === 0 && allCompiled) break; + if (this.embedDeclarations.size === 0 && this.external.compileExternalLibraryImports.size === 0 && allCompiled) break; for (const [node, d] of [...this.compileDeclarations.entries()]) { if (d.compiled) continue; @@ -972,13 +997,48 @@ export class ReflectionTransformer implements CustomTransformer { newTopStatements.push(...this.createProgramVarFromNode(node, d.name, d.sourceFile)); } } + + if (this.external.compileExternalLibraryImports.size) { + for (const imports of this.external.compileExternalLibraryImports.values()) { + for (const { declaration } of imports.values()) { + this.compiledDeclarations.add(declaration); + } + } + const entries = Array.from(this.external.compileExternalLibraryImports.entries()); + this.external.compileExternalLibraryImports.clear(); + for (const [library, imports] of entries) { + if (!this.external.embeddedLibraryVariables.has(library)) { + const objectLiteral = this.f.createObjectLiteralExpression(); + const variableDeclaration = this.f.createVariableDeclaration( + this.f.createIdentifier(getExternalRuntimeTypeName(library)), + undefined, + undefined, + objectLiteral, + ); + const variableStatement = this.f.createVariableStatement( + [], + this.f.createVariableDeclarationList([ + variableDeclaration + ], NodeFlags.Const), + ); + newTopStatements.unshift(variableStatement); + this.external.embeddedLibraryVariables.add(library); + } + + for (const value of imports.values()) { + this.external.startEmbeddingExternalLibraryImport(value); + newTopStatements.push(this.createProgramVarForExternalLibraryImport(value.declaration, value.name, value.sourceFile, value.module.packageId.name)); + this.external.finishEmbeddingExternalLibraryImport(); + } + } + } } //externalize type aliases const compileDeclarations = (node: Node): any => { node = visitEachChild(node, compileDeclarations, this.context); - if ((isTypeAliasDeclaration(node) || isInterfaceDeclaration(node) || isEnumDeclaration(node))) { + if (isTypeAliasDeclaration(node) || isInterfaceDeclaration(node) || isEnumDeclaration(node)) { const d = this.compileDeclarations.get(node); if (!d) { return node; @@ -992,7 +1052,7 @@ export class ReflectionTransformer implements CustomTransformer { return node; }; - this.sourceFile = visitNode(this.sourceFile, compileDeclarations); + this.setSourceFile(visitNode(this.sourceFile, compileDeclarations)); if (this.addImports.length) { const handledIdentifier: string[] = []; @@ -1188,7 +1248,14 @@ export class ReflectionTransformer implements CustomTransformer { return node; } - protected createProgramVarFromNode(node: Node, name: EntityName, sourceFile: SourceFile): Statement[] { + protected createProgramVarForExternalLibraryImport(node: Node, name: EntityName, sourceFile: SourceFile, libraryName: string): Statement { + const typeProgramExpression = this.createTypeProgramExpression(node, sourceFile); + const left = this.f.createPropertyAccessExpression(this.f.createIdentifier(getExternalRuntimeTypeName(libraryName)), getNameAsString(name)) + const assignment = this.f.createAssignment(left, typeProgramExpression!); + return this.f.createExpressionStatement(assignment); + } + + protected createTypeProgramExpression(node: Node, sourceFile: SourceFile): Expression | undefined { const typeProgram = new CompilerProgram(node, sourceFile); if ((isTypeAliasDeclaration(node) || isInterfaceDeclaration(node)) && node.typeParameters) { @@ -1211,7 +1278,11 @@ export class ReflectionTransformer implements CustomTransformer { typeProgram.pushOp(ReflectionOp.nominal); } - const typeProgramExpression = this.packOpsAndStack(typeProgram); + return this.packOpsAndStack(typeProgram); + } + + protected createProgramVarFromNode(node: Node, name: EntityName, sourceFile: SourceFile): Statement[] { + const typeProgramExpression = this.createTypeProgramExpression(node, sourceFile); const variable = this.f.createVariableStatement( [], @@ -1351,7 +1422,7 @@ export class ReflectionTransformer implements CustomTransformer { this.extractPackStructOfType(member, program); } - program.pushOp(ReflectionOp.class); + program.pushOp(ReflectionOp.class) if (narrowed.heritageClauses) { for (const heritageClause of narrowed.heritageClauses) { @@ -1373,7 +1444,9 @@ export class ReflectionTransformer implements CustomTransformer { } } - if (narrowed.name) this.resolveTypeName(getIdentifierName(narrowed.name), program); + if (narrowed.name) { + this.resolveTypeName(getIdentifierName(narrowed.name), program); + } // for whatever reason: narrowed.name.parent !== narrowed. narrowed.name.parent has jsDoc, narrowed.name not. const description = extractJSDocAttribute(narrowed.name?.parent, 'description'); @@ -1824,14 +1897,13 @@ export class ReflectionTransformer implements CustomTransformer { // this.addImports.push({ identifier: narrowed.exprName, from: originImportStatement.moduleSpecifier }); // } // } + let expression: Expression = serializeEntityNameAsExpression(this.f, narrowed.exprName); if (isIdentifier(narrowed.exprName)) { const resolved = this.resolveDeclaration(narrowed.exprName); if (resolved && findSourceFile(resolved.declaration) !== this.sourceFile && resolved.importDeclaration) { - ensureImportIsEmitted(resolved.importDeclaration, narrowed.exprName); + expression = this.resolveImportExpression(resolved.declaration, resolved.importDeclaration, narrowed.exprName, expression, program); } } - - const expression = serializeEntityNameAsExpression(this.f, narrowed.exprName); program.pushOp(ReflectionOp.typeof, program.pushStack(this.f.createArrowFunction(undefined, undefined, [], undefined, undefined, expression))); break; } @@ -1940,11 +2012,12 @@ export class ReflectionTransformer implements CustomTransformer { * This is a custom resolver based on populated `locals` from the binder. It uses a custom resolution algorithm since * we have no access to the binder/TypeChecker directly and instantiating a TypeChecker per file/transformer is incredible slow. */ - protected resolveDeclaration(typeName: EntityName): { declaration: Node, importDeclaration?: ImportDeclaration, typeOnly?: boolean } | void { - let current: Node = typeName.parent; + protected resolveDeclaration(typeName: EntityName): { declaration: Node, importDeclaration?: ImportDeclaration, sourceFile: SourceFile, typeOnly: boolean } | void { + let current: Node = typeName.parent if (typeName.kind === SyntaxKind.QualifiedName) return; //namespace access not supported yet, e.g. type a = Namespace.X; let declaration: Node | undefined = undefined; + let sourceFile: SourceFile = this.sourceFile; while (current) { if (isNodeWithLocals(current) && current.locals) { @@ -1989,6 +2062,11 @@ export class ReflectionTransformer implements CustomTransformer { if (importDeclaration) { if (importDeclaration.importClause && importDeclaration.importClause.isTypeOnly) typeOnly = true; declaration = this.resolveImportSpecifier(typeName.escapedText, importDeclaration, this.sourceFile); + //might be an external library + if (!declaration && hasSourceFile(importDeclaration)) { + sourceFile = importDeclaration.getSourceFile(); + declaration = this.resolveImportSpecifier(typeName.escapedText, importDeclaration, sourceFile); + } } if (declaration && declaration.kind === SyntaxKind.TypeParameter && declaration.parent.kind === SyntaxKind.TypeAliasDeclaration) { @@ -1998,7 +2076,7 @@ export class ReflectionTransformer implements CustomTransformer { if (!declaration) return; - return { declaration, importDeclaration, typeOnly }; + return { declaration, importDeclaration, typeOnly, sourceFile }; } // protected resolveType(node: TypeNode): Declaration | Node { @@ -2021,17 +2099,45 @@ export class ReflectionTransformer implements CustomTransformer { // return node; // } + protected getRuntimeTypeName(typeName: EntityName): Identifier { + return this.f.createIdentifier(getRuntimeTypeName(getNameAsString(typeName))); + } + + protected getExternalRuntimeTypeName(typeName: EntityName): Identifier { + const { expression, name } = this.nodeConverter.createExternalRuntimeTypePropertyAccessExpression(getNameAsString(typeName)); + return this.f.createIdentifier(`${getNameAsString(expression as any)}.${getNameAsString(name)}`); + } + protected getDeclarationVariableName(typeName: EntityName): Identifier { - if (isIdentifier(typeName)) { - return this.f.createIdentifier('__Ω' + getIdentifierName(typeName)); - } + return this.external.isEmbeddingExternalLibraryImport() && !this.external.knownGlobalTypeNames.has(getNameAsString(typeName)) + ? this.getExternalRuntimeTypeName(typeName) + : this.getRuntimeTypeName(typeName); + } + + protected resolveImportExpression(declaration: Node, importDeclaration: ImportDeclaration, typeName: Identifier, expression: Expression, program: CompilerProgram): Expression { + ensureImportIsEmitted(importDeclaration, typeName); + + if (!hasSourceFile(importDeclaration)) return expression; + + // these will be inferred at runtime + if (isTypeAliasDeclaration(declaration) || isVariableDeclaration(declaration)) return expression; + + // check if the referenced declaration has reflection disabled + const declarationReflection = this.findReflectionConfig(declaration, program); + if (declarationReflection.mode !== 'never') { + const declarationSourceFile = importDeclaration.getSourceFile(); - function joinQualifiedName(name: EntityName): string { - if (isIdentifier(name)) return getIdentifierName(name); - return joinQualifiedName(name.left) + '_' + getIdentifierName(name.right); + const runtimeTypeName = this.getRuntimeTypeName(typeName); + + const builtType = isBuiltType(runtimeTypeName, declarationSourceFile); + + if (this.external.isEmbeddingExternalLibraryImport() || (!builtType && this.external.shouldInlineExternalLibraryImport(importDeclaration, typeName, declarationReflection))) { + const { module } = this.external.processExternalLibraryImport(typeName, declaration, declarationSourceFile, importDeclaration); + return this.f.createPropertyAccessExpression(this.f.createIdentifier(getExternalRuntimeTypeName(module.packageId.name)), getNameAsString(typeName)); + } } - return this.f.createIdentifier('__Ω' + joinQualifiedName(typeName)); + return expression; } protected isExcluded(filePath: string): boolean { @@ -2131,12 +2237,15 @@ export class ReflectionTransformer implements CustomTransformer { } if (isModuleDeclaration(declaration) && resolved.importDeclaration) { - if (isIdentifier(typeName)) ensureImportIsEmitted(resolved.importDeclaration, typeName); + let expression: Expression = serializeEntityNameAsExpression(this.f, typeName); + if (isIdentifier(typeName)) { + expression = this.resolveImportExpression(declaration, resolved.importDeclaration, typeName, expression, program) + } //we can not infer from module declaration, so do `typeof T` in runtime program.pushOp( ReflectionOp.typeof, - program.pushStack(this.f.createArrowFunction(undefined, undefined, [], undefined, undefined, serializeEntityNameAsExpression(this.f, typeName))) + program.pushStack(this.f.createArrowFunction(undefined, undefined, [], undefined, undefined, expression)) ); } else if (isTypeAliasDeclaration(declaration) || isInterfaceDeclaration(declaration) || isEnumDeclaration(declaration)) { //Set/Map are interface declarations @@ -2178,85 +2287,81 @@ export class ReflectionTransformer implements CustomTransformer { return; } - const runtimeTypeName = this.getDeclarationVariableName(typeName); + const runtimeTypeName = this.getRuntimeTypeName(typeName); //to break recursion, we track which declaration has already been compiled if (!this.compiledDeclarations.has(declaration) && !this.compileDeclarations.has(declaration)) { const declarationSourceFile = findSourceFile(declaration) || this.sourceFile; - const isGlobal = resolved.importDeclaration === undefined && declarationSourceFile.fileName !== this.sourceFile.fileName; - const isFromImport = resolved.importDeclaration !== undefined; - if (this.isExcluded(declarationSourceFile.fileName)) { program.pushOp(ReflectionOp.any); return; } - if (isGlobal) { - //we don't embed non-global imported declarations anymore, only globals - this.embedDeclarations.set(declaration, { - name: typeName, - sourceFile: declarationSourceFile - }); - } else if (isFromImport) { - if (resolved.importDeclaration) { - //if explicit `import {type T}`, we do not emit an import and instead push any - if (resolved.typeOnly) { - this.resolveTypeOnlyImport(typeName, program); - return; - } + if (this.external.hasSourceFile(declarationSourceFile) && this.external.isEmbeddingExternalLibraryImport()) { + this.external.processExternalLibraryImport(typeName, declaration, declarationSourceFile, resolved.importDeclaration); + } else { + const isGlobal = resolved.importDeclaration === undefined && declarationSourceFile.fileName !== this.sourceFile.fileName; + const isFromImport = resolved.importDeclaration !== undefined; + + if (isGlobal) { + this.external.knownGlobalTypeNames.add(getNameAsString(typeName)); + this.embedDeclarations.set(declaration, { + name: typeName, + sourceFile: declarationSourceFile + }); + } else if (isFromImport) { + if (resolved.importDeclaration) { + //if explicit `import {type T}`, we do not emit an import and instead push any + if (resolved.typeOnly) { + this.resolveTypeOnlyImport(typeName, program); + return; + } - // check if the referenced declaration has reflection disabled - const declarationReflection = this.findReflectionConfig(declaration, program); - if (declarationReflection.mode === 'never') { - program.pushOp(ReflectionOp.any); - return; - } + // check if the referenced declaration has reflection disabled + const declarationReflection = this.findReflectionConfig(declaration, program); + if (declarationReflection.mode === 'never') { + program.pushOp(ReflectionOp.any); + return; + } - const found = this.resolver.resolve(this.sourceFile, resolved.importDeclaration); - if (!found) { - debug('module not found'); - program.pushOp(ReflectionOp.any); - return; - } + const found = this.resolver.resolve(resolved.sourceFile, resolved.importDeclaration); + if (!found) { + debug('module not found'); + program.pushOp(ReflectionOp.any); + return; + } - // check if this is a viable option: - // //check if the referenced file has reflection info emitted. if not, any is emitted for that reference - // const typeVar = runtimeRypeName; - // //check if typeVar is exported in referenced file - // const builtType = isNodeWithLocals(found) && found.locals && found.locals.has(typeVar.escapedText); - // if (!builtType) { - // program.pushOp(ReflectionOp.any); - // return; - // } - - //check if the referenced file has reflection info emitted. if not, any is emitted for that reference - const reflection = this.findReflectionFromPath(found.fileName); + const reflection = this.findReflectionFromPath(found.fileName); + if (reflection.mode !== 'never') { + this.addImports.push({ identifier: runtimeTypeName, from: resolved.importDeclaration.moduleSpecifier }); + } else { + if (this.external.shouldInlineExternalLibraryImport(resolved.importDeclaration, typeName, declarationReflection)) { + this.external.processExternalLibraryImport(typeName, declaration, declarationSourceFile, resolved.importDeclaration); + } else { + program.pushOp(ReflectionOp.any); + return; + } + } + } + } else { + //it's a reference type inside the same file. Make sure its type is reflected + const reflection = this.findReflectionConfig(declaration, program); if (reflection.mode === 'never') { program.pushOp(ReflectionOp.any); return; } - // this.addImports.push({ identifier: typeVar, from: resolved.importDeclaration.moduleSpecifier }); - this.addImports.push({ identifier: runtimeTypeName, from: resolved.importDeclaration.moduleSpecifier }); - } - } else { - //it's a reference type inside the same file. Make sure its type is reflected - const reflection = this.findReflectionConfig(declaration, program); - if (reflection.mode === 'never') { - program.pushOp(ReflectionOp.any); - return; + this.compileDeclarations.set(declaration, { + name: typeName, + sourceFile: declarationSourceFile, + }); } - - this.compileDeclarations.set(declaration, { - name: typeName, - sourceFile: declarationSourceFile, - }); } } const index = program.pushStack( - program.forNode === declaration ? 0 : this.f.createArrowFunction(undefined, undefined, [], undefined, undefined, runtimeTypeName) + program.forNode === declaration ? 0 : this.f.createArrowFunction(undefined, undefined, [], undefined, undefined, this.getDeclarationVariableName(typeName)) ); if (type.typeArguments) { for (const argument of type.typeArguments) { @@ -2288,15 +2393,16 @@ export class ReflectionTransformer implements CustomTransformer { this.resolveTypeOnlyImport(typeName, program); return; } - - if (resolved.importDeclaration && isIdentifier(typeName)) ensureImportIsEmitted(resolved.importDeclaration, typeName); program.pushFrame(); if (type.typeArguments) { for (const typeArgument of type.typeArguments) { this.extractPackStructOfType(typeArgument, program); } } - const body = isIdentifier(typeName) ? typeName : this.createAccessorForEntityName(typeName); + let body: Identifier | Expression = isIdentifier(typeName) ? typeName : this.createAccessorForEntityName(typeName); + if (resolved.importDeclaration && isIdentifier(typeName)) { + body = this.resolveImportExpression(resolved.declaration, resolved.importDeclaration, typeName, body, program); + } const index = program.pushStack(this.f.createArrowFunction(undefined, undefined, [], undefined, undefined, body)); program.pushOp(isClassDeclaration(declaration) ? ReflectionOp.classReference : ReflectionOp.functionReference, index); program.popFrameImplicit(); @@ -2389,9 +2495,7 @@ export class ReflectionTransformer implements CustomTransformer { protected resolveTypeOnlyImport(entityName: EntityName, program: CompilerProgram) { program.pushOp(ReflectionOp.any); - const typeName = ts.isIdentifier(entityName) - ? getIdentifierName(entityName) - : getIdentifierName(entityName.right); + const typeName = getEntityName(entityName); this.resolveTypeName(typeName, program); } @@ -2823,11 +2927,11 @@ export class ReflectionTransformer implements CustomTransformer { }; } - if (reflection === undefined && tsConfig.reflection !== undefined) { + if (reflection === undefined && tsConfig.deepkitTypeCompilerOptions !== undefined) { return { - mode: this.parseReflectionMode(tsConfig.reflection, currentDir), + mode: this.parseReflectionMode(tsConfig.deepkitTypeCompilerOptions.reflection, currentDir), baseDir: currentDir, - options: this.parseReflectionOptionsDefaults(tsConfig.reflectionOptions || {}) + options: this.parseReflectionOptionsDefaults(tsConfig.deepkitTypeCompilerOptions || {}) }; } @@ -2854,7 +2958,7 @@ export class DeclarationTransformer extends ReflectionTransformer { if ((sourceFile as any).deepkitDeclarationTransformed) return sourceFile; (sourceFile as any).deepkitDeclarationTransformed = true; - this.sourceFile = sourceFile; + this.setSourceFile(sourceFile); this.addExports = []; const reflection = this.findReflectionConfig(sourceFile); @@ -2875,7 +2979,7 @@ export class DeclarationTransformer extends ReflectionTransformer { return node; }; - this.sourceFile = visitNode(this.sourceFile, visitor); + this.setSourceFile(visitNode(this.sourceFile, visitor)); if (this.addExports.length) { const exports: Statement[] = []; @@ -2894,7 +2998,7 @@ export class DeclarationTransformer extends ReflectionTransformer { )); } - this.sourceFile = this.f.updateSourceFile(this.sourceFile, [...this.sourceFile.statements, ...exports]); + this.setSourceFile(this.f.updateSourceFile(this.sourceFile, [...this.sourceFile.statements, ...exports])); } return this.sourceFile; diff --git a/packages/type-compiler/src/external.ts b/packages/type-compiler/src/external.ts new file mode 100644 index 000000000..7ccea8ea4 --- /dev/null +++ b/packages/type-compiler/src/external.ts @@ -0,0 +1,127 @@ +import type { + Node, + ResolvedModuleFull, + EntityName, + ImportDeclaration, + SourceFile, +} from 'typescript'; +import ts from 'typescript'; + +const { + isStringLiteral, +} = ts; + +import { getEntityName, getNameAsString, hasSourceFile } from './reflection-ast.js'; +import { ReflectionConfig } from './compiler.js'; +import { Resolver } from './resolver.js'; + +export interface ExternalLibraryImport { + declaration: Node; + name: EntityName; + sourceFile: SourceFile; + module: Required; +} + +export class External { + protected sourceFileNames = new Set(); + + public compileExternalLibraryImports = new Map>; + + protected processedEntities = new Set; + + public embeddedLibraryVariables = new Set(); + + public knownGlobalTypeNames = new Set(); + + public sourceFile?: SourceFile; + + protected embeddingExternalLibraryImport?: ExternalLibraryImport; + + constructor(protected resolver: Resolver) {} + + startEmbeddingExternalLibraryImport(value: ExternalLibraryImport): void { + if (this.embeddingExternalLibraryImport) { + throw new Error('Already embedding external library import'); + } + this.embeddingExternalLibraryImport = value; + } + + getEmbeddingExternalLibraryImport(): ExternalLibraryImport { + if (!this.embeddingExternalLibraryImport) { + throw new Error('Not embedding external library import'); + } + return this.embeddingExternalLibraryImport; + } + + isEmbeddingExternalLibraryImport(): boolean { + return !!this.embeddingExternalLibraryImport; + } + + finishEmbeddingExternalLibraryImport(): void { + delete this.embeddingExternalLibraryImport; + } + + public addSourceFile(sourceFile: SourceFile): void { + this.sourceFileNames.add(sourceFile.fileName); + } + + public hasSourceFile(sourceFile: SourceFile): boolean { + return this.sourceFileNames.has(sourceFile.fileName); + } + + public shouldInlineExternalLibraryImport(importDeclaration: ImportDeclaration, entityName: EntityName, config: ReflectionConfig): boolean { + if (!isStringLiteral(importDeclaration.moduleSpecifier)) return false; + if (!hasSourceFile(importDeclaration)) return false; + let resolvedModule; + try { + // throws an error if import is not an external library + resolvedModule = this.resolver.resolveExternalLibraryImport(importDeclaration); + } catch { + return false; + } + if (config.options.inlineExternalLibraryImports === true) return true; + const imports = config.options.inlineExternalLibraryImports?.[resolvedModule.packageId.name]; + if (!imports) return false; + if (imports === true) return true; + if (!importDeclaration.moduleSpecifier.text.startsWith(resolvedModule.packageId.name)) return true; + const typeName = getEntityName(entityName); + return imports.includes(typeName); + } + + public hasProcessedEntity(typeName: EntityName): boolean { + return this.processedEntities.has(getNameAsString(typeName)); + } + + public processExternalLibraryImport(typeName: EntityName, declaration: Node, sourceFile: SourceFile, importDeclaration?: ImportDeclaration): ExternalLibraryImport { + const module = importDeclaration + ? this.resolver.resolveExternalLibraryImport(importDeclaration) + : this.getEmbeddingExternalLibraryImport().module; + + const entityName = getNameAsString(typeName); + if (this.processedEntities.has(entityName)) { + return { + name: typeName, + declaration, + sourceFile, + module, + } + } + + this.processedEntities.add(entityName); + + const imports = this.compileExternalLibraryImports.get(module.packageId.name) || new Map(); + const externalLibraryImport: ExternalLibraryImport = { + name: typeName, + declaration, + sourceFile, + module, + } + this.compileExternalLibraryImports.set(module.packageId.name, imports.set(entityName, externalLibraryImport)); + + if (sourceFile.fileName !== this.sourceFile?.fileName) { + this.addSourceFile(sourceFile); + } + + return externalLibraryImport!; + } +} diff --git a/packages/type-compiler/src/reflection-ast.ts b/packages/type-compiler/src/reflection-ast.ts index 0f74aefe5..b966777e8 100644 --- a/packages/type-compiler/src/reflection-ast.ts +++ b/packages/type-compiler/src/reflection-ast.ts @@ -29,12 +29,22 @@ import type { StringLiteral, StringLiteralLike, SymbolTable, + ClassDeclaration, + ClassElement, + InterfaceDeclaration, + Modifier, + TypeElement, ClassExpression, } from 'typescript'; import ts from 'typescript'; import { cloneNode as tsNodeClone, CloneNodeHook } from '@marcj/ts-clone-node'; import { SourceFile } from './ts-types.js'; +import { External } from './external.js'; const { + isInterfaceDeclaration, + isConstructorDeclaration, + isMethodDeclaration, + isPropertyDeclaration, isArrowFunction, isComputedPropertyName, isIdentifier, @@ -54,6 +64,10 @@ export function getIdentifierName(node: Identifier | PrivateIdentifier): string return ts.unescapeLeadingUnderscores(node.escapedText); } +export function hasSourceFile(node: Node): boolean { + return typeof node.getSourceFile === 'function'; +} + export function joinQualifiedName(name: EntityName): string { if (isIdentifier(name)) return getIdentifierName(name); return joinQualifiedName(name.left) + '_' + getIdentifierName(name.right); @@ -109,6 +123,14 @@ export function hasModifier(node: { modifiers?: NodeArray }, modif return node.modifiers.some(v => v.kind === modifier); } +export function getExternalRuntimeTypeName(importPath: string): string { + return `__ɵΩ${importPath.replace(/[^a-zA-Z0-9]+/g, '_')}`; +} + +export function getRuntimeTypeName(typeName: string): string { + return `__Ω${typeName}`; +} + const cloneHook = (node: T, payload: { depth: number }): CloneNodeHook | undefined => { if (isIdentifier(node)) { //ts-clone-node wants to read `node.text` which does not exist. we hook into it and provide the correct value. @@ -122,9 +144,11 @@ const cloneHook = (node: T, payload: { depth: number }): CloneNo }; export class NodeConverter { - constructor(protected f: NodeFactory) { + constructor(protected f: NodeFactory, protected external: External) {} + createExternalRuntimeTypePropertyAccessExpression(name: string): PropertyAccessExpression { + const { module } = this.external.getEmbeddingExternalLibraryImport(); + return this.f.createPropertyAccessExpression(this.f.createIdentifier(getExternalRuntimeTypeName(module.packageId.name)), name) } - toExpression(node?: T): Expression { if (node === undefined) return this.f.createIdentifier('undefined'); @@ -144,9 +168,13 @@ export class NodeConverter { } return node; } + switch (node.kind) { case SyntaxKind.Identifier: - return finish(node, this.f.createIdentifier(getIdentifierName(node as Identifier))); + const name = getIdentifierName(node as Identifier); + return this.external.isEmbeddingExternalLibraryImport() && !this.external.knownGlobalTypeNames.has(name) + ? this.createExternalRuntimeTypePropertyAccessExpression(name) + : finish(node, this.f.createIdentifier(name)); case SyntaxKind.StringLiteral: return finish(node, this.f.createStringLiteral((node as StringLiteral).text)); case SyntaxKind.NumericLiteral: @@ -186,10 +214,20 @@ function isExternalOrCommonJsModule(file: SourceFile): boolean { return (file.externalModuleIndicator || file.commonJsModuleIndicator) !== undefined; } +export function isBuiltType(typeVar: Identifier, sourceFile: SourceFile): boolean { + return isNodeWithLocals(sourceFile) && !!sourceFile.locals?.has(typeVar.escapedText); +} + export function isNodeWithLocals(node: Node): node is (Node & { locals: SymbolTable | undefined }) { return 'locals' in node; } +export function getEntityName(typeName: EntityName): string { + return isIdentifier(typeName) + ? getIdentifierName(typeName) + : getIdentifierName(typeName.right); +} + //logic copied from typescript export function getGlobalsOfSourceFile(file: SourceFile): SymbolTable | void { if (file.redirectInfo) return; diff --git a/packages/type-compiler/src/resolver.ts b/packages/type-compiler/src/resolver.ts index 8ed21000f..7b249eaff 100644 --- a/packages/type-compiler/src/resolver.ts +++ b/packages/type-compiler/src/resolver.ts @@ -1,4 +1,13 @@ -import type { CompilerHost, CompilerOptions, ExportDeclaration, Expression, ImportDeclaration, ResolvedModule, SourceFile, StringLiteral, } from 'typescript'; +import type { + CompilerHost, + CompilerOptions, + ExportDeclaration, + Expression, + ImportDeclaration, + ResolvedModuleFull, + SourceFile, + StringLiteral, +} from 'typescript'; import ts from 'typescript'; import * as micromatch from 'micromatch'; import { isAbsolute } from 'path'; @@ -6,6 +15,7 @@ import { isAbsolute } from 'path'; const { createSourceFile, resolveModuleName, + isStringLiteral, SyntaxKind, ScriptTarget, } = ts; @@ -61,9 +71,36 @@ export class Resolver { return this.resolveSourceFile(from.fileName, (moduleSpecifier as StringLiteral).text); } - resolveImpl(modulePath: string, fromPath: string): ResolvedModule | undefined { + resolveExternalLibraryImport(importDeclaration: ImportDeclaration): Required { + const resolvedModule = this.resolveImport(importDeclaration); + if (!resolvedModule.isExternalLibraryImport) { + throw new Error('Resolved module is not an external library import'); + } + if (!resolvedModule.packageId) { + // packageId will be undefined when importing from sub-paths such as `rxjs/operators` + resolvedModule.packageId = { + name: (importDeclaration.moduleSpecifier as StringLiteral).text, + subModuleName: 'unknown', + version: 'unknown', + }; + } + return resolvedModule as Required; + } + + resolveImport(importDeclaration: ImportDeclaration): ResolvedModuleFull { + if (!isStringLiteral(importDeclaration.moduleSpecifier)) { + throw new Error('Invalid import declaration module specifier'); + } + const resolvedModule = this.resolveImpl(importDeclaration.moduleSpecifier.text, importDeclaration.getSourceFile().fileName); + if (!resolvedModule) { + throw new Error('Cannot resolve module'); + } + return resolvedModule; + } + + resolveImpl(modulePath: string, fromPath: string): ResolvedModuleFull | undefined { if (this.host.resolveModuleNames !== undefined) { - return this.host.resolveModuleNames([modulePath], fromPath, /*reusedNames*/ undefined, /*redirectedReference*/ undefined, this.compilerOptions)[0]; + return this.host.resolveModuleNames([modulePath], fromPath, /*reusedNames*/ undefined, /*redirectedReference*/ undefined, this.compilerOptions)[0] as ResolvedModuleFull | undefined; } const result = resolveModuleName(modulePath, fromPath, this.compilerOptions, this.host); return result.resolvedModule; diff --git a/packages/type-compiler/tests/inline-external-library-imports.spec.ts b/packages/type-compiler/tests/inline-external-library-imports.spec.ts new file mode 100644 index 000000000..ab47211bd --- /dev/null +++ b/packages/type-compiler/tests/inline-external-library-imports.spec.ts @@ -0,0 +1,375 @@ +import { test, expect } from '@jest/globals'; +import { TypeClass, TypeFunction } from '@deepkit/type'; + +import { transpile, transpileAndRun } from './utils'; + +test('string type alias', () => { + const res = transpile({ + app: `import { NIL } from 'uuid'; + + type T = typeof NIL; + ` + }, undefined, { + inlineExternalLibraryImports: { + 'uuid': ['NIL'], + }, + }); + + expect(res.app).not.toContain('const __ΩNIL = ['); + expect(res.app).not.toContain('() => __assignType(uuid_1.NIL, __ΩNIL)'); + expect(res.app).toContain('() => uuid_1.NIL'); +}); + +test('typeOf string type alias', () => { + const res = transpileAndRun({ + app: `import { typeOf } from '@deepkit/type'; + import { NIL } from 'uuid'; + + typeOf(); + ` + }); + + expect(res).toMatchInlineSnapshot(` + { + "kind": 13, + "literal": "00000000-0000-0000-0000-000000000000", + "typeName": undefined, + } + `); +}); + +test('object type alias', () => { + const res = transpile({ + app: `import { config } from 'rxjs'; + + type A = typeof config; + ` + }, undefined, { + inlineExternalLibraryImports: { + 'rxjs': ['config'], + }, + }); + + expect(res.app).not.toContain('const __Ωconfig = ['); + expect(res.app).not.toContain('() => __assignType(rxjs_1.config, __Ωconfig)'); + expect(res.app).toContain('() => rxjs_1.config'); +}); + +test('typeOf object type alias', () => { + const res = transpileAndRun({ + app: `import { typeOf } from '@deepkit/type'; + import { config } from 'rxjs'; + + typeOf(); + ` + }); + + expect(res).toMatchInlineSnapshot(` + { + "annotations": {}, + "id": 2, + "kind": 30, + "typeName": undefined, + "types": [ + { + "kind": 32, + "name": "onUnhandledError", + "parent": [Circular], + "type": { + "kind": 10, + "parent": [Circular], + }, + }, + { + "kind": 32, + "name": "onStoppedNotification", + "parent": [Circular], + "type": { + "kind": 10, + "parent": [Circular], + }, + }, + { + "kind": 32, + "name": "Promise", + "parent": [Circular], + "type": { + "kind": 11, + "parent": [Circular], + }, + }, + { + "kind": 32, + "name": "useDeprecatedSynchronousErrorHandling", + "parent": [Circular], + "type": { + "jit": {}, + "kind": 7, + "origin": { + "kind": 13, + "literal": false, + }, + "parent": [Circular], + }, + }, + { + "kind": 32, + "name": "useDeprecatedNextContext", + "parent": [Circular], + "type": { + "jit": {}, + "kind": 7, + "origin": { + "kind": 13, + "literal": false, + }, + "parent": [Circular], + }, + }, + ], + } + `); +}); + +test('declares scoped variable', () => { + const res = transpile({ + app: `import { map } from 'rxjs/operators'; + + type A = typeof map; + ` + }, undefined, { + inlineExternalLibraryImports: { + 'rxjs/operators': ['map'], + }, + }); + + expect(res.app).toContain('__ɵΩrxjs_operators = {}'); +}) + +test('function type alias', () => { + const res = transpile({ + app: `import { map } from 'rxjs/operators'; + + type A = typeof map; + ` + }, undefined, { + inlineExternalLibraryImports: { + 'rxjs/operators': ['map'], + }, + }); + + expect(res.app).toContain('__ɵΩrxjs_operators.map = ['); + expect(res.app).toContain('() => __ɵΩrxjs_operators.map'); +}); + +test('typeOf function type alias', () => { + const res = transpileAndRun({ + app: `import { map } from 'rxjs/operators'; + import { typeOf } from '@deepkit/type'; + + typeOf(); + ` + }, undefined, { + inlineExternalLibraryImports: { + 'rxjs/operators': ['map'], + }, + }) as TypeFunction; + + expect(res).toMatchInlineSnapshot(` +{ + "kind": 25, + "type": { + "kind": 23, + "types": [ + { + "jit": {}, + "kind": 5, + "origin": { + "kind": 13, + "literal": "value", + }, + }, + { + "function": [Function], + "kind": 17, + "name": "", + "parameters": [], + "return": { + "kind": 1, + }, + }, + ], + }, + "typeName": undefined, +} +`); +}) + +test('class type var', () => { + const res = transpile({ + app: `import { Observable } from 'rxjs'; + + type A = Observable; + ` + }, undefined, { + inlineExternalLibraryImports: { + 'rxjs': ['Observable'], + }, + }); + + expect(res.app).toMatchInlineSnapshot(` +""use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const __ɵΩrxjs = {}; +__ɵΩrxjs.Observable = ['T', () => __ɵΩrxjs.Observable, 'source', () => __ɵΩrxjs.Operator, 'operator', () => __ɵΩrxjs.Observable, 'this', () => __ɵΩrxjs.Subscriber, 'subscriber', () => __ɵΩrxjs.TeardownLogic, '', 'subscribe', 'constructor', 'args', 'create', () => __ɵΩrxjs.Operator, () => __ɵΩrxjs.Observable, 'lift', () => __ΩPartial, () => __ɵΩrxjs.Observer, 'observer', () => __ɵΩrxjs.Subscription, 'value', 'next', 'forEach', () => __ɵΩrxjs.Observable, 'pipe', 'toPromise', () => __ɵΩrxjs.Subscribable, 'Observable', 'b!PP"7"-J3#P"e"!o$#-J3%PPPe$!7&2\\'Pe$!7(2)n*/+2,8"0-P"@2."/+3/sPe"!"o0#2%8P"7102Pe"!o4"o3"258P760,PPe#!27$/+28$\`09PPe#!7:0;PPe#!-J\`0<5e!!o="x"w>y']; +const __ΩPartial = ['T', 'l+e#!e"!fRb!Pde"!gN#"y']; +__ɵΩrxjs.Operator = ['T', 'R', () => __ɵΩrxjs.Subscriber, 'subscriber', 'source', () => __ɵΩrxjs.TeardownLogic, 'call', 'b!b"PPPe$"7#2$"2%n&1\\'My']; +__ɵΩrxjs.Subscriber = ['T', () => __ɵΩrxjs.Subscription, 'x', '', 'next', 'e', 'error', 'complete', () => __ɵΩrxjs.Subscriber, 'create', 'isStopped', () => __ɵΩrxjs.Subscriber, () => __ɵΩrxjs.Observer, 'destination', () => __ɵΩrxjs.Subscriber, () => __ɵΩrxjs.Observer, 'constructor', 'value', 'err', 'unsubscribe', '_next', '_error', '_complete', () => __ɵΩrxjs.Observer, 'Subscriber', 'b!P7"PPe#!2#8$/$2%8P"2&8$/$2\\'8P$/$2(8Pe#!7)0*s)3+ __ɵΩrxjs.Subscription, () => __ɵΩrxjs.Unsubscribable, '', 'PP7!n"P$/#$Jy']; +__ɵΩrxjs.Observer = ['T', 'value', '', 'next', 'err', 'error', 'complete', 'b!PPe#!2"$/#4$P"2%$/#4&P$/#4\\'My']; +__ɵΩrxjs.Subscription = [() => __ɵΩrxjs.Subscription, 'EMPTY', 'closed', '', 'initialTeardown', 'constructor', 'unsubscribe', () => __ɵΩrxjs.TeardownLogic, 'teardown', 'add', () => __ΩExclude, () => __ɵΩrxjs.TeardownLogic, 'remove', () => __ɵΩrxjs.SubscriptionLike, 'Subscription', 'P7!3"s)3#PPP$/$-J2%8"0&P$0\\'Pn(2)$0*Pn,$o+#2)$0-5n.x"w/y']; +__ɵΩrxjs.Subscribable = ['T', () => __ΩPartial, () => __ɵΩrxjs.Observer, 'observer', () => __ɵΩrxjs.Unsubscribable, 'subscribe', 'b!PPe#!o#"o""2$n%1&My']; +const __ΩExclude = ['T', 'U', 'l6!Re$!RPe#!e$"qk#%QRb!b"Pde"!p)y']; +__ɵΩrxjs.Unsubscribable = ['unsubscribe', 'PP$1!My']; +__ɵΩrxjs.SubscriptionLike = [() => __ɵΩrxjs.Unsubscribable, 'unsubscribe', 'closed', 'Pn!P$1")4#9My']; +const rxjs_1 = require("rxjs"); +const __ΩA = [() => __ɵΩrxjs.Observable, 'P#7!y']; +" +`); +}) + +test('runtime type name clashing', () => { + const res = transpile({ + app: `import { Observable } from 'rxjs'; + + type Subscribable = any; + + type A = Observable; + ` + }, undefined, { + inlineExternalLibraryImports: { + 'rxjs': ['Observable'], + }, + }); + + expect(res.app).toContain('__ɵΩrxjs.Subscribable = ['); + expect(res.app).toContain('const __ΩSubscribable = ['); +}); + +test('class typeOf', () => { + const res = transpileAndRun({ + app: `import { Observable } from 'rxjs'; + import { typeOf } from '@deepkit/type'; + + typeOf>(); + ` + }, undefined, { + inlineExternalLibraryImports: { + 'rxjs': ['Observable'], + }, + }) as TypeClass; + + console.log(res); +}) + +test('only a single type is transformed', () => { + const res = transpile({ + app: `import { ConfigEnv, CorsOrigin } from 'vite'; + + type A = ConfigEnv; + + type B = CorsOrigin; + ` + }, undefined, { + inlineExternalLibraryImports: { + 'vite': ['ConfigEnv'], + }, + }); + + expect(res.app).toContain('__ɵΩvite.ConfigEnv = ['); + expect(res.app).not.toContain('__ɵΩvite.CorsOrigin = ['); +}) + +test('interface typeOf', () => { + const res = transpileAndRun({ + app: `import { ConfigEnv, CorsOrigin } from 'vite'; + import { typeOf } from '@deepkit/type'; + + typeOf(); + ` + }, undefined, { + inlineExternalLibraryImports: { + 'vite': ['ConfigEnv'], + }, + }); + + expect(res).toMatchInlineSnapshot(` + { + "annotations": {}, + "id": 2, + "kind": 30, + "typeArguments": undefined, + "typeName": "ConfigEnv", + "types": [ + { + "kind": 32, + "name": "command", + "parent": [Circular], + "type": { + "kind": 23, + "parent": [Circular], + "types": [ + { + "kind": 13, + "literal": "build", + "parent": [Circular], + }, + { + "kind": 13, + "literal": "serve", + "parent": [Circular], + }, + ], + }, + }, + { + "kind": 32, + "name": "mode", + "parent": [Circular], + "type": { + "kind": 5, + "parent": [Circular], + }, + }, + { + "kind": 32, + "name": "ssrBuild", + "optional": true, + "parent": [Circular], + "type": { + "kind": 7, + "parent": [Circular], + }, + }, + ], + } + `); +}); + +test('inline all external type imports for package', () => { + const res = transpile({ + app: `import { ConfigEnv, CorsOrigin } from 'vite'; + import { typeOf } from '@deepkit/type'; + + type A = ConfigEnv; + type B = CorsOrigin; + ` + }, undefined, { + inlineExternalLibraryImports: { + 'vite': true, + }, + }); + + expect(res.app).toContain('__ɵΩvite.ConfigEnv = ['); + expect(res.app).toContain('__ɵΩvite.CorsOrigin = ['); +}); diff --git a/packages/type-compiler/tests/setup/suite1/tsconfig.json b/packages/type-compiler/tests/setup/suite1/tsconfig.json index d234bc79a..f2f76c67c 100644 --- a/packages/type-compiler/tests/setup/suite1/tsconfig.json +++ b/packages/type-compiler/tests/setup/suite1/tsconfig.json @@ -6,7 +6,9 @@ "module": "CommonJS", "esModuleInterop": true }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "files": [ "app.ts" ] diff --git a/packages/type-compiler/tests/setup/suite1/tsconfig.no-types.json b/packages/type-compiler/tests/setup/suite1/tsconfig.no-types.json index 0d630d445..66bac7b24 100644 --- a/packages/type-compiler/tests/setup/suite1/tsconfig.no-types.json +++ b/packages/type-compiler/tests/setup/suite1/tsconfig.no-types.json @@ -1,6 +1,8 @@ { "extends": "./tsconfig.json", - "reflection": [ - "**/*/file1.ts" - ] + "deepkitTypeCompilerOptions": { + "reflection": [ + "**/*/file1.ts" + ] + } } diff --git a/packages/type-compiler/tests/utils.ts b/packages/type-compiler/tests/utils.ts index f83186144..670b330b8 100644 --- a/packages/type-compiler/tests/utils.ts +++ b/packages/type-compiler/tests/utils.ts @@ -1,9 +1,9 @@ import * as ts from 'typescript'; import { createSourceFile, getPreEmitDiagnostics, ScriptTarget, ScriptKind, TransformationContext } from 'typescript'; import { createFSBackedSystem, createVirtualCompilerHost, knownLibFilesForCompilerOptions } from '@typescript/vfs'; -import { ReflectionTransformer } from '../src/compiler.js'; -import { readFileSync } from 'fs'; -import { dirname, join } from 'path'; +import { ReflectionOptions, ReflectionTransformer } from '../src/compiler.js'; +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; const defaultLibLocation = dirname(require.resolve('typescript')) + '/'; //node_modules/typescript/lib/ @@ -25,8 +25,8 @@ function readLibs(compilerOptions: ts.CompilerOptions, files: Map, options: ts.CompilerOptions = {}): Record { - const compilerOptions: ts.CompilerOptions = { +export function transform(files: Record, compilerOptions: ts.CompilerOptions = {}, reflectionOptions?: ReflectionOptions): Record { + compilerOptions = { ...defaultCompilerOptions, target: ts.ScriptTarget.ES2016, allowNonTsExtensions: true, @@ -34,7 +34,7 @@ export function transform(files: Record, options: ts.CompilerOpt moduleResolution: ts.ModuleResolutionKind.NodeJs, experimentalDecorators: true, esModuleInterop: true, - ...options + ...compilerOptions }; const fsMap = new Map(); @@ -52,7 +52,7 @@ export function transform(files: Record, options: ts.CompilerOpt for (const fileName of Object.keys(files)) { const sourceFile = host.compilerHost.getSourceFile(fullPath(fileName), ScriptTarget.ES2022); if (!sourceFile) continue; - const transform = ts.transform(sourceFile, [(context) => (node) => new ReflectionTransformer(context).forHost(host.compilerHost).withReflectionMode('always').transformSourceFile(node)]); + const transform = ts.transform(sourceFile, [(context) => (node) => new ReflectionTransformer(context).forHost(host.compilerHost).withReflectionMode('always', reflectionOptions).transformSourceFile(node)]); const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); const code = printer.printNode(ts.EmitHint.SourceFile, transform.transformed[0], transform.transformed[0]); res[fileName] = code; @@ -64,16 +64,16 @@ export function transform(files: Record, options: ts.CompilerOpt /** * The first entry in files is executed as main script */ -export function transpileAndRun(files: Record, options: ts.CompilerOptions = {}): any { - const source = transpile(files, options); +export function transpileAndRun(files: Record, compilerOptions: ts.CompilerOptions = {}, reflectionOptions?: ReflectionOptions): any { + const source = transpile(files, compilerOptions, reflectionOptions); console.log('transpiled', source); const first = Object.keys(files)[0]; return eval(source[first]); } -export function transpile(files: Record, options: ts.CompilerOptions = {}): Record { - const compilerOptions: ts.CompilerOptions = { +export function transpile(files: Record, compilerOptions: ts.CompilerOptions = {}, reflectionOptions?: ReflectionOptions): Record { + compilerOptions = { ...defaultCompilerOptions, target: ts.ScriptTarget.ES2015, allowNonTsExtensions: true, @@ -82,7 +82,7 @@ export function transpile(files: Record, options: ts.CompilerOpt experimentalDecorators: true, esModuleInterop: true, skipLibCheck: true, - ...options + ...compilerOptions }; const fsMap = new Map(); @@ -108,7 +108,7 @@ export function transpile(files: Record, options: ts.CompilerOpt program.emit(undefined, (fileName, data) => { res[fileName.slice(__dirname.length + 1).replace(/\.js$/, '')] = data; }, undefined, undefined, { - before: [(context: TransformationContext) => new ReflectionTransformer(context).forHost(host.compilerHost).withReflectionMode('always')], + before: [(context: TransformationContext) => new ReflectionTransformer(context).forHost(host.compilerHost).withReflectionMode('always', reflectionOptions)], }); return res; diff --git a/packages/fs/dist/.gitkeep b/packages/type-spec/dist/.gitkeep similarity index 100% rename from packages/fs/dist/.gitkeep rename to packages/type-spec/dist/.gitkeep diff --git a/packages/type/src/reflection/extends.ts b/packages/type/src/reflection/extends.ts index 18264a3a1..202b3d3c3 100644 --- a/packages/type/src/reflection/extends.ts +++ b/packages/type/src/reflection/extends.ts @@ -33,7 +33,7 @@ import { TypeString, TypeTemplateLiteral, TypeTuple, - TypeUnion + TypeUnion, } from './type.js'; import { isPrototypeOfBase } from '@deepkit/core'; import { typeInfer } from './processor.js'; diff --git a/packages/type/src/reflection/processor.ts b/packages/type/src/reflection/processor.ts index eb39142d4..2537b5ca6 100644 --- a/packages/type/src/reflection/processor.ts +++ b/packages/type/src/reflection/processor.ts @@ -704,13 +704,15 @@ export class Processor { case ReflectionOp.classReference: { const ref = this.eatParameter() as number; const classOrFunction = resolveFunction(program.stack[ref] as Function, program.object); + // will be packed if external library import + const packed = isPack(classOrFunction); const inputs = this.popFrame() as Type[]; if (!classOrFunction) { this.pushType({ kind: ReflectionKind.unknown }); break; } - if (!classOrFunction.__type) { + if (!packed && !classOrFunction.__type) { if (op === ReflectionOp.classReference) { this.pushType({ kind: ReflectionKind.class, classType: classOrFunction, typeArguments: inputs, types: [] }); } else if (op === ReflectionOp.functionReference) { @@ -1274,7 +1276,7 @@ export class Processor { // process.stdout.write(`Done ${program.depth} in ${Date.now() - program.started}ms with ${stringifyValueWithType(program.object)} -> ${stringifyShortResolvedType(result as Type)}\n`); if (isType(result)) { - if (program.object) { + if (program.object && !isPack(program.object)) { if (result.kind === ReflectionKind.class && result.classType === Object) { result.classType = program.object; applyClassDecorators(result); @@ -1770,7 +1772,6 @@ export function typeInfer(value: any): Type { } if (isClass(value)) { - //unknown class return { kind: ReflectionKind.class, classType: value as ClassType, types: [] }; } diff --git a/packages/type/src/reflection/reflection.ts b/packages/type/src/reflection/reflection.ts index 7a457de01..477d13563 100644 --- a/packages/type/src/reflection/reflection.ts +++ b/packages/type/src/reflection/reflection.ts @@ -1435,8 +1435,10 @@ export class ReflectionClass { export function extractTypeNameFromFunction(fn: Function): string { const str = fn.toString(); - //either it starts with __Ω* or __\u{3a9}* (bun.js) - const match = str.match(/(?:__Ω|__\\u\{3a9\})([\w]+)/); + // either it starts with + // __Ω* or __ɵΩ* (node.js) + // __\u{3a9}* or __\u{275}\u{3a9}* (bun.js) + const match = str.match(/(?:__Ω|__\\u\{3a9\}|__ɵΩ|__\\u{275}\\u{3a9}*)([\w]+)/); if (match) { return match[1]; } diff --git a/packages/type/src/reflection/type.ts b/packages/type/src/reflection/type.ts index f2f645b83..ec65d31a0 100644 --- a/packages/type/src/reflection/type.ts +++ b/packages/type/src/reflection/type.ts @@ -512,8 +512,7 @@ export type Type = | TypeTupleMember | TypeRest | TypeRegexp - | TypeCallSignature - ; + | TypeCallSignature; export type Widen = T extends string ? string @@ -2480,6 +2479,7 @@ export function stringifyType(type: Type, stateIn: Partial stack.push({ type: type.arguments![0], depth: depth + 1 }); break; } + if (binaryTypes.includes(type.classType)) { result.push(getClassName(type.classType)); break; diff --git a/packages/type/src/serializer.ts b/packages/type/src/serializer.ts index ced4823c9..0799982f2 100644 --- a/packages/type/src/serializer.ts +++ b/packages/type/src/serializer.ts @@ -65,7 +65,7 @@ import { typeToObject, TypeTuple, TypeUnion, - validationAnnotation + validationAnnotation, } from './reflection/type.js'; import { TypeNumberBrand } from '@deepkit/type-spec'; import { hasCircularReference, ReflectionClass, ReflectionProperty } from './reflection/reflection.js'; diff --git a/packages/type/src/type-serialization.ts b/packages/type/src/type-serialization.ts index ccf0cca13..063f51958 100644 --- a/packages/type/src/type-serialization.ts +++ b/packages/type/src/type-serialization.ts @@ -20,7 +20,7 @@ import { TypeProperty, TypeRest, TypeTuple, - TypeTupleMember + TypeTupleMember, } from './reflection/type.js'; import { getClassName, getParentClass } from '@deepkit/core'; import { reflect, ReflectionClass, typeOf } from './reflection/reflection.js'; diff --git a/packages/type/tests/type-serialization.spec.ts b/packages/type/tests/type-serialization.spec.ts index 8cef1f9b8..7c365fd47 100644 --- a/packages/type/tests/type-serialization.spec.ts +++ b/packages/type/tests/type-serialization.spec.ts @@ -13,7 +13,7 @@ import { ReflectionKind, Type, TypeClass, - TypeProperty + TypeProperty, } from '../src/reflection/type.js'; import { deserializeType, serializeType } from '../src/type-serialization.js'; import { entity } from '../src/decorator.js'; diff --git a/packages/type/tsconfig.json b/packages/type/tsconfig.json index 457e1783e..ae9f91e84 100644 --- a/packages/type/tsconfig.json +++ b/packages/type/tsconfig.json @@ -20,7 +20,9 @@ "node" ] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "index.ts" diff --git a/packages/workflow/tsconfig.json b/packages/workflow/tsconfig.json index 37c3201ef..1fd6456c9 100644 --- a/packages/workflow/tsconfig.json +++ b/packages/workflow/tsconfig.json @@ -16,7 +16,9 @@ "composite": true, "types": [] }, - "reflection": true, + "deepkitTypeCompilerOptions": { + "reflection": true + }, "include": [ "src", "tests",