@kithinji/pod 1.0.0
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/build.js +22 -0
- package/dist/main.js +4464 -0
- package/dist/main.js.map +7 -0
- package/dist/types/add/component/component.d.ts +5 -0
- package/dist/types/add/component/component.d.ts.map +1 -0
- package/dist/types/add/component/index.d.ts +2 -0
- package/dist/types/add/component/index.d.ts.map +1 -0
- package/dist/types/add/index.d.ts +4 -0
- package/dist/types/add/index.d.ts.map +1 -0
- package/dist/types/add/module/index.d.ts +2 -0
- package/dist/types/add/module/index.d.ts.map +1 -0
- package/dist/types/add/module/module.d.ts +3 -0
- package/dist/types/add/module/module.d.ts.map +1 -0
- package/dist/types/add/new/index.d.ts +2 -0
- package/dist/types/add/new/index.d.ts.map +1 -0
- package/dist/types/config/config.d.ts +18 -0
- package/dist/types/config/config.d.ts.map +1 -0
- package/dist/types/config/index.d.ts +2 -0
- package/dist/types/config/index.d.ts.map +1 -0
- package/dist/types/dev/index.d.ts +2 -0
- package/dist/types/dev/index.d.ts.map +1 -0
- package/dist/types/dev/project.d.ts +9 -0
- package/dist/types/dev/project.d.ts.map +1 -0
- package/dist/types/dev/server.d.ts +2 -0
- package/dist/types/dev/server.d.ts.map +1 -0
- package/dist/types/docker/docker.d.ts +2 -0
- package/dist/types/docker/docker.d.ts.map +1 -0
- package/dist/types/docker/index.d.ts +2 -0
- package/dist/types/docker/index.d.ts.map +1 -0
- package/dist/types/macros/expand_macros.d.ts +48 -0
- package/dist/types/macros/expand_macros.d.ts.map +1 -0
- package/dist/types/macros/index.d.ts +3 -0
- package/dist/types/macros/index.d.ts.map +1 -0
- package/dist/types/macros/macro_executer.d.ts +12 -0
- package/dist/types/macros/macro_executer.d.ts.map +1 -0
- package/dist/types/main.d.ts +13 -0
- package/dist/types/main.d.ts.map +1 -0
- package/dist/types/plugins/analyzers/graph.d.ts +25 -0
- package/dist/types/plugins/analyzers/graph.d.ts.map +1 -0
- package/dist/types/plugins/css/index.d.ts +7 -0
- package/dist/types/plugins/css/index.d.ts.map +1 -0
- package/dist/types/plugins/generators/generate_controller.d.ts +2 -0
- package/dist/types/plugins/generators/generate_controller.d.ts.map +1 -0
- package/dist/types/plugins/generators/generate_rsc.d.ts +2 -0
- package/dist/types/plugins/generators/generate_rsc.d.ts.map +1 -0
- package/dist/types/plugins/generators/generate_server_component.d.ts +2 -0
- package/dist/types/plugins/generators/generate_server_component.d.ts.map +1 -0
- package/dist/types/plugins/generators/tsx_server_stub.d.ts +2 -0
- package/dist/types/plugins/generators/tsx_server_stub.d.ts.map +1 -0
- package/dist/types/plugins/index.d.ts +4 -0
- package/dist/types/plugins/index.d.ts.map +1 -0
- package/dist/types/plugins/my.d.ts +10 -0
- package/dist/types/plugins/my.d.ts.map +1 -0
- package/dist/types/plugins/transformers/j2d.d.ts +11 -0
- package/dist/types/plugins/transformers/j2d.d.ts.map +1 -0
- package/dist/types/store/index.d.ts +2 -0
- package/dist/types/store/index.d.ts.map +1 -0
- package/dist/types/store/store.d.ts +14 -0
- package/dist/types/store/store.d.ts.map +1 -0
- package/dist/types/utils/cases.d.ts +4 -0
- package/dist/types/utils/cases.d.ts.map +1 -0
- package/dist/types/utils/create.d.ts +12 -0
- package/dist/types/utils/create.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +3 -0
- package/dist/types/utils/index.d.ts.map +1 -0
- package/package.json +44 -0
- package/src/add/component/component.ts +496 -0
- package/src/add/component/index.ts +1 -0
- package/src/add/index.ts +3 -0
- package/src/add/module/index.ts +1 -0
- package/src/add/module/module.ts +521 -0
- package/src/add/new/index.ts +135 -0
- package/src/config/config.ts +141 -0
- package/src/config/index.ts +1 -0
- package/src/dev/index.ts +1 -0
- package/src/dev/project.ts +45 -0
- package/src/dev/server.ts +190 -0
- package/src/docker/docker.ts +452 -0
- package/src/docker/index.ts +1 -0
- package/src/macros/expand_macros.ts +791 -0
- package/src/macros/index.ts +2 -0
- package/src/macros/macro_executer.ts +189 -0
- package/src/main.ts +95 -0
- package/src/plugins/analyzers/graph.ts +291 -0
- package/src/plugins/css/index.ts +25 -0
- package/src/plugins/generators/generate_controller.ts +308 -0
- package/src/plugins/generators/generate_rsc.ts +274 -0
- package/src/plugins/generators/generate_server_component.ts +279 -0
- package/src/plugins/generators/tsx_server_stub.ts +295 -0
- package/src/plugins/index.ts +3 -0
- package/src/plugins/my.ts +274 -0
- package/src/plugins/transformers/j2d.ts +1014 -0
- package/src/store/index.ts +1 -0
- package/src/store/store.ts +44 -0
- package/src/utils/cases.ts +15 -0
- package/src/utils/create.ts +26 -0
- package/src/utils/index.ts +2 -0
- package/tsconfig.json +27 -0
|
@@ -0,0 +1,791 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import Module from "node:module";
|
|
4
|
+
import { macroExecuter } from "./macro_executer";
|
|
5
|
+
import { Store } from "@/store";
|
|
6
|
+
|
|
7
|
+
export interface MacroContext {
|
|
8
|
+
node: ts.CallExpression;
|
|
9
|
+
sourceFile: ts.SourceFile;
|
|
10
|
+
ts: typeof ts;
|
|
11
|
+
factory: ts.NodeFactory;
|
|
12
|
+
store: Store;
|
|
13
|
+
graph: MacroDependencyGraph;
|
|
14
|
+
get program(): ts.Program;
|
|
15
|
+
get checker(): ts.TypeChecker;
|
|
16
|
+
error(msg: string): never;
|
|
17
|
+
resolveNodeValue(node: ts.Node): any;
|
|
18
|
+
resolveIdentifier(identifier: ts.Identifier): ts.Node;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface MacroNode {
|
|
22
|
+
key: string;
|
|
23
|
+
variableName: string;
|
|
24
|
+
node: ts.CallExpression;
|
|
25
|
+
sourceFile: ts.SourceFile;
|
|
26
|
+
filePath: string;
|
|
27
|
+
dependencies: Set<string>;
|
|
28
|
+
astResult?: ts.Node;
|
|
29
|
+
computed: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class MacroDependencyGraph {
|
|
33
|
+
private nodes = new Map<string, MacroNode>();
|
|
34
|
+
private projectRoot: string;
|
|
35
|
+
private typeChecker?: ts.TypeChecker;
|
|
36
|
+
|
|
37
|
+
constructor(projectRoot: string) {
|
|
38
|
+
this.projectRoot = projectRoot;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
setTypeChecker(checker: ts.TypeChecker) {
|
|
42
|
+
this.typeChecker = checker;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
createKey(sourceFile: ts.SourceFile, variableName: string): string {
|
|
46
|
+
const relativePath = path.relative(this.projectRoot, sourceFile.fileName);
|
|
47
|
+
const normalized = relativePath.replace(/\\/g, "/");
|
|
48
|
+
return `${normalized}:${variableName}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
addNode(
|
|
52
|
+
key: string,
|
|
53
|
+
variableName: string,
|
|
54
|
+
node: ts.CallExpression,
|
|
55
|
+
sourceFile: ts.SourceFile
|
|
56
|
+
) {
|
|
57
|
+
if (!this.nodes.has(key)) {
|
|
58
|
+
this.nodes.set(key, {
|
|
59
|
+
key,
|
|
60
|
+
variableName,
|
|
61
|
+
node,
|
|
62
|
+
sourceFile,
|
|
63
|
+
filePath: sourceFile.fileName,
|
|
64
|
+
dependencies: new Set(),
|
|
65
|
+
astResult: undefined,
|
|
66
|
+
computed: false,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getNode(key: string): MacroNode | undefined {
|
|
72
|
+
return this.nodes.get(key);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
addDependency(fromKey: string, toKey: string) {
|
|
76
|
+
const node = this.nodes.get(fromKey);
|
|
77
|
+
if (node) {
|
|
78
|
+
node.dependencies.add(toKey);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
setResult(key: string, astResult: ts.Node) {
|
|
83
|
+
const node = this.nodes.get(key);
|
|
84
|
+
if (node) {
|
|
85
|
+
node.computed = true;
|
|
86
|
+
node.astResult = astResult;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getResult(key: string): ts.Node | undefined {
|
|
91
|
+
return this.nodes.get(key)?.astResult;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
isComputed(key: string): boolean {
|
|
95
|
+
return this.nodes.get(key)?.computed ?? false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
topologicalSort(): string[] {
|
|
99
|
+
const visited = new Set<string>();
|
|
100
|
+
const inProgress = new Set<string>();
|
|
101
|
+
const sorted: string[] = [];
|
|
102
|
+
|
|
103
|
+
const visit = (key: string, path: string[] = []) => {
|
|
104
|
+
if (visited.has(key)) return;
|
|
105
|
+
|
|
106
|
+
if (inProgress.has(key)) {
|
|
107
|
+
const cycle = [...path, key].join(" -> ");
|
|
108
|
+
throw new Error(`Circular macro dependency detected: ${cycle}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const node = this.nodes.get(key);
|
|
112
|
+
if (!node) return;
|
|
113
|
+
|
|
114
|
+
inProgress.add(key);
|
|
115
|
+
|
|
116
|
+
for (const depKey of node.dependencies) {
|
|
117
|
+
visit(depKey, [...path, key]);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
inProgress.delete(key);
|
|
121
|
+
visited.add(key);
|
|
122
|
+
sorted.push(key);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
for (const key of this.nodes.keys()) {
|
|
126
|
+
visit(key);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return sorted;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
clear() {
|
|
133
|
+
this.nodes.clear();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
getNodesForFile(filePath: string): MacroNode[] {
|
|
137
|
+
return Array.from(this.nodes.values()).filter(
|
|
138
|
+
(node) => node.filePath === filePath
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let globalGraph: MacroDependencyGraph | null = null;
|
|
144
|
+
|
|
145
|
+
export function getGlobalMacroGraph(projectRoot: string): MacroDependencyGraph {
|
|
146
|
+
if (!globalGraph) {
|
|
147
|
+
globalGraph = new MacroDependencyGraph(projectRoot);
|
|
148
|
+
}
|
|
149
|
+
return globalGraph;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function resetGlobalMacroGraph() {
|
|
153
|
+
globalGraph = null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function resolveImportSpecifier(
|
|
157
|
+
importPath: string,
|
|
158
|
+
fromFile: string,
|
|
159
|
+
compilerOptions: ts.CompilerOptions
|
|
160
|
+
): string | undefined {
|
|
161
|
+
const resolved = ts.resolveModuleName(
|
|
162
|
+
importPath,
|
|
163
|
+
fromFile,
|
|
164
|
+
compilerOptions,
|
|
165
|
+
ts.sys
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (resolved.resolvedModule?.resolvedFileName) {
|
|
169
|
+
return resolved.resolvedModule.resolvedFileName;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const requireFromFile = Module.createRequire(fromFile);
|
|
174
|
+
return requireFromFile.resolve(importPath);
|
|
175
|
+
} catch (e) {
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function resolveImportFullPath(
|
|
181
|
+
symbolName: string,
|
|
182
|
+
sourceFile: ts.SourceFile,
|
|
183
|
+
compilerOptions: ts.CompilerOptions
|
|
184
|
+
): { importPath: string; resolvedPath: string | undefined } | undefined {
|
|
185
|
+
let importPath: string | undefined;
|
|
186
|
+
|
|
187
|
+
sourceFile.forEachChild((node) => {
|
|
188
|
+
if (!ts.isImportDeclaration(node) || !node.importClause) return;
|
|
189
|
+
|
|
190
|
+
const { namedBindings, name } = node.importClause;
|
|
191
|
+
|
|
192
|
+
if (name && name.text === symbolName) {
|
|
193
|
+
importPath = (node.moduleSpecifier as ts.StringLiteral).text;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (namedBindings && ts.isNamedImports(namedBindings)) {
|
|
197
|
+
for (const specifier of namedBindings.elements) {
|
|
198
|
+
const importedName = specifier.name.text;
|
|
199
|
+
if (importedName === symbolName) {
|
|
200
|
+
importPath = (node.moduleSpecifier as ts.StringLiteral).text;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (namedBindings && ts.isNamespaceImport(namedBindings)) {
|
|
206
|
+
if (symbolName.startsWith(namedBindings.name.text + ".")) {
|
|
207
|
+
importPath = (node.moduleSpecifier as ts.StringLiteral).text;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
if (!importPath) return undefined;
|
|
213
|
+
|
|
214
|
+
const resolvedPath = resolveImportSpecifier(
|
|
215
|
+
importPath,
|
|
216
|
+
sourceFile.fileName,
|
|
217
|
+
compilerOptions
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
importPath: importPath.startsWith(".") ? resolvedPath! : importPath,
|
|
222
|
+
resolvedPath,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isNpmPackage(importPath: string): boolean {
|
|
227
|
+
return (
|
|
228
|
+
!importPath.startsWith(".") &&
|
|
229
|
+
!importPath.startsWith("/") &&
|
|
230
|
+
!path.isAbsolute(importPath)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function findVariableDeclarationInFile(
|
|
235
|
+
variableName: string,
|
|
236
|
+
sourceFile: ts.SourceFile
|
|
237
|
+
): ts.VariableDeclaration | undefined {
|
|
238
|
+
let found: ts.VariableDeclaration | undefined;
|
|
239
|
+
|
|
240
|
+
function visit(node: ts.Node) {
|
|
241
|
+
if (found) return;
|
|
242
|
+
|
|
243
|
+
if (ts.isVariableDeclaration(node)) {
|
|
244
|
+
if (ts.isIdentifier(node.name) && node.name.text === variableName) {
|
|
245
|
+
found = node;
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
ts.forEachChild(node, visit);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
visit(sourceFile);
|
|
254
|
+
return found;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function extractValueFromNode(node: ts.Node): any | undefined {
|
|
258
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
259
|
+
return node.text;
|
|
260
|
+
}
|
|
261
|
+
if (ts.isNumericLiteral(node)) return Number(node.text);
|
|
262
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
263
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) return false;
|
|
264
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) return null;
|
|
265
|
+
if (node.kind === ts.SyntaxKind.UndefinedKeyword) return undefined;
|
|
266
|
+
|
|
267
|
+
if (ts.isTemplateExpression(node)) {
|
|
268
|
+
let result = node.head.text;
|
|
269
|
+
for (const span of node.templateSpans) {
|
|
270
|
+
const exprValue = extractValueFromNode(span.expression);
|
|
271
|
+
result += String(exprValue) + span.literal.text;
|
|
272
|
+
}
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
277
|
+
const obj: any = {};
|
|
278
|
+
for (const prop of node.properties) {
|
|
279
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
280
|
+
const key = ts.isIdentifier(prop.name)
|
|
281
|
+
? prop.name.text
|
|
282
|
+
: ts.isStringLiteral(prop.name)
|
|
283
|
+
? prop.name.text
|
|
284
|
+
: ts.isNumericLiteral(prop.name)
|
|
285
|
+
? prop.name.text
|
|
286
|
+
: ts.isComputedPropertyName(prop.name)
|
|
287
|
+
? extractValueFromNode(prop.name.expression)
|
|
288
|
+
: undefined;
|
|
289
|
+
|
|
290
|
+
if (key !== undefined) {
|
|
291
|
+
obj[key] = extractValueFromNode(prop.initializer);
|
|
292
|
+
}
|
|
293
|
+
} else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
294
|
+
obj[prop.name.text] = prop.name.text;
|
|
295
|
+
} else if (ts.isSpreadAssignment(prop)) {
|
|
296
|
+
const spread = extractValueFromNode(prop.expression);
|
|
297
|
+
if (typeof spread === "object" && spread !== null) {
|
|
298
|
+
Object.assign(obj, spread);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return obj;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
306
|
+
return node.elements
|
|
307
|
+
.map((el) => {
|
|
308
|
+
if (ts.isSpreadElement(el)) {
|
|
309
|
+
const spread = extractValueFromNode(el.expression);
|
|
310
|
+
return Array.isArray(spread) ? spread : [spread];
|
|
311
|
+
}
|
|
312
|
+
return extractValueFromNode(el);
|
|
313
|
+
})
|
|
314
|
+
.flat();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
318
|
+
const operand = extractValueFromNode(node.operand);
|
|
319
|
+
switch (node.operator) {
|
|
320
|
+
case ts.SyntaxKind.MinusToken:
|
|
321
|
+
return -operand;
|
|
322
|
+
case ts.SyntaxKind.PlusToken:
|
|
323
|
+
return +operand;
|
|
324
|
+
case ts.SyntaxKind.ExclamationToken:
|
|
325
|
+
return !operand;
|
|
326
|
+
case ts.SyntaxKind.TildeToken:
|
|
327
|
+
return ~operand;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (ts.isBinaryExpression(node)) {
|
|
332
|
+
const left = extractValueFromNode(node.left);
|
|
333
|
+
const right = extractValueFromNode(node.right);
|
|
334
|
+
|
|
335
|
+
switch (node.operatorToken.kind) {
|
|
336
|
+
case ts.SyntaxKind.PlusToken:
|
|
337
|
+
return left + right;
|
|
338
|
+
case ts.SyntaxKind.MinusToken:
|
|
339
|
+
return left - right;
|
|
340
|
+
case ts.SyntaxKind.AsteriskToken:
|
|
341
|
+
return left * right;
|
|
342
|
+
case ts.SyntaxKind.SlashToken:
|
|
343
|
+
return left / right;
|
|
344
|
+
case ts.SyntaxKind.PercentToken:
|
|
345
|
+
return left % right;
|
|
346
|
+
case ts.SyntaxKind.AsteriskAsteriskToken:
|
|
347
|
+
return left ** right;
|
|
348
|
+
case ts.SyntaxKind.EqualsEqualsToken:
|
|
349
|
+
return left == right;
|
|
350
|
+
case ts.SyntaxKind.EqualsEqualsEqualsToken:
|
|
351
|
+
return left === right;
|
|
352
|
+
case ts.SyntaxKind.ExclamationEqualsToken:
|
|
353
|
+
return left != right;
|
|
354
|
+
case ts.SyntaxKind.ExclamationEqualsEqualsToken:
|
|
355
|
+
return left !== right;
|
|
356
|
+
case ts.SyntaxKind.LessThanToken:
|
|
357
|
+
return left < right;
|
|
358
|
+
case ts.SyntaxKind.LessThanEqualsToken:
|
|
359
|
+
return left <= right;
|
|
360
|
+
case ts.SyntaxKind.GreaterThanToken:
|
|
361
|
+
return left > right;
|
|
362
|
+
case ts.SyntaxKind.GreaterThanEqualsToken:
|
|
363
|
+
return left >= right;
|
|
364
|
+
case ts.SyntaxKind.AmpersandAmpersandToken:
|
|
365
|
+
return left && right;
|
|
366
|
+
case ts.SyntaxKind.BarBarToken:
|
|
367
|
+
return left || right;
|
|
368
|
+
case ts.SyntaxKind.QuestionQuestionToken:
|
|
369
|
+
return left ?? right;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (ts.isParenthesizedExpression(node)) {
|
|
374
|
+
return extractValueFromNode(node.expression);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (ts.isConditionalExpression(node)) {
|
|
378
|
+
const condition = extractValueFromNode(node.condition);
|
|
379
|
+
return condition
|
|
380
|
+
? extractValueFromNode(node.whenTrue)
|
|
381
|
+
: extractValueFromNode(node.whenFalse);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
385
|
+
const obj = extractValueFromNode(node.expression);
|
|
386
|
+
const propName = node.name.text;
|
|
387
|
+
return obj?.[propName];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (ts.isElementAccessExpression(node)) {
|
|
391
|
+
const obj = extractValueFromNode(node.expression);
|
|
392
|
+
const index = extractValueFromNode(node.argumentExpression);
|
|
393
|
+
return obj?.[index];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function createNodeResolver(
|
|
400
|
+
graph: MacroDependencyGraph,
|
|
401
|
+
currentFileKey: string,
|
|
402
|
+
sourceFile: ts.SourceFile,
|
|
403
|
+
compilerOptions: ts.CompilerOptions
|
|
404
|
+
) {
|
|
405
|
+
const trackedDependencies: string[] = [];
|
|
406
|
+
|
|
407
|
+
function resolveIdentifierToNode(identifier: ts.Identifier): ts.Node {
|
|
408
|
+
const name = identifier.text;
|
|
409
|
+
|
|
410
|
+
// Look for local variable declaration
|
|
411
|
+
const declaration = findVariableDeclarationInFile(name, sourceFile);
|
|
412
|
+
|
|
413
|
+
if (declaration && declaration.initializer) {
|
|
414
|
+
const varStatement = declaration.parent.parent as ts.VariableStatement;
|
|
415
|
+
const isConst = varStatement.declarationList.flags & ts.NodeFlags.Const;
|
|
416
|
+
|
|
417
|
+
if (!isConst) {
|
|
418
|
+
throw new Error(
|
|
419
|
+
`Macro argument '${name}' must be a const variable. let/var are not allowed.`
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Check if it's a macro call
|
|
424
|
+
if (ts.isCallExpression(declaration.initializer)) {
|
|
425
|
+
const expr = declaration.initializer.expression;
|
|
426
|
+
|
|
427
|
+
if (ts.isIdentifier(expr) && expr.text.endsWith("$")) {
|
|
428
|
+
const depKey = graph.createKey(sourceFile, name);
|
|
429
|
+
trackedDependencies.push(depKey);
|
|
430
|
+
|
|
431
|
+
const result = graph.getResult(depKey);
|
|
432
|
+
if (result !== undefined) {
|
|
433
|
+
return result;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
throw new Error(
|
|
437
|
+
`Macro dependency '${name}' has not been computed yet. This should not happen.`
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return declaration.initializer;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Try to resolve from imports
|
|
446
|
+
const resolved = resolveImportFullPath(name, sourceFile, compilerOptions);
|
|
447
|
+
|
|
448
|
+
if (resolved) {
|
|
449
|
+
if (isNpmPackage(resolved.importPath)) {
|
|
450
|
+
throw new Error(
|
|
451
|
+
`Cannot resolve identifier '${name}' from npm package '${resolved.importPath}'. ` +
|
|
452
|
+
`Macro arguments from npm packages must be constants that can be evaluated at compile time.`
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
if (!resolved.resolvedPath) {
|
|
457
|
+
throw new Error(
|
|
458
|
+
`Could not resolve import path: ${resolved.importPath}`
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const importedSource = ts.sys.readFile(resolved.resolvedPath);
|
|
463
|
+
if (!importedSource) {
|
|
464
|
+
throw new Error(
|
|
465
|
+
`Could not read imported file: ${resolved.resolvedPath}`
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const importedSourceFile = ts.createSourceFile(
|
|
470
|
+
resolved.resolvedPath,
|
|
471
|
+
importedSource,
|
|
472
|
+
ts.ScriptTarget.Latest,
|
|
473
|
+
true
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
const importedDecl = findVariableDeclarationInFile(
|
|
477
|
+
name,
|
|
478
|
+
importedSourceFile
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
if (importedDecl && importedDecl.initializer) {
|
|
482
|
+
// Check if it's a macro call
|
|
483
|
+
if (ts.isCallExpression(importedDecl.initializer)) {
|
|
484
|
+
const expr = importedDecl.initializer.expression;
|
|
485
|
+
if (ts.isIdentifier(expr) && expr.text.endsWith("$")) {
|
|
486
|
+
const depKey = graph.createKey(importedSourceFile, name);
|
|
487
|
+
trackedDependencies.push(depKey);
|
|
488
|
+
|
|
489
|
+
const result = graph.getResult(depKey);
|
|
490
|
+
if (result !== undefined) {
|
|
491
|
+
return result;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
throw new Error(
|
|
495
|
+
`Cross-file macro dependency '${name}' from '${resolved.resolvedPath}' needs to be computed first.`
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return importedDecl.initializer;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
throw new Error(
|
|
505
|
+
`Could not resolve identifier '${name}'. Make sure it's a const variable or imported constant.`
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function resolveNodeValue(node: ts.Node): any {
|
|
510
|
+
if (ts.isIdentifier(node)) {
|
|
511
|
+
const resolvedNode = resolveIdentifierToNode(node);
|
|
512
|
+
return extractValueFromNode(resolvedNode);
|
|
513
|
+
}
|
|
514
|
+
return extractValueFromNode(node);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return {
|
|
518
|
+
resolveIdentifierToNode,
|
|
519
|
+
resolveNodeValue,
|
|
520
|
+
getTrackedDependencies: () => trackedDependencies,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
export async function expandMacros(
|
|
525
|
+
source: string,
|
|
526
|
+
filePath: string,
|
|
527
|
+
projectRoot: string = process.cwd()
|
|
528
|
+
): Promise<string> {
|
|
529
|
+
if (!source.includes("$(") && !source.includes("$`")) {
|
|
530
|
+
return source;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const compilerOptions: ts.CompilerOptions = {
|
|
534
|
+
module: ts.ModuleKind.ESNext,
|
|
535
|
+
target: ts.ScriptTarget.Latest,
|
|
536
|
+
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
const sourceFile = ts.createSourceFile(
|
|
540
|
+
filePath,
|
|
541
|
+
source,
|
|
542
|
+
ts.ScriptTarget.Latest,
|
|
543
|
+
true,
|
|
544
|
+
filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
const graph = getGlobalMacroGraph(projectRoot);
|
|
548
|
+
|
|
549
|
+
const getProgram = () =>
|
|
550
|
+
ts.createProgram([filePath], {
|
|
551
|
+
target: ts.ScriptTarget.ESNext,
|
|
552
|
+
module: ts.ModuleKind.ESNext,
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
const getTypeChecker = () => getProgram()?.getTypeChecker();
|
|
556
|
+
|
|
557
|
+
// Discover all macro calls
|
|
558
|
+
function discoverMacros(node: ts.Node) {
|
|
559
|
+
if (ts.isVariableDeclaration(node) && node.initializer) {
|
|
560
|
+
if (
|
|
561
|
+
ts.isCallExpression(node.initializer) &&
|
|
562
|
+
ts.isIdentifier(node.initializer.expression) &&
|
|
563
|
+
node.initializer.expression.text.endsWith("$")
|
|
564
|
+
) {
|
|
565
|
+
if (ts.isIdentifier(node.name)) {
|
|
566
|
+
const variableName = node.name.text;
|
|
567
|
+
const key = graph.createKey(sourceFile, variableName);
|
|
568
|
+
graph.addNode(key, variableName, node.initializer, sourceFile);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
ts.forEachChild(node, discoverMacros);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
discoverMacros(sourceFile);
|
|
576
|
+
|
|
577
|
+
// Build dependency graph
|
|
578
|
+
const fileNodes = graph.getNodesForFile(filePath);
|
|
579
|
+
|
|
580
|
+
for (const macroNode of fileNodes) {
|
|
581
|
+
if (graph.isComputed(macroNode.key)) continue;
|
|
582
|
+
|
|
583
|
+
const resolver = createNodeResolver(
|
|
584
|
+
graph,
|
|
585
|
+
macroNode.key,
|
|
586
|
+
macroNode.sourceFile,
|
|
587
|
+
compilerOptions
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
try {
|
|
591
|
+
// Walk through arguments to discover dependencies
|
|
592
|
+
for (const arg of macroNode.node.arguments) {
|
|
593
|
+
function visitForDeps(n: ts.Node) {
|
|
594
|
+
if (ts.isIdentifier(n)) {
|
|
595
|
+
try {
|
|
596
|
+
resolver.resolveIdentifierToNode(n);
|
|
597
|
+
} catch (e) {
|
|
598
|
+
// Ignore resolution errors during dependency discovery
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
ts.forEachChild(n, visitForDeps);
|
|
602
|
+
}
|
|
603
|
+
visitForDeps(arg);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
const deps = resolver.getTrackedDependencies();
|
|
607
|
+
for (const dep of deps) {
|
|
608
|
+
graph.addDependency(macroNode.key, dep);
|
|
609
|
+
}
|
|
610
|
+
} catch (e) {
|
|
611
|
+
// Ignore errors during dependency discovery
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Execute macros in topological order
|
|
616
|
+
const sortedKeys = graph.topologicalSort();
|
|
617
|
+
|
|
618
|
+
for (const key of sortedKeys) {
|
|
619
|
+
const macroNode = graph.getNode(key);
|
|
620
|
+
if (!macroNode || graph.isComputed(key)) continue;
|
|
621
|
+
|
|
622
|
+
const node = macroNode.node;
|
|
623
|
+
const name = (node.expression as ts.Identifier).text;
|
|
624
|
+
|
|
625
|
+
const resolved = resolveImportFullPath(
|
|
626
|
+
name,
|
|
627
|
+
macroNode.sourceFile,
|
|
628
|
+
compilerOptions
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
if (!resolved) {
|
|
632
|
+
throw new Error(`Could not resolve macro import for '${name}'`);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const macro = macroExecuter().getMacro(resolved.importPath, name);
|
|
636
|
+
|
|
637
|
+
if (!macro) {
|
|
638
|
+
throw new Error(`Could not get macro '${name}' for key '${key}'`);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const resolver = createNodeResolver(
|
|
642
|
+
graph,
|
|
643
|
+
key,
|
|
644
|
+
macroNode.sourceFile,
|
|
645
|
+
compilerOptions
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
const macroContext: MacroContext = {
|
|
649
|
+
node,
|
|
650
|
+
sourceFile: macroNode.sourceFile,
|
|
651
|
+
ts,
|
|
652
|
+
store: Store.getInstance(),
|
|
653
|
+
factory: ts.factory,
|
|
654
|
+
graph,
|
|
655
|
+
get program() {
|
|
656
|
+
return getProgram();
|
|
657
|
+
},
|
|
658
|
+
get checker() {
|
|
659
|
+
return getTypeChecker();
|
|
660
|
+
},
|
|
661
|
+
error: (msg: string) => {
|
|
662
|
+
throw new Error(msg);
|
|
663
|
+
},
|
|
664
|
+
resolveNodeValue: resolver.resolveNodeValue,
|
|
665
|
+
resolveIdentifier: resolver.resolveIdentifierToNode,
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
try {
|
|
669
|
+
// Pass AST nodes directly to macro
|
|
670
|
+
const result = macro(...node.arguments, macroContext);
|
|
671
|
+
|
|
672
|
+
if (!result || typeof result !== "object" || !("kind" in result)) {
|
|
673
|
+
throw new Error(`Macro '${name}' must return a TypeScript AST node`);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
graph.setResult(key, result);
|
|
677
|
+
} catch (e: any) {
|
|
678
|
+
console.error(`Macro '${name}' execution failed: ${e?.message ?? e}`);
|
|
679
|
+
throw e;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Transform the AST
|
|
684
|
+
const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => {
|
|
685
|
+
const visit = (node: ts.Node): ts.Node => {
|
|
686
|
+
if (ts.isVariableDeclaration(node) && node.initializer) {
|
|
687
|
+
if (
|
|
688
|
+
ts.isCallExpression(node.initializer) &&
|
|
689
|
+
ts.isIdentifier(node.initializer.expression) &&
|
|
690
|
+
node.initializer.expression.text.endsWith("$") &&
|
|
691
|
+
ts.isIdentifier(node.name)
|
|
692
|
+
) {
|
|
693
|
+
const key = graph.createKey(sourceFile, node.name.text);
|
|
694
|
+
const macroNode = graph.getNode(key);
|
|
695
|
+
|
|
696
|
+
if (macroNode && graph.isComputed(key)) {
|
|
697
|
+
const result = graph.getResult(key)!;
|
|
698
|
+
|
|
699
|
+
return context.factory.updateVariableDeclaration(
|
|
700
|
+
node,
|
|
701
|
+
node.name,
|
|
702
|
+
node.exclamationToken,
|
|
703
|
+
node.type,
|
|
704
|
+
result as any
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
|
|
711
|
+
const name = node.expression.text;
|
|
712
|
+
|
|
713
|
+
if (name.endsWith("$")) {
|
|
714
|
+
const resolved = resolveImportFullPath(
|
|
715
|
+
name,
|
|
716
|
+
sourceFile,
|
|
717
|
+
compilerOptions
|
|
718
|
+
);
|
|
719
|
+
|
|
720
|
+
if (resolved) {
|
|
721
|
+
const macro = macroExecuter().getMacro(resolved.importPath, name);
|
|
722
|
+
|
|
723
|
+
if (macro) {
|
|
724
|
+
const tempKey = `${graph.createKey(sourceFile, "__temp__")}:${
|
|
725
|
+
node.pos
|
|
726
|
+
}`;
|
|
727
|
+
const resolver = createNodeResolver(
|
|
728
|
+
graph,
|
|
729
|
+
tempKey,
|
|
730
|
+
sourceFile,
|
|
731
|
+
compilerOptions
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
const macroContext: MacroContext = {
|
|
735
|
+
node,
|
|
736
|
+
sourceFile,
|
|
737
|
+
ts,
|
|
738
|
+
graph,
|
|
739
|
+
store: Store.getInstance(),
|
|
740
|
+
factory: context.factory,
|
|
741
|
+
get program() {
|
|
742
|
+
return getProgram();
|
|
743
|
+
},
|
|
744
|
+
get checker() {
|
|
745
|
+
return getTypeChecker();
|
|
746
|
+
},
|
|
747
|
+
error: (msg: string) => {
|
|
748
|
+
throw new Error(msg);
|
|
749
|
+
},
|
|
750
|
+
resolveNodeValue: resolver.resolveNodeValue,
|
|
751
|
+
resolveIdentifier: resolver.resolveIdentifierToNode,
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
try {
|
|
755
|
+
// Pass AST nodes directly to macro
|
|
756
|
+
const result = macro(...node.arguments, macroContext);
|
|
757
|
+
|
|
758
|
+
if (
|
|
759
|
+
!result ||
|
|
760
|
+
typeof result !== "object" ||
|
|
761
|
+
!("kind" in result)
|
|
762
|
+
) {
|
|
763
|
+
throw new Error(
|
|
764
|
+
`Macro '${name}' must return a TypeScript AST node`
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
return result;
|
|
769
|
+
} catch (e: any) {
|
|
770
|
+
console.log(
|
|
771
|
+
`Macro '${name}' execution failed: ${e?.message ?? e}`
|
|
772
|
+
);
|
|
773
|
+
return node;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return ts.visitEachChild(node, visit, context);
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
return (sf) => ts.visitNode(sf, visit) as ts.SourceFile;
|
|
784
|
+
};
|
|
785
|
+
|
|
786
|
+
const result = ts.transform(sourceFile, [transformer]);
|
|
787
|
+
const output = ts.createPrinter().printFile(result.transformed[0]);
|
|
788
|
+
result.dispose();
|
|
789
|
+
|
|
790
|
+
return output;
|
|
791
|
+
}
|