@pikku/inspector 0.6.3 → 0.7.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/dist/utils.js CHANGED
@@ -1,248 +1,670 @@
1
1
  import * as ts from 'typescript';
2
- export const extractTypeKeys = (type) => {
3
- return type.getProperties().map((symbol) => symbol.getName());
4
- };
5
- export const nullifyTypes = (type) => {
6
- if (type === 'void' ||
7
- type === 'undefined' ||
8
- type === 'unknown' ||
9
- type === 'any') {
10
- return null;
11
- }
12
- return type;
13
- };
14
- const isValidVariableName = (name) => {
15
- const regex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
16
- return regex.test(name);
17
- };
18
- export const getNamesAndTypes = (checker, typesMap, direction, funcName, type) => {
19
- const result = {
20
- names: new Set(),
21
- types: [],
22
- };
23
- const { names, types } = resolveUnionTypes(checker, type);
24
- const firstName = names[0];
25
- if (names.length > 1 || (firstName && !isValidVariableName(firstName))) {
26
- const aliasType = names.join(' | ');
27
- const aliasName = `${funcName.charAt(0).toUpperCase()}${funcName.slice(1)}${direction}`;
28
- result.names = new Set([aliasName]);
29
- result.types = types;
30
- const references = types
31
- .map((t) => resolveTypeImports(t, typesMap, true))
32
- .flat();
33
- typesMap.addCustomType(aliasName, aliasType, references);
2
+ /**
3
+ * Generate a deterministic "anonymous" name for any expression node,
4
+ * but if it's an Identifier pointing to a function, resolve it back
5
+ * to the function's declaration (so you get the true source location).
6
+ */
7
+ export function makeDeterministicAnonName(start, checker) {
8
+ let node = start;
9
+ let target = start;
10
+ // Handle the case where we're starting with an identifier directly
11
+ if (ts.isIdentifier(node)) {
12
+ const sym = checker.getSymbolAtLocation(node);
13
+ if (sym) {
14
+ let resolvedSym = sym;
15
+ if (resolvedSym.flags & ts.SymbolFlags.Alias) {
16
+ resolvedSym = checker.getAliasedSymbol(resolvedSym) ?? resolvedSym;
17
+ }
18
+ const decls = resolvedSym.declarations ?? [];
19
+ if (decls.length > 0) {
20
+ // Start with the declaration, not the reference
21
+ const decl = decls[0];
22
+ // If it's a variable declaration with a function initializer, use the function directly
23
+ if (ts.isVariableDeclaration(decl) &&
24
+ decl.initializer &&
25
+ (ts.isFunctionExpression(decl.initializer) ||
26
+ ts.isArrowFunction(decl.initializer))) {
27
+ target = decl.initializer;
28
+ // Return early - we found the function directly
29
+ const sf = target.getSourceFile();
30
+ const file = sf.fileName.replace(/[^A-Za-z0-9_]/g, '_');
31
+ const { line, character } = ts.getLineAndCharacterOfPosition(sf, target.getStart());
32
+ return `pikkuFn_${file}_L${line + 1}C${character + 1}`;
33
+ }
34
+ // Otherwise continue resolution with the declaration
35
+ node = decl;
36
+ target = decl;
37
+ }
38
+ }
34
39
  }
35
- else {
36
- const uniqueNames = names
37
- .map((name, i) => {
38
- const type = types[i];
39
- if (!type) {
40
- throw new Error('TODO: Expected a type here to match name');
41
- }
42
- if (isPrimitiveType(type)) {
43
- return name;
44
- }
45
- return resolveTypeImports(type, typesMap, false);
46
- })
47
- .flat();
48
- result.names = new Set(uniqueNames);
49
- result.types = types;
50
- }
51
- return {
52
- names: Array.from(result.names),
53
- types: result.types,
54
- };
55
- };
56
- export const isPrimitiveType = (type) => {
57
- const primitiveFlags = ts.TypeFlags.Number |
58
- ts.TypeFlags.String |
59
- ts.TypeFlags.Boolean |
60
- ts.TypeFlags.BigInt |
61
- ts.TypeFlags.ESSymbol |
62
- ts.TypeFlags.Void |
63
- ts.TypeFlags.Undefined |
64
- ts.TypeFlags.Null |
65
- ts.TypeFlags.Any |
66
- ts.TypeFlags.Unknown;
67
- return (type.flags & primitiveFlags) !== 0;
68
- };
69
- export const resolveUnionTypes = (checker, type) => {
70
- const types = [];
71
- const names = [];
72
- // Check if it's a union type AND not part of an intersection
73
- if (type.isUnion() && !(type.flags & ts.TypeFlags.Intersection)) {
74
- for (const t of type.types) {
75
- const name = nullifyTypes(checker.typeToString(t));
76
- if (name) {
77
- types.push(t);
78
- names.push(name);
40
+ // In an object literal property value, first try to resolve the identifier
41
+ if (ts.isPropertyAssignment(node.parent) &&
42
+ node === node.parent.initializer &&
43
+ ts.isIdentifier(node)) {
44
+ const sym = checker.getSymbolAtLocation(node);
45
+ if (sym) {
46
+ // Process the symbol to find the real declaration
47
+ let resolvedSym = sym;
48
+ if (resolvedSym.flags & ts.SymbolFlags.Alias) {
49
+ resolvedSym = checker.getAliasedSymbol(resolvedSym) ?? resolvedSym;
50
+ }
51
+ const decls = resolvedSym.declarations ?? [];
52
+ if (decls.length > 0) {
53
+ // Found a declaration - use it as our new target
54
+ const decl = decls[0];
55
+ if (!decl) {
56
+ throw new Error('No declaration found');
57
+ }
58
+ // If it's a variable declaration with an initializer function, use that
59
+ if (ts.isVariableDeclaration(decl) && decl.initializer) {
60
+ if (ts.isFunctionExpression(decl.initializer) ||
61
+ ts.isArrowFunction(decl.initializer)) {
62
+ target = decl.initializer;
63
+ // Return early - we found the function directly
64
+ const sf = target.getSourceFile();
65
+ const file = sf.fileName.replace(/[^A-Za-z0-9_]/g, '_');
66
+ const { line, character } = ts.getLineAndCharacterOfPosition(sf, target.getStart());
67
+ return `pikkuFn_${file}_L${line + 1}C${character + 1}`;
68
+ }
69
+ }
70
+ else if (ts.isFunctionDeclaration(decl)) {
71
+ // Already a function declaration
72
+ target = decl;
73
+ // Return early
74
+ const sf = target.getSourceFile();
75
+ const file = sf.fileName.replace(/[^A-Za-z0-9_]/g, '_');
76
+ const { line, character } = ts.getLineAndCharacterOfPosition(sf, target.getStart());
77
+ return `pikkuFn_${file}_L${line + 1}C${character + 1}`;
78
+ }
79
+ // If we didn't return early, continue with this declaration
80
+ node = decl;
81
+ target = decl;
79
82
  }
80
83
  }
81
84
  }
82
- else {
83
- const name = nullifyTypes(checker.typeToString(type));
84
- if (name) {
85
- types.push(type);
86
- names.push(name);
85
+ const seen = new Set();
86
+ for (let depth = 0; depth < 10; depth++) {
87
+ if (!ts.isIdentifier(node) || seen.has(node))
88
+ break;
89
+ seen.add(node);
90
+ let sym = checker.getSymbolAtLocation(node);
91
+ if (!sym)
92
+ break;
93
+ if (sym.flags & ts.SymbolFlags.Alias) {
94
+ sym = checker.getAliasedSymbol(sym) ?? sym;
87
95
  }
96
+ const allDecls = sym.declarations ?? [];
97
+ // prefer real .ts/.tsx implementation files
98
+ const implDecls = allDecls.filter((d) => !d.getSourceFile().isDeclarationFile);
99
+ const decls = implDecls.length ? implDecls : allDecls;
100
+ let didResolve = false;
101
+ for (const decl of decls) {
102
+ // 1) direct function foo() {} or function-expression
103
+ if (ts.isFunctionDeclaration(decl) ||
104
+ ts.isFunctionExpression(decl) ||
105
+ ts.isArrowFunction(decl)) {
106
+ target = decl;
107
+ didResolve = true;
108
+ break;
109
+ }
110
+ // 2) const foo = () => {} or foo = function() {}
111
+ if (ts.isVariableDeclaration(decl) && decl.initializer) {
112
+ const init = decl.initializer;
113
+ if (ts.isFunctionExpression(init) || ts.isArrowFunction(init)) {
114
+ target = init;
115
+ didResolve = true;
116
+ break;
117
+ }
118
+ // 2b) const foo = bar; (follow the next identifier)
119
+ if (ts.isIdentifier(init)) {
120
+ node = init;
121
+ target = init;
122
+ didResolve = true;
123
+ break;
124
+ }
125
+ }
126
+ // 3) Handle shorthand property assignments: { foo } (equivalent to { foo: foo })
127
+ if (ts.isShorthandPropertyAssignment(decl)) {
128
+ // Get the symbol for the shorthand property
129
+ const shorthandSym = checker.getShorthandAssignmentValueSymbol(decl);
130
+ if (shorthandSym &&
131
+ shorthandSym.declarations &&
132
+ shorthandSym.declarations.length > 0) {
133
+ // Use the first declaration as our new target
134
+ const shorthandDecl = shorthandSym.declarations[0];
135
+ target = shorthandDecl;
136
+ if (!shorthandDecl) {
137
+ throw new Error('No shorthand declaration found');
138
+ }
139
+ // Check the type of declaration and extract the appropriate identifier to continue resolving
140
+ if (ts.isVariableDeclaration(shorthandDecl) &&
141
+ ts.isIdentifier(shorthandDecl.name)) {
142
+ node = shorthandDecl.name;
143
+ didResolve = true;
144
+ break;
145
+ }
146
+ else if (ts.isFunctionDeclaration(shorthandDecl) &&
147
+ shorthandDecl.name &&
148
+ ts.isIdentifier(shorthandDecl.name)) {
149
+ node = shorthandDecl.name;
150
+ didResolve = true;
151
+ break;
152
+ }
153
+ else if (ts.isParameter(shorthandDecl) &&
154
+ ts.isIdentifier(shorthandDecl.name)) {
155
+ node = shorthandDecl.name;
156
+ didResolve = true;
157
+ break;
158
+ }
159
+ else if (ts.isPropertyDeclaration(shorthandDecl) &&
160
+ ts.isIdentifier(shorthandDecl.name)) {
161
+ node = shorthandDecl.name;
162
+ didResolve = true;
163
+ break;
164
+ }
165
+ else if (ts.isMethodDeclaration(shorthandDecl) &&
166
+ ts.isIdentifier(shorthandDecl.name)) {
167
+ node = shorthandDecl.name;
168
+ didResolve = true;
169
+ break;
170
+ }
171
+ }
172
+ }
173
+ // 4) Handle method declarations in classes/objects
174
+ if (ts.isMethodDeclaration(decl)) {
175
+ target = decl;
176
+ didResolve = true;
177
+ break;
178
+ }
179
+ // you can add more cases here if your setup uses imports, etc.
180
+ }
181
+ if (!didResolve)
182
+ break;
88
183
  }
89
- return { types, names };
90
- };
91
- export const resolveTypeImports = (type, resolvedTypes, isCustom) => {
92
- const types = [];
93
- const visitType = (currentType) => {
94
- const symbol = currentType.aliasSymbol || currentType.getSymbol();
95
- if (symbol) {
96
- const declarations = symbol.getDeclarations();
97
- const declaration = declarations?.[0];
98
- if (declaration) {
99
- const sourceFile = declaration.getSourceFile();
100
- const path = sourceFile.fileName;
101
- // Skip built-in utility types or TypeScript lib types
102
- if (!path.includes('node_modules/typescript') &&
103
- symbol.getName() !== '__type' &&
104
- !isPrimitiveType(currentType)) {
105
- const originalName = symbol.getName();
106
- // Check if the type is already in the map
107
- let uniqueName = resolvedTypes.exists(originalName, path);
108
- if (!uniqueName) {
109
- if (isCustom) {
110
- uniqueName = resolvedTypes.addUniqueType(originalName, path);
111
- }
112
- else {
113
- resolvedTypes.addType(originalName, path);
114
- uniqueName = originalName;
184
+ const sf = target.getSourceFile();
185
+ const file = sf.fileName.replace(/[^A-Za-z0-9_]/g, '_');
186
+ const { line, character } = ts.getLineAndCharacterOfPosition(sf, target.getStart());
187
+ return `pikkuFn_${file}_L${line + 1}C${character + 1}`;
188
+ }
189
+ /**
190
+ * Updated function to extract and prioritize function names correctly
191
+ * This function follows the priority:
192
+ * 1. Object with a name property
193
+ * 2. Exported name
194
+ * 3. Fallback to deterministic name
195
+ */
196
+ export function extractFunctionName(callExpr, checker) {
197
+ const parent = callExpr.parent;
198
+ // Initialize the result
199
+ const result = {
200
+ pikkuFuncName: '', // Will be populated later
201
+ name: '', // This will hold our "best" name based on priority
202
+ exportedName: null,
203
+ functionName: null,
204
+ propertyName: null,
205
+ };
206
+ // Special case for addHTTPRoute: if this is an identifier within an object literal,
207
+ // it might be coming from the HTTP route handling flow
208
+ if (ts.isIdentifier(callExpr) &&
209
+ callExpr.parent &&
210
+ ts.isPropertyAssignment(callExpr.parent)) {
211
+ // Try to handle the special case for HTTP route functions
212
+ const sym = checker.getSymbolAtLocation(callExpr);
213
+ if (sym) {
214
+ let resolvedSym = sym;
215
+ if (resolvedSym.flags & ts.SymbolFlags.Alias) {
216
+ resolvedSym = checker.getAliasedSymbol(resolvedSym) ?? resolvedSym;
217
+ }
218
+ const decls = resolvedSym.declarations ?? [];
219
+ if (decls.length > 0) {
220
+ const decl = decls[0];
221
+ // Check if the declaration is a variable that uses pikkuSessionlessFunc
222
+ if (ts.isVariableDeclaration(decl) && decl.initializer) {
223
+ if (ts.isCallExpression(decl.initializer) &&
224
+ ts.isIdentifier(decl.initializer.expression) &&
225
+ decl.initializer.expression.text.startsWith('pikku')) {
226
+ const args = decl.initializer.arguments;
227
+ const firstArg = args[0];
228
+ if (firstArg &&
229
+ (ts.isArrowFunction(firstArg) ||
230
+ ts.isFunctionExpression(firstArg))) {
231
+ // Use the function directly for position calculation
232
+ result.pikkuFuncName = makeDeterministicAnonName(firstArg, checker);
233
+ // Continue with name extraction
234
+ if (ts.isIdentifier(parent.name)) {
235
+ result.propertyName = parent.name.text;
236
+ }
237
+ // Check if the variable is exported
238
+ if (ts.isVariableDeclaration(decl) &&
239
+ isNamedExport(decl) &&
240
+ ts.isIdentifier(decl.name)) {
241
+ result.exportedName = decl.name.text;
242
+ }
243
+ else if (ts.isIdentifier(decl.name)) {
244
+ // If not exported, still capture the variable name
245
+ result.functionName = decl.name.text;
246
+ }
247
+ // Apply name priority logic
248
+ populateNameByPriority(result);
249
+ return result;
115
250
  }
116
251
  }
117
- types.push(uniqueName);
118
252
  }
119
253
  }
120
254
  }
121
- if (isCustom) {
122
- // Handle nested utility types like Partial, Pick, etc.
123
- if (currentType.aliasTypeArguments) {
124
- currentType.aliasTypeArguments.forEach(visitType);
125
- }
126
- // Handle intersections and unions
127
- if (currentType.isUnionOrIntersection()) {
128
- currentType.types.forEach(visitType);
255
+ }
256
+ // First, figure out what function we're really dealing with
257
+ let mainFunc = callExpr;
258
+ let originalCallExpr = callExpr; // Keep track of the original call expression for name extraction
259
+ // For direct pikku function calls where callExpr is the call expression itself
260
+ if (ts.isCallExpression(callExpr)) {
261
+ const { expression, arguments: args } = callExpr;
262
+ // Check if this is a pikku function call (pikkuFunc, pikkuSessionlessFunc, etc)
263
+ if (ts.isIdentifier(expression) && expression.text.startsWith('pikku')) {
264
+ // Check for object with 'name' property in first argument
265
+ const firstArg = args[0];
266
+ if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
267
+ for (const prop of firstArg.properties) {
268
+ if (ts.isPropertyAssignment(prop) &&
269
+ ts.isIdentifier(prop.name) &&
270
+ prop.name.text === 'name' &&
271
+ ts.isStringLiteral(prop.initializer)) {
272
+ // Priority 1: Object with name property
273
+ result.functionName = prop.initializer.text;
274
+ break;
275
+ }
276
+ }
129
277
  }
130
- // Handle object types with type arguments
131
- if (currentType.flags & ts.TypeFlags.Object &&
132
- currentType.objectFlags & ts.ObjectFlags.Reference) {
133
- const typeRef = currentType;
134
- typeRef.typeArguments?.forEach(visitType);
278
+ // Special handling for pikkuSessionlessFunc pattern - use the arrow function directly
279
+ if (expression.text.startsWith('pikku')) {
280
+ if (args.length > 0) {
281
+ const firstArg = args[0];
282
+ if (ts.isArrowFunction(firstArg) ||
283
+ ts.isFunctionExpression(firstArg)) {
284
+ mainFunc = firstArg; // Use the arrow function directly instead of the call expression
285
+ }
286
+ }
135
287
  }
136
288
  }
137
- };
138
- visitType(type);
139
- return types;
140
- };
141
- export const getPropertyAssignment = (obj, name) => {
142
- const property = obj.properties.find((p) => (ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) &&
143
- ts.isIdentifier(p.name) &&
144
- p.name.text === name);
145
- if (!property) {
146
- console.error(`Missing property '${name}' in object`);
147
- return null;
148
- }
149
- return property;
150
- };
151
- export const getTypeArgumentsOfType = (checker, type) => {
152
- if (type.isUnionOrIntersection()) {
153
- const types = [];
154
- for (const subType of type.types) {
155
- const subTypeArgs = getTypeArgumentsOfType(checker, subType);
156
- if (subTypeArgs) {
157
- types.push(...subTypeArgs);
289
+ // Handle object initializer with a func property (for both patterns)
290
+ if (args.length > 0) {
291
+ const firstArg = args[0];
292
+ if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
293
+ // Look for func property in the object
294
+ for (const prop of firstArg.properties) {
295
+ if (ts.isPropertyAssignment(prop) &&
296
+ ts.isIdentifier(prop.name) &&
297
+ prop.name.text === 'func') {
298
+ if (ts.isIdentifier(prop.initializer)) {
299
+ // func: someFunction - resolve the function
300
+ const funcSym = checker.getSymbolAtLocation(prop.initializer);
301
+ if (funcSym) {
302
+ let resolvedFuncSym = funcSym;
303
+ if (resolvedFuncSym.flags & ts.SymbolFlags.Alias) {
304
+ resolvedFuncSym =
305
+ checker.getAliasedSymbol(resolvedFuncSym) ?? resolvedFuncSym;
306
+ }
307
+ const funcDecls = resolvedFuncSym.declarations ?? [];
308
+ if (funcDecls.length > 0) {
309
+ const funcDecl = funcDecls[0];
310
+ // Check if it's a pikkuSessionlessFunc
311
+ if (ts.isVariableDeclaration(funcDecl) &&
312
+ funcDecl.initializer) {
313
+ if (ts.isCallExpression(funcDecl.initializer) &&
314
+ ts.isIdentifier(funcDecl.initializer.expression) &&
315
+ funcDecl.initializer.expression.text.startsWith('pikku')) {
316
+ const funcArgs = funcDecl.initializer.arguments;
317
+ const firstArg = funcArgs[0];
318
+ if (firstArg &&
319
+ (ts.isArrowFunction(firstArg) ||
320
+ ts.isFunctionExpression(firstArg))) {
321
+ mainFunc = firstArg;
322
+ // Check if the variable is exported
323
+ if (isNamedExport(funcDecl) &&
324
+ ts.isIdentifier(funcDecl.name)) {
325
+ result.exportedName = funcDecl.name.text;
326
+ }
327
+ else if (ts.isIdentifier(funcDecl.name)) {
328
+ // If not exported, still capture the variable name
329
+ result.functionName = funcDecl.name.text;
330
+ }
331
+ break;
332
+ }
333
+ }
334
+ else if (ts.isFunctionExpression(funcDecl.initializer) ||
335
+ ts.isArrowFunction(funcDecl.initializer)) {
336
+ mainFunc = funcDecl.initializer;
337
+ // Check if the variable is exported
338
+ if (isNamedExport(funcDecl) &&
339
+ ts.isIdentifier(funcDecl.name)) {
340
+ result.exportedName = funcDecl.name.text;
341
+ }
342
+ else if (ts.isIdentifier(funcDecl.name)) {
343
+ // If not exported, still capture the variable name
344
+ result.functionName = funcDecl.name.text;
345
+ }
346
+ break;
347
+ }
348
+ }
349
+ else if (ts.isFunctionDeclaration(funcDecl)) {
350
+ mainFunc = funcDecl;
351
+ // Check if the function is exported
352
+ if (funcDecl.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) &&
353
+ funcDecl.name &&
354
+ ts.isIdentifier(funcDecl.name)) {
355
+ result.exportedName = funcDecl.name.text;
356
+ }
357
+ else if (funcDecl.name &&
358
+ ts.isIdentifier(funcDecl.name)) {
359
+ // If not exported, still capture the function name
360
+ result.functionName = funcDecl.name.text;
361
+ }
362
+ break;
363
+ }
364
+ }
365
+ }
366
+ else {
367
+ // If we can't resolve the symbol, use the identifier itself
368
+ mainFunc = prop.initializer;
369
+ }
370
+ break;
371
+ }
372
+ else if (ts.isFunctionExpression(prop.initializer) ||
373
+ ts.isArrowFunction(prop.initializer)) {
374
+ // func: () => {} or func: function() {} - use directly
375
+ mainFunc = prop.initializer;
376
+ break;
377
+ }
378
+ }
379
+ else if (ts.isShorthandPropertyAssignment(prop) &&
380
+ ts.isIdentifier(prop.name) &&
381
+ prop.name.text === 'func') {
382
+ // Handle func shorthand property
383
+ const shorthandSym = checker.getShorthandAssignmentValueSymbol(prop);
384
+ if (shorthandSym &&
385
+ shorthandSym.declarations &&
386
+ shorthandSym.declarations.length > 0) {
387
+ const shorthandDecl = shorthandSym.declarations[0];
388
+ if (!shorthandDecl) {
389
+ throw new Error('No shorthand declaration found');
390
+ }
391
+ if (ts.isVariableDeclaration(shorthandDecl) &&
392
+ shorthandDecl.initializer) {
393
+ if (ts.isCallExpression(shorthandDecl.initializer) &&
394
+ ts.isIdentifier(shorthandDecl.initializer.expression) &&
395
+ shorthandDecl.initializer.expression.text.startsWith('pikku')) {
396
+ const args = shorthandDecl.initializer.arguments;
397
+ const firstArg = args[0];
398
+ if (firstArg &&
399
+ (ts.isArrowFunction(firstArg) ||
400
+ ts.isFunctionExpression(firstArg))) {
401
+ mainFunc = firstArg;
402
+ // Check if the variable is exported
403
+ if (isNamedExport(shorthandDecl) &&
404
+ ts.isIdentifier(shorthandDecl.name)) {
405
+ result.exportedName = shorthandDecl.name.text;
406
+ }
407
+ else if (ts.isIdentifier(shorthandDecl.name)) {
408
+ // If not exported, still capture the variable name
409
+ result.functionName = shorthandDecl.name.text;
410
+ }
411
+ break;
412
+ }
413
+ }
414
+ else if (ts.isFunctionExpression(shorthandDecl.initializer) ||
415
+ ts.isArrowFunction(shorthandDecl.initializer)) {
416
+ mainFunc = shorthandDecl.initializer;
417
+ // Check if the variable is exported
418
+ if (isNamedExport(shorthandDecl) &&
419
+ ts.isIdentifier(shorthandDecl.name)) {
420
+ result.exportedName = shorthandDecl.name.text;
421
+ }
422
+ else if (ts.isIdentifier(shorthandDecl.name)) {
423
+ // If not exported, still capture the variable name
424
+ result.functionName = shorthandDecl.name.text;
425
+ }
426
+ break;
427
+ }
428
+ }
429
+ else if (ts.isFunctionDeclaration(shorthandDecl)) {
430
+ mainFunc = shorthandDecl;
431
+ // Check if the function is exported
432
+ if (shorthandDecl.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) &&
433
+ shorthandDecl.name &&
434
+ ts.isIdentifier(shorthandDecl.name)) {
435
+ result.exportedName = shorthandDecl.name.text;
436
+ }
437
+ else if (shorthandDecl.name &&
438
+ ts.isIdentifier(shorthandDecl.name)) {
439
+ // If not exported, still capture the function name
440
+ result.functionName = shorthandDecl.name.text;
441
+ }
442
+ break;
443
+ }
444
+ }
445
+ }
446
+ }
158
447
  }
159
448
  }
160
- return types.length > 0 ? types : null;
161
- }
162
- // If the type is a TypeReference with typeArguments, return them
163
- if (type.flags & ts.TypeFlags.Object &&
164
- type.objectFlags & ts.ObjectFlags.Reference) {
165
- const typeRef = type;
166
- if (typeRef.typeArguments && typeRef.typeArguments.length > 0) {
167
- return typeRef.typeArguments;
168
- }
169
449
  }
170
- // If the type is an alias with aliasTypeArguments, return them
171
- if (type.aliasTypeArguments && type.aliasTypeArguments.length > 0) {
172
- return type.aliasTypeArguments;
173
- }
174
- return null;
175
- };
176
- export const getFunctionTypes = (checker, obj, { typesMap, funcName, subFunctionName = funcName, inputIndex, outputIndex, }) => {
177
- const result = {
178
- inputTypes: [],
179
- inputs: null,
180
- outputTypes: [],
181
- outputs: null,
182
- };
183
- const property = getPropertyAssignment(obj, subFunctionName);
184
- if (!property) {
185
- return result;
186
- }
187
- let type;
188
- // Handle shorthand property assignment
189
- if (ts.isShorthandPropertyAssignment(property)) {
190
- const symbol = checker.getShorthandAssignmentValueSymbol(property);
191
- if (symbol) {
192
- type = checker.getTypeOfSymbolAtLocation(symbol, property);
193
- if (funcName === 'func') {
194
- funcName = symbol.name;
450
+ // Handle direct identifier case
451
+ else if (ts.isIdentifier(callExpr)) {
452
+ const sym = checker.getSymbolAtLocation(callExpr);
453
+ if (sym) {
454
+ let resolvedSym = sym;
455
+ if (resolvedSym.flags & ts.SymbolFlags.Alias) {
456
+ resolvedSym = checker.getAliasedSymbol(resolvedSym) ?? resolvedSym;
457
+ }
458
+ const decls = resolvedSym.declarations ?? [];
459
+ if (decls.length > 0) {
460
+ const decl = decls[0];
461
+ if (!decl) {
462
+ throw new Error('No declaration found');
463
+ }
464
+ if (ts.isVariableDeclaration(decl) && decl.initializer) {
465
+ if (ts.isCallExpression(decl.initializer) &&
466
+ ts.isIdentifier(decl.initializer.expression) &&
467
+ decl.initializer.expression.text.startsWith('pikku')) {
468
+ // Check for object with 'name' property in first argument
469
+ const firstArg = decl.initializer.arguments[0];
470
+ if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
471
+ for (const prop of firstArg.properties) {
472
+ if (ts.isPropertyAssignment(prop) &&
473
+ ts.isIdentifier(prop.name) &&
474
+ prop.name.text === 'name' &&
475
+ ts.isStringLiteral(prop.initializer)) {
476
+ // Priority 1: Object with name property
477
+ result.functionName = prop.initializer.text;
478
+ break;
479
+ }
480
+ }
481
+ }
482
+ if (decl.initializer.expression.text.startsWith('pikku')) {
483
+ if (firstArg &&
484
+ (ts.isArrowFunction(firstArg) ||
485
+ ts.isFunctionExpression(firstArg))) {
486
+ mainFunc = firstArg;
487
+ }
488
+ }
489
+ // Check if the variable is exported
490
+ if (isNamedExport(decl) && ts.isIdentifier(decl.name)) {
491
+ result.exportedName = decl.name.text;
492
+ }
493
+ else if (ts.isIdentifier(decl.name)) {
494
+ // If not explicitly set by name property above, set functionName
495
+ if (!result.functionName) {
496
+ result.functionName = decl.name.text;
497
+ }
498
+ }
499
+ }
500
+ else if (ts.isFunctionExpression(decl.initializer) ||
501
+ ts.isArrowFunction(decl.initializer)) {
502
+ mainFunc = decl.initializer;
503
+ // Check if the variable is exported
504
+ if (isNamedExport(decl) && ts.isIdentifier(decl.name)) {
505
+ result.exportedName = decl.name.text;
506
+ }
507
+ else if (ts.isIdentifier(decl.name)) {
508
+ result.functionName = decl.name.text;
509
+ }
510
+ }
511
+ }
512
+ else if (ts.isFunctionDeclaration(decl)) {
513
+ mainFunc = decl;
514
+ // Check if the function is exported
515
+ if (decl.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) &&
516
+ decl.name &&
517
+ ts.isIdentifier(decl.name)) {
518
+ result.exportedName = decl.name.text;
519
+ }
520
+ else if (decl.name && ts.isIdentifier(decl.name)) {
521
+ result.functionName = decl.name.text;
522
+ }
523
+ }
195
524
  }
196
525
  }
197
526
  }
198
- // Handle regular property assignment
199
- else if (ts.isPropertyAssignment(property)) {
200
- if (ts.isObjectLiteralExpression(property.initializer)) {
201
- return getFunctionTypes(checker, property.initializer, {
202
- typesMap,
203
- funcName,
204
- subFunctionName: 'func',
205
- inputIndex,
206
- outputIndex,
207
- });
527
+ // Now generate the deterministic function name based on the resolved function
528
+ result.pikkuFuncName = makeDeterministicAnonName(mainFunc, checker);
529
+ // Continue with regular name extraction for remaining cases
530
+ // 1) const foo = pikkuFunc(...)
531
+ if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
532
+ if (isNamedExport(parent)) {
533
+ result.exportedName = parent.name.text;
208
534
  }
209
- if (property.initializer) {
210
- type = checker.getTypeAtLocation(property.initializer);
211
- if (funcName === 'func') {
212
- funcName = property.initializer.getText();
213
- }
535
+ else {
536
+ // Still capture the variable name even if not exported
537
+ result.functionName = parent.name.text;
214
538
  }
215
539
  }
216
- if (!type) {
217
- console.error(`Unable to resolve type for property '${funcName}'`);
218
- return result;
540
+ // 2) { foo: pikkuFunc(...) }
541
+ else if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name)) {
542
+ result.propertyName = parent.name.text;
543
+ }
544
+ // 2b) Handle shorthand property { foo } - which is equivalent to { foo: foo }
545
+ else if (ts.isShorthandPropertyAssignment(parent) &&
546
+ ts.isIdentifier(parent.name)) {
547
+ result.propertyName = parent.name.text;
219
548
  }
220
- // Access type arguments from TypeReference
221
- const typeArguments = getTypeArgumentsOfType(checker, type);
222
- if (!typeArguments || typeArguments.length === 0) {
223
- // This is the case for inline functions. In this case we would want to
224
- // get the types from the second argument of the function...
225
- console.error(`\x1b[31m• No generic type arguments found for ${funcName}. Support for inline functions is not yet implemented.\x1b[0m`);
226
- return result;
549
+ // 3) Handle any remaining cases for pikkuFunc({ name: '…', func: … })
550
+ else if (ts.isCallExpression(originalCallExpr)) {
551
+ const firstArg = originalCallExpr.arguments[0];
552
+ if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
553
+ for (const prop of firstArg.properties) {
554
+ if (ts.isPropertyAssignment(prop) &&
555
+ ts.isIdentifier(prop.name) &&
556
+ prop.name.text === 'name' &&
557
+ ts.isStringLiteral(prop.initializer) &&
558
+ !result.functionName // Only set if not already set
559
+ ) {
560
+ result.functionName = prop.initializer.text;
561
+ break;
562
+ }
563
+ }
564
+ }
227
565
  }
228
- if (inputIndex !== undefined && inputIndex < typeArguments.length) {
229
- const { names, types } = getNamesAndTypes(checker, typesMap, 'Input', funcName, typeArguments[inputIndex]);
230
- result.inputs = names;
231
- result.inputTypes = types;
566
+ // Apply name priority logic
567
+ populateNameByPriority(result);
568
+ return result;
569
+ }
570
+ /**
571
+ * Helper function to populate the 'name' field based on priority
572
+ */
573
+ function populateNameByPriority(result) {
574
+ // Priority 1: If we have a functionName (from name property or variable name), use that
575
+ if (result.functionName) {
576
+ result.name = result.functionName;
232
577
  }
233
- else {
234
- console.log(`No input defined for ${funcName}`);
578
+ // Priority 2: If we have an exported name, use that
579
+ else if (result.exportedName) {
580
+ result.name = result.exportedName;
235
581
  }
236
- if (outputIndex !== undefined && outputIndex < typeArguments.length) {
237
- const { names, types } = getNamesAndTypes(checker, typesMap, 'Output', funcName, typeArguments[outputIndex]);
238
- result.outputs = names;
239
- result.outputTypes = types;
582
+ // Priority 3: If we have a property name, use that
583
+ else if (result.propertyName) {
584
+ result.name = result.propertyName;
240
585
  }
586
+ // Fallback: Use the deterministic name, but we could shorten it in the future
241
587
  else {
242
- console.info(`No output defined for ${funcName}`);
588
+ // For now, just use the full pikkuFuncName
589
+ result.name = result.pikkuFuncName;
590
+ // Alternative: extract just the filename and line/column from pikkuFuncName
591
+ // const nameParts = result.pikkuFuncName.split('_');
592
+ // if (nameParts.length >= 3) {
593
+ // // Extract just filename + line/column info
594
+ // result.name = `${nameParts[1]}_${nameParts[2]}`;
595
+ // }
243
596
  }
244
- return result;
597
+ }
598
+ /**
599
+ * Helper function to check if a variable declaration is a named export
600
+ */
601
+ function isNamedExport(declaration) {
602
+ let parent = declaration.parent;
603
+ if (!parent)
604
+ return false;
605
+ // Check if it's part of a variable declaration list
606
+ if (ts.isVariableDeclarationList(parent)) {
607
+ parent = parent.parent;
608
+ if (!parent)
609
+ return false;
610
+ // Check if it's in an export declaration
611
+ if (ts.isVariableStatement(parent)) {
612
+ return (parent.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ??
613
+ false);
614
+ }
615
+ }
616
+ return false;
617
+ }
618
+ // Until here
619
+ export const extractTypeKeys = (type) => {
620
+ return type.getProperties().map((symbol) => symbol.getName());
245
621
  };
622
+ export function getPropertyAssignmentInitializer(obj, propName, followShorthand = false, checker) {
623
+ for (const prop of obj.properties) {
624
+ // ① foo: () => {}
625
+ if (ts.isPropertyAssignment(prop) &&
626
+ ts.isIdentifier(prop.name) &&
627
+ prop.name.text === propName) {
628
+ return prop.initializer;
629
+ }
630
+ // ② foo() { … }
631
+ if (ts.isMethodDeclaration(prop) &&
632
+ ts.isIdentifier(prop.name) &&
633
+ prop.name.text === propName) {
634
+ return prop.name; // the method node *is* the function
635
+ }
636
+ // ③ { foo } (shorthand)
637
+ if (followShorthand &&
638
+ ts.isShorthandPropertyAssignment(prop) &&
639
+ prop.name.text === propName) {
640
+ if (!checker)
641
+ return prop.name; // best effort without a checker
642
+ let sym = checker.getSymbolAtLocation(prop.name);
643
+ if (sym && sym.flags & ts.SymbolFlags.Alias) {
644
+ sym = checker.getAliasedSymbol(sym);
645
+ }
646
+ const decl = sym?.declarations?.[0];
647
+ // const foo = () => {}
648
+ if (decl &&
649
+ ts.isVariableDeclaration(decl) &&
650
+ decl.initializer &&
651
+ (ts.isArrowFunction(decl.initializer) ||
652
+ ts.isFunctionExpression(decl.initializer))) {
653
+ return decl.initializer;
654
+ }
655
+ // function foo() {}
656
+ if (decl &&
657
+ (ts.isFunctionDeclaration(decl) ||
658
+ ts.isArrowFunction(decl) ||
659
+ ts.isFunctionExpression(decl))) {
660
+ return decl;
661
+ }
662
+ // fallback – just give back the identifier
663
+ return prop.name;
664
+ }
665
+ }
666
+ return undefined;
667
+ }
246
668
  export const matchesFilters = (filters, params, meta) => {
247
669
  if (Object.keys(filters).length === 0 || filters.tags?.length === 0) {
248
670
  return true;