cg-code-graph 0.10.1__py3-none-any.whl

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.
Files changed (174) hide show
  1. cg_code_graph-0.10.1.dist-info/METADATA +678 -0
  2. cg_code_graph-0.10.1.dist-info/RECORD +174 -0
  3. cg_code_graph-0.10.1.dist-info/WHEEL +5 -0
  4. cg_code_graph-0.10.1.dist-info/entry_points.txt +3 -0
  5. cg_code_graph-0.10.1.dist-info/licenses/LICENSE +21 -0
  6. cg_code_graph-0.10.1.dist-info/top_level.txt +1 -0
  7. codegraph/__init__.py +2 -0
  8. codegraph/aitools.py +129 -0
  9. codegraph/apps.py +76 -0
  10. codegraph/blindspots.py +428 -0
  11. codegraph/bridges.py +1701 -0
  12. codegraph/cli.py +725 -0
  13. codegraph/concepts.py +362 -0
  14. codegraph/config.py +559 -0
  15. codegraph/core/__init__.py +0 -0
  16. codegraph/core/cache.py +375 -0
  17. codegraph/core/detect.py +80 -0
  18. codegraph/core/extractors.py +187 -0
  19. codegraph/core/fsutil.py +61 -0
  20. codegraph/core/generated.py +575 -0
  21. codegraph/core/model.py +174 -0
  22. codegraph/core/paths.py +175 -0
  23. codegraph/core/plugin.py +160 -0
  24. codegraph/core/store.py +80 -0
  25. codegraph/core/syntax_errors.py +132 -0
  26. codegraph/coverage.py +928 -0
  27. codegraph/doctor.py +453 -0
  28. codegraph/external.py +613 -0
  29. codegraph/indexer.py +336 -0
  30. codegraph/link.py +434 -0
  31. codegraph/lint_async.py +524 -0
  32. codegraph/mcp_server.py +1303 -0
  33. codegraph/parity.py +473 -0
  34. codegraph/parity_structure.py +307 -0
  35. codegraph/payload.py +321 -0
  36. codegraph/plans.py +1285 -0
  37. codegraph/platform_scan.py +643 -0
  38. codegraph/platforms.py +1369 -0
  39. codegraph/plugins/__init__.py +0 -0
  40. codegraph/plugins/cfamily/__init__.py +0 -0
  41. codegraph/plugins/cfamily/plugin.py +930 -0
  42. codegraph/plugins/cfamily/syntax.py +881 -0
  43. codegraph/plugins/dart/__init__.py +0 -0
  44. codegraph/plugins/dart/bridges.py +345 -0
  45. codegraph/plugins/dart/extractor/bin/extract.dart +717 -0
  46. codegraph/plugins/dart/extractor/pubspec.lock +149 -0
  47. codegraph/plugins/dart/extractor/pubspec.yaml +7 -0
  48. codegraph/plugins/dart/http.py +904 -0
  49. codegraph/plugins/dart/models.py +308 -0
  50. codegraph/plugins/dart/plugin.py +625 -0
  51. codegraph/plugins/dart/program.py +907 -0
  52. codegraph/plugins/django/__init__.py +0 -0
  53. codegraph/plugins/django/extras.py +378 -0
  54. codegraph/plugins/django/models.py +508 -0
  55. codegraph/plugins/django/plugin.py +728 -0
  56. codegraph/plugins/django/schemas.py +339 -0
  57. codegraph/plugins/django/shapes.py +216 -0
  58. codegraph/plugins/django/urls.py +603 -0
  59. codegraph/plugins/express/__init__.py +0 -0
  60. codegraph/plugins/express/plugin.py +428 -0
  61. codegraph/plugins/flutter/__init__.py +0 -0
  62. codegraph/plugins/flutter/plugin.py +538 -0
  63. codegraph/plugins/kotlin/__init__.py +0 -0
  64. codegraph/plugins/kotlin/exact.py +457 -0
  65. codegraph/plugins/kotlin/plugin.py +1961 -0
  66. codegraph/plugins/kotlin/reparse.py +234 -0
  67. codegraph/plugins/laravel/__init__.py +0 -0
  68. codegraph/plugins/laravel/broadcast.py +351 -0
  69. codegraph/plugins/laravel/plugin.py +863 -0
  70. codegraph/plugins/laravel/tests.py +262 -0
  71. codegraph/plugins/laravel/values.py +728 -0
  72. codegraph/plugins/native/__init__.py +0 -0
  73. codegraph/plugins/native/gates.py +286 -0
  74. codegraph/plugins/native/runner.py +183 -0
  75. codegraph/plugins/native/scipread.py +194 -0
  76. codegraph/plugins/native/ts.py +54 -0
  77. codegraph/plugins/nest/__init__.py +0 -0
  78. codegraph/plugins/nest/plugin.py +654 -0
  79. codegraph/plugins/nextjs/__init__.py +0 -0
  80. codegraph/plugins/nextjs/plugin.py +336 -0
  81. codegraph/plugins/nuxt/__init__.py +0 -0
  82. codegraph/plugins/nuxt/plugin.py +308 -0
  83. codegraph/plugins/php/__init__.py +0 -0
  84. codegraph/plugins/php/extractor/composer.json +5 -0
  85. codegraph/plugins/php/extractor/composer.lock +76 -0
  86. codegraph/plugins/php/extractor/extract.php +743 -0
  87. codegraph/plugins/php/gating.py +573 -0
  88. codegraph/plugins/php/plugin.py +668 -0
  89. codegraph/plugins/php/strings.py +197 -0
  90. codegraph/plugins/python/__init__.py +0 -0
  91. codegraph/plugins/python/aitools.py +664 -0
  92. codegraph/plugins/python/external.py +245 -0
  93. codegraph/plugins/python/fields.py +107 -0
  94. codegraph/plugins/python/plugin.py +1733 -0
  95. codegraph/plugins/python/refs.py +485 -0
  96. codegraph/plugins/python/roots.py +412 -0
  97. codegraph/plugins/python/socketio.py +210 -0
  98. codegraph/plugins/python/subproc.py +864 -0
  99. codegraph/plugins/python/tests.py +1040 -0
  100. codegraph/plugins/python/values.py +179 -0
  101. codegraph/plugins/pyweb/__init__.py +0 -0
  102. codegraph/plugins/pyweb/plugin.py +1334 -0
  103. codegraph/plugins/pyweb/values.py +68 -0
  104. codegraph/plugins/rust/__init__.py +0 -0
  105. codegraph/plugins/rust/cargo.py +226 -0
  106. codegraph/plugins/rust/plugin.py +980 -0
  107. codegraph/plugins/rust/syntax.py +678 -0
  108. codegraph/plugins/scip/__init__.py +0 -0
  109. codegraph/plugins/scip/importer.py +129 -0
  110. codegraph/plugins/scip/scip.proto +962 -0
  111. codegraph/plugins/scip/scip_pb2.py +97 -0
  112. codegraph/plugins/stubs/__init__.py +0 -0
  113. codegraph/plugins/stubs/plugins.py +38 -0
  114. codegraph/plugins/swift/__init__.py +0 -0
  115. codegraph/plugins/swift/baseurl.py +109 -0
  116. codegraph/plugins/swift/exact.py +415 -0
  117. codegraph/plugins/swift/indexstore.py +209 -0
  118. codegraph/plugins/swift/packages.py +174 -0
  119. codegraph/plugins/swift/plugin.py +2890 -0
  120. codegraph/plugins/ts/__init__.py +0 -0
  121. codegraph/plugins/ts/baseurl.py +185 -0
  122. codegraph/plugins/ts/extractor/extract.mjs +2652 -0
  123. codegraph/plugins/ts/extractor/fw.mjs +685 -0
  124. codegraph/plugins/ts/extractor/package-lock.json +205 -0
  125. codegraph/plugins/ts/extractor/package.json +9 -0
  126. codegraph/plugins/ts/plugin.py +480 -0
  127. codegraph/plugins/tsweb/__init__.py +0 -0
  128. codegraph/plugins/tsweb/common.py +290 -0
  129. codegraph/plugins/tsweb/data.py +276 -0
  130. codegraph/presets/__init__.py +146 -0
  131. codegraph/presets/c_cpp.yaml +9 -0
  132. codegraph/presets/common.yaml +66 -0
  133. codegraph/presets/dart.yaml +9 -0
  134. codegraph/presets/django-ninja.yaml +15 -0
  135. codegraph/presets/django.yaml +25 -0
  136. codegraph/presets/djangorestframework.yaml +17 -0
  137. codegraph/presets/express.yaml +17 -0
  138. codegraph/presets/kotlin.yaml +11 -0
  139. codegraph/presets/laravel.yaml +40 -0
  140. codegraph/presets/nest.yaml +11 -0
  141. codegraph/presets/nextjs.yaml +15 -0
  142. codegraph/presets/nuxt.yaml +9 -0
  143. codegraph/presets/php.yaml +5 -0
  144. codegraph/presets/python.yaml +10 -0
  145. codegraph/presets/rust.yaml +5 -0
  146. codegraph/presets/swift.yaml +10 -0
  147. codegraph/presets/typescript.yaml +13 -0
  148. codegraph/process_runs.py +328 -0
  149. codegraph/protocols/__init__.py +299 -0
  150. codegraph/protocols/builtin.py +67 -0
  151. codegraph/protocols/matchers.py +144 -0
  152. codegraph/protocols/view.py +334 -0
  153. codegraph/query.py +2089 -0
  154. codegraph/realtime.py +260 -0
  155. codegraph/roundtrip.py +346 -0
  156. codegraph/routes.py +442 -0
  157. codegraph/starters.py +218 -0
  158. codegraph/tests_index.py +117 -0
  159. codegraph/viz/__init__.py +0 -0
  160. codegraph/viz/graph.py +369 -0
  161. codegraph/viz/server.py +198 -0
  162. codegraph/viz/static/app.css +148 -0
  163. codegraph/viz/static/app.js +1082 -0
  164. codegraph/viz/static/index.html +81 -0
  165. codegraph/viz/static/layered.js +237 -0
  166. codegraph/viz/static/vendor/VERSIONS.txt +4 -0
  167. codegraph/viz/static/vendor/cose-base.js +3214 -0
  168. codegraph/viz/static/vendor/cytoscape-fcose.js +1549 -0
  169. codegraph/viz/static/vendor/cytoscape.min.js +31 -0
  170. codegraph/viz/static/vendor/layout-base.js +5230 -0
  171. codegraph/viz/tools/package-lock.json +303 -0
  172. codegraph/viz/tools/package.json +7 -0
  173. codegraph/viz/tools/shoot.mjs +165 -0
  174. codegraph/xcode.py +251 -0
@@ -0,0 +1,685 @@
1
+ // Framework facts for code-graph's TypeScript extractor (NestJS, Next.js, Express/Fastify/Koa/Hono, ORMs, env).
2
+ //
3
+ // The extractor resolves symbols; this module only *describes* syntax that server frameworks give meaning to,
4
+ // as plain JSON, and the Python framework plugins (plugins/nest, plugins/nextjs, plugins/express) interpret it:
5
+ //
6
+ // classes decorators (+ evaluated args), constructor params (type, @Inject token), decorated properties,
7
+ // methods (decorators, param decorators/types)
8
+ // calls router-style calls: x.get('/p', ...handlers), x.use('/p', sub), x.register(fn, {prefix}), ...
9
+ // member_calls this.<prop>.<method>(...) and ORM / event / queue calls (prisma.user.findMany, emit('evt'), ...)
10
+ // instances declarations referenced as receivers/arguments (router variables, params, class properties)
11
+ // modules per file: directives ('use server'/'use client'), exports (+ node ids), req.method checks
12
+ // env / config_defs process.env / import.meta.env / ConfigService.get keys, registerAs() namespaces
13
+ //
14
+ // Values are described, never executed: {s} string (URL-template folded by evalStr), {n}, {b}, {arr}, {obj},
15
+ // {fn,node,body}, {ref,key,node}, {call,node,args,recv,ret}, {new,node,args}, {require,module,exp}, {expr}.
16
+ import nodePath from 'node:path'
17
+ const ROUTE_VERBS = new Set(['get', 'post', 'put', 'patch', 'delete', 'del', 'head', 'options', 'all', 'any'])
18
+ const ROUTER_METHODS = new Set([...ROUTE_VERBS, 'use', 'lazyUse', 'route', 'register', 'mount', 'basePath', 'prefix',
19
+ 'setGlobalPrefix', 'enableVersioning', 'useGlobalGuards', 'useGlobalInterceptors', 'useGlobalPipes', 'useGlobalFilters',
20
+ 'on', 'method', 'addHook', 'group', 'routes', 'connectMicroservice'])
21
+ const HTTP_UPPER = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'])
22
+ const DATA_METHODS = new Set([
23
+ // Prisma
24
+ 'findUnique', 'findUniqueOrThrow', 'findFirst', 'findFirstOrThrow', 'findMany', 'create', 'createMany', 'createManyAndReturn',
25
+ 'update', 'updateMany', 'updateManyAndReturn', 'upsert', 'delete', 'deleteMany', 'count', 'aggregate', 'groupBy',
26
+ // TypeORM / Sequelize / Mongoose
27
+ 'find', 'findOne', 'findOneBy', 'findBy', 'findAndCount', 'findAndCountBy', 'findOneOrFail', 'findOneByOrFail', 'findByIds',
28
+ 'save', 'insert', 'remove', 'softDelete', 'softRemove', 'restore', 'increment', 'decrement', 'exist', 'exists', 'existsBy',
29
+ 'createQueryBuilder', 'query', 'findAll', 'findByPk', 'findOrCreate', 'bulkCreate', 'destroy',
30
+ 'findById', 'findByIdAndUpdate', 'findByIdAndDelete', 'findOneAndUpdate', 'findOneAndDelete', 'findOneAndReplace',
31
+ 'updateOne', 'deleteOne', 'insertMany', 'countDocuments', 'estimatedDocumentCount', 'distinct', 'replaceOne', 'bulkWrite',
32
+ // Kysely / Knex / Drizzle
33
+ 'selectFrom', 'insertInto', 'updateTable', 'deleteFrom', 'replaceInto', 'mergeInto', 'from', 'into', 'table',
34
+ 'innerJoin', 'leftJoin', 'rightJoin', 'fullJoin',
35
+ ])
36
+ const EVENT_METHODS = new Set(['emit', 'emitAsync', 'add', 'addBulk', 'send', 'publish', 'dispatch'])
37
+
38
+ export function collectFrameworkFacts(X) {
39
+ const { ts, checker, sourceFiles, rel, realFile, lineOf, declId, declToNode, resolveSymbol, evalStr, render, unwrap, projectSf, fileNode } = X
40
+ const classes = [], calls = [], memberCalls = [], env = [], configDefs = [], modules = {}, provideObjs = []
41
+ const instances = {}
42
+ let budget = 400000 // describe() node budget (very large repos)
43
+
44
+ // source file a require()/import specifier points at; relative specs are also resolved by path, because a .ts file
45
+ // without import/export statements (only `module.exports = ...`) is a script, not a module, for the checker
46
+ const sfByPath = new Map(sourceFiles.map(sf => [sf.fileName, sf]))
47
+ function sfOfSpec(spec) {
48
+ try {
49
+ const sym = checker.getSymbolAtLocation(spec)
50
+ const d = sym && (sym.declarations || [])[0]
51
+ if (d && ts.isSourceFile(d)) return d
52
+ } catch { }
53
+ const t = spec.text
54
+ if (!t || !t.startsWith('.')) return null
55
+ const base = nodePath.resolve(nodePath.dirname(spec.getSourceFile().fileName), t)
56
+ for (const ext of ['', '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '/index.ts', '/index.tsx', '/index.js', '/index.jsx']) {
57
+ const sf = sfByPath.get(base + ext)
58
+ if (sf) return sf
59
+ }
60
+ return null
61
+ }
62
+ const text = (n, max = 120) => { try { return n.getText().replace(/\s+/g, ' ').slice(0, max) } catch { return '?' } }
63
+ const isStrish = e => e && (ts.isStringLiteralLike(e) || ts.isTemplateExpression(e) ||
64
+ (ts.isBinaryExpression(e) && e.operatorToken.kind === ts.SyntaxKind.PlusToken))
65
+ const rootIdent = e => { e = unwrap(e); while (e && (ts.isPropertyAccessExpression(e) || ts.isElementAccessExpression(e) || ts.isCallExpression(e))) e = unwrap(e.expression); return e && ts.isIdentifier(e) ? e : null }
66
+
67
+ function importSource(expr) {
68
+ const id = rootIdent(expr)
69
+ if (!id) return null
70
+ let sym
71
+ try { sym = checker.getSymbolAtLocation(id) } catch { return null }
72
+ const d = sym && (sym.declarations || [])[0]
73
+ if (!d) return null
74
+ let p = d
75
+ if (ts.isImportSpecifier(p) || ts.isImportClause(p) || ts.isNamespaceImport(p) || ts.isNamedImports(p)) {
76
+ while (p && !ts.isImportDeclaration(p)) p = p.parent
77
+ return p && ts.isStringLiteral(p.moduleSpecifier) ? p.moduleSpecifier.text : null
78
+ }
79
+ if (ts.isImportEqualsDeclaration(p) && p.moduleReference && ts.isExternalModuleReference(p.moduleReference)) return p.moduleReference.expression.text
80
+ if (ts.isBindingElement(p)) { while (p && !ts.isVariableDeclaration(p)) p = p.parent }
81
+ if (p && ts.isVariableDeclaration(p) && p.initializer) {
82
+ let i = unwrap(p.initializer)
83
+ while (i && ts.isPropertyAccessExpression(i)) i = unwrap(i.expression)
84
+ if (i && ts.isAwaitExpression(i)) i = unwrap(i.expression)
85
+ if (i && ts.isCallExpression(i) && ((ts.isIdentifier(i.expression) && i.expression.text === 'require') || i.expression.kind === ts.SyntaxKind.ImportKeyword) && i.arguments[0] && ts.isStringLiteralLike(i.arguments[0])) return i.arguments[0].text
86
+ }
87
+ return null
88
+ }
89
+ X.importSource = importSource
90
+
91
+ function symOf(e) {
92
+ e = unwrap(e)
93
+ try {
94
+ if (ts.isIdentifier(e)) return e.parent && ts.isShorthandPropertyAssignment(e.parent) && e.parent.name === e
95
+ ? checker.getShorthandAssignmentValueSymbol(e.parent) : checker.getSymbolAtLocation(e)
96
+ if (ts.isPropertyAccessExpression(e)) return checker.getSymbolAtLocation(e.name)
97
+ } catch { }
98
+ return null
99
+ }
100
+ // method names chained after a call: db('t').where(..).insert(..) -> ['where', 'insert']
101
+ function chainAfter(call) {
102
+ const out = []
103
+ for (let n = call; out.length < 12; ) {
104
+ const pa = n.parent
105
+ if (!pa || !ts.isPropertyAccessExpression(pa) || pa.expression !== n || !pa.parent || !ts.isCallExpression(pa.parent)) break
106
+ out.push(pa.name.text); n = pa.parent
107
+ }
108
+ return out
109
+ }
110
+ // `registry.posts.browse` where `registry.posts` is a getter / property whose value wraps `require('./posts')`
111
+ // (lazy controller registries: `get posts() { return pipeline(require('./posts'), utils) }`): the member of the
112
+ // required module's exported object
113
+ const isRequireCall = n => n && ts.isCallExpression(n) && ts.isIdentifier(n.expression) && n.expression.text === 'require' && n.arguments[0] && ts.isStringLiteralLike(n.arguments[0])
114
+ // object literal an expression evaluates to (identifier -> initializer, require('./x') -> the module's exported object)
115
+ function objOfExpr(o, depth = 0) {
116
+ o = o && unwrap(o)
117
+ if (!o || depth > 3) return null
118
+ if (ts.isIdentifier(o)) { const d2 = origDecl(checker.getSymbolAtLocation(o)); return d2 && ts.isVariableDeclaration(d2) && d2.initializer ? objOfExpr(d2.initializer, depth + 1) : null }
119
+ if (isRequireCall(o)) { const r = objOfModule(o.arguments[0], null, depth + 1); return r && r.named === undefined ? r : null }
120
+ return ts.isObjectLiteralExpression(o) ? o : null
121
+ }
122
+ function objOfModule(spec, named, depth = 0) {
123
+ try {
124
+ if (depth > 3) return null
125
+ const sfd = sfOfSpec(spec)
126
+ if (!sfd || !projectSf(sfd)) return null
127
+ const ms = sfd.symbol ? checker.getMergedSymbol(sfd.symbol) : {}
128
+ const objOf = d => {
129
+ if (d && ts.isPropertyAccessExpression(d) && d.parent && ts.isBinaryExpression(d.parent)) d = d.parent // module.exports.x = ...
130
+ const o = d && (ts.isBinaryExpression(d) ? d.right : ts.isVariableDeclaration(d) ? d.initializer : ts.isExportAssignment(d) ? d.expression : null)
131
+ return objOfExpr(o, depth + 1)
132
+ }
133
+ // require('./x').controller on an ES module: the named export
134
+ if (named && ms.exports && ms.exports.get(named) && !ms.exports.get('export=')) return { named: objOf(origDecl(ms.exports.get(named))) }
135
+ const eq = ms.exports && ms.exports.get('export=')
136
+ if (eq) return objOf(origDecl(eq))
137
+ // `module.exports = x` in a .ts file (not a CommonJS export for the checker, but what require() returns at runtime)
138
+ for (const st of sfd.statements) {
139
+ const e = ts.isExpressionStatement(st) && st.expression
140
+ if (e && ts.isBinaryExpression(e) && e.operatorToken.kind === ts.SyntaxKind.EqualsToken && e.left.getText(sfd) === 'module.exports') return objOf(e)
141
+ }
142
+ return null
143
+ } catch { return null }
144
+ }
145
+ // `X.prop` where X comes from `const X = require('./x')` (or `require('./x').member`) in a file where the checker
146
+ // does not treat require() as an import (.ts files): the declaration of `prop` in the required module's object
147
+ function holderViaRequire(expr) {
148
+ expr = unwrap(expr)
149
+ if (!ts.isPropertyAccessExpression(expr) || !ts.isIdentifier(expr.expression)) return null
150
+ let sym; try { sym = checker.getSymbolAtLocation(expr.expression) } catch { return null }
151
+ const vd = sym && (sym.declarations || [])[0]
152
+ let init = vd && ts.isVariableDeclaration(vd) && vd.initializer && unwrap(vd.initializer)
153
+ let member = null
154
+ if (init && ts.isPropertyAccessExpression(init)) { member = init.name.text; init = unwrap(init.expression) }
155
+ if (!isRequireCall(init)) return null
156
+ let o = objOfModule(init.arguments[0], member)
157
+ if (o && o.named !== undefined) o = o.named
158
+ else if (o && member) {
159
+ const p = o.properties.find(p => p.name && p.name.getText().replace(/['"]/g, '') === member)
160
+ o = p && ts.isPropertyAssignment(p) ? objOfExpr(p.initializer) : null
161
+ }
162
+ return o ? o.properties.find(p => p.name && p.name.getText().replace(/['"]/g, '') === expr.name.text) || null : null
163
+ }
164
+ function memberThroughRegistry(e) {
165
+ const holder = symOf(e.expression)
166
+ let hd = holder && (holder.declarations || [])[0]
167
+ if (!hd || !projectSf(hd.getSourceFile())) hd = holderViaRequire(e.expression)
168
+ if (!hd || !projectSf(hd.getSourceFile())) return null
169
+ const fnLike = ts.isGetAccessorDeclaration(hd) || ts.isMethodDeclaration(hd) ? hd : null
170
+ const exprs = fnLike ? returnsOf(fnLike) : (ts.isPropertyAssignment(hd) && hd.initializer ? [hd.initializer] : [])
171
+ for (const r of exprs) {
172
+ let found = null
173
+ const v = n => {
174
+ if (found) return
175
+ if (ts.isCallExpression(n) && ts.isIdentifier(n.expression) && n.expression.text === 'require' && n.arguments[0] && ts.isStringLiteralLike(n.arguments[0])) {
176
+ const named = n.parent && ts.isPropertyAccessExpression(n.parent) && n.parent.expression === n ? n.parent.name.text : null
177
+ let o = objOfModule(n.arguments[0], named)
178
+ if (o && o.named !== undefined) o = o.named
179
+ // require('./x').controller
180
+ else if (o && named) {
181
+ const p = o.properties.find(p => p.name && p.name.getText() === n.parent.name.text)
182
+ const i = p && ts.isPropertyAssignment(p) ? unwrap(p.initializer) : null
183
+ o = i && ts.isObjectLiteralExpression(i) ? i : (i && ts.isIdentifier(i) ? (() => { const d = origDecl(symOf(i)); const x = d && ts.isVariableDeclaration(d) && d.initializer && unwrap(d.initializer); return x && ts.isObjectLiteralExpression(x) ? x : null })() : null)
184
+ }
185
+ if (o) found = o
186
+ return
187
+ }
188
+ ts.forEachChild(n, v)
189
+ }
190
+ v(r)
191
+ if (!found) continue
192
+ const p = found.properties.find(p => p.name && p.name.getText().replace(/['"]/g, '') === e.name.text)
193
+ if (!p) return null
194
+ const init = ts.isPropertyAssignment(p) ? unwrap(p.initializer) : null
195
+ const node = declId.get(p) || (init && declId.get(init))
196
+ if (node) return { node, via_registry: true }
197
+ if (init && ts.isObjectLiteralExpression(init)) {
198
+ const fns = init.properties.map(q => declId.get(q) || (ts.isPropertyAssignment(q) && declId.get(unwrap(q.initializer)))).filter(Boolean)
199
+ if (fns.length) return { obj_fns: fns, via_registry: true }
200
+ }
201
+ return null
202
+ }
203
+ return null
204
+ }
205
+ // original declaration behind imports / re-exports / export= / `const a = b` aliases
206
+ function origDecl(sym) {
207
+ for (let i = 0; sym && i < 12; i++) {
208
+ if (sym.flags & ts.SymbolFlags.Alias) { try { sym = checker.getAliasedSymbol(sym) } catch { return null } continue }
209
+ const ds = sym.declarations || []
210
+ const d = ds.find(x => projectSf(x.getSourceFile())) || ds[0]
211
+ if (!d) return null
212
+ if (ts.isShorthandPropertyAssignment(d)) { sym = checker.getShorthandAssignmentValueSymbol(d); continue }
213
+ if (ts.isExportSpecifier(d)) { try { sym = checker.getExportSpecifierLocalTargetSymbol(d) } catch { return null } continue }
214
+ if (ts.isExportAssignment(d)) { const ex = unwrap(d.expression); if (ts.isIdentifier(ex)) { sym = checker.getSymbolAtLocation(ex); continue } return d }
215
+ if (ts.isBinaryExpression(d) && d.operatorToken.kind === ts.SyntaxKind.EqualsToken) { const r = unwrap(d.right); if (ts.isIdentifier(r)) { sym = checker.getSymbolAtLocation(r); continue } return d }
216
+ if (ts.isVariableDeclaration(d) && d.initializer && projectSf(d.getSourceFile())) {
217
+ const init = unwrap(d.initializer)
218
+ if (ts.isIdentifier(init)) { const s2 = checker.getSymbolAtLocation(init); if (s2 && s2 !== sym) { sym = s2; continue } }
219
+ }
220
+ return d
221
+ }
222
+ return null
223
+ }
224
+ function keyOfDecl(d) {
225
+ const sf = d.getSourceFile()
226
+ if (!projectSf(sf)) return null
227
+ let nm = ''
228
+ try { nm = d.name ? d.name.getText(sf) : (ts.isBinaryExpression(d) ? d.left.getText(sf) : '') } catch { }
229
+ return `${rel(realFile(sf))}:${lineOf(d, sf)}:${nm.slice(0, 60)}`
230
+ }
231
+ function enclosingFnNode(n) {
232
+ for (let p = n.parent; p; p = p.parent) if (declId.has(p)) return declId.get(p)
233
+ return null
234
+ }
235
+ function addInstance(key, d) {
236
+ if (instances[key] !== undefined) return
237
+ instances[key] = null
238
+ const sf = d.getSourceFile()
239
+ const inst = { file: rel(realFile(sf)), line: lineOf(d, sf), name: d.name ? text(d.name, 60) : null }
240
+ if (ts.isParameter(d)) {
241
+ inst.kind = 'param'
242
+ const fn = d.parent
243
+ inst.fn = declToNode(fn) || enclosingFnNode(d)
244
+ inst.index = fn.parameters ? fn.parameters.indexOf(d) : null
245
+ if (d.type) inst.type = text(d.type, 80)
246
+ } else if (ts.isVariableDeclaration(d) || ts.isPropertyDeclaration(d) || ts.isPropertyAssignment(d)) {
247
+ inst.kind = ts.isVariableDeclaration(d) ? 'var' : 'prop'
248
+ if (d.initializer) inst.init = describe(d.initializer, 1)
249
+ if (d.type) inst.type = text(d.type, 80)
250
+ } else if (ts.isBinaryExpression(d)) {
251
+ inst.kind = 'assign'; inst.init = describe(d.right, 1)
252
+ } else if (ts.isExportAssignment(d)) {
253
+ inst.kind = 'var'; inst.name = 'default'; inst.init = describe(d.expression, 1)
254
+ } else inst.kind = ts.SyntaxKind[d.kind]
255
+ instances[key] = inst
256
+ }
257
+ // returned expressions of a project function (1 level)
258
+ function returnsOf(fn) {
259
+ if (!fn) return []
260
+ if (ts.isArrowFunction(fn) && !ts.isBlock(fn.body)) return [fn.body]
261
+ const out = []
262
+ const v = n => { if (ts.isReturnStatement(n) && n.expression) out.push(n.expression); if (!ts.isFunctionLike(n)) ts.forEachChild(n, v) }
263
+ if (fn.body) ts.forEachChild(fn.body, v)
264
+ return out.slice(0, 4)
265
+ }
266
+ function fnDeclFromSym(sym) {
267
+ const d = origDecl(sym)
268
+ if (!d) return null
269
+ if (ts.isFunctionDeclaration(d) || ts.isMethodDeclaration(d) || ts.isArrowFunction(d) || ts.isFunctionExpression(d)) return d
270
+ if ((ts.isVariableDeclaration(d) || ts.isPropertyDeclaration(d) || ts.isPropertyAssignment(d)) && d.initializer) {
271
+ const i = unwrap(d.initializer)
272
+ if (ts.isArrowFunction(i) || ts.isFunctionExpression(i)) return i
273
+ }
274
+ if (ts.isBinaryExpression(d)) { const r = unwrap(d.right); if (ts.isArrowFunction(r) || ts.isFunctionExpression(r)) return r }
275
+ if (ts.isExportAssignment(d)) { const r = unwrap(d.expression); if (ts.isArrowFunction(r) || ts.isFunctionExpression(r)) return r }
276
+ return null
277
+ }
278
+ function moduleExport(spec, from) {
279
+ // require('./x') / import('./x'): the module's export= / default export value
280
+ try {
281
+ const d = sfOfSpec(spec)
282
+ if (!d || !projectSf(d)) return null
283
+ const ms = d.symbol ? checker.getMergedSymbol(d.symbol) : null
284
+ const exps = ms ? checker.getExportsOfModule(ms) : []
285
+ const ex = (ms && ms.exports && ms.exports.get('export=')) || exps.find(s => s.name === 'default')
286
+ const res = { module: fileNode.get(realFile(d)) || null }
287
+ if (!ex) {
288
+ // `module.exports = x` in a .ts file: not an export for the checker, but what require() returns at runtime
289
+ for (const st of d.statements) {
290
+ const e = ts.isExpressionStatement(st) && st.expression
291
+ if (e && ts.isBinaryExpression(e) && e.operatorToken.kind === ts.SyntaxKind.EqualsToken && e.left.getText(d) === 'module.exports') { res.exp = describe(e.right, 2); break }
292
+ }
293
+ }
294
+ if (ex) {
295
+ const od = origDecl(ex)
296
+ if (od) {
297
+ if (ts.isBinaryExpression(od)) res.exp = describe(od.right, 2)
298
+ else if (ts.isExportAssignment(od)) res.exp = describe(od.expression, 2)
299
+ else if (ts.isFunctionDeclaration(od)) res.exp = { fn: declToNode(od), ret: returnsOf(od).map(r => describe(r, 3)) }
300
+ else { const k = keyOfDecl(od); if (k) { addInstance(k, od); res.exp = { ref: od.name ? text(od.name, 60) : '?', key: k, node: declToNode(od) } } }
301
+ }
302
+ }
303
+ return res
304
+ } catch { return null }
305
+ }
306
+
307
+ function describe(e, depth = 0) {
308
+ e = unwrap(e)
309
+ if (!e) return null
310
+ if (--budget < 0 || depth > 7) return { expr: text(e, 60) }
311
+ if (isStrish(e)) {
312
+ const v = evalStr(e)
313
+ return v.vals.length === 1 ? { s: render(v.vals[0]), conf: v.conf } : { ss: v.vals.map(render), conf: v.conf }
314
+ }
315
+ if (ts.isNumericLiteral(e)) return { n: Number(e.text) }
316
+ if (e.kind === ts.SyntaxKind.TrueKeyword || e.kind === ts.SyntaxKind.FalseKeyword) return { b: e.kind === ts.SyntaxKind.TrueKeyword }
317
+ if (e.kind === ts.SyntaxKind.NullKeyword || (ts.isIdentifier(e) && e.text === 'undefined')) return { nul: 1 }
318
+ if (ts.isRegularExpressionLiteral(e)) return { re: e.text }
319
+ if (ts.isArrayLiteralExpression(e)) return { arr: e.elements.slice(0, 80).map(x => ts.isSpreadElement(x) ? { spread: describe(x.expression, depth + 1) } : describe(x, depth + 1)) }
320
+ if (ts.isObjectLiteralExpression(e)) {
321
+ const o = {}
322
+ for (const p of e.properties.slice(0, 60)) {
323
+ if (ts.isPropertyAssignment(p)) o[p.name.getText().replace(/^['"`]|['"`]$/g, '')] = describe(p.initializer, depth + 1)
324
+ else if (ts.isShorthandPropertyAssignment(p)) o[p.name.text] = describe(p.name, depth + 1)
325
+ else if (ts.isMethodDeclaration(p) && p.name) { const rs = depth < 3 ? returnsOf(p) : []; o[p.name.getText()] = { fn: declToNode(p), line: lineOf(p), ret: rs.length ? rs.map(x => describe(x, depth + 2)) : undefined } }
326
+ else if (ts.isSpreadAssignment(p)) o['...' + Object.keys(o).length] = { spread: describe(p.expression, depth + 1) }
327
+ }
328
+ return { obj: o, line: lineOf(e) }
329
+ }
330
+ if (ts.isArrowFunction(e) || ts.isFunctionExpression(e)) {
331
+ const out = { fn: declToNode(e) || null, line: lineOf(e) }
332
+ if (ts.isArrowFunction(e) && !ts.isBlock(e.body) && depth < 4) out.body = describe(e.body, depth + 1)
333
+ else if (depth < 3) { const rs = returnsOf(e); if (rs.length) out.ret = rs.map(r => describe(r, depth + 2)) }
334
+ return out
335
+ }
336
+ if (ts.isClassExpression(e)) return { cls: declToNode(e) || null }
337
+ if (ts.isIdentifier(e) || ts.isPropertyAccessExpression(e) || ts.isElementAccessExpression(e)) {
338
+ if (ts.isPropertyAccessExpression(e) || ts.isIdentifier(e)) {
339
+ const v = evalStr(e)
340
+ if (v.vals.length && v.vals.every(x => !/\u0001/.test(x)) && v.vals.length <= 8) return v.vals.length === 1 ? { s: v.vals[0], conf: v.conf, ref: text(e, 80) } : { ss: v.vals, conf: v.conf, ref: text(e, 80) }
341
+ }
342
+ const out = { ref: text(e, 80) }
343
+ const sym = symOf(e)
344
+ if (sym) {
345
+ const t = resolveSymbol(sym)
346
+ if (t) out.node = t.id
347
+ const d = origDecl(sym)
348
+ if (d) {
349
+ const k = keyOfDecl(d)
350
+ if (k) { out.key = k; addInstance(k, d) }
351
+ if (!out.node && (ts.isPropertyAssignment(d) || ts.isVariableDeclaration(d)) && d.initializer && ts.isObjectLiteralExpression(unwrap(d.initializer))) {
352
+ // handler objects (e.g. { permissions, query(frame) {} }): the functions declared directly in it
353
+ const fns = unwrap(d.initializer).properties.map(p => declId.get(p) || (ts.isPropertyAssignment(p) && declId.get(unwrap(p.initializer)))).filter(Boolean)
354
+ if (fns.length) out.obj_fns = fns
355
+ }
356
+ }
357
+ }
358
+ if (ts.isPropertyAccessExpression(e) && !out.node && !out.obj_fns) {
359
+ const t = memberThroughRegistry(e)
360
+ if (t) Object.assign(out, t)
361
+ }
362
+ if (ts.isPropertyAccessExpression(e) && !out.node && !out.key && depth < 4) out.of = describe(e.expression, depth + 1)
363
+ if (!out.node && !out.key && ts.isIdentifier(e)) { const m = importSource(e); if (m) out.mod = m } // imported from a package
364
+ return out
365
+ }
366
+ if (ts.isCallExpression(e)) {
367
+ const c = unwrap(e.expression)
368
+ if (ts.isIdentifier(c) && c.text === 'require' && e.arguments[0] && ts.isStringLiteralLike(e.arguments[0])) {
369
+ return { require: e.arguments[0].text, ...(moduleExport(e.arguments[0], e) || {}) }
370
+ }
371
+ const out = { call: text(c, 80) }
372
+ const mod = importSource(c); if (mod) out.mod = mod
373
+ const sym = symOf(ts.isPropertyAccessExpression(c) ? c : c)
374
+ if (sym) {
375
+ const t = resolveSymbol(sym); if (t) out.node = t.id
376
+ if (depth < 3) { const fd = fnDeclFromSym(sym); if (fd && projectSf(fd.getSourceFile())) { const rs = returnsOf(fd); if (rs.length) out.ret = rs.map(r => describe(r, depth + 2)) } }
377
+ // `const f = require('./x'); f()`: the function exported by the module (through `module.exports = require(..)` chains)
378
+ if (!out.ret && depth < 3 && ts.isIdentifier(c)) {
379
+ const vd = (sym.declarations || [])[0]
380
+ const ri = vd && ts.isVariableDeclaration(vd) && vd.initializer && unwrap(vd.initializer)
381
+ if (ri && ts.isCallExpression(ri) && ts.isIdentifier(ri.expression) && ri.expression.text === 'require' && ri.arguments[0] && ts.isStringLiteralLike(ri.arguments[0])) {
382
+ let x = (moduleExport(ri.arguments[0], ri) || {}).exp
383
+ for (let i = 0; x && x.require && i < 4; i++) x = x.exp
384
+ if (x && x.ret) out.ret = x.ret
385
+ if (x && x.fn && !out.node) out.node = x.fn
386
+ }
387
+ }
388
+ }
389
+ out.args = e.arguments.slice(0, 12).map(a => describe(a, depth + 1))
390
+ if (ts.isPropertyAccessExpression(c)) out.recv = describe(c.expression, depth + 1)
391
+ return out
392
+ }
393
+ if (ts.isNewExpression(e)) {
394
+ const out = { new: text(e.expression, 80) }
395
+ const mod = importSource(e.expression); if (mod) out.mod = mod
396
+ const sym = symOf(e.expression); if (sym) { const t = resolveSymbol(sym); if (t) out.node = t.id }
397
+ out.args = (e.arguments || []).slice(0, 8).map(a => describe(a, depth + 1))
398
+ return out
399
+ }
400
+ if (ts.isAwaitExpression(e)) return describe(e.expression, depth)
401
+ if (ts.isConditionalExpression(e)) return { cond: [describe(e.whenTrue, depth + 1), describe(e.whenFalse, depth + 1)] }
402
+ if (ts.isBinaryExpression(e) && [ts.SyntaxKind.BarBarToken, ts.SyntaxKind.QuestionQuestionToken].includes(e.operatorToken.kind))
403
+ return { cond: [describe(e.left, depth + 1), describe(e.right, depth + 1)] }
404
+ return { expr: text(e, 60) }
405
+ }
406
+ X.describe = describe
407
+
408
+ function typeInfo(tn) {
409
+ if (!tn) return null
410
+ const out = { text: text(tn, 100) }
411
+ let ref = tn
412
+ while (ref && (ts.isArrayTypeNode(ref) || ts.isParenthesizedTypeNode(ref))) ref = ref.elementType || ref.type
413
+ if (ref && ts.isUnionTypeNode(ref)) ref = ref.types.find(t => ts.isTypeReferenceNode(t)) || ref
414
+ if (ref && ts.isTypeReferenceNode(ref)) {
415
+ out.name = ts.isQualifiedName(ref.typeName) ? ref.typeName.right.text : ref.typeName.text
416
+ const sym = checker.getSymbolAtLocation(ts.isQualifiedName(ref.typeName) ? ref.typeName.right : ref.typeName)
417
+ const t = sym && resolveSymbol(sym); if (t) out.id = t.id
418
+ const mod = importSource(ts.isQualifiedName(ref.typeName) ? ref.typeName.left : ref.typeName); if (mod) out.mod = mod
419
+ if (ref.typeArguments) out.args = ref.typeArguments.slice(0, 3).map(typeInfo)
420
+ }
421
+ if (ts.isArrayTypeNode(tn)) out.array = true
422
+ return out
423
+ }
424
+ function decs(n) {
425
+ let ds = []
426
+ try { ds = (ts.getDecorators ? ts.getDecorators(n) : n.decorators) || [] } catch { ds = [] }
427
+ return ds.map(d => {
428
+ const e = unwrap(d.expression)
429
+ if (ts.isCallExpression(e)) return { name: text(e.expression, 60), mod: importSource(e.expression) || undefined, args: e.arguments.map(a => describe(a, 0)), line: lineOf(d) }
430
+ return { name: text(e, 60), mod: importSource(e) || undefined, args: [], line: lineOf(d) }
431
+ })
432
+ }
433
+ const hasMod = (n, k) => !!(ts.getCombinedModifierFlags(n) & k)
434
+
435
+ // ---------- classes ----------
436
+ for (const sf of sourceFiles) {
437
+ if (realFile(sf).endsWith('.vue')) continue
438
+ const visit = n => {
439
+ if ((ts.isClassDeclaration(n) || ts.isClassExpression(n)) && declId.has(n)) {
440
+ const c = { id: declId.get(n), name: n.name ? n.name.text : null, file: rel(realFile(sf)), line: lineOf(n, sf),
441
+ abstract: hasMod(n, ts.ModifierFlags.Abstract) || undefined, decorators: decs(n), ctor: [], props: [], methods: [] }
442
+ for (const h of n.heritageClauses || []) {
443
+ for (const t of h.types) {
444
+ const sym = checker.getSymbolAtLocation(t.expression)
445
+ const r = sym && resolveSymbol(sym)
446
+ const info = { text: text(t, 100), id: r ? r.id : undefined, mod: importSource(t.expression) || undefined,
447
+ args: t.typeArguments ? t.typeArguments.map(typeInfo) : undefined }
448
+ if (ts.isCallExpression(unwrap(t.expression))) info.call = describe(t.expression, 1)
449
+ if (h.token === ts.SyntaxKind.ExtendsKeyword) c.extends = info; else (c.implements = c.implements || []).push(info)
450
+ }
451
+ }
452
+ for (const m of n.members) {
453
+ if (ts.isConstructorDeclaration(m)) {
454
+ c.ctor_line = lineOf(m, sf)
455
+ for (const p of m.parameters) c.ctor.push({ name: text(p.name, 60), line: lineOf(p, sf), type: typeInfo(p.type), decorators: decs(p),
456
+ prop: hasMod(p, ts.ModifierFlags.ParameterPropertyModifier) || undefined })
457
+ } else if (ts.isPropertyDeclaration(m) && m.name) {
458
+ const ds = decs(m)
459
+ const pr = { name: text(m.name, 60), line: lineOf(m, sf), type: typeInfo(m.type), decorators: ds, optional: !!m.questionToken || undefined,
460
+ node: declId.get(m) || undefined, static: hasMod(m, ts.ModifierFlags.Static) || undefined }
461
+ if (m.initializer && (ds.length || !declId.get(m))) pr.init = describe(m.initializer, 1)
462
+ c.props.push(pr)
463
+ } else if ((ts.isMethodDeclaration(m) || ts.isGetAccessor(m)) && m.name) {
464
+ c.methods.push({ name: text(m.name, 60), id: declId.get(m) || undefined, line: lineOf(m, sf), decorators: decs(m),
465
+ static: hasMod(m, ts.ModifierFlags.Static) || undefined,
466
+ params: m.parameters.map(p => ({ name: text(p.name, 60), type: typeInfo(p.type), decorators: decs(p) })) })
467
+ }
468
+ }
469
+ classes.push(c)
470
+ }
471
+ ts.forEachChild(n, visit)
472
+ }
473
+ ts.forEachChild(sf, visit)
474
+ }
475
+
476
+ // ---------- calls / member calls / env / modules ----------
477
+ const isProcessEnv = e => { e = unwrap(e); return e && ((ts.isPropertyAccessExpression(e) && e.name.text === 'env' && /^(process|Bun)$/.test(text(e.expression))) || text(e) === 'import.meta.env') }
478
+ const thisProp = e => { e = unwrap(e); return e && ts.isPropertyAccessExpression(e) && unwrap(e.expression).kind === ts.SyntaxKind.ThisKeyword ? e.name.text : null }
479
+ function chainOf(e) {
480
+ // a.b.c -> ['a','b','c'] (identifiers / this only), else null
481
+ const out = []
482
+ e = unwrap(e)
483
+ while (e && ts.isPropertyAccessExpression(e)) { out.unshift(e.name.text); e = unwrap(e.expression) }
484
+ if (e && ts.isIdentifier(e)) out.unshift(e.text)
485
+ else if (e && e.kind === ts.SyntaxKind.ThisKeyword) out.unshift('this')
486
+ else return null
487
+ return out
488
+ }
489
+ function classOf(n) { for (let p = n.parent; p; p = p.parent) if (ts.isClassLike(p)) return declId.get(p) || null; return null }
490
+
491
+ for (const sf of sourceFiles) {
492
+ const real = realFile(sf), r = rel(real), fid = fileNode.get(real), isVue = real.endsWith('.vue')
493
+ const mod = { directives: [], server_fns: [], method_checks: [] }
494
+ for (const st of sf.statements) {
495
+ if (ts.isExpressionStatement(st) && ts.isStringLiteral(st.expression)) mod.directives.push(st.expression.text); else break
496
+ }
497
+ const stack = [fid]
498
+ const visit = node => {
499
+ const own = !isVue && declId.get(node)
500
+ if (own) stack.push(own)
501
+ const cur = stack[stack.length - 1]
502
+ // DI provider objects anywhere ({ provide: TOKEN, useClass | useValue | useFactory | useExisting }): providers
503
+ // listed through spread arrays, custom provider files and dynamic modules (static forRoot() { return {...} })
504
+ if (ts.isObjectLiteralExpression(node) && budget > 0 && node.properties.some(p => ts.isPropertyAssignment(p) && p.name && ts.isIdentifier(p.name) && p.name.text === 'provide'))
505
+ provideObjs.push({ file: r, line: lineOf(node, sf), desc: describe(node, 1) })
506
+ // inline 'use server' functions (server actions declared in components)
507
+ if (own && ts.isFunctionLike(node) && node.body && ts.isBlock(node.body)) {
508
+ const s0 = node.body.statements[0]
509
+ if (s0 && ts.isExpressionStatement(s0) && ts.isStringLiteral(s0.expression) && s0.expression.text === 'use server') mod.server_fns.push(own)
510
+ } else if (own && (ts.isVariableDeclaration(node) || ts.isPropertyAssignment(node)) && node.initializer) {
511
+ const f = unwrap(node.initializer)
512
+ if (f && (ts.isArrowFunction(f) || ts.isFunctionExpression(f)) && f.body && ts.isBlock(f.body)) {
513
+ const s0 = f.body.statements[0]
514
+ if (s0 && ts.isExpressionStatement(s0) && ts.isStringLiteral(s0.expression) && s0.expression.text === 'use server') mod.server_fns.push(own)
515
+ }
516
+ }
517
+ // env reads
518
+ if (ts.isPropertyAccessExpression(node) && isProcessEnv(node.expression)) env.push({ src: cur, key: node.name.text, file: r, line: lineOf(node, sf), via: text(node.expression) })
519
+ else if (ts.isElementAccessExpression(node) && isProcessEnv(node.expression) && node.argumentExpression && ts.isStringLiteralLike(node.argumentExpression))
520
+ env.push({ src: cur, key: node.argumentExpression.text, file: r, line: lineOf(node, sf), via: text(node.expression) })
521
+ else if (ts.isVariableDeclaration(node) && ts.isObjectBindingPattern(node.name) && node.initializer && isProcessEnv(node.initializer)) {
522
+ for (const el of node.name.elements) env.push({ src: cur, key: text(el.propertyName || el.name), file: r, line: lineOf(node, sf), via: text(node.initializer) })
523
+ }
524
+ // req.method === 'POST' / switch (req.method) { case 'POST': }
525
+ // (`method` may also be destructured: const { method } = req)
526
+ const isMethodRef = x => (ts.isPropertyAccessExpression(x) && x.name.text === 'method') || (ts.isIdentifier(x) && x.text === 'method')
527
+ if (ts.isBinaryExpression(node) && [ts.SyntaxKind.EqualsEqualsEqualsToken, ts.SyntaxKind.EqualsEqualsToken].includes(node.operatorToken.kind)) {
528
+ const [a, b] = [unwrap(node.left), unwrap(node.right)]
529
+ for (const [x, y] of [[a, b], [b, a]]) if (isMethodRef(x) && ts.isStringLiteralLike(y) && HTTP_UPPER.has(y.text.toUpperCase()))
530
+ mod.method_checks.push({ src: cur, method: y.text.toUpperCase(), line: lineOf(node, sf) })
531
+ }
532
+ if (ts.isSwitchStatement(node) && isMethodRef(unwrap(node.expression))) {
533
+ for (const cl of node.caseBlock.clauses) if (ts.isCaseClause(cl) && ts.isStringLiteralLike(cl.expression) && HTTP_UPPER.has(cl.expression.text.toUpperCase()))
534
+ mod.method_checks.push({ src: cur, method: cl.expression.text.toUpperCase(), line: lineOf(cl, sf) })
535
+ }
536
+ if (ts.isCallExpression(node)) {
537
+ const c = unwrap(node.expression)
538
+ if (ts.isPropertyAccessExpression(c)) {
539
+ const m = c.name.text
540
+ const a0 = node.arguments[0] && unwrap(node.arguments[0])
541
+ // router-style calls
542
+ let take = false
543
+ if (ROUTER_METHODS.has(m)) {
544
+ const recvIsChain = ts.isCallExpression(unwrap(c.expression)) && ts.isPropertyAccessExpression(unwrap(unwrap(c.expression).expression)) &&
545
+ (ROUTE_VERBS.has(unwrap(unwrap(c.expression).expression).name.text) || unwrap(unwrap(c.expression).expression).name.text === 'route')
546
+ if (ROUTE_VERBS.has(m)) take = (node.arguments.length >= 2 && (isStrish(a0) || (a0 && ts.isArrayLiteralExpression(a0)) || (a0 && ts.isIdentifier(a0)) || (a0 && ts.isRegularExpressionLiteral(a0)))) || (recvIsChain && node.arguments.length >= 1)
547
+ else if (m === 'on') take = node.arguments.length >= 3 && a0 && (ts.isArrayLiteralExpression(a0) || (ts.isStringLiteralLike(a0) && HTTP_UPPER.has(a0.text.toUpperCase())))
548
+ else if (m === 'routes') take = node.arguments.length === 0
549
+ else take = node.arguments.length >= 1 || m === 'routes'
550
+ if (m === 'get' && node.arguments.length === 2 && a0 && ts.isStringLiteralLike(a0) && !/^\/|^\*$|^:/.test(a0.text)) take = false // map.get('k', d)
551
+ }
552
+ if (take && budget > 0) {
553
+ const chain = []
554
+ let base = unwrap(c.expression)
555
+ while (ts.isCallExpression(base) && ts.isPropertyAccessExpression(unwrap(base.expression)) && chain.length < 12) {
556
+ const bc = unwrap(base.expression)
557
+ if (!(ROUTE_VERBS.has(bc.name.text) || ['route', 'use', 'basePath', 'prefix', 'on'].includes(bc.name.text))) break
558
+ chain.unshift({ method: bc.name.text, args: base.arguments.slice(0, 1).map(a => describe(a, 2)), line: lineOf(base, sf) })
559
+ base = unwrap(bc.expression)
560
+ }
561
+ // `const api = Router().use(a).use(b)` / `export default new Hono().route(..)`: the declaration owning the chain
562
+ let owner
563
+ if (ts.isCallExpression(base)) {
564
+ let top = node
565
+ while (top.parent && ts.isPropertyAccessExpression(top.parent) && top.parent.expression === top && top.parent.parent && ts.isCallExpression(top.parent.parent)) top = top.parent.parent
566
+ const od = top.parent && (ts.isVariableDeclaration(top.parent) || ts.isExportAssignment(top.parent)) ? top.parent : null
567
+ const ok = od && keyOfDecl(od)
568
+ if (ok) { owner = ok; addInstance(ok, od) }
569
+ }
570
+ calls.push({ src: cur, file: r, line: lineOf(node, sf), method: m, recv: describe(base, 0), chain: chain.length ? chain : undefined,
571
+ recv_text: text(base, 60), args: node.arguments.slice(0, 16).map(a => describe(a, 0)), owner })
572
+ }
573
+ // this.<prop>.<method>(...) and data/event calls
574
+ const tp = thisProp(c.expression)
575
+ const ch = tp ? null : chainOf(c.expression)
576
+ if (tp || (ch && (DATA_METHODS.has(m) || EVENT_METHODS.has(m)) && ch[0] !== 'Array' && ch[0] !== 'Buffer' && ch[0] !== 'Object' && ch[0] !== 'Promise')
577
+ || (!ch && (m === 'from' || m === 'into' || m === 'table' || m === 'innerJoin' || m === 'leftJoin' || m === 'returning'))) {
578
+ const mc = { src: cur, cls: classOf(node) || undefined, file: r, line: lineOf(node, sf), method: m }
579
+ if (tp) mc.prop = tp
580
+ else if (ch) { mc.chain = ch; const rs = symOf(c.expression); if (rs) { const d = origDecl(rs); if (d) { const k = keyOfDecl(d); if (k) { mc.key = k; addInstance(k, d) } } } }
581
+ else mc.root = (rootIdent(c.expression) || {}).text
582
+ if (a0 && (isStrish(a0) || ts.isIdentifier(a0) || ts.isPropertyAccessExpression(a0) || ts.isObjectLiteralExpression(a0) && DATA_METHODS.has(m))) mc.a0 = describe(a0, 2)
583
+ if (ch || mc.root) mc.recv_type = (() => { try { return checker.typeToString(checker.getTypeAtLocation(c.expression)).slice(0, 80) } catch { return undefined } })()
584
+ memberCalls.push(mc)
585
+ } else if (!tp && !ch && (DATA_METHODS.has(m) || EVENT_METHODS.has(m))) {
586
+ // chained receivers (db.select().from(t), knex('t').where(...))
587
+ const rt = rootIdent(c.expression)
588
+ if (rt && a0) memberCalls.push({ src: cur, cls: classOf(node) || undefined, file: r, line: lineOf(node, sf), method: m, root: rt.text, a0: describe(a0, 2), chained: true })
589
+ }
590
+ // ConfigService.get('KEY')
591
+ if ((m === 'get' || m === 'getOrThrow') && a0 && ts.isStringLiteralLike(a0)) {
592
+ let rt = ''
593
+ try { rt = checker.typeToString(checker.getTypeAtLocation(c.expression)) } catch { }
594
+ if (/ConfigService/.test(rt) || /(^|\.)(config|configService|cfg)$/i.test(text(c.expression))) env.push({ src: cur, key: a0.text, file: r, line: lineOf(node, sf), via: 'ConfigService.' + m })
595
+ }
596
+ if (m === 'get' && a0 && ts.isStringLiteralLike(a0) && /^(Deno\.env)$/.test(text(c.expression))) env.push({ src: cur, key: a0.text, file: r, line: lineOf(node, sf), via: 'Deno.env' })
597
+ } else if (ts.isIdentifier(c)) {
598
+ // knex('table') / registerAs('ns', () => ({...}))
599
+ if (c.text === 'registerAs' && node.arguments[0] && ts.isStringLiteralLike(node.arguments[0]) && node.arguments[1]) {
600
+ const ns = node.arguments[0].text
601
+ const f = unwrap(node.arguments[1])
602
+ let body = f && (ts.isArrowFunction(f) || ts.isFunctionExpression(f)) ? (ts.isBlock(f.body) ? returnsOf(f)[0] : f.body) : null
603
+ body = body && unwrap(body)
604
+ if (body && ts.isObjectLiteralExpression(body)) {
605
+ const walkObj = (o, prefix) => {
606
+ for (const p of o.properties) {
607
+ if (!ts.isPropertyAssignment(p) && !ts.isShorthandPropertyAssignment(p)) continue
608
+ const k = `${prefix}.${p.name.getText().replace(/['"]/g, '')}`
609
+ const init = ts.isPropertyAssignment(p) ? unwrap(p.initializer) : null
610
+ if (init && ts.isObjectLiteralExpression(init)) { walkObj(init, k); continue }
611
+ const envs = []
612
+ const v = n => { if (ts.isPropertyAccessExpression(n) && isProcessEnv(n.expression)) envs.push(n.name.text); else if (ts.isElementAccessExpression(n) && isProcessEnv(n.expression) && n.argumentExpression && ts.isStringLiteralLike(n.argumentExpression)) envs.push(n.argumentExpression.text); ts.forEachChild(n, v) }
613
+ if (init) v(init)
614
+ configDefs.push({ ns, key: k, env: envs, file: r, line: lineOf(p, sf), src: cur })
615
+ }
616
+ }
617
+ walkObj(body, ns)
618
+ }
619
+ }
620
+ if (/^(knex|db|trx|sql)$/.test(c.text) && node.arguments.length === 1 && ts.isStringLiteralLike(node.arguments[0]))
621
+ memberCalls.push({ src: cur, cls: classOf(node) || undefined, file: r, line: lineOf(node, sf), method: 'knex', root: c.text, a0: { s: node.arguments[0].text }, chain: chainAfter(node) })
622
+ }
623
+ }
624
+ ts.forEachChild(node, visit)
625
+ if (own) stack.pop()
626
+ }
627
+ ts.forEachChild(sf, visit)
628
+ // exports
629
+ if (!isVue) {
630
+ const exps = []
631
+ try {
632
+ const ms = sf.symbol && checker.getMergedSymbol(sf.symbol)
633
+ const eq = ms && ms.exports && ms.exports.get('export=') // CommonJS module.exports = x / TS export = x
634
+ const list = ms ? checker.getExportsOfModule(ms) : []
635
+ for (const s of eq && !list.includes(eq) ? [eq, ...list] : list) {
636
+ if (budget <= 0) break
637
+ const t = resolveSymbol(s)
638
+ const d = origDecl(s)
639
+ const ex = { name: s.name, node: t ? t.id : undefined }
640
+ if (d) {
641
+ const dsf = d.getSourceFile()
642
+ ex.line = lineOf(d, dsf)
643
+ if (!t || ['config', 'runtime', 'dynamic', 'revalidate', 'matcher', 'metadata', 'preferredRegion', 'maxDuration', 'default', 'export='].includes(s.name)) {
644
+ if ((ts.isVariableDeclaration(d) || ts.isPropertyAssignment(d)) && d.initializer) ex.desc = describe(d.initializer, 1)
645
+ else if (ts.isBindingElement(d)) { let p = d; while (p && !ts.isVariableDeclaration(p)) p = p.parent; if (p && p.initializer) ex.desc = { destructured_from: describe(p.initializer, 1) } }
646
+ else if (ts.isExportAssignment(d)) ex.desc = describe(d.expression, 1)
647
+ else if (ts.isBinaryExpression(d)) ex.desc = describe(d.right, 1)
648
+ }
649
+ }
650
+ exps.push(ex)
651
+ }
652
+ } catch { }
653
+ mod.exports = exps
654
+ }
655
+ if (mod.directives.length || mod.server_fns.length || mod.method_checks.length || (mod.exports && mod.exports.length)) modules[r] = mod
656
+ }
657
+ // functions whose parameter is used as a router (`export default (app) => { app.get(...) }`, fastify plugins):
658
+ // their call sites bind the parameter to the argument instance
659
+ const paramFns = new Set()
660
+ for (const c of calls) {
661
+ const k = c.recv && c.recv.key, inst = k && instances[k]
662
+ if (inst && inst.kind === 'param' && inst.fn) paramFns.add(inst.fn)
663
+ }
664
+ const bindCalls = []
665
+ if (paramFns.size) {
666
+ for (const sf of sourceFiles) {
667
+ if (realFile(sf).endsWith('.vue')) continue
668
+ const r = rel(realFile(sf))
669
+ const v = node => {
670
+ if (ts.isCallExpression(node) && node.arguments.length) {
671
+ const c = unwrap(node.expression)
672
+ let fnId = null
673
+ if (ts.isIdentifier(c) || ts.isPropertyAccessExpression(c)) { const sym = symOf(c); const t = sym && resolveSymbol(sym); fnId = t && t.id }
674
+ else if (ts.isCallExpression(c) && ts.isIdentifier(unwrap(c.expression)) && unwrap(c.expression).text === 'require' && c.arguments[0]) {
675
+ const me = moduleExport(c.arguments[0], c); fnId = me && me.exp && me.exp.fn
676
+ }
677
+ if (fnId && paramFns.has(fnId)) bindCalls.push({ fn: fnId, file: r, line: lineOf(node, sf), args: node.arguments.slice(0, 6).map(a => describe(a, 1)) })
678
+ }
679
+ ts.forEachChild(node, v)
680
+ }
681
+ ts.forEachChild(sf, v)
682
+ }
683
+ }
684
+ return { classes, calls, member_calls: memberCalls, instances, modules, env, config_defs: configDefs, provide_objs: provideObjs, bind_calls: bindCalls, budget_left: budget }
685
+ }