@pikku/inspector 0.7.0 → 0.7.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/CHANGELOG.md +6 -0
- package/dist/add-functions.js +10 -1
- package/package.json +1 -1
- package/src/add-channel.ts +482 -0
- package/src/add-file-extends-core-type.ts +50 -0
- package/src/add-file-with-config.ts +45 -0
- package/src/add-file-with-factory.ts +65 -0
- package/src/add-functions.ts +376 -0
- package/src/add-http-route.ts +123 -0
- package/src/add-schedule.ts +76 -0
- package/src/does-type-extend-core-type.ts +53 -0
- package/src/get-property-value.ts +84 -0
- package/src/index.ts +4 -0
- package/src/inspector.ts +75 -0
- package/src/types-map.ts +130 -0
- package/src/types.ts +58 -0
- package/src/utils.ts +863 -0
- package/src/visit.ts +67 -0
- package/tsconfig.json +19 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/dist/events/add-channel.d.ts +0 -1
- package/dist/events/add-channel.js +0 -170
- package/dist/events/add-http-route.d.ts +0 -16
- package/dist/events/add-http-route.js +0 -83
- package/dist/events/add-schedule.d.ts +0 -3
- package/dist/events/add-schedule.js +0 -38
package/src/utils.ts
ADDED
|
@@ -0,0 +1,863 @@
|
|
|
1
|
+
import * as ts from 'typescript'
|
|
2
|
+
import { InspectorFilters } from './types.js'
|
|
3
|
+
|
|
4
|
+
type ExtractedFunctionName = {
|
|
5
|
+
pikkuFuncName: string
|
|
6
|
+
name: string
|
|
7
|
+
exportedName: string | null
|
|
8
|
+
functionName: string | null
|
|
9
|
+
propertyName: string | null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Generate a deterministic "anonymous" name for any expression node,
|
|
14
|
+
* but if it's an Identifier pointing to a function, resolve it back
|
|
15
|
+
* to the function's declaration (so you get the true source location).
|
|
16
|
+
*/
|
|
17
|
+
export function makeDeterministicAnonName(
|
|
18
|
+
start: ts.Node,
|
|
19
|
+
checker: ts.TypeChecker
|
|
20
|
+
): string {
|
|
21
|
+
let node: ts.Node = start
|
|
22
|
+
let target: ts.Node = start
|
|
23
|
+
|
|
24
|
+
// Handle the case where we're starting with an identifier directly
|
|
25
|
+
if (ts.isIdentifier(node)) {
|
|
26
|
+
const sym = checker.getSymbolAtLocation(node)
|
|
27
|
+
if (sym) {
|
|
28
|
+
let resolvedSym = sym
|
|
29
|
+
if (resolvedSym.flags & ts.SymbolFlags.Alias) {
|
|
30
|
+
resolvedSym = checker.getAliasedSymbol(resolvedSym) ?? resolvedSym
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const decls = resolvedSym.declarations ?? []
|
|
34
|
+
if (decls.length > 0) {
|
|
35
|
+
// Start with the declaration, not the reference
|
|
36
|
+
const decl = decls[0]!
|
|
37
|
+
|
|
38
|
+
// If it's a variable declaration with a function initializer, use the function directly
|
|
39
|
+
if (
|
|
40
|
+
ts.isVariableDeclaration(decl) &&
|
|
41
|
+
decl.initializer &&
|
|
42
|
+
(ts.isFunctionExpression(decl.initializer) ||
|
|
43
|
+
ts.isArrowFunction(decl.initializer))
|
|
44
|
+
) {
|
|
45
|
+
target = decl.initializer
|
|
46
|
+
// Return early - we found the function directly
|
|
47
|
+
const sf = target.getSourceFile()
|
|
48
|
+
const file = sf.fileName.replace(/[^A-Za-z0-9_]/g, '_')
|
|
49
|
+
const { line, character } = ts.getLineAndCharacterOfPosition(
|
|
50
|
+
sf,
|
|
51
|
+
target.getStart()
|
|
52
|
+
)
|
|
53
|
+
return `pikkuFn_${file}_L${line + 1}C${character + 1}`
|
|
54
|
+
}
|
|
55
|
+
// Otherwise continue resolution with the declaration
|
|
56
|
+
node = decl
|
|
57
|
+
target = decl!
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// In an object literal property value, first try to resolve the identifier
|
|
63
|
+
if (
|
|
64
|
+
ts.isPropertyAssignment(node.parent) &&
|
|
65
|
+
node === node.parent.initializer &&
|
|
66
|
+
ts.isIdentifier(node)
|
|
67
|
+
) {
|
|
68
|
+
const sym = checker.getSymbolAtLocation(node)
|
|
69
|
+
if (sym) {
|
|
70
|
+
// Process the symbol to find the real declaration
|
|
71
|
+
let resolvedSym = sym
|
|
72
|
+
if (resolvedSym.flags & ts.SymbolFlags.Alias) {
|
|
73
|
+
resolvedSym = checker.getAliasedSymbol(resolvedSym) ?? resolvedSym
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const decls = resolvedSym.declarations ?? []
|
|
77
|
+
if (decls.length > 0) {
|
|
78
|
+
// Found a declaration - use it as our new target
|
|
79
|
+
const decl = decls[0]
|
|
80
|
+
|
|
81
|
+
if (!decl) {
|
|
82
|
+
throw new Error('No declaration found')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// If it's a variable declaration with an initializer function, use that
|
|
86
|
+
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
87
|
+
if (
|
|
88
|
+
ts.isFunctionExpression(decl.initializer) ||
|
|
89
|
+
ts.isArrowFunction(decl.initializer)
|
|
90
|
+
) {
|
|
91
|
+
target = decl.initializer
|
|
92
|
+
// Return early - we found the function directly
|
|
93
|
+
const sf = target.getSourceFile()
|
|
94
|
+
const file = sf.fileName.replace(/[^A-Za-z0-9_]/g, '_')
|
|
95
|
+
const { line, character } = ts.getLineAndCharacterOfPosition(
|
|
96
|
+
sf,
|
|
97
|
+
target.getStart()
|
|
98
|
+
)
|
|
99
|
+
return `pikkuFn_${file}_L${line + 1}C${character + 1}`
|
|
100
|
+
}
|
|
101
|
+
} else if (ts.isFunctionDeclaration(decl)) {
|
|
102
|
+
// Already a function declaration
|
|
103
|
+
target = decl
|
|
104
|
+
// Return early
|
|
105
|
+
const sf = target.getSourceFile()
|
|
106
|
+
const file = sf.fileName.replace(/[^A-Za-z0-9_]/g, '_')
|
|
107
|
+
const { line, character } = ts.getLineAndCharacterOfPosition(
|
|
108
|
+
sf,
|
|
109
|
+
target.getStart()
|
|
110
|
+
)
|
|
111
|
+
return `pikkuFn_${file}_L${line + 1}C${character + 1}`
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// If we didn't return early, continue with this declaration
|
|
115
|
+
node = decl
|
|
116
|
+
target = decl
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const seen = new Set<ts.Node>()
|
|
122
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
123
|
+
if (!ts.isIdentifier(node) || seen.has(node)) break
|
|
124
|
+
seen.add(node)
|
|
125
|
+
|
|
126
|
+
let sym = checker.getSymbolAtLocation(node)
|
|
127
|
+
if (!sym) break
|
|
128
|
+
if (sym.flags & ts.SymbolFlags.Alias) {
|
|
129
|
+
sym = checker.getAliasedSymbol(sym) ?? sym
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const allDecls = sym.declarations ?? []
|
|
133
|
+
// prefer real .ts/.tsx implementation files
|
|
134
|
+
const implDecls = allDecls.filter(
|
|
135
|
+
(d) => !d.getSourceFile().isDeclarationFile
|
|
136
|
+
)
|
|
137
|
+
const decls = implDecls.length ? implDecls : allDecls
|
|
138
|
+
|
|
139
|
+
let didResolve = false
|
|
140
|
+
for (const decl of decls) {
|
|
141
|
+
// 1) direct function foo() {} or function-expression
|
|
142
|
+
if (
|
|
143
|
+
ts.isFunctionDeclaration(decl) ||
|
|
144
|
+
ts.isFunctionExpression(decl) ||
|
|
145
|
+
ts.isArrowFunction(decl)
|
|
146
|
+
) {
|
|
147
|
+
target = decl
|
|
148
|
+
didResolve = true
|
|
149
|
+
break
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 2) const foo = () => {} or foo = function() {}
|
|
153
|
+
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
154
|
+
const init = decl.initializer
|
|
155
|
+
if (ts.isFunctionExpression(init) || ts.isArrowFunction(init)) {
|
|
156
|
+
target = init
|
|
157
|
+
didResolve = true
|
|
158
|
+
break
|
|
159
|
+
}
|
|
160
|
+
// 2b) const foo = bar; (follow the next identifier)
|
|
161
|
+
if (ts.isIdentifier(init)) {
|
|
162
|
+
node = init
|
|
163
|
+
target = init
|
|
164
|
+
didResolve = true
|
|
165
|
+
break
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 3) Handle shorthand property assignments: { foo } (equivalent to { foo: foo })
|
|
170
|
+
if (ts.isShorthandPropertyAssignment(decl)) {
|
|
171
|
+
// Get the symbol for the shorthand property
|
|
172
|
+
const shorthandSym = checker.getShorthandAssignmentValueSymbol(decl)
|
|
173
|
+
if (
|
|
174
|
+
shorthandSym &&
|
|
175
|
+
shorthandSym.declarations &&
|
|
176
|
+
shorthandSym.declarations.length > 0
|
|
177
|
+
) {
|
|
178
|
+
// Use the first declaration as our new target
|
|
179
|
+
const shorthandDecl = shorthandSym.declarations[0]!
|
|
180
|
+
target = shorthandDecl
|
|
181
|
+
|
|
182
|
+
if (!shorthandDecl) {
|
|
183
|
+
throw new Error('No shorthand declaration found')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check the type of declaration and extract the appropriate identifier to continue resolving
|
|
187
|
+
if (
|
|
188
|
+
ts.isVariableDeclaration(shorthandDecl) &&
|
|
189
|
+
ts.isIdentifier(shorthandDecl.name)
|
|
190
|
+
) {
|
|
191
|
+
node = shorthandDecl.name
|
|
192
|
+
didResolve = true
|
|
193
|
+
break
|
|
194
|
+
} else if (
|
|
195
|
+
ts.isFunctionDeclaration(shorthandDecl) &&
|
|
196
|
+
shorthandDecl.name &&
|
|
197
|
+
ts.isIdentifier(shorthandDecl.name)
|
|
198
|
+
) {
|
|
199
|
+
node = shorthandDecl.name
|
|
200
|
+
didResolve = true
|
|
201
|
+
break
|
|
202
|
+
} else if (
|
|
203
|
+
ts.isParameter(shorthandDecl) &&
|
|
204
|
+
ts.isIdentifier(shorthandDecl.name)
|
|
205
|
+
) {
|
|
206
|
+
node = shorthandDecl.name
|
|
207
|
+
didResolve = true
|
|
208
|
+
break
|
|
209
|
+
} else if (
|
|
210
|
+
ts.isPropertyDeclaration(shorthandDecl) &&
|
|
211
|
+
ts.isIdentifier(shorthandDecl.name)
|
|
212
|
+
) {
|
|
213
|
+
node = shorthandDecl.name
|
|
214
|
+
didResolve = true
|
|
215
|
+
break
|
|
216
|
+
} else if (
|
|
217
|
+
ts.isMethodDeclaration(shorthandDecl) &&
|
|
218
|
+
ts.isIdentifier(shorthandDecl.name)
|
|
219
|
+
) {
|
|
220
|
+
node = shorthandDecl.name
|
|
221
|
+
didResolve = true
|
|
222
|
+
break
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 4) Handle method declarations in classes/objects
|
|
228
|
+
if (ts.isMethodDeclaration(decl)) {
|
|
229
|
+
target = decl
|
|
230
|
+
didResolve = true
|
|
231
|
+
break
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// you can add more cases here if your setup uses imports, etc.
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!didResolve) break
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const sf = target.getSourceFile()
|
|
241
|
+
const file = sf.fileName.replace(/[^A-Za-z0-9_]/g, '_')
|
|
242
|
+
const { line, character } = ts.getLineAndCharacterOfPosition(
|
|
243
|
+
sf,
|
|
244
|
+
target.getStart()
|
|
245
|
+
)
|
|
246
|
+
return `pikkuFn_${file}_L${line + 1}C${character + 1}`
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Updated function to extract and prioritize function names correctly
|
|
251
|
+
* This function follows the priority:
|
|
252
|
+
* 1. Object with a name property
|
|
253
|
+
* 2. Exported name
|
|
254
|
+
* 3. Fallback to deterministic name
|
|
255
|
+
*/
|
|
256
|
+
export function extractFunctionName(
|
|
257
|
+
callExpr: ts.Node,
|
|
258
|
+
checker: ts.TypeChecker
|
|
259
|
+
): ExtractedFunctionName {
|
|
260
|
+
const parent: any = callExpr.parent
|
|
261
|
+
|
|
262
|
+
// Initialize the result
|
|
263
|
+
const result: ExtractedFunctionName = {
|
|
264
|
+
pikkuFuncName: '', // Will be populated later
|
|
265
|
+
name: '', // This will hold our "best" name based on priority
|
|
266
|
+
exportedName: null,
|
|
267
|
+
functionName: null,
|
|
268
|
+
propertyName: null,
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Special case for addHTTPRoute: if this is an identifier within an object literal,
|
|
272
|
+
// it might be coming from the HTTP route handling flow
|
|
273
|
+
if (
|
|
274
|
+
ts.isIdentifier(callExpr) &&
|
|
275
|
+
callExpr.parent &&
|
|
276
|
+
ts.isPropertyAssignment(callExpr.parent)
|
|
277
|
+
) {
|
|
278
|
+
// Try to handle the special case for HTTP route functions
|
|
279
|
+
const sym = checker.getSymbolAtLocation(callExpr)
|
|
280
|
+
if (sym) {
|
|
281
|
+
let resolvedSym = sym
|
|
282
|
+
if (resolvedSym.flags & ts.SymbolFlags.Alias) {
|
|
283
|
+
resolvedSym = checker.getAliasedSymbol(resolvedSym) ?? resolvedSym
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const decls = resolvedSym.declarations ?? []
|
|
287
|
+
if (decls.length > 0) {
|
|
288
|
+
const decl = decls[0]!
|
|
289
|
+
// Check if the declaration is a variable that uses pikkuSessionlessFunc
|
|
290
|
+
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
291
|
+
if (
|
|
292
|
+
ts.isCallExpression(decl.initializer) &&
|
|
293
|
+
ts.isIdentifier(decl.initializer.expression) &&
|
|
294
|
+
decl.initializer.expression.text.startsWith('pikku')
|
|
295
|
+
) {
|
|
296
|
+
const args = decl.initializer.arguments
|
|
297
|
+
const firstArg = args[0]
|
|
298
|
+
if (
|
|
299
|
+
firstArg &&
|
|
300
|
+
(ts.isArrowFunction(firstArg) ||
|
|
301
|
+
ts.isFunctionExpression(firstArg))
|
|
302
|
+
) {
|
|
303
|
+
// Use the function directly for position calculation
|
|
304
|
+
result.pikkuFuncName = makeDeterministicAnonName(
|
|
305
|
+
firstArg,
|
|
306
|
+
checker
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
// Continue with name extraction
|
|
310
|
+
if (ts.isIdentifier(parent.name)) {
|
|
311
|
+
result.propertyName = parent.name.text
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Check if the variable is exported
|
|
315
|
+
if (
|
|
316
|
+
ts.isVariableDeclaration(decl) &&
|
|
317
|
+
isNamedExport(decl) &&
|
|
318
|
+
ts.isIdentifier(decl.name)
|
|
319
|
+
) {
|
|
320
|
+
result.exportedName = decl.name.text
|
|
321
|
+
} else if (ts.isIdentifier(decl.name)) {
|
|
322
|
+
// If not exported, still capture the variable name
|
|
323
|
+
result.functionName = decl.name.text
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Apply name priority logic
|
|
327
|
+
populateNameByPriority(result)
|
|
328
|
+
return result
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// First, figure out what function we're really dealing with
|
|
337
|
+
let mainFunc = callExpr
|
|
338
|
+
let originalCallExpr = callExpr // Keep track of the original call expression for name extraction
|
|
339
|
+
|
|
340
|
+
// For direct pikku function calls where callExpr is the call expression itself
|
|
341
|
+
if (ts.isCallExpression(callExpr)) {
|
|
342
|
+
const { expression, arguments: args } = callExpr
|
|
343
|
+
|
|
344
|
+
// Check if this is a pikku function call (pikkuFunc, pikkuSessionlessFunc, etc)
|
|
345
|
+
if (ts.isIdentifier(expression) && expression.text.startsWith('pikku')) {
|
|
346
|
+
// Check for object with 'name' property in first argument
|
|
347
|
+
const firstArg = args[0]
|
|
348
|
+
if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
|
|
349
|
+
for (const prop of firstArg.properties) {
|
|
350
|
+
if (
|
|
351
|
+
ts.isPropertyAssignment(prop) &&
|
|
352
|
+
ts.isIdentifier(prop.name) &&
|
|
353
|
+
prop.name.text === 'name' &&
|
|
354
|
+
ts.isStringLiteral(prop.initializer)
|
|
355
|
+
) {
|
|
356
|
+
// Priority 1: Object with name property
|
|
357
|
+
result.functionName = prop.initializer.text
|
|
358
|
+
break
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Special handling for pikkuSessionlessFunc pattern - use the arrow function directly
|
|
364
|
+
if (expression.text.startsWith('pikku')) {
|
|
365
|
+
if (args.length > 0) {
|
|
366
|
+
const firstArg = args[0]!
|
|
367
|
+
if (
|
|
368
|
+
ts.isArrowFunction(firstArg) ||
|
|
369
|
+
ts.isFunctionExpression(firstArg)
|
|
370
|
+
) {
|
|
371
|
+
mainFunc = firstArg // Use the arrow function directly instead of the call expression
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Handle object initializer with a func property (for both patterns)
|
|
378
|
+
if (args.length > 0) {
|
|
379
|
+
const firstArg = args[0]
|
|
380
|
+
if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
|
|
381
|
+
// Look for func property in the object
|
|
382
|
+
for (const prop of firstArg.properties) {
|
|
383
|
+
if (
|
|
384
|
+
ts.isPropertyAssignment(prop) &&
|
|
385
|
+
ts.isIdentifier(prop.name) &&
|
|
386
|
+
prop.name.text === 'func'
|
|
387
|
+
) {
|
|
388
|
+
if (ts.isIdentifier(prop.initializer)) {
|
|
389
|
+
// func: someFunction - resolve the function
|
|
390
|
+
const funcSym = checker.getSymbolAtLocation(prop.initializer)
|
|
391
|
+
if (funcSym) {
|
|
392
|
+
let resolvedFuncSym = funcSym
|
|
393
|
+
if (resolvedFuncSym.flags & ts.SymbolFlags.Alias) {
|
|
394
|
+
resolvedFuncSym =
|
|
395
|
+
checker.getAliasedSymbol(resolvedFuncSym) ?? resolvedFuncSym
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const funcDecls = resolvedFuncSym.declarations ?? []
|
|
399
|
+
if (funcDecls.length > 0) {
|
|
400
|
+
const funcDecl = funcDecls[0]!
|
|
401
|
+
// Check if it's a pikkuSessionlessFunc
|
|
402
|
+
if (
|
|
403
|
+
ts.isVariableDeclaration(funcDecl) &&
|
|
404
|
+
funcDecl.initializer
|
|
405
|
+
) {
|
|
406
|
+
if (
|
|
407
|
+
ts.isCallExpression(funcDecl.initializer) &&
|
|
408
|
+
ts.isIdentifier(funcDecl.initializer.expression) &&
|
|
409
|
+
funcDecl.initializer.expression.text.startsWith('pikku')
|
|
410
|
+
) {
|
|
411
|
+
const funcArgs = funcDecl.initializer.arguments
|
|
412
|
+
const firstArg = funcArgs[0]
|
|
413
|
+
if (
|
|
414
|
+
firstArg &&
|
|
415
|
+
(ts.isArrowFunction(firstArg) ||
|
|
416
|
+
ts.isFunctionExpression(firstArg))
|
|
417
|
+
) {
|
|
418
|
+
mainFunc = firstArg
|
|
419
|
+
|
|
420
|
+
// Check if the variable is exported
|
|
421
|
+
if (
|
|
422
|
+
isNamedExport(funcDecl) &&
|
|
423
|
+
ts.isIdentifier(funcDecl.name)
|
|
424
|
+
) {
|
|
425
|
+
result.exportedName = funcDecl.name.text
|
|
426
|
+
} else if (ts.isIdentifier(funcDecl.name)) {
|
|
427
|
+
// If not exported, still capture the variable name
|
|
428
|
+
result.functionName = funcDecl.name.text
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
break
|
|
432
|
+
}
|
|
433
|
+
} else if (
|
|
434
|
+
ts.isFunctionExpression(funcDecl.initializer) ||
|
|
435
|
+
ts.isArrowFunction(funcDecl.initializer)
|
|
436
|
+
) {
|
|
437
|
+
mainFunc = funcDecl.initializer
|
|
438
|
+
|
|
439
|
+
// Check if the variable is exported
|
|
440
|
+
if (
|
|
441
|
+
isNamedExport(funcDecl) &&
|
|
442
|
+
ts.isIdentifier(funcDecl.name)
|
|
443
|
+
) {
|
|
444
|
+
result.exportedName = funcDecl.name.text
|
|
445
|
+
} else if (ts.isIdentifier(funcDecl.name)) {
|
|
446
|
+
// If not exported, still capture the variable name
|
|
447
|
+
result.functionName = funcDecl.name.text
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
break
|
|
451
|
+
}
|
|
452
|
+
} else if (ts.isFunctionDeclaration(funcDecl)) {
|
|
453
|
+
mainFunc = funcDecl
|
|
454
|
+
|
|
455
|
+
// Check if the function is exported
|
|
456
|
+
if (
|
|
457
|
+
funcDecl.modifiers?.some(
|
|
458
|
+
(m) => m.kind === ts.SyntaxKind.ExportKeyword
|
|
459
|
+
) &&
|
|
460
|
+
funcDecl.name &&
|
|
461
|
+
ts.isIdentifier(funcDecl.name)
|
|
462
|
+
) {
|
|
463
|
+
result.exportedName = funcDecl.name.text
|
|
464
|
+
} else if (
|
|
465
|
+
funcDecl.name &&
|
|
466
|
+
ts.isIdentifier(funcDecl.name)
|
|
467
|
+
) {
|
|
468
|
+
// If not exported, still capture the function name
|
|
469
|
+
result.functionName = funcDecl.name.text
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
break
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
} else {
|
|
476
|
+
// If we can't resolve the symbol, use the identifier itself
|
|
477
|
+
mainFunc = prop.initializer
|
|
478
|
+
}
|
|
479
|
+
break
|
|
480
|
+
} else if (
|
|
481
|
+
ts.isFunctionExpression(prop.initializer) ||
|
|
482
|
+
ts.isArrowFunction(prop.initializer)
|
|
483
|
+
) {
|
|
484
|
+
// func: () => {} or func: function() {} - use directly
|
|
485
|
+
mainFunc = prop.initializer
|
|
486
|
+
break
|
|
487
|
+
}
|
|
488
|
+
} else if (
|
|
489
|
+
ts.isShorthandPropertyAssignment(prop) &&
|
|
490
|
+
ts.isIdentifier(prop.name) &&
|
|
491
|
+
prop.name.text === 'func'
|
|
492
|
+
) {
|
|
493
|
+
// Handle func shorthand property
|
|
494
|
+
const shorthandSym = checker.getShorthandAssignmentValueSymbol(prop)
|
|
495
|
+
if (
|
|
496
|
+
shorthandSym &&
|
|
497
|
+
shorthandSym.declarations &&
|
|
498
|
+
shorthandSym.declarations.length > 0
|
|
499
|
+
) {
|
|
500
|
+
const shorthandDecl = shorthandSym.declarations[0]
|
|
501
|
+
if (!shorthandDecl) {
|
|
502
|
+
throw new Error('No shorthand declaration found')
|
|
503
|
+
}
|
|
504
|
+
if (
|
|
505
|
+
ts.isVariableDeclaration(shorthandDecl) &&
|
|
506
|
+
shorthandDecl.initializer
|
|
507
|
+
) {
|
|
508
|
+
if (
|
|
509
|
+
ts.isCallExpression(shorthandDecl.initializer) &&
|
|
510
|
+
ts.isIdentifier(shorthandDecl.initializer.expression) &&
|
|
511
|
+
shorthandDecl.initializer.expression.text.startsWith('pikku')
|
|
512
|
+
) {
|
|
513
|
+
const args = shorthandDecl.initializer.arguments
|
|
514
|
+
const firstArg = args[0]
|
|
515
|
+
if (
|
|
516
|
+
firstArg &&
|
|
517
|
+
(ts.isArrowFunction(firstArg) ||
|
|
518
|
+
ts.isFunctionExpression(firstArg))
|
|
519
|
+
) {
|
|
520
|
+
mainFunc = firstArg
|
|
521
|
+
|
|
522
|
+
// Check if the variable is exported
|
|
523
|
+
if (
|
|
524
|
+
isNamedExport(shorthandDecl) &&
|
|
525
|
+
ts.isIdentifier(shorthandDecl.name)
|
|
526
|
+
) {
|
|
527
|
+
result.exportedName = shorthandDecl.name.text
|
|
528
|
+
} else if (ts.isIdentifier(shorthandDecl.name)) {
|
|
529
|
+
// If not exported, still capture the variable name
|
|
530
|
+
result.functionName = shorthandDecl.name.text
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
break
|
|
534
|
+
}
|
|
535
|
+
} else if (
|
|
536
|
+
ts.isFunctionExpression(shorthandDecl.initializer) ||
|
|
537
|
+
ts.isArrowFunction(shorthandDecl.initializer)
|
|
538
|
+
) {
|
|
539
|
+
mainFunc = shorthandDecl.initializer
|
|
540
|
+
|
|
541
|
+
// Check if the variable is exported
|
|
542
|
+
if (
|
|
543
|
+
isNamedExport(shorthandDecl) &&
|
|
544
|
+
ts.isIdentifier(shorthandDecl.name)
|
|
545
|
+
) {
|
|
546
|
+
result.exportedName = shorthandDecl.name.text
|
|
547
|
+
} else if (ts.isIdentifier(shorthandDecl.name)) {
|
|
548
|
+
// If not exported, still capture the variable name
|
|
549
|
+
result.functionName = shorthandDecl.name.text
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
break
|
|
553
|
+
}
|
|
554
|
+
} else if (ts.isFunctionDeclaration(shorthandDecl)) {
|
|
555
|
+
mainFunc = shorthandDecl
|
|
556
|
+
|
|
557
|
+
// Check if the function is exported
|
|
558
|
+
if (
|
|
559
|
+
shorthandDecl.modifiers?.some(
|
|
560
|
+
(m) => m.kind === ts.SyntaxKind.ExportKeyword
|
|
561
|
+
) &&
|
|
562
|
+
shorthandDecl.name &&
|
|
563
|
+
ts.isIdentifier(shorthandDecl.name)
|
|
564
|
+
) {
|
|
565
|
+
result.exportedName = shorthandDecl.name.text
|
|
566
|
+
} else if (
|
|
567
|
+
shorthandDecl.name &&
|
|
568
|
+
ts.isIdentifier(shorthandDecl.name)
|
|
569
|
+
) {
|
|
570
|
+
// If not exported, still capture the function name
|
|
571
|
+
result.functionName = shorthandDecl.name.text
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
break
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
// Handle direct identifier case
|
|
583
|
+
else if (ts.isIdentifier(callExpr)) {
|
|
584
|
+
const sym = checker.getSymbolAtLocation(callExpr)
|
|
585
|
+
if (sym) {
|
|
586
|
+
let resolvedSym = sym
|
|
587
|
+
if (resolvedSym.flags & ts.SymbolFlags.Alias) {
|
|
588
|
+
resolvedSym = checker.getAliasedSymbol(resolvedSym) ?? resolvedSym
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const decls = resolvedSym.declarations ?? []
|
|
592
|
+
if (decls.length > 0) {
|
|
593
|
+
const decl = decls[0]
|
|
594
|
+
if (!decl) {
|
|
595
|
+
throw new Error('No declaration found')
|
|
596
|
+
}
|
|
597
|
+
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
598
|
+
if (
|
|
599
|
+
ts.isCallExpression(decl.initializer) &&
|
|
600
|
+
ts.isIdentifier(decl.initializer.expression) &&
|
|
601
|
+
decl.initializer.expression.text.startsWith('pikku')
|
|
602
|
+
) {
|
|
603
|
+
// Check for object with 'name' property in first argument
|
|
604
|
+
const firstArg = decl.initializer.arguments[0]
|
|
605
|
+
if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
|
|
606
|
+
for (const prop of firstArg.properties) {
|
|
607
|
+
if (
|
|
608
|
+
ts.isPropertyAssignment(prop) &&
|
|
609
|
+
ts.isIdentifier(prop.name) &&
|
|
610
|
+
prop.name.text === 'name' &&
|
|
611
|
+
ts.isStringLiteral(prop.initializer)
|
|
612
|
+
) {
|
|
613
|
+
// Priority 1: Object with name property
|
|
614
|
+
result.functionName = prop.initializer.text
|
|
615
|
+
break
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (decl.initializer.expression.text.startsWith('pikku')) {
|
|
621
|
+
if (
|
|
622
|
+
firstArg &&
|
|
623
|
+
(ts.isArrowFunction(firstArg) ||
|
|
624
|
+
ts.isFunctionExpression(firstArg))
|
|
625
|
+
) {
|
|
626
|
+
mainFunc = firstArg
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Check if the variable is exported
|
|
631
|
+
if (isNamedExport(decl) && ts.isIdentifier(decl.name)) {
|
|
632
|
+
result.exportedName = decl.name.text
|
|
633
|
+
} else if (ts.isIdentifier(decl.name)) {
|
|
634
|
+
// If not explicitly set by name property above, set functionName
|
|
635
|
+
if (!result.functionName) {
|
|
636
|
+
result.functionName = decl.name.text
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
} else if (
|
|
640
|
+
ts.isFunctionExpression(decl.initializer) ||
|
|
641
|
+
ts.isArrowFunction(decl.initializer)
|
|
642
|
+
) {
|
|
643
|
+
mainFunc = decl.initializer
|
|
644
|
+
|
|
645
|
+
// Check if the variable is exported
|
|
646
|
+
if (isNamedExport(decl) && ts.isIdentifier(decl.name)) {
|
|
647
|
+
result.exportedName = decl.name.text
|
|
648
|
+
} else if (ts.isIdentifier(decl.name)) {
|
|
649
|
+
result.functionName = decl.name.text
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
} else if (ts.isFunctionDeclaration(decl)) {
|
|
653
|
+
mainFunc = decl
|
|
654
|
+
|
|
655
|
+
// Check if the function is exported
|
|
656
|
+
if (
|
|
657
|
+
decl.modifiers?.some(
|
|
658
|
+
(m) => m.kind === ts.SyntaxKind.ExportKeyword
|
|
659
|
+
) &&
|
|
660
|
+
decl.name &&
|
|
661
|
+
ts.isIdentifier(decl.name)
|
|
662
|
+
) {
|
|
663
|
+
result.exportedName = decl.name.text
|
|
664
|
+
} else if (decl.name && ts.isIdentifier(decl.name)) {
|
|
665
|
+
result.functionName = decl.name.text
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Now generate the deterministic function name based on the resolved function
|
|
673
|
+
result.pikkuFuncName = makeDeterministicAnonName(mainFunc, checker)
|
|
674
|
+
|
|
675
|
+
// Continue with regular name extraction for remaining cases
|
|
676
|
+
// 1) const foo = pikkuFunc(...)
|
|
677
|
+
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
|
|
678
|
+
if (isNamedExport(parent)) {
|
|
679
|
+
result.exportedName = parent.name.text
|
|
680
|
+
} else {
|
|
681
|
+
// Still capture the variable name even if not exported
|
|
682
|
+
result.functionName = parent.name.text
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
// 2) { foo: pikkuFunc(...) }
|
|
686
|
+
else if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name)) {
|
|
687
|
+
result.propertyName = parent.name.text
|
|
688
|
+
}
|
|
689
|
+
// 2b) Handle shorthand property { foo } - which is equivalent to { foo: foo }
|
|
690
|
+
else if (
|
|
691
|
+
ts.isShorthandPropertyAssignment(parent) &&
|
|
692
|
+
ts.isIdentifier(parent.name)
|
|
693
|
+
) {
|
|
694
|
+
result.propertyName = parent.name.text
|
|
695
|
+
}
|
|
696
|
+
// 3) Handle any remaining cases for pikkuFunc({ name: '…', func: … })
|
|
697
|
+
else if (ts.isCallExpression(originalCallExpr)) {
|
|
698
|
+
const firstArg = originalCallExpr.arguments[0]
|
|
699
|
+
if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
|
|
700
|
+
for (const prop of firstArg.properties) {
|
|
701
|
+
if (
|
|
702
|
+
ts.isPropertyAssignment(prop) &&
|
|
703
|
+
ts.isIdentifier(prop.name) &&
|
|
704
|
+
prop.name.text === 'name' &&
|
|
705
|
+
ts.isStringLiteral(prop.initializer) &&
|
|
706
|
+
!result.functionName // Only set if not already set
|
|
707
|
+
) {
|
|
708
|
+
result.functionName = prop.initializer.text
|
|
709
|
+
break
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Apply name priority logic
|
|
716
|
+
populateNameByPriority(result)
|
|
717
|
+
return result
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Helper function to populate the 'name' field based on priority
|
|
722
|
+
*/
|
|
723
|
+
function populateNameByPriority(result: ExtractedFunctionName): void {
|
|
724
|
+
// Priority 1: If we have a functionName (from name property or variable name), use that
|
|
725
|
+
if (result.functionName) {
|
|
726
|
+
result.name = result.functionName
|
|
727
|
+
}
|
|
728
|
+
// Priority 2: If we have an exported name, use that
|
|
729
|
+
else if (result.exportedName) {
|
|
730
|
+
result.name = result.exportedName
|
|
731
|
+
}
|
|
732
|
+
// Priority 3: If we have a property name, use that
|
|
733
|
+
else if (result.propertyName) {
|
|
734
|
+
result.name = result.propertyName
|
|
735
|
+
}
|
|
736
|
+
// Fallback: Use the deterministic name, but we could shorten it in the future
|
|
737
|
+
else {
|
|
738
|
+
// For now, just use the full pikkuFuncName
|
|
739
|
+
result.name = result.pikkuFuncName
|
|
740
|
+
|
|
741
|
+
// Alternative: extract just the filename and line/column from pikkuFuncName
|
|
742
|
+
// const nameParts = result.pikkuFuncName.split('_');
|
|
743
|
+
// if (nameParts.length >= 3) {
|
|
744
|
+
// // Extract just filename + line/column info
|
|
745
|
+
// result.name = `${nameParts[1]}_${nameParts[2]}`;
|
|
746
|
+
// }
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Helper function to check if a variable declaration is a named export
|
|
752
|
+
*/
|
|
753
|
+
function isNamedExport(declaration: ts.VariableDeclaration): boolean {
|
|
754
|
+
let parent: any = declaration.parent
|
|
755
|
+
if (!parent) return false
|
|
756
|
+
|
|
757
|
+
// Check if it's part of a variable declaration list
|
|
758
|
+
if (ts.isVariableDeclarationList(parent)) {
|
|
759
|
+
parent = parent.parent
|
|
760
|
+
if (!parent) return false
|
|
761
|
+
|
|
762
|
+
// Check if it's in an export declaration
|
|
763
|
+
if (ts.isVariableStatement(parent)) {
|
|
764
|
+
return (
|
|
765
|
+
parent.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ??
|
|
766
|
+
false
|
|
767
|
+
)
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
return false
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Until here
|
|
775
|
+
export const extractTypeKeys = (type: ts.Type): string[] => {
|
|
776
|
+
return type.getProperties().map((symbol) => symbol.getName())
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
export function getPropertyAssignmentInitializer(
|
|
780
|
+
obj: ts.ObjectLiteralExpression,
|
|
781
|
+
propName: string,
|
|
782
|
+
followShorthand = false,
|
|
783
|
+
checker?: ts.TypeChecker
|
|
784
|
+
): ts.Expression | undefined {
|
|
785
|
+
for (const prop of obj.properties) {
|
|
786
|
+
// ① foo: () => {}
|
|
787
|
+
if (
|
|
788
|
+
ts.isPropertyAssignment(prop) &&
|
|
789
|
+
ts.isIdentifier(prop.name) &&
|
|
790
|
+
prop.name.text === propName
|
|
791
|
+
) {
|
|
792
|
+
return prop.initializer
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// ② foo() { … }
|
|
796
|
+
if (
|
|
797
|
+
ts.isMethodDeclaration(prop) &&
|
|
798
|
+
ts.isIdentifier(prop.name) &&
|
|
799
|
+
prop.name.text === propName
|
|
800
|
+
) {
|
|
801
|
+
return prop.name // the method node *is* the function
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// ③ { foo } (shorthand)
|
|
805
|
+
if (
|
|
806
|
+
followShorthand &&
|
|
807
|
+
ts.isShorthandPropertyAssignment(prop) &&
|
|
808
|
+
prop.name.text === propName
|
|
809
|
+
) {
|
|
810
|
+
if (!checker) return prop.name // best effort without a checker
|
|
811
|
+
|
|
812
|
+
let sym = checker.getSymbolAtLocation(prop.name)
|
|
813
|
+
if (sym && sym.flags & ts.SymbolFlags.Alias) {
|
|
814
|
+
sym = checker.getAliasedSymbol(sym)
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const decl = sym?.declarations?.[0]
|
|
818
|
+
|
|
819
|
+
// const foo = () => {}
|
|
820
|
+
if (
|
|
821
|
+
decl &&
|
|
822
|
+
ts.isVariableDeclaration(decl) &&
|
|
823
|
+
decl.initializer &&
|
|
824
|
+
(ts.isArrowFunction(decl.initializer) ||
|
|
825
|
+
ts.isFunctionExpression(decl.initializer))
|
|
826
|
+
) {
|
|
827
|
+
return decl.initializer
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// function foo() {}
|
|
831
|
+
if (
|
|
832
|
+
decl &&
|
|
833
|
+
(ts.isFunctionDeclaration(decl) ||
|
|
834
|
+
ts.isArrowFunction(decl) ||
|
|
835
|
+
ts.isFunctionExpression(decl))
|
|
836
|
+
) {
|
|
837
|
+
return decl as ts.Expression
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// fallback – just give back the identifier
|
|
841
|
+
return prop.name
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return undefined
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
export const matchesFilters = (
|
|
849
|
+
filters: InspectorFilters,
|
|
850
|
+
params: { tags?: string[] },
|
|
851
|
+
meta: { type: 'schedule' | 'http' | 'channel'; name: string }
|
|
852
|
+
) => {
|
|
853
|
+
if (Object.keys(filters).length === 0 || filters.tags?.length === 0) {
|
|
854
|
+
return true
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (filters.tags?.some((tag) => params.tags?.includes(tag))) {
|
|
858
|
+
return true
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
console.debug(`⒡ Filtered: ${meta.type}:${meta.name}`)
|
|
862
|
+
return false
|
|
863
|
+
}
|