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.
- cg_code_graph-0.10.1.dist-info/METADATA +678 -0
- cg_code_graph-0.10.1.dist-info/RECORD +174 -0
- cg_code_graph-0.10.1.dist-info/WHEEL +5 -0
- cg_code_graph-0.10.1.dist-info/entry_points.txt +3 -0
- cg_code_graph-0.10.1.dist-info/licenses/LICENSE +21 -0
- cg_code_graph-0.10.1.dist-info/top_level.txt +1 -0
- codegraph/__init__.py +2 -0
- codegraph/aitools.py +129 -0
- codegraph/apps.py +76 -0
- codegraph/blindspots.py +428 -0
- codegraph/bridges.py +1701 -0
- codegraph/cli.py +725 -0
- codegraph/concepts.py +362 -0
- codegraph/config.py +559 -0
- codegraph/core/__init__.py +0 -0
- codegraph/core/cache.py +375 -0
- codegraph/core/detect.py +80 -0
- codegraph/core/extractors.py +187 -0
- codegraph/core/fsutil.py +61 -0
- codegraph/core/generated.py +575 -0
- codegraph/core/model.py +174 -0
- codegraph/core/paths.py +175 -0
- codegraph/core/plugin.py +160 -0
- codegraph/core/store.py +80 -0
- codegraph/core/syntax_errors.py +132 -0
- codegraph/coverage.py +928 -0
- codegraph/doctor.py +453 -0
- codegraph/external.py +613 -0
- codegraph/indexer.py +336 -0
- codegraph/link.py +434 -0
- codegraph/lint_async.py +524 -0
- codegraph/mcp_server.py +1303 -0
- codegraph/parity.py +473 -0
- codegraph/parity_structure.py +307 -0
- codegraph/payload.py +321 -0
- codegraph/plans.py +1285 -0
- codegraph/platform_scan.py +643 -0
- codegraph/platforms.py +1369 -0
- codegraph/plugins/__init__.py +0 -0
- codegraph/plugins/cfamily/__init__.py +0 -0
- codegraph/plugins/cfamily/plugin.py +930 -0
- codegraph/plugins/cfamily/syntax.py +881 -0
- codegraph/plugins/dart/__init__.py +0 -0
- codegraph/plugins/dart/bridges.py +345 -0
- codegraph/plugins/dart/extractor/bin/extract.dart +717 -0
- codegraph/plugins/dart/extractor/pubspec.lock +149 -0
- codegraph/plugins/dart/extractor/pubspec.yaml +7 -0
- codegraph/plugins/dart/http.py +904 -0
- codegraph/plugins/dart/models.py +308 -0
- codegraph/plugins/dart/plugin.py +625 -0
- codegraph/plugins/dart/program.py +907 -0
- codegraph/plugins/django/__init__.py +0 -0
- codegraph/plugins/django/extras.py +378 -0
- codegraph/plugins/django/models.py +508 -0
- codegraph/plugins/django/plugin.py +728 -0
- codegraph/plugins/django/schemas.py +339 -0
- codegraph/plugins/django/shapes.py +216 -0
- codegraph/plugins/django/urls.py +603 -0
- codegraph/plugins/express/__init__.py +0 -0
- codegraph/plugins/express/plugin.py +428 -0
- codegraph/plugins/flutter/__init__.py +0 -0
- codegraph/plugins/flutter/plugin.py +538 -0
- codegraph/plugins/kotlin/__init__.py +0 -0
- codegraph/plugins/kotlin/exact.py +457 -0
- codegraph/plugins/kotlin/plugin.py +1961 -0
- codegraph/plugins/kotlin/reparse.py +234 -0
- codegraph/plugins/laravel/__init__.py +0 -0
- codegraph/plugins/laravel/broadcast.py +351 -0
- codegraph/plugins/laravel/plugin.py +863 -0
- codegraph/plugins/laravel/tests.py +262 -0
- codegraph/plugins/laravel/values.py +728 -0
- codegraph/plugins/native/__init__.py +0 -0
- codegraph/plugins/native/gates.py +286 -0
- codegraph/plugins/native/runner.py +183 -0
- codegraph/plugins/native/scipread.py +194 -0
- codegraph/plugins/native/ts.py +54 -0
- codegraph/plugins/nest/__init__.py +0 -0
- codegraph/plugins/nest/plugin.py +654 -0
- codegraph/plugins/nextjs/__init__.py +0 -0
- codegraph/plugins/nextjs/plugin.py +336 -0
- codegraph/plugins/nuxt/__init__.py +0 -0
- codegraph/plugins/nuxt/plugin.py +308 -0
- codegraph/plugins/php/__init__.py +0 -0
- codegraph/plugins/php/extractor/composer.json +5 -0
- codegraph/plugins/php/extractor/composer.lock +76 -0
- codegraph/plugins/php/extractor/extract.php +743 -0
- codegraph/plugins/php/gating.py +573 -0
- codegraph/plugins/php/plugin.py +668 -0
- codegraph/plugins/php/strings.py +197 -0
- codegraph/plugins/python/__init__.py +0 -0
- codegraph/plugins/python/aitools.py +664 -0
- codegraph/plugins/python/external.py +245 -0
- codegraph/plugins/python/fields.py +107 -0
- codegraph/plugins/python/plugin.py +1733 -0
- codegraph/plugins/python/refs.py +485 -0
- codegraph/plugins/python/roots.py +412 -0
- codegraph/plugins/python/socketio.py +210 -0
- codegraph/plugins/python/subproc.py +864 -0
- codegraph/plugins/python/tests.py +1040 -0
- codegraph/plugins/python/values.py +179 -0
- codegraph/plugins/pyweb/__init__.py +0 -0
- codegraph/plugins/pyweb/plugin.py +1334 -0
- codegraph/plugins/pyweb/values.py +68 -0
- codegraph/plugins/rust/__init__.py +0 -0
- codegraph/plugins/rust/cargo.py +226 -0
- codegraph/plugins/rust/plugin.py +980 -0
- codegraph/plugins/rust/syntax.py +678 -0
- codegraph/plugins/scip/__init__.py +0 -0
- codegraph/plugins/scip/importer.py +129 -0
- codegraph/plugins/scip/scip.proto +962 -0
- codegraph/plugins/scip/scip_pb2.py +97 -0
- codegraph/plugins/stubs/__init__.py +0 -0
- codegraph/plugins/stubs/plugins.py +38 -0
- codegraph/plugins/swift/__init__.py +0 -0
- codegraph/plugins/swift/baseurl.py +109 -0
- codegraph/plugins/swift/exact.py +415 -0
- codegraph/plugins/swift/indexstore.py +209 -0
- codegraph/plugins/swift/packages.py +174 -0
- codegraph/plugins/swift/plugin.py +2890 -0
- codegraph/plugins/ts/__init__.py +0 -0
- codegraph/plugins/ts/baseurl.py +185 -0
- codegraph/plugins/ts/extractor/extract.mjs +2652 -0
- codegraph/plugins/ts/extractor/fw.mjs +685 -0
- codegraph/plugins/ts/extractor/package-lock.json +205 -0
- codegraph/plugins/ts/extractor/package.json +9 -0
- codegraph/plugins/ts/plugin.py +480 -0
- codegraph/plugins/tsweb/__init__.py +0 -0
- codegraph/plugins/tsweb/common.py +290 -0
- codegraph/plugins/tsweb/data.py +276 -0
- codegraph/presets/__init__.py +146 -0
- codegraph/presets/c_cpp.yaml +9 -0
- codegraph/presets/common.yaml +66 -0
- codegraph/presets/dart.yaml +9 -0
- codegraph/presets/django-ninja.yaml +15 -0
- codegraph/presets/django.yaml +25 -0
- codegraph/presets/djangorestframework.yaml +17 -0
- codegraph/presets/express.yaml +17 -0
- codegraph/presets/kotlin.yaml +11 -0
- codegraph/presets/laravel.yaml +40 -0
- codegraph/presets/nest.yaml +11 -0
- codegraph/presets/nextjs.yaml +15 -0
- codegraph/presets/nuxt.yaml +9 -0
- codegraph/presets/php.yaml +5 -0
- codegraph/presets/python.yaml +10 -0
- codegraph/presets/rust.yaml +5 -0
- codegraph/presets/swift.yaml +10 -0
- codegraph/presets/typescript.yaml +13 -0
- codegraph/process_runs.py +328 -0
- codegraph/protocols/__init__.py +299 -0
- codegraph/protocols/builtin.py +67 -0
- codegraph/protocols/matchers.py +144 -0
- codegraph/protocols/view.py +334 -0
- codegraph/query.py +2089 -0
- codegraph/realtime.py +260 -0
- codegraph/roundtrip.py +346 -0
- codegraph/routes.py +442 -0
- codegraph/starters.py +218 -0
- codegraph/tests_index.py +117 -0
- codegraph/viz/__init__.py +0 -0
- codegraph/viz/graph.py +369 -0
- codegraph/viz/server.py +198 -0
- codegraph/viz/static/app.css +148 -0
- codegraph/viz/static/app.js +1082 -0
- codegraph/viz/static/index.html +81 -0
- codegraph/viz/static/layered.js +237 -0
- codegraph/viz/static/vendor/VERSIONS.txt +4 -0
- codegraph/viz/static/vendor/cose-base.js +3214 -0
- codegraph/viz/static/vendor/cytoscape-fcose.js +1549 -0
- codegraph/viz/static/vendor/cytoscape.min.js +31 -0
- codegraph/viz/static/vendor/layout-base.js +5230 -0
- codegraph/viz/tools/package-lock.json +303 -0
- codegraph/viz/tools/package.json +7 -0
- codegraph/viz/tools/shoot.mjs +165 -0
- 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
|
+
}
|