@djodjonx/neosyringe-plugin 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # @djodjonx/neosyringe-plugin
2
+
3
+ The build plugin for NeoSyringe. Supports Vite, Rollup, Webpack, esbuild.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -D @djodjonx/neosyringe-plugin
9
+ ```
10
+
11
+ See [Documentation](https://djodjonx.github.io/neosyringe/guide/getting-started).
package/dist/index.cjs ADDED
@@ -0,0 +1,163 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) {
13
+ __defProp(to, key, {
14
+ get: ((k) => from[k]).bind(null, key),
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ });
17
+ }
18
+ }
19
+ }
20
+ return to;
21
+ };
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
23
+ value: mod,
24
+ enumerable: true
25
+ }) : target, mod));
26
+
27
+ //#endregion
28
+ let unplugin = require("unplugin");
29
+ let typescript = require("typescript");
30
+ typescript = __toESM(typescript);
31
+ let _djodjonx_neosyringe_core_analyzer = require("@djodjonx/neosyringe-core/analyzer");
32
+ let _djodjonx_neosyringe_core_generator = require("@djodjonx/neosyringe-core/generator");
33
+
34
+ //#region src/index.ts
35
+ /**
36
+ * Registry of tokens that are registered in container files.
37
+ * Used to validate useInterface<T>() calls in other files.
38
+ */
39
+ const registeredTokens = /* @__PURE__ */ new Set();
40
+ /**
41
+ * Registry of tokens that are used (resolved) in non-container files.
42
+ * Used to validate that all used tokens are registered.
43
+ */
44
+ const usedTokens = /* @__PURE__ */ new Map();
45
+ /**
46
+ * Transforms useInterface<T>() calls into their tokenId string values.
47
+ * This runs on ALL TypeScript files, not just container files.
48
+ *
49
+ * @param excludeRange - Optional range to exclude from transformation (used for defineBuilderConfig content)
50
+ * @param collectUsedTokens - If true, collects used tokens for later validation
51
+ */
52
+ function transformUseInterfaceCalls(code, id, compilerOptions, excludeRange, collectUsedTokens = false) {
53
+ if (!code.includes("useInterface")) return null;
54
+ const sourceFile = typescript.createSourceFile(id, code, typescript.ScriptTarget.Latest, true);
55
+ const compilerHost = typescript.createCompilerHost(compilerOptions);
56
+ const originalGetSourceFile = compilerHost.getSourceFile;
57
+ compilerHost.getSourceFile = (fileName, languageVersion) => {
58
+ if (fileName === id) return sourceFile;
59
+ return originalGetSourceFile(fileName, languageVersion);
60
+ };
61
+ const checker = typescript.createProgram([id], compilerOptions, compilerHost).getTypeChecker();
62
+ const replacements = [];
63
+ function visit(node) {
64
+ if (typescript.isCallExpression(node) && typescript.isIdentifier(node.expression) && node.expression.text === "useInterface" && node.typeArguments && node.typeArguments.length > 0) {
65
+ const nodeStart = node.getStart();
66
+ const nodeEnd = node.getEnd();
67
+ if (excludeRange && nodeStart >= excludeRange.start && nodeEnd <= excludeRange.end) {
68
+ typescript.forEachChild(node, visit);
69
+ return;
70
+ }
71
+ const typeArg = node.typeArguments[0];
72
+ const symbol = checker.getTypeFromTypeNode(typeArg).getSymbol();
73
+ if (symbol) {
74
+ const declarations = symbol.getDeclarations();
75
+ let tokenId;
76
+ if (declarations && declarations.length > 0) tokenId = (0, _djodjonx_neosyringe_core_analyzer.generateTokenId)(symbol, declarations[0].getSourceFile());
77
+ else tokenId = symbol.getName();
78
+ replacements.push({
79
+ start: nodeStart,
80
+ end: nodeEnd,
81
+ text: `"${tokenId}"`
82
+ });
83
+ if (collectUsedTokens && !usedTokens.has(tokenId)) {
84
+ const line = sourceFile.getLineAndCharacterOfPosition(nodeStart);
85
+ usedTokens.set(tokenId, {
86
+ file: id,
87
+ line: line.line + 1,
88
+ column: line.character + 1,
89
+ interfaceName: symbol.getName()
90
+ });
91
+ }
92
+ }
93
+ }
94
+ typescript.forEachChild(node, visit);
95
+ }
96
+ visit(sourceFile);
97
+ if (replacements.length === 0) return null;
98
+ let result = code;
99
+ for (const r of replacements.sort((a, b) => b.start - a.start)) result = result.slice(0, r.start) + r.text + result.slice(r.end);
100
+ return result;
101
+ }
102
+ /**
103
+ * NeoSyringe build plugin for Vite, Rollup, Webpack, and other bundlers.
104
+ *
105
+ * Two transformations:
106
+ * 1. Replaces `useInterface<T>()` calls with their tokenId strings (all files)
107
+ * 2. Replaces `defineBuilderConfig` with generated container code (container files)
108
+ *
109
+ * @example
110
+ * ```typescript
111
+ * // vite.config.ts
112
+ * import { neoSyringePlugin } from '@djodjonx/neosyringe/plugin';
113
+ *
114
+ * export default defineConfig({
115
+ * plugins: [neoSyringePlugin.vite()]
116
+ * });
117
+ * ```
118
+ */
119
+ const neoSyringePlugin = (0, unplugin.createUnplugin)(() => {
120
+ return {
121
+ name: "neosyringe-plugin",
122
+ enforce: "pre",
123
+ transformInclude(id) {
124
+ return id.endsWith(".ts") || id.endsWith(".tsx");
125
+ },
126
+ transform(code, id) {
127
+ const configFile = typescript.findConfigFile(process.cwd(), typescript.sys.fileExists, "tsconfig.json");
128
+ if (!configFile) return;
129
+ const { config } = typescript.readConfigFile(configFile, typescript.sys.readFile);
130
+ const { options: compilerOptions } = typescript.parseJsonConfigFileContent(config, typescript.sys, process.cwd());
131
+ if (code.includes("defineBuilderConfig")) {
132
+ const graph = new _djodjonx_neosyringe_core_analyzer.Analyzer(typescript.createProgram([id], compilerOptions)).extract();
133
+ if (graph.nodes.size > 0 && graph.defineBuilderConfigStart !== void 0 && graph.defineBuilderConfigEnd !== void 0 && graph.variableStatementStart !== void 0) {
134
+ for (const tokenId of graph.nodes.keys()) registeredTokens.add(tokenId);
135
+ new _djodjonx_neosyringe_core_generator.GraphValidator().validate(graph);
136
+ const generator = new _djodjonx_neosyringe_core_generator.Generator(graph, true);
137
+ const containerClass = generator.generate();
138
+ const instantiation = generator.generateInstantiation();
139
+ const codeBeforeStatement = code.slice(0, graph.variableStatementStart);
140
+ const codeAfterDefineBuilder = code.slice(graph.defineBuilderConfigEnd);
141
+ const variableDeclaration = code.slice(graph.variableStatementStart, graph.defineBuilderConfigStart);
142
+ const codeWithContainer = codeBeforeStatement + containerClass + "\n" + variableDeclaration + instantiation + codeAfterDefineBuilder;
143
+ return transformUseInterfaceCalls(codeWithContainer, id, compilerOptions) || codeWithContainer;
144
+ }
145
+ }
146
+ return transformUseInterfaceCalls(code, id, compilerOptions, void 0, true);
147
+ },
148
+ buildEnd() {
149
+ const errors = [];
150
+ for (const [tokenId, usage] of usedTokens) if (!registeredTokens.has(tokenId)) errors.push(`[NeoSyringe] Unregistered token: useInterface<${usage.interfaceName}>() at ${usage.file}:${usage.line}:${usage.column}\n Token "${tokenId}" is not registered in any container.\n Add it to your defineBuilderConfig injections.`);
151
+ if (errors.length > 0) {
152
+ registeredTokens.clear();
153
+ usedTokens.clear();
154
+ throw new Error(`\n${"=".repeat(60)}\nNEO-SYRINGE: ${errors.length} unregistered token(s) found!\n${"=".repeat(60)}\n\n` + errors.join("\n\n") + `\n\n${"=".repeat(60)}\n`);
155
+ }
156
+ registeredTokens.clear();
157
+ usedTokens.clear();
158
+ }
159
+ };
160
+ });
161
+
162
+ //#endregion
163
+ exports.neoSyringePlugin = neoSyringePlugin;
@@ -0,0 +1,23 @@
1
+ import * as unplugin0 from "unplugin";
2
+
3
+ //#region src/index.d.ts
4
+ /**
5
+ * NeoSyringe build plugin for Vite, Rollup, Webpack, and other bundlers.
6
+ *
7
+ * Two transformations:
8
+ * 1. Replaces `useInterface<T>()` calls with their tokenId strings (all files)
9
+ * 2. Replaces `defineBuilderConfig` with generated container code (container files)
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * // vite.config.ts
14
+ * import { neoSyringePlugin } from '@djodjonx/neosyringe/plugin';
15
+ *
16
+ * export default defineConfig({
17
+ * plugins: [neoSyringePlugin.vite()]
18
+ * });
19
+ * ```
20
+ */
21
+ declare const neoSyringePlugin: unplugin0.UnpluginInstance<unknown, boolean>;
22
+ //#endregion
23
+ export { neoSyringePlugin };
@@ -0,0 +1,23 @@
1
+ import * as unplugin0 from "unplugin";
2
+
3
+ //#region src/index.d.ts
4
+ /**
5
+ * NeoSyringe build plugin for Vite, Rollup, Webpack, and other bundlers.
6
+ *
7
+ * Two transformations:
8
+ * 1. Replaces `useInterface<T>()` calls with their tokenId strings (all files)
9
+ * 2. Replaces `defineBuilderConfig` with generated container code (container files)
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * // vite.config.ts
14
+ * import { neoSyringePlugin } from '@djodjonx/neosyringe/plugin';
15
+ *
16
+ * export default defineConfig({
17
+ * plugins: [neoSyringePlugin.vite()]
18
+ * });
19
+ * ```
20
+ */
21
+ declare const neoSyringePlugin: unplugin0.UnpluginInstance<unknown, boolean>;
22
+ //#endregion
23
+ export { neoSyringePlugin };
package/dist/index.mjs ADDED
@@ -0,0 +1,135 @@
1
+ import { createUnplugin } from "unplugin";
2
+ import * as ts from "typescript";
3
+ import { Analyzer, generateTokenId } from "@djodjonx/neosyringe-core/analyzer";
4
+ import { Generator, GraphValidator } from "@djodjonx/neosyringe-core/generator";
5
+
6
+ //#region src/index.ts
7
+ /**
8
+ * Registry of tokens that are registered in container files.
9
+ * Used to validate useInterface<T>() calls in other files.
10
+ */
11
+ const registeredTokens = /* @__PURE__ */ new Set();
12
+ /**
13
+ * Registry of tokens that are used (resolved) in non-container files.
14
+ * Used to validate that all used tokens are registered.
15
+ */
16
+ const usedTokens = /* @__PURE__ */ new Map();
17
+ /**
18
+ * Transforms useInterface<T>() calls into their tokenId string values.
19
+ * This runs on ALL TypeScript files, not just container files.
20
+ *
21
+ * @param excludeRange - Optional range to exclude from transformation (used for defineBuilderConfig content)
22
+ * @param collectUsedTokens - If true, collects used tokens for later validation
23
+ */
24
+ function transformUseInterfaceCalls(code, id, compilerOptions, excludeRange, collectUsedTokens = false) {
25
+ if (!code.includes("useInterface")) return null;
26
+ const sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true);
27
+ const compilerHost = ts.createCompilerHost(compilerOptions);
28
+ const originalGetSourceFile = compilerHost.getSourceFile;
29
+ compilerHost.getSourceFile = (fileName, languageVersion) => {
30
+ if (fileName === id) return sourceFile;
31
+ return originalGetSourceFile(fileName, languageVersion);
32
+ };
33
+ const checker = ts.createProgram([id], compilerOptions, compilerHost).getTypeChecker();
34
+ const replacements = [];
35
+ function visit(node) {
36
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "useInterface" && node.typeArguments && node.typeArguments.length > 0) {
37
+ const nodeStart = node.getStart();
38
+ const nodeEnd = node.getEnd();
39
+ if (excludeRange && nodeStart >= excludeRange.start && nodeEnd <= excludeRange.end) {
40
+ ts.forEachChild(node, visit);
41
+ return;
42
+ }
43
+ const typeArg = node.typeArguments[0];
44
+ const symbol = checker.getTypeFromTypeNode(typeArg).getSymbol();
45
+ if (symbol) {
46
+ const declarations = symbol.getDeclarations();
47
+ let tokenId;
48
+ if (declarations && declarations.length > 0) tokenId = generateTokenId(symbol, declarations[0].getSourceFile());
49
+ else tokenId = symbol.getName();
50
+ replacements.push({
51
+ start: nodeStart,
52
+ end: nodeEnd,
53
+ text: `"${tokenId}"`
54
+ });
55
+ if (collectUsedTokens && !usedTokens.has(tokenId)) {
56
+ const line = sourceFile.getLineAndCharacterOfPosition(nodeStart);
57
+ usedTokens.set(tokenId, {
58
+ file: id,
59
+ line: line.line + 1,
60
+ column: line.character + 1,
61
+ interfaceName: symbol.getName()
62
+ });
63
+ }
64
+ }
65
+ }
66
+ ts.forEachChild(node, visit);
67
+ }
68
+ visit(sourceFile);
69
+ if (replacements.length === 0) return null;
70
+ let result = code;
71
+ for (const r of replacements.sort((a, b) => b.start - a.start)) result = result.slice(0, r.start) + r.text + result.slice(r.end);
72
+ return result;
73
+ }
74
+ /**
75
+ * NeoSyringe build plugin for Vite, Rollup, Webpack, and other bundlers.
76
+ *
77
+ * Two transformations:
78
+ * 1. Replaces `useInterface<T>()` calls with their tokenId strings (all files)
79
+ * 2. Replaces `defineBuilderConfig` with generated container code (container files)
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * // vite.config.ts
84
+ * import { neoSyringePlugin } from '@djodjonx/neosyringe/plugin';
85
+ *
86
+ * export default defineConfig({
87
+ * plugins: [neoSyringePlugin.vite()]
88
+ * });
89
+ * ```
90
+ */
91
+ const neoSyringePlugin = createUnplugin(() => {
92
+ return {
93
+ name: "neosyringe-plugin",
94
+ enforce: "pre",
95
+ transformInclude(id) {
96
+ return id.endsWith(".ts") || id.endsWith(".tsx");
97
+ },
98
+ transform(code, id) {
99
+ const configFile = ts.findConfigFile(process.cwd(), ts.sys.fileExists, "tsconfig.json");
100
+ if (!configFile) return;
101
+ const { config } = ts.readConfigFile(configFile, ts.sys.readFile);
102
+ const { options: compilerOptions } = ts.parseJsonConfigFileContent(config, ts.sys, process.cwd());
103
+ if (code.includes("defineBuilderConfig")) {
104
+ const graph = new Analyzer(ts.createProgram([id], compilerOptions)).extract();
105
+ if (graph.nodes.size > 0 && graph.defineBuilderConfigStart !== void 0 && graph.defineBuilderConfigEnd !== void 0 && graph.variableStatementStart !== void 0) {
106
+ for (const tokenId of graph.nodes.keys()) registeredTokens.add(tokenId);
107
+ new GraphValidator().validate(graph);
108
+ const generator = new Generator(graph, true);
109
+ const containerClass = generator.generate();
110
+ const instantiation = generator.generateInstantiation();
111
+ const codeBeforeStatement = code.slice(0, graph.variableStatementStart);
112
+ const codeAfterDefineBuilder = code.slice(graph.defineBuilderConfigEnd);
113
+ const variableDeclaration = code.slice(graph.variableStatementStart, graph.defineBuilderConfigStart);
114
+ const codeWithContainer = codeBeforeStatement + containerClass + "\n" + variableDeclaration + instantiation + codeAfterDefineBuilder;
115
+ return transformUseInterfaceCalls(codeWithContainer, id, compilerOptions) || codeWithContainer;
116
+ }
117
+ }
118
+ return transformUseInterfaceCalls(code, id, compilerOptions, void 0, true);
119
+ },
120
+ buildEnd() {
121
+ const errors = [];
122
+ for (const [tokenId, usage] of usedTokens) if (!registeredTokens.has(tokenId)) errors.push(`[NeoSyringe] Unregistered token: useInterface<${usage.interfaceName}>() at ${usage.file}:${usage.line}:${usage.column}\n Token "${tokenId}" is not registered in any container.\n Add it to your defineBuilderConfig injections.`);
123
+ if (errors.length > 0) {
124
+ registeredTokens.clear();
125
+ usedTokens.clear();
126
+ throw new Error(`\n${"=".repeat(60)}\nNEO-SYRINGE: ${errors.length} unregistered token(s) found!\n${"=".repeat(60)}\n\n` + errors.join("\n\n") + `\n\n${"=".repeat(60)}\n`);
127
+ }
128
+ registeredTokens.clear();
129
+ usedTokens.clear();
130
+ }
131
+ };
132
+ });
133
+
134
+ //#endregion
135
+ export { neoSyringePlugin };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@djodjonx/neosyringe-plugin",
3
+ "version": "0.0.1",
4
+ "description": "NeoSyringe build plugin (Vite, Rollup, Webpack)",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.mts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.mts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "dependencies": {
23
+ "unplugin": "^2.0.0",
24
+ "@djodjonx/neosyringe-core": "0.1.0"
25
+ },
26
+ "peerDependencies": {
27
+ "typescript": ">=5.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "tsdown": "0.20.0-beta.3",
31
+ "typescript": "^5.9.3",
32
+ "vitest": "^4.0.17"
33
+ },
34
+ "scripts": {
35
+ "build": "tsdown",
36
+ "test": "vitest run"
37
+ }
38
+ }