diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c75cc05..09ba508 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -30,3 +30,6 @@ jobs: - name: Run build run: bun run build + + - name: Run tests + run: bun test diff --git a/.gitignore b/.gitignore index ab5afb2..a92b276 100644 --- a/.gitignore +++ b/.gitignore @@ -174,3 +174,4 @@ dist # Finder (MacOS) folder config .DS_Store +tests/**/**/test \ No newline at end of file diff --git a/package.json b/package.json index c1941c0..52621ca 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "url": "https://softnetics.tech" }, "scripts": { - "build": "tsup" + "build": "tsup", + "test": "bun test" }, "devDependencies": { "@vercel/ncc": "^0.38.1", diff --git a/src/plugin.ts b/src/plugin.ts index fafe07c..775dfba 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -2,7 +2,9 @@ import { BunPlugin, OnLoadResult, OnResolveResult, Transpiler } from "bun"; import { transformer } from "./transformer"; // no option for now -type WhatTheDepOptions = {}; +export type WhatTheDepOptions = { + debug?: boolean; +}; function plugin(opts?: WhatTheDepOptions): BunPlugin { return { @@ -10,7 +12,7 @@ function plugin(opts?: WhatTheDepOptions): BunPlugin { setup(build) { build.onLoad({ filter: /\.ts$/ }, async (args): Promise => { const transpiler = new Transpiler({ loader: "ts" }); - const newSource = await transformer(args.path); + const newSource = await transformer(args.path, opts ?? {}); return { contents: transpiler.transformSync(newSource), }; diff --git a/src/transformer.ts b/src/transformer.ts index e7dfb00..df876aa 100644 --- a/src/transformer.ts +++ b/src/transformer.ts @@ -1,5 +1,6 @@ import ts from "typescript"; import { transformerFactory } from "./walk"; +import { WhatTheDepOptions } from "."; const tsConfigPath = ts.findConfigFile( "./", @@ -19,14 +20,17 @@ if (!parsedConfig) { const program = ts.createProgram(parsedConfig.fileNames, parsedConfig.options); const printer = ts.createPrinter(); -export const transformer = async (filePath: string): Promise => { +export const transformer = async ( + filePath: string, + config: WhatTheDepOptions +): Promise => { const sourceFile = program.getSourceFile(filePath); if (!sourceFile) return await require("fs").promises.readFile(filePath, "utf8"); const transformedSourceFile = ts.transform( sourceFile, - [transformerFactory(program)], + [transformerFactory(program, config)], parsedConfig.options ).transformed[0] as ts.SourceFile; diff --git a/src/walk/1-create-container.ts b/src/walk/1-create-container.ts index 76bcf7c..34c71bb 100644 --- a/src/walk/1-create-container.ts +++ b/src/walk/1-create-container.ts @@ -36,38 +36,51 @@ export const handleContainer = ( ) { // dig into expression to find the identifier let expressionChildren = node.expression.getChildren(); - - while (expressionChildren[0] && !(ts.isNewExpression(expressionChildren[0]) || ts.isIdentifier(expressionChildren[0]))) { + + while ( + expressionChildren[0] && + !( + ts.isNewExpression(expressionChildren[0]) || + ts.isIdentifier(expressionChildren[0]) + ) + ) { expressionChildren = expressionChildren[0].getChildren(); } - if(expressionChildren[0] && ts.isNewExpression(expressionChildren[0])) { - const maybeWhatTheDepContainer = expressionChildren[0].getChildren()[1] - if(!maybeWhatTheDepContainer) { - return ts.visitEachChild(node, visitor, globalContext); - } - - if( - !(ts.isIdentifier(maybeWhatTheDepContainer) && - globalTypeChecker.getTypeAtLocation(maybeWhatTheDepContainer).getSymbol() === container) - ) { - return ts.visitEachChild(node, visitor, globalContext); - } + if ( + expressionChildren[0] && + ts.isNewExpression(expressionChildren[0]) + ) { + const maybeWhatTheDepContainer = + expressionChildren[0].getChildren()[1]; + if (!maybeWhatTheDepContainer) { + return ts.visitEachChild(node, visitor, globalContext); + } - expressionChildren[0] = maybeWhatTheDepContainer; + if ( + !( + ts.isIdentifier(maybeWhatTheDepContainer) && + globalTypeChecker + .getTypeAtLocation(maybeWhatTheDepContainer) + .getSymbol() === container + ) + ) { + return ts.visitEachChild(node, visitor, globalContext); } - + + expressionChildren[0] = maybeWhatTheDepContainer; + } if ( !isWhatTheDepMethod(expressionChildren[2]) || !expressionChildren[0] ) { - return node; + return ts.visitEachChild(node, visitor, globalContext); } if (expressionChildren[2].getText() === "get") { - const identifierSymbol = globalTypeChecker.getTypeAtLocation( - expressionChildren[0] - ).getSymbol(); + const identifierSymbol = globalTypeChecker + .getTypeAtLocation(expressionChildren[0]) + .getSymbol(); if (identifierSymbol) { if (ts.isVariableDeclaration(identifierSymbol.valueDeclaration!)) { // case 1 of get @@ -89,11 +102,11 @@ export const handleContainer = ( } } } - const symbol = globalTypeChecker.getTypeAtLocation( - expressionChildren[0] - ).getSymbol(); + const symbol = globalTypeChecker + .getTypeAtLocation(expressionChildren[0]) + .getSymbol(); // make sure this is a method calling of the container - if (symbol === container) { + if (symbol === container) { // this iterate through all register and registerSingleton if (ts.isCallExpression(node)) { // method calling expression structure is @@ -119,13 +132,14 @@ export const handleContainer = ( const InterfaceOrClass = node.typeArguments![0]; const Class = node.typeArguments?.[1]; - if (ts.isTypeReferenceNode(InterfaceOrClass)) { - // this type reference should have only one child + if (ts.isTypeReferenceNode(InterfaceOrClass)) { + // this type reference should have only one child const identifier = Class?.getChildren()[0] ?? InterfaceOrClass.getChildren()[0]; - - const OriginalClassSymbol = - globalTypeChecker.getTypeAtLocation(identifier).getSymbol() ; + + const OriginalClassSymbol = globalTypeChecker + .getTypeAtLocation(identifier) + .getSymbol(); let dependencies: string[] = []; @@ -145,25 +159,25 @@ export const handleContainer = ( if (Class) { // declare class using interface hash const classSymbol = globalTypeChecker - .getTypeAtLocation(InterfaceOrClass) - .getSymbol() + .getTypeAtLocation(InterfaceOrClass) + .getSymbol(); graph.register( graphRegisterMethod, - classSymbol ? hashSymbol( - classSymbol - ) : hashNode(InterfaceOrClass), + classSymbol + ? hashSymbol(classSymbol) + : hashNode(InterfaceOrClass), factoryNode, Class.getText(), dependencies ); - return node; + return ts.visitEachChild(node, visitor, globalContext); } - - graph.register( graphRegisterMethod, - OriginalClassSymbol ? hashSymbol(OriginalClassSymbol) : hashNode(identifier), + OriginalClassSymbol + ? hashSymbol(OriginalClassSymbol) + : hashNode(identifier), factoryNode, InterfaceOrClass.getText(), dependencies diff --git a/src/walk/2-list-dependency.ts b/src/walk/2-list-dependency.ts index 635e46f..a26d76d 100644 --- a/src/walk/2-list-dependency.ts +++ b/src/walk/2-list-dependency.ts @@ -13,6 +13,9 @@ export const listDependenciesOfClass = ( .getSymbol()?.valueDeclaration; if (!classDeclaration) { + if (!classSymbol?.name) { + throw new Error(`There are only type provided, please provide a factory`); + } throw new Error(`Could not find class declaration of ${classSymbol.name}`); } @@ -41,10 +44,10 @@ export const listDependenciesOfClass = ( const type = globalTypeChecker.getTypeAtLocation(parameter.type); const symbol = type.symbol ?? type.getSymbol(); - let moduleHash: string - if( symbol ) { + let moduleHash: string; + if (symbol) { moduleHash = hashSymbol(symbol); - }else{ + } else { moduleHash = hashNode(parameter.type); } if (symbol && moduleHash) { diff --git a/src/walk/4-create-context.ts b/src/walk/4-create-context.ts index 059ebc4..0945696 100644 --- a/src/walk/4-create-context.ts +++ b/src/walk/4-create-context.ts @@ -1,6 +1,6 @@ import ts from "typescript"; import { DependencyGraph } from "./graph"; -import { globalTypeChecker } from "."; +import { globalConfig, globalTypeChecker } from "."; import { hashSymbol } from "./utils"; import { defaultFactoryTemplate, @@ -39,7 +39,7 @@ export const createContextFromGraph = (graph: DependencyGraph) => { }); properties.push(initSingletonsTemplate(graph.getSingletons())); - + if (globalConfig.debug) graph.print(); const objectLiteral = ts.factory.createObjectLiteralExpression( properties, true diff --git a/src/walk/graph.ts b/src/walk/graph.ts index 863db83..94fc87d 100644 --- a/src/walk/graph.ts +++ b/src/walk/graph.ts @@ -19,6 +19,7 @@ export class DependencyGraph { className: string, dependencies: string[] ): void { + console.log("register", interfaceHash, className); this.dependencies.set(interfaceHash, { type, dependencies, diff --git a/src/walk/index.ts b/src/walk/index.ts index 9971011..849f9cc 100644 --- a/src/walk/index.ts +++ b/src/walk/index.ts @@ -1,16 +1,21 @@ import * as ts from "typescript"; import { __WTD_MODULE__ } from "./constants"; import { handleContainer } from "./1-create-container"; +import { WhatTheDepOptions } from ".."; +import { handleGet } from "./3-modify-get"; let whatTheDepModule: ts.Symbol | undefined; export let globalContext: ts.TransformationContext; export let globalTypeChecker: ts.TypeChecker; +export let globalConfig: WhatTheDepOptions; export const transformerFactory = ( - program: ts.Program + program: ts.Program, + config: WhatTheDepOptions ): ts.TransformerFactory => { const transformList = new Map(); + globalConfig = config; globalTypeChecker = program.getTypeChecker(); return (context) => (rootNode) => { globalContext = context; @@ -39,7 +44,9 @@ export const transformerFactory = ( handleContainer( rootNode, declaration.initializer.expression, - globalTypeChecker.getSymbolAtLocation(declaration.name)!, + globalTypeChecker + .getTypeAtLocation(declaration.name) + .getSymbol()!, program, transformList ); @@ -53,7 +60,9 @@ export const transformerFactory = ( handleContainer( rootNode, declaration.initializer.expression, - globalTypeChecker.getSymbolAtLocation(declaration.name)!, + globalTypeChecker + .getTypeAtLocation(declaration.name) + .getSymbol()!, program, transformList ); @@ -76,10 +85,14 @@ export const transformerFactory = ( if (ts.isNewExpression(expressionChildren[0].expression)) { const containerNode = expressionChildren[0].expression.getChildren()[1]!; - const containerSymbol = globalTypeChecker.getTypeAtLocation( - containerNode - ).getSymbol(); - if(containerSymbol?.valueDeclaration?.getText().includes(__WTD_MODULE__)) { + const containerSymbol = globalTypeChecker + .getTypeAtLocation(containerNode) + .getSymbol(); + if ( + containerSymbol?.valueDeclaration + ?.getText() + .includes(__WTD_MODULE__) + ) { handleContainer( rootNode, containerNode, @@ -92,12 +105,15 @@ export const transformerFactory = ( } if (ts.isNewExpression(expressionChildren[0])) { const containerNode = expressionChildren[0].getChildren()[1]!; - const containerSymbol = globalTypeChecker.getTypeAtLocation( - containerNode - ).getSymbol(); + const containerSymbol = globalTypeChecker + .getTypeAtLocation(containerNode) + .getSymbol(); - - if(containerSymbol?.valueDeclaration?.getText().includes(__WTD_MODULE__)) { + if ( + containerSymbol?.valueDeclaration + ?.getText() + .includes(__WTD_MODULE__) + ) { handleContainer( rootNode, containerNode, @@ -113,6 +129,24 @@ export const transformerFactory = ( } } } + + // Find a property access expression .get + if (ts.isPropertyAccessExpression(node)) { + // is .get + if (node.name.getText() === "get") { + const symbol = globalTypeChecker + .getTypeAtLocation(node.expression) + .getSymbol(); + if (symbol) { + if (symbol?.valueDeclaration?.getText().includes(__WTD_MODULE__)) { + if (!ts.isCallExpression(node.parent)) { + return ts.visitEachChild(node, visitor, context); + } + handleGet(node.parent, transformList); + } + } + } + } return ts.visitEachChild(node, visitor, context); }; @@ -121,7 +155,11 @@ export const transformerFactory = ( if (transformList.size > 0) { const transform = (node: ts.Node): ts.Node => { if (transformList.has(node)) { - return transformList.get(node)!; + return ts.visitEachChild( + transformList.get(node)!, + transform, + context + ); } return ts.visitEachChild(node, transform, context); }; diff --git a/src/walk/utils.ts b/src/walk/utils.ts index 899e14b..1a70c93 100644 --- a/src/walk/utils.ts +++ b/src/walk/utils.ts @@ -15,6 +15,7 @@ export const hashSymbol = (symbol: ts.Symbol) => { .update(symbolName) .digest("hex") .substring(0, 8); + return hash; }; diff --git a/tests/common.ts b/tests/common.ts new file mode 100644 index 0000000..fc85423 --- /dev/null +++ b/tests/common.ts @@ -0,0 +1,24 @@ +import { readFile } from "fs/promises"; +import { whatTheDep } from "../src"; + +export const buildAndTransform = async ( + entrypoints: string[], + outdir: string, + fileName: string +) => { + try { + await Bun.build({ + entrypoints: entrypoints, + outdir: outdir, + target: "bun", + plugins: [ + whatTheDep({ + debug: true, + }), + ], + }); + return readFile(`${outdir}/${fileName}`, "utf8"); + } catch (error) { + console.error(error); + } +}; diff --git a/tests/container-resolution/container-resolution.test.ts b/tests/container-resolution/container-resolution.test.ts new file mode 100644 index 0000000..92e0ec4 --- /dev/null +++ b/tests/container-resolution/container-resolution.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect } from "bun:test"; +import { buildAndTransform } from "../common"; + +describe("container resolution", () => { + it("should update context when init container before register", async () => { + expect( + await buildAndTransform( + [ + "./tests/container-resolution/mock/1-init-container-before-register.ts", + ], + "./tests/container-resolution/test", + "1-init-container-before-register.js" + ) + ).toEqual( + // snapshot + '// @bun\n// src/container.ts\nclass Container {\n context;\n memo = {};\n singletonsInitialized = false;\n constructor(context = {}) {\n this.context = context;\n }\n import() {\n return this;\n }\n register(factory = () => ({})) {\n return this;\n }\n registerSingleton(factory = () => ({})) {\n return this;\n }\n async get(interfaceHash = "") {\n if (!this.singletonsInitialized) {\n this.singletonsInitialized = true;\n if (!this.context["init_singletons"]) {\n throw new Error("what-the-dep is not running correctly, please check your bunfig.toml and make sure it is preload what-the-dep plugin");\n }\n this.context["init_singletons"](this.memo, this.get.bind(this));\n }\n if (this.context[interfaceHash]) {\n return this.context[interfaceHash](this.memo, this.get.bind(this));\n }\n return {};\n }\n static __WTD_MODULE__ = true;\n}\n\n// tests/container-resolution/mock/1-init-container-before-register.ts\nclass A {\n constructor() {\n console.log("A");\n }\n}\n\nclass B {\n a;\n constructor(a) {\n this.a = a;\n console.log("B");\n }\n}\nvar container2 = new Container({\n "155ad188": async (singletons, get) => {\n {\n return new B(await get("c559563c"));\n }\n },\n c559563c: async (singletons, get) => {\n {\n return new A;\n }\n },\n init_singletons: async (singletons, get) => {\n {\n }\n }\n});\ncontainer2.register().register().get("155ad188");\n' + ); + }); + + it("should update context when register directly from new", async () => { + expect( + await buildAndTransform( + ["./tests/container-resolution/mock/2-register-directly-from-new.ts"], + "./tests/container-resolution/test", + "2-register-directly-from-new.js" + ) + ).toEqual( + // snapshot + '// @bun\n// src/container.ts\nclass Container {\n context;\n memo = {};\n singletonsInitialized = false;\n constructor(context = {}) {\n this.context = context;\n }\n import() {\n return this;\n }\n register(factory = () => ({})) {\n return this;\n }\n registerSingleton(factory = () => ({})) {\n return this;\n }\n async get(interfaceHash = "") {\n if (!this.singletonsInitialized) {\n this.singletonsInitialized = true;\n if (!this.context["init_singletons"]) {\n throw new Error("what-the-dep is not running correctly, please check your bunfig.toml and make sure it is preload what-the-dep plugin");\n }\n this.context["init_singletons"](this.memo, this.get.bind(this));\n }\n if (this.context[interfaceHash]) {\n return this.context[interfaceHash](this.memo, this.get.bind(this));\n }\n return {};\n }\n static __WTD_MODULE__ = true;\n}\n\n// tests/container-resolution/mock/2-register-directly-from-new.ts\nclass A {\n constructor() {\n console.log("A");\n }\n}\n\nclass B {\n a;\n constructor(a) {\n this.a = a;\n console.log("B");\n }\n}\nvar b = new Container({\n "155ad188": async (singletons, get) => {\n {\n return new B(await get("c559563c"));\n }\n },\n c559563c: async (singletons, get) => {\n {\n return new A;\n }\n },\n init_singletons: async (singletons, get) => {\n {\n }\n }\n}).register().register().get("155ad188");\n' + ); + }); +}); diff --git a/tests/container-resolution/mock/1-init-container-before-register.ts b/tests/container-resolution/mock/1-init-container-before-register.ts new file mode 100644 index 0000000..ec08bfd --- /dev/null +++ b/tests/container-resolution/mock/1-init-container-before-register.ts @@ -0,0 +1,17 @@ +import { Container } from "../../../src/container"; + +class A { + constructor() { + console.log("A"); + } +} + +class B { + constructor(private a: A) { + console.log("B"); + } +} + +const container = new Container(); + +container.register().register().get(); diff --git a/tests/container-resolution/mock/2-register-directly-from-new.ts b/tests/container-resolution/mock/2-register-directly-from-new.ts new file mode 100644 index 0000000..62825e8 --- /dev/null +++ b/tests/container-resolution/mock/2-register-directly-from-new.ts @@ -0,0 +1,15 @@ +import { Container } from "../../../src/container"; + +class A { + constructor() { + console.log("A"); + } +} + +class B { + constructor(private a: A) { + console.log("B"); + } +} + +const b = new Container().register().register().get(); diff --git a/tests/dependency-resolution/dependency-resolution.test.ts b/tests/dependency-resolution/dependency-resolution.test.ts new file mode 100644 index 0000000..edbeccc --- /dev/null +++ b/tests/dependency-resolution/dependency-resolution.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect } from "bun:test"; +import { buildAndTransform } from "../common"; + +describe("dependency resolution", () => { + it("should inject class when defined class using interface", async () => { + expect( + await buildAndTransform( + ["./tests/dependency-resolution/mock/1-interface-defined-class.ts"], + "./tests/dependency-resolution/test", + "1-interface-defined-class.js" + ) + ).toEqual( + '// @bun\n// src/container.ts\nclass Container {\n context;\n memo = {};\n singletonsInitialized = false;\n constructor(context = {}) {\n this.context = context;\n }\n import() {\n return this;\n }\n register(factory = () => ({})) {\n return this;\n }\n registerSingleton(factory = () => ({})) {\n return this;\n }\n async get(interfaceHash = "") {\n if (!this.singletonsInitialized) {\n this.singletonsInitialized = true;\n if (!this.context["init_singletons"]) {\n throw new Error("what-the-dep is not running correctly, please check your bunfig.toml and make sure it is preload what-the-dep plugin");\n }\n this.context["init_singletons"](this.memo, this.get.bind(this));\n }\n if (this.context[interfaceHash]) {\n return this.context[interfaceHash](this.memo, this.get.bind(this));\n }\n return {};\n }\n static __WTD_MODULE__ = true;\n}\n\n// tests/dependency-resolution/mock/1-interface-defined-class.ts\nclass A {\n constructor() {\n console.log("A");\n }\n}\n\nclass B {\n a;\n constructor(a) {\n this.a = a;\n console.log("B");\n }\n}\nvar container2 = new Container({\n "78e48431": async (singletons, get) => {\n {\n return new B(await get("450d3380"));\n }\n },\n "450d3380": async (singletons, get) => {\n {\n return new A;\n }\n },\n init_singletons: async (singletons, get) => {\n {\n }\n }\n});\ncontainer2.register().register().get("78e48431");\n' + ); + }); + + it("should resolve dependencies when provide factory", async () => { + expect( + await buildAndTransform( + ["./tests/dependency-resolution/mock/2-factory-get-dependency.ts"], + "./tests/dependency-resolution/test", + "2-factory-get-dependency.js" + ) + ).toEqual( + '// @bun\n// src/container.ts\nclass Container {\n context;\n memo = {};\n singletonsInitialized = false;\n constructor(context = {}) {\n this.context = context;\n }\n import() {\n return this;\n }\n register(factory = () => ({})) {\n return this;\n }\n registerSingleton(factory = () => ({})) {\n return this;\n }\n async get(interfaceHash = "") {\n if (!this.singletonsInitialized) {\n this.singletonsInitialized = true;\n if (!this.context["init_singletons"]) {\n throw new Error("what-the-dep is not running correctly, please check your bunfig.toml and make sure it is preload what-the-dep plugin");\n }\n this.context["init_singletons"](this.memo, this.get.bind(this));\n }\n if (this.context[interfaceHash]) {\n return this.context[interfaceHash](this.memo, this.get.bind(this));\n }\n return {};\n }\n static __WTD_MODULE__ = true;\n}\n\n// tests/dependency-resolution/mock/2-factory-get-dependency.ts\nclass A {\n constructor() {\n console.log("A");\n }\n}\n\nclass B {\n a;\n constructor(a) {\n this.a = a;\n console.log("B");\n }\n}\nvar container2 = new Container({\n "78e48431": async (singletons, get) => {\n {\n return await (async (get2) => new B(await get2("450d3380")))(get);\n }\n },\n "450d3380": async (singletons, get) => {\n {\n return new A;\n }\n },\n init_singletons: async (singletons, get) => {\n {\n }\n }\n});\ncontainer2.register().register(async (get) => new B(await get())).get("78e48431");\n' + ); + }); + + it("should resolve dependencies when provide InstanceType", async () => { + expect( + await buildAndTransform( + ["./tests/dependency-resolution/mock/3-external-class.ts"], + "./tests/dependency-resolution/test", + "3-external-class.js" + ) + ).toEqual( + '// @bun\n// src/container.ts\nclass Container {\n context;\n memo = {};\n singletonsInitialized = false;\n constructor(context = {}) {\n this.context = context;\n }\n import() {\n return this;\n }\n register(factory = () => ({})) {\n return this;\n }\n registerSingleton(factory = () => ({})) {\n return this;\n }\n async get(interfaceHash = "") {\n if (!this.singletonsInitialized) {\n this.singletonsInitialized = true;\n if (!this.context["init_singletons"]) {\n throw new Error("what-the-dep is not running correctly, please check your bunfig.toml and make sure it is preload what-the-dep plugin");\n }\n this.context["init_singletons"](this.memo, this.get.bind(this));\n }\n if (this.context[interfaceHash]) {\n return this.context[interfaceHash](this.memo, this.get.bind(this));\n }\n return {};\n }\n static __WTD_MODULE__ = true;\n}\n\n// tests/dependency-resolution/mock/3-external-class.ts\nclass A {\n constructor() {\n console.log("A");\n }\n}\n\nclass B {\n a;\n constructor(a) {\n this.a = a;\n console.log("B");\n }\n}\nvar container2 = new Container({\n fd2d9496: async (singletons, get) => {\n {\n return await (async (get2) => {\n return new B(await get2("c559563c"));\n })(get);\n }\n },\n c559563c: async (singletons, get) => {\n {\n return new A;\n }\n },\n init_singletons: async (singletons, get) => {\n {\n }\n }\n});\ncontainer2.register().register(async (get) => {\n return new B(await get());\n}).get("3847949c");\n' + ); + }); +}); diff --git a/tests/dependency-resolution/mock/1-interface-defined-class.ts b/tests/dependency-resolution/mock/1-interface-defined-class.ts new file mode 100644 index 0000000..64358c0 --- /dev/null +++ b/tests/dependency-resolution/mock/1-interface-defined-class.ts @@ -0,0 +1,23 @@ +import { Container } from "../../../src/container"; + +interface IB { + a: A; +} + +interface IA {} + +class A implements IA { + constructor() { + console.log("A"); + } +} + +class B implements IB { + constructor(public readonly a: IA) { + console.log("B"); + } +} + +const container = new Container(); + +container.register().register().get(); diff --git a/tests/dependency-resolution/mock/2-factory-get-dependency.ts b/tests/dependency-resolution/mock/2-factory-get-dependency.ts new file mode 100644 index 0000000..bd0f1f8 --- /dev/null +++ b/tests/dependency-resolution/mock/2-factory-get-dependency.ts @@ -0,0 +1,27 @@ +import { get } from "https"; +import { Container } from "../../../src/container"; + +interface IB { + a: A; +} + +interface IA {} + +class A implements IA { + constructor() { + console.log("A"); + } +} + +class B implements IB { + constructor(public readonly a: A) { + console.log("B"); + } +} + +const container = new Container(); + +container + .register() + .register(async (get) => new B(await get())) + .get(); diff --git a/tests/dependency-resolution/mock/3-external-class.ts b/tests/dependency-resolution/mock/3-external-class.ts new file mode 100644 index 0000000..8b5dc6c --- /dev/null +++ b/tests/dependency-resolution/mock/3-external-class.ts @@ -0,0 +1,23 @@ +import { get } from "https"; +import { Container } from "../../../src/container"; + +class A { + constructor() { + console.log("A"); + } +} + +class B { + constructor(public readonly a: A) { + console.log("B"); + } +} + +const container = new Container(); + +container + .register() + .register>(async (get) => { + return new B(await get()); + }) + .get(); diff --git a/tests/export-container/export-container.test.ts b/tests/export-container/export-container.test.ts new file mode 100644 index 0000000..153880e --- /dev/null +++ b/tests/export-container/export-container.test.ts @@ -0,0 +1,16 @@ +import { describe, it, expect } from "bun:test"; +import { buildAndTransform } from "../common"; + +describe("export container", () => { + it("should resolve get when export a container", async () => { + expect( + await buildAndTransform( + ["./tests/export-container/mock/1-export-container.ts"], + "./tests/export-container/test", + "1-export-container.js" + ) + ).toEqual( + '// @bun\n// src/container.ts\nclass Container {\n context;\n memo = {};\n singletonsInitialized = false;\n constructor(context = {}) {\n this.context = context;\n }\n import() {\n return this;\n }\n register(factory = () => ({})) {\n return this;\n }\n registerSingleton(factory = () => ({})) {\n return this;\n }\n async get(interfaceHash = "") {\n if (!this.singletonsInitialized) {\n this.singletonsInitialized = true;\n if (!this.context["init_singletons"]) {\n throw new Error("what-the-dep is not running correctly, please check your bunfig.toml and make sure it is preload what-the-dep plugin");\n }\n this.context["init_singletons"](this.memo, this.get.bind(this));\n }\n if (this.context[interfaceHash]) {\n return this.context[interfaceHash](this.memo, this.get.bind(this));\n }\n return {};\n }\n static __WTD_MODULE__ = true;\n}\n\n// tests/export-container/mock/1-export-container-container.ts\nclass A {\n constructor() {\n console.log("A");\n }\n}\n\nclass B {\n a;\n constructor(a) {\n this.a = a;\n console.log("B");\n }\n}\nvar container2 = new Container({\n "430e5895": async (singletons, get) => {\n {\n return new B(await get("c559563c"));\n }\n },\n c559563c: async (singletons, get) => {\n {\n return new A;\n }\n },\n init_singletons: async (singletons, get) => {\n {\n }\n }\n});\nvar App = container2.register().register();\n\n// tests/export-container/mock/1-export-container.ts\nconsole.log(App.get("430e5895"));\n' + ); + }); +}); diff --git a/tests/export-container/mock/1-export-container-container.ts b/tests/export-container/mock/1-export-container-container.ts new file mode 100644 index 0000000..f8f9c6d --- /dev/null +++ b/tests/export-container/mock/1-export-container-container.ts @@ -0,0 +1,17 @@ +import { Container } from "../../../src/container"; + +class A { + constructor() { + console.log("A"); + } +} + +export class B { + constructor(private a: A) { + console.log("B"); + } +} + +const container = new Container(); + +export const App = container.register().register(); diff --git a/tests/export-container/mock/1-export-container.ts b/tests/export-container/mock/1-export-container.ts new file mode 100644 index 0000000..53f51b2 --- /dev/null +++ b/tests/export-container/mock/1-export-container.ts @@ -0,0 +1,3 @@ +import { App, B } from "./1-export-container-container"; + +console.log(App.get());