@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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @pikku/inspector
2
2
 
3
+ ## 0.7.1
4
+
5
+ ### Patch Changes
6
+
7
+ - ebfb786: fix: only inspect function calls with pikku\*func in name
8
+
3
9
  ## 0.7.0
4
10
 
5
11
  This has changed significantly. The inspector now finds all functions and then links them to events.
@@ -176,6 +176,15 @@ export function addFunctions(node, checker, state, filters) {
176
176
  return;
177
177
  const { expression, arguments: args, typeArguments } = node;
178
178
  // only handle calls like pikkuFunc(...)
179
+ if (!ts.isIdentifier(expression)) {
180
+ return;
181
+ }
182
+ // Match identifiers that contain both "pikku" and "func" (case insensitive)
183
+ const pikkuFuncPattern = /pikku.*func/i;
184
+ if (!pikkuFuncPattern.test(expression.text)) {
185
+ return;
186
+ }
187
+ // only handle calls like pikkuFunc(...)
179
188
  if (!ts.isIdentifier(expression) || !expression.text.startsWith('pikku')) {
180
189
  return;
181
190
  }
@@ -196,7 +205,7 @@ export function addFunctions(node, checker, state, filters) {
196
205
  }
197
206
  if (!ts.isArrowFunction(handlerNode) &&
198
207
  !ts.isFunctionExpression(handlerNode)) {
199
- console.error(`• Handler for TODO is not a function.`);
208
+ console.error(`• Handler for ${name} is not a function.`);
200
209
  return;
201
210
  }
202
211
  const services = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/inspector",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,482 @@
1
+ import * as ts from 'typescript'
2
+ import { getPropertyValue } from './get-property-value.js'
3
+ import { pathToRegexp } from 'path-to-regexp'
4
+ import { APIDocs } from '@pikku/core'
5
+ import { getInputTypes } from './add-http-route.js'
6
+ import {
7
+ extractFunctionName,
8
+ getPropertyAssignmentInitializer,
9
+ matchesFilters,
10
+ } from './utils.js'
11
+ import type { ChannelMessageMeta, ChannelMeta } from '@pikku/core/channel'
12
+ import type { InspectorFilters, InspectorState } from './types.js'
13
+
14
+ /**
15
+ * Safely get the “initializer” expression of a property-like AST node:
16
+ * - for `foo: expr`, returns `expr`
17
+ * - for `{ foo }` shorthand, returns the identifier `foo`
18
+ * - otherwise, returns undefined
19
+ */
20
+ function getInitializerOf(
21
+ elem: ts.ObjectLiteralElementLike
22
+ ): ts.Expression | undefined {
23
+ if (ts.isPropertyAssignment(elem)) {
24
+ return elem.initializer
25
+ }
26
+ if (ts.isShorthandPropertyAssignment(elem)) {
27
+ return elem.name
28
+ }
29
+ return undefined
30
+ }
31
+
32
+ /**
33
+ * Resolve a handler expression (Identifier, CallExpression, or { func })
34
+ * into its underlying function name.
35
+ */
36
+ function getHandlerNameFromExpression(
37
+ expr: ts.Expression,
38
+ checker: ts.TypeChecker
39
+ ): string | null {
40
+ // Handle direct identifier case (which includes shorthand properties)
41
+ if (ts.isIdentifier(expr)) {
42
+ const sym = checker.getSymbolAtLocation(expr)
43
+ if (sym) {
44
+ let resolvedSym = sym
45
+ if (resolvedSym.flags & ts.SymbolFlags.Alias) {
46
+ resolvedSym = checker.getAliasedSymbol(resolvedSym) ?? resolvedSym
47
+ }
48
+
49
+ // Try to get declarations
50
+ const decls = resolvedSym.declarations ?? []
51
+ if (decls.length > 0) {
52
+ const decl = decls[0]!
53
+
54
+ // For variable declarations, look at the initializer
55
+ if (ts.isVariableDeclaration(decl) && decl.initializer) {
56
+ if (
57
+ ts.isCallExpression(decl.initializer) ||
58
+ ts.isArrowFunction(decl.initializer) ||
59
+ ts.isFunctionExpression(decl.initializer)
60
+ ) {
61
+ // Extract function name from the declaration's initializer
62
+ const { pikkuFuncName } = extractFunctionName(
63
+ decl.initializer,
64
+ checker
65
+ )
66
+ return pikkuFuncName
67
+ }
68
+ }
69
+ // For function declarations, use directly
70
+ else if (ts.isFunctionDeclaration(decl)) {
71
+ const { pikkuFuncName } = extractFunctionName(decl, checker)
72
+ return pikkuFuncName
73
+ }
74
+ }
75
+ }
76
+
77
+ // Fallback: try to extract directly from the identifier
78
+ const { pikkuFuncName } = extractFunctionName(expr, checker)
79
+ return pikkuFuncName
80
+ }
81
+
82
+ // Handle call expressions
83
+ if (ts.isCallExpression(expr)) {
84
+ const { pikkuFuncName } = extractFunctionName(expr, checker)
85
+ return pikkuFuncName
86
+ }
87
+
88
+ // Handle object literals with 'func' property
89
+ if (ts.isObjectLiteralExpression(expr)) {
90
+ const fnProp = getPropertyAssignmentInitializer(expr, 'func', true, checker)
91
+ if (fnProp) {
92
+ return getHandlerNameFromExpression(fnProp, checker)
93
+ }
94
+ }
95
+
96
+ return null
97
+ }
98
+
99
+ /**
100
+ * Build out the nested message-routes by looking up each handler
101
+ * in state.functions.meta instead of re-inferring it here.
102
+ */
103
+ export function addMessagesRoutes(
104
+ obj: ts.ObjectLiteralExpression,
105
+ state: InspectorState,
106
+ checker: ts.TypeChecker
107
+ ): ChannelMeta['messageRoutes'] {
108
+ const result: ChannelMeta['messageRoutes'] = {}
109
+ const onMsgRouteProp = getPropertyAssignmentInitializer(
110
+ obj,
111
+ 'onMessageRoute',
112
+ true,
113
+ checker
114
+ )
115
+ if (!onMsgRouteProp) return result
116
+
117
+ if (!onMsgRouteProp || !ts.isObjectLiteralExpression(onMsgRouteProp))
118
+ return result
119
+
120
+ for (const chanElem of onMsgRouteProp.properties) {
121
+ const chanInit = getInitializerOf(chanElem)
122
+ if (!chanInit || !ts.isObjectLiteralExpression(chanInit)) continue
123
+
124
+ const channelKey = chanElem.name!.getText()
125
+ result[channelKey] = {}
126
+
127
+ for (const routeElem of chanInit.properties) {
128
+ const init = getInitializerOf(routeElem)
129
+ if (!init) continue
130
+
131
+ const routeKey = routeElem.name!.getText()
132
+
133
+ // For shorthand properties, we need to resolve the identifier to its declaration
134
+ if (ts.isShorthandPropertyAssignment(routeElem)) {
135
+ // Get the symbol for the shorthand property
136
+ const shorthandSym =
137
+ checker.getShorthandAssignmentValueSymbol(routeElem)
138
+
139
+ if (
140
+ shorthandSym &&
141
+ shorthandSym.declarations &&
142
+ shorthandSym.declarations.length > 0
143
+ ) {
144
+ const shorthandDecl = shorthandSym.declarations[0]
145
+ if (!shorthandDecl) {
146
+ throw new Error(
147
+ `No declaration found for shorthand property '${routeKey}'`
148
+ )
149
+ }
150
+
151
+ // Handle import specifiers
152
+ if (ts.isImportSpecifier(shorthandDecl)) {
153
+ // Get the imported symbol
154
+ const importedSymbol = checker.getSymbolAtLocation(
155
+ shorthandDecl.name
156
+ )
157
+ if (importedSymbol) {
158
+ // Try to resolve the alias to get the original symbol
159
+ let resolvedSymbol = importedSymbol
160
+ if (resolvedSymbol.flags & ts.SymbolFlags.Alias) {
161
+ resolvedSymbol =
162
+ checker.getAliasedSymbol(resolvedSymbol) ?? resolvedSymbol
163
+ }
164
+
165
+ // Try to get the declarations of the resolved symbol
166
+ const importDecls = resolvedSymbol.declarations ?? []
167
+ if (importDecls.length > 0) {
168
+ const importDecl = importDecls[0]!
169
+
170
+ // Handle different kinds of declarations
171
+ if (
172
+ ts.isVariableDeclaration(importDecl) &&
173
+ importDecl.initializer
174
+ ) {
175
+ // Extract from the initializer if it's a function
176
+ if (
177
+ ts.isArrowFunction(importDecl.initializer) ||
178
+ ts.isFunctionExpression(importDecl.initializer) ||
179
+ ts.isCallExpression(importDecl.initializer)
180
+ ) {
181
+ const { pikkuFuncName } = extractFunctionName(
182
+ importDecl.initializer,
183
+ checker
184
+ )
185
+ const handlerName = pikkuFuncName
186
+
187
+ // Look up in the registry
188
+ const fnMeta = state.functions.meta[handlerName]
189
+ if (fnMeta) {
190
+ result[channelKey]![routeKey] = {
191
+ pikkuFuncName: handlerName,
192
+ inputs: fnMeta.inputs ?? null,
193
+ outputs: fnMeta.outputs ?? null,
194
+ }
195
+ continue
196
+ }
197
+ }
198
+ } else if (ts.isFunctionDeclaration(importDecl)) {
199
+ // Extract from the function declaration
200
+ const { pikkuFuncName } = extractFunctionName(
201
+ importDecl,
202
+ checker
203
+ )
204
+ const handlerName = pikkuFuncName
205
+
206
+ // Look up in the registry
207
+ const fnMeta = state.functions.meta[handlerName]
208
+ if (fnMeta) {
209
+ result[channelKey]![routeKey] = {
210
+ pikkuFuncName: handlerName,
211
+ inputs: fnMeta.inputs ?? null,
212
+ outputs: fnMeta.outputs ?? null,
213
+ }
214
+ continue
215
+ }
216
+ } else if (ts.isExportSpecifier(importDecl)) {
217
+ // For re-exports, we need to follow another level of indirection
218
+ const exportSymbol = checker.getSymbolAtLocation(
219
+ importDecl.name
220
+ )
221
+ if (exportSymbol) {
222
+ let resolvedExportSymbol = exportSymbol
223
+ if (resolvedExportSymbol.flags & ts.SymbolFlags.Alias) {
224
+ resolvedExportSymbol =
225
+ checker.getAliasedSymbol(resolvedExportSymbol) ??
226
+ resolvedExportSymbol
227
+ }
228
+
229
+ const exportDecls = resolvedExportSymbol.declarations ?? []
230
+ if (exportDecls.length > 0) {
231
+ const exportDecl = exportDecls[0]!
232
+
233
+ if (
234
+ ts.isVariableDeclaration(exportDecl) &&
235
+ exportDecl.initializer
236
+ ) {
237
+ const { pikkuFuncName } = extractFunctionName(
238
+ exportDecl.initializer,
239
+ checker
240
+ )
241
+ const handlerName = pikkuFuncName
242
+
243
+ const fnMeta = state.functions.meta[handlerName]
244
+ if (fnMeta) {
245
+ result[channelKey]![routeKey] = {
246
+ pikkuFuncName: handlerName,
247
+ inputs: fnMeta.inputs ?? null,
248
+ outputs: fnMeta.outputs ?? null,
249
+ }
250
+ continue
251
+ }
252
+ } else if (ts.isFunctionDeclaration(exportDecl)) {
253
+ const { pikkuFuncName } = extractFunctionName(
254
+ exportDecl,
255
+ checker
256
+ )
257
+ const handlerName = pikkuFuncName
258
+
259
+ const fnMeta = state.functions.meta[handlerName]
260
+ if (fnMeta) {
261
+ result[channelKey]![routeKey] = {
262
+ pikkuFuncName: handlerName,
263
+ inputs: fnMeta.inputs ?? null,
264
+ outputs: fnMeta.outputs ?? null,
265
+ }
266
+ continue
267
+ }
268
+ }
269
+ }
270
+ }
271
+ }
272
+ }
273
+ }
274
+
275
+ // As a fallback, try to look up by name
276
+ const funcName = shorthandDecl.name.getText()
277
+
278
+ // Look for any function in the registry that ends with this name
279
+ const possibleMatch = Object.keys(state.functions.meta).find(
280
+ (key) => {
281
+ const parts = key.split('_')
282
+ const filename = parts[parts.length - 3] || ''
283
+ return filename.endsWith(funcName)
284
+ }
285
+ )
286
+
287
+ if (possibleMatch) {
288
+ const fnMeta = state.functions.meta[possibleMatch]
289
+ if (!fnMeta) {
290
+ console.error(
291
+ `No function metadata found for handler '${possibleMatch}'`
292
+ )
293
+ continue
294
+ }
295
+ result[channelKey]![routeKey] = {
296
+ pikkuFuncName: possibleMatch,
297
+ inputs: fnMeta.inputs ?? null,
298
+ outputs: fnMeta.outputs ?? null,
299
+ }
300
+ continue
301
+ }
302
+ } else {
303
+ // Handle other declaration types (variable, function, etc.)
304
+ let actualFunction: ts.Node | undefined = undefined
305
+
306
+ if (ts.isVariableDeclaration(shorthandDecl)) {
307
+ // Check if it has an initializer
308
+ if (shorthandDecl.initializer) {
309
+ // If it's a function expression or similar, use that
310
+ if (
311
+ ts.isArrowFunction(shorthandDecl.initializer) ||
312
+ ts.isFunctionExpression(shorthandDecl.initializer) ||
313
+ ts.isCallExpression(shorthandDecl.initializer)
314
+ ) {
315
+ actualFunction = shorthandDecl.initializer
316
+ }
317
+ }
318
+ } else if (ts.isFunctionDeclaration(shorthandDecl)) {
319
+ actualFunction = shorthandDecl
320
+ }
321
+
322
+ // If we found the actual function, extract its name
323
+ if (actualFunction) {
324
+ // Extract the function name directly from the actual function
325
+ const { pikkuFuncName } = extractFunctionName(
326
+ actualFunction,
327
+ checker
328
+ )
329
+ const handlerName = pikkuFuncName
330
+
331
+ // Now use this handlerName to look up in the registry
332
+ const fnMeta = state.functions.meta[handlerName]
333
+
334
+ if (fnMeta) {
335
+ result[channelKey]![routeKey] = {
336
+ pikkuFuncName: handlerName,
337
+ inputs: fnMeta.inputs ?? null,
338
+ outputs: fnMeta.outputs ?? null,
339
+ }
340
+ continue // Skip the normal processing below
341
+ }
342
+ }
343
+ }
344
+ }
345
+ }
346
+
347
+ // Normal processing for non-shorthand properties
348
+ const handlerName = getHandlerNameFromExpression(init, checker)
349
+ if (!handlerName) {
350
+ console.error(
351
+ `Could not resolve handler for message route '${routeKey}'`
352
+ )
353
+ continue
354
+ }
355
+
356
+ const fnMeta = state.functions.meta[handlerName]
357
+ if (!fnMeta) {
358
+ console.error(`No function metadata found for handler '${handlerName}'`)
359
+ continue
360
+ }
361
+
362
+ result[channelKey]![routeKey] = {
363
+ pikkuFuncName: handlerName,
364
+ inputs: fnMeta.inputs ?? null,
365
+ outputs: fnMeta.outputs ?? null,
366
+ }
367
+ }
368
+ }
369
+
370
+ return result
371
+ }
372
+
373
+ /**
374
+ * Inspect addChannel calls, look up all handlers in state.functions.meta,
375
+ * and emit one entry into state.channels.meta.
376
+ */
377
+ export function addChannel(
378
+ node: ts.Node,
379
+ checker: ts.TypeChecker,
380
+ state: InspectorState,
381
+ filters: InspectorFilters
382
+ ) {
383
+ if (!ts.isCallExpression(node)) return
384
+ const { expression, arguments: args } = node
385
+ if (!ts.isIdentifier(expression) || expression.text !== 'addChannel') return
386
+ const first = args[0]
387
+ if (!first || !ts.isObjectLiteralExpression(first)) return
388
+
389
+ const obj = first
390
+ const name = getPropertyValue(obj, 'name') as string | undefined
391
+ const route = (getPropertyValue(obj, 'route') as string) ?? ''
392
+
393
+ if (!name) {
394
+ console.error('Channel name is required')
395
+ return
396
+ }
397
+
398
+ // path parameters
399
+ const params = route
400
+ ? pathToRegexp(route)
401
+ .keys.filter((k) => k.type === 'param')
402
+ .map((k) => k.name)
403
+ : []
404
+
405
+ const docs = getPropertyValue(obj, 'docs') as APIDocs | undefined
406
+ const tags = getPropertyValue(obj, 'tags') as string[] | undefined
407
+ const query = getPropertyValue(obj, 'query') as string[] | []
408
+
409
+ if (!matchesFilters(filters, { tags }, { type: 'channel', name })) return
410
+
411
+ const connect = getPropertyAssignmentInitializer(
412
+ obj,
413
+ 'onConnect',
414
+ false,
415
+ checker
416
+ )
417
+ const disconnect = getPropertyAssignmentInitializer(
418
+ obj,
419
+ 'onDisconnect',
420
+ false,
421
+ checker
422
+ )
423
+
424
+ // default onMessage handler
425
+ let message: ChannelMessageMeta | null = null
426
+ const onMsgProp = getPropertyAssignmentInitializer(
427
+ obj,
428
+ 'onMessage',
429
+ false,
430
+ checker
431
+ )
432
+ if (onMsgProp) {
433
+ const handlerName =
434
+ onMsgProp && getHandlerNameFromExpression(onMsgProp, checker)
435
+ const fnMeta = handlerName && state.functions.meta[handlerName]
436
+ if (!fnMeta) {
437
+ console.error(
438
+ `No function metadata for onMessage handler '${handlerName}'`
439
+ )
440
+ throw new Error()
441
+ } else {
442
+ message = {
443
+ pikkuFuncName: extractFunctionName(onMsgProp as any, checker)
444
+ .pikkuFuncName,
445
+ inputs: fnMeta.inputs ?? null,
446
+ outputs: fnMeta.outputs ?? null,
447
+ }
448
+ }
449
+ }
450
+
451
+ // nested message-routes
452
+ const messageRoutes = addMessagesRoutes(obj, state, checker)
453
+
454
+ // record into state
455
+ state.channels.files.add(node.getSourceFile().fileName)
456
+ state.channels.meta[name] = {
457
+ name,
458
+ route,
459
+ input: null,
460
+ params: params.length ? params : undefined,
461
+ query: query?.length ? query : undefined,
462
+ inputTypes: getInputTypes(
463
+ state.channels.metaInputTypes,
464
+ 'get',
465
+ message?.inputs?.[0] ?? null,
466
+ query,
467
+ params
468
+ ),
469
+ connectPikkuFuncName: connect
470
+ ? extractFunctionName(connect, checker).pikkuFuncName
471
+ : null,
472
+ connect: !!connect,
473
+ disconnectPikkuFuncName: disconnect
474
+ ? extractFunctionName(disconnect as any, checker).pikkuFuncName
475
+ : null,
476
+ disconnect: !!disconnect,
477
+ message,
478
+ messageRoutes,
479
+ docs: docs ?? undefined,
480
+ tags: tags ?? undefined,
481
+ }
482
+ }
@@ -0,0 +1,50 @@
1
+ import * as ts from 'typescript'
2
+ import { PathToNameAndType } from './types.js'
3
+
4
+ // const VRAMEWORK_TYPES = ['CoreConfig', 'CoreService', 'CoreServices', 'CoreSingletonService', 'CoreSessionService']
5
+
6
+ export const addFileExtendsCoreType = (
7
+ node: ts.Node,
8
+ checker: ts.TypeChecker,
9
+ methods: PathToNameAndType,
10
+ expectedTypeName: string
11
+ ) => {
12
+ if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
13
+ const fileName = node.getSourceFile().fileName
14
+ const typeName = node.name?.getText()
15
+
16
+ // Check if the class or interface extends the expected type
17
+ if (node.heritageClauses) {
18
+ for (const clause of node.heritageClauses) {
19
+ if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
20
+ for (const type of clause.types) {
21
+ const extendedTypeName = type.expression.getText()
22
+ let extendedTypeDeclarationPath: string | null = null
23
+
24
+ // Check if the extended type matches the expected type name
25
+ if (extendedTypeName === expectedTypeName) {
26
+ // Retrieve the symbol of the extended type
27
+ const typeSymbol = checker.getSymbolAtLocation(type.expression)
28
+ const declaration =
29
+ typeSymbol &&
30
+ typeSymbol.declarations &&
31
+ typeSymbol.declarations[0]
32
+ if (declaration) {
33
+ const sourceFile = declaration.getSourceFile()
34
+ extendedTypeDeclarationPath = sourceFile.fileName // Get the path of the file where the extended type was declared
35
+ }
36
+
37
+ const variables = methods[fileName] || []
38
+ variables.push({
39
+ variable: undefined,
40
+ type: typeName,
41
+ typePath: extendedTypeDeclarationPath,
42
+ })
43
+ methods[fileName] = variables
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,45 @@
1
+ import * as ts from 'typescript'
2
+ import { doesTypeExtendsCore } from './does-type-extend-core-type.js'
3
+ import { PathToNameAndType } from './types.js'
4
+
5
+ export const addFileWithConfig = (
6
+ node: ts.Node,
7
+ checker: ts.TypeChecker,
8
+ configs: PathToNameAndType
9
+ ) => {
10
+ if (ts.isVariableDeclaration(node)) {
11
+ const fileName = node.getSourceFile().fileName
12
+ const variableSymbol = checker.getSymbolAtLocation(node.name)
13
+
14
+ if (variableSymbol) {
15
+ const variableType = checker.getTypeOfSymbolAtLocation(
16
+ variableSymbol,
17
+ node.name
18
+ )
19
+ const variableName = node.name.getText()
20
+ const variableTypeText = node.type?.getText()
21
+
22
+ // Check if the type extends CoreConfig
23
+ if (doesTypeExtendsCore(variableType, checker, new Set(), 'CoreConfig')) {
24
+ // Retrieve the symbol of the type (if it has one)
25
+ const typeSymbol = variableType.symbol
26
+ let typeDeclarationPath: string | null = null
27
+ const declaration =
28
+ typeSymbol && typeSymbol.declarations && typeSymbol.declarations[0]
29
+
30
+ if (declaration) {
31
+ const sourceFile = declaration.getSourceFile()
32
+ typeDeclarationPath = sourceFile.fileName // Get the path of the file where the type was declared
33
+ }
34
+
35
+ const variables = configs[fileName] || []
36
+ variables.push({
37
+ variable: variableName,
38
+ type: variableTypeText || null,
39
+ typePath: typeDeclarationPath,
40
+ })
41
+ configs[fileName] = variables
42
+ }
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,65 @@
1
+ import * as ts from 'typescript'
2
+ import { PathToNameAndType } from './types.js'
3
+
4
+ export const addFileWithFactory = (
5
+ node: ts.Node,
6
+ checker: ts.TypeChecker,
7
+ methods: PathToNameAndType = new Map(),
8
+ expectedTypeName: string
9
+ ) => {
10
+ if (ts.isVariableDeclaration(node)) {
11
+ const fileName = node.getSourceFile().fileName
12
+ const variableTypeNode = node.type
13
+ const variableName = node.name.getText()
14
+
15
+ if (variableTypeNode && ts.isTypeReferenceNode(variableTypeNode)) {
16
+ const typeNameNode = variableTypeNode.typeName || null
17
+
18
+ let typeDeclarationPath: string | null = null
19
+
20
+ // Check if the type name matches the expected type name
21
+ if (
22
+ ts.isIdentifier(typeNameNode) &&
23
+ typeNameNode.text === expectedTypeName
24
+ ) {
25
+ const typeSymbol = checker.getSymbolAtLocation(typeNameNode)
26
+ const declaration =
27
+ typeSymbol && typeSymbol.declarations && typeSymbol.declarations[0]
28
+ if (declaration) {
29
+ const sourceFile = declaration.getSourceFile()
30
+ typeDeclarationPath = sourceFile.fileName // Get the path of the file where the type was declared
31
+ }
32
+
33
+ const variables = methods[fileName] || []
34
+ variables.push({
35
+ variable: variableName,
36
+ type: typeNameNode.getText(),
37
+ typePath: typeDeclarationPath,
38
+ })
39
+ methods[fileName] = variables
40
+ }
41
+
42
+ // Handle qualified type names if necessary
43
+ else if (ts.isQualifiedName(typeNameNode)) {
44
+ const lastName = typeNameNode.right.text
45
+ if (lastName === expectedTypeName) {
46
+ const typeSymbol = checker.getSymbolAtLocation(typeNameNode.right)
47
+ const declaration =
48
+ typeSymbol && typeSymbol.declarations && typeSymbol.declarations[0]
49
+ if (declaration) {
50
+ const sourceFile = declaration.getSourceFile()
51
+ typeDeclarationPath = sourceFile.fileName // Get the path of the file where the type was declared
52
+ }
53
+
54
+ const variables = methods[fileName] || []
55
+ variables.push({
56
+ variable: variableName,
57
+ type: typeNameNode.getText(),
58
+ typePath: typeDeclarationPath,
59
+ })
60
+ methods[fileName] = variables
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }