@pikku/inspector 0.7.0 → 0.7.2
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 +14 -0
- package/dist/add-functions.js +12 -3
- package/package.json +2 -2
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @pikku/inspector
|
|
2
2
|
|
|
3
|
+
## 0.7.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 7acd53a: fix: ignore return type if it's void
|
|
8
|
+
- Updated dependencies [bb59874]
|
|
9
|
+
- @pikku/core@0.7.2
|
|
10
|
+
|
|
11
|
+
## 0.7.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- ebfb786: fix: only inspect function calls with pikku\*func in name
|
|
16
|
+
|
|
3
17
|
## 0.7.0
|
|
4
18
|
|
|
5
19
|
This has changed significantly. The inspector now finds all functions and then links them to events.
|
package/dist/add-functions.js
CHANGED
|
@@ -92,8 +92,8 @@ const getNamesAndTypes = (checker, typesMap, direction, funcName, type) => {
|
|
|
92
92
|
// 1) Handle an explicit void (or undefined) type up front
|
|
93
93
|
if (type.flags & ts.TypeFlags.VoidLike) {
|
|
94
94
|
return {
|
|
95
|
-
names: [
|
|
96
|
-
types: [
|
|
95
|
+
names: [],
|
|
96
|
+
types: [],
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
// 2) For unions, resolve all member names/types
|
|
@@ -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
|
|
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.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"test:coverage": "bash run-tests.sh --coverage"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@pikku/core": "^0.7.
|
|
20
|
+
"@pikku/core": "^0.7.2",
|
|
21
21
|
"path-to-regexp": "^8.2.0",
|
|
22
22
|
"typescript": "^5.6"
|
|
23
23
|
},
|
|
@@ -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
|
+
}
|