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,428 @@
|
|
|
1
|
+
"""Express / Koa (+ @koa/router) / Fastify / Hono router layer (on the TypeScript plugin; plain-JS projects too).
|
|
2
|
+
|
|
3
|
+
Router instances are declarations (variables, class properties, function parameters) whose initialiser is a known
|
|
4
|
+
factory (express(), express.Router(), Router(), new Router({prefix}), fastify(), new Koa(), new Hono(), app.basePath(p),
|
|
5
|
+
...), identified across files through imports, require(), module.exports and factory functions that return them.
|
|
6
|
+
|
|
7
|
+
routes x.get|post|put|patch|delete|all|head|options(path, ...middleware, handler), x.route(path).get(h).post(h),
|
|
8
|
+
fastify.route({method, url, handler, preHandler}), fastify.get(path, {preHandler, schema}, handler),
|
|
9
|
+
hono.on(methods, path, h); paths `:id` -> `{id}`, `*` -> `{wildcard*}`
|
|
10
|
+
mounting x.use(prefix?, ...mw, sub) (Express, koa-router sub.routes()), fastify.register(plugin, {prefix}) (the
|
|
11
|
+
plugin's first parameter is the child instance), hono.route(prefix, sub), basePath; chains across files are
|
|
12
|
+
joined into full paths; a function called with a router argument (`routes(app)`) binds its parameter
|
|
13
|
+
middleware route-level middleware, x.use(mw) router-level middleware and middleware passed with a mount are recorded on
|
|
14
|
+
the route (attrs.middleware) with USES_MIDDLEWARE edges where the function resolves
|
|
15
|
+
confidence exact: literal path, known factory, direct handler; resolved: through wrappers / handler objects / aliases
|
|
16
|
+
/ factory functions / params; heuristic: router never mounted from an app (prefix unknown) or receiver only
|
|
17
|
+
recognised by name
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import re
|
|
22
|
+
from collections import defaultdict
|
|
23
|
+
|
|
24
|
+
from ...core.plugin import FrameworkPlugin, GraphBuilder, Project
|
|
25
|
+
from ..tsweb.common import (TEST_SKIP_RE, add_route, express_path, finish, fw_facts, has_server_framework, join_path,
|
|
26
|
+
last_name, merge_extractor_cfg, obj, ref_nodes, register, sval, svals)
|
|
27
|
+
|
|
28
|
+
VERBS = {"get": "GET", "post": "POST", "put": "PUT", "patch": "PATCH", "delete": "DELETE", "del": "DELETE", "head": "HEAD",
|
|
29
|
+
"options": "OPTIONS", "all": "ANY", "any": "ANY"}
|
|
30
|
+
MODS = {"express": "express", "koa": "koa", "@koa/router": "koa-router", "koa-router": "koa-router", "fastify": "fastify",
|
|
31
|
+
"hono": "hono", "elysia": "elysia", "polka": "polka", "restify": "restify", "h3": "h3", "@hono/zod-openapi": "hono"}
|
|
32
|
+
APP_FACTORIES = {"express": "express", "Fastify": "fastify", "fastify": "fastify", "Koa": "koa", "Hono": "hono", "OpenAPIHono": "hono",
|
|
33
|
+
"Elysia": "elysia", "polka": "polka", "createServer": "restify", "createApp": "h3"}
|
|
34
|
+
ROUTER_FACTORIES = {"Router": "express", "KoaRouter": "koa-router", "createRouter": "h3"}
|
|
35
|
+
HOOKS = ("onRequest", "preParsing", "preValidation", "preHandler")
|
|
36
|
+
MW_OPTS = ("preHandler", "onRequest", "preValidation", "preParsing", "beforeHandler", "middleware")
|
|
37
|
+
NAME_HINT = re.compile(r"(^|\.)(app|server|router|routes?|api|fastify|instance|r)$", re.I)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ExpressPlugin(FrameworkPlugin):
|
|
41
|
+
name, language = "express", "typescript"
|
|
42
|
+
|
|
43
|
+
def detect(self, project: Project) -> bool:
|
|
44
|
+
return has_server_framework(project, "express")
|
|
45
|
+
|
|
46
|
+
def register_hooks(self, ctx) -> None:
|
|
47
|
+
root = ctx.project.root
|
|
48
|
+
dirs = [d for d in ("src", "server", "api", "app", "lib", "routes", "core") if (root / d).is_dir()]
|
|
49
|
+
merge_extractor_cfg(ctx.extractor_cfg, src_dirs=dirs or ["."], skip_re=TEST_SKIP_RE, walk_src=True, allow_js=True)
|
|
50
|
+
if dirs: # entry files at the root (index.js, app.js, server.ts)
|
|
51
|
+
merge_extractor_cfg(ctx.extractor_cfg, extra_files=[f for f in ("index.js", "index.ts", "app.js", "app.ts", "server.js", "server.ts", "main.ts", "main.js") if (root / f).exists()],
|
|
52
|
+
src_dirs=["."] if any((root / f).exists() for f in ("index.js", "app.js", "server.js", "index.ts", "app.ts", "server.ts")) else None)
|
|
53
|
+
register(ctx, self.name)
|
|
54
|
+
|
|
55
|
+
# ------------------------------------------------------------
|
|
56
|
+
def contribute(self, project: Project, b: GraphBuilder, ctx) -> dict:
|
|
57
|
+
st = {"routers": 0, "apps": 0, "mounts": 0, "routes": 0, "routes_unmounted": 0, "routes_unresolved_handler": 0}
|
|
58
|
+
F = fw_facts(ctx)
|
|
59
|
+
if not F:
|
|
60
|
+
return {**st, "status": "no facts"}
|
|
61
|
+
self.b, self.F = b, F
|
|
62
|
+
self._route_keys = None
|
|
63
|
+
self.I = F.get("instances") or {}
|
|
64
|
+
calls = F.get("calls") or []
|
|
65
|
+
self.kinds = {} # key -> (framework, "app"|"router"|"derived"|"param"|"name")
|
|
66
|
+
self.own_prefix = defaultdict(str)
|
|
67
|
+
self.mounts = defaultdict(list) # child -> [(parent, prefix, conf, mw)]
|
|
68
|
+
self.router_mw = defaultdict(list) # key -> [(prefix, (node, name, conf), file, line)]
|
|
69
|
+
for k, inst in self.I.items():
|
|
70
|
+
c = self.classify(k)
|
|
71
|
+
if c:
|
|
72
|
+
self.kinds[k] = c
|
|
73
|
+
route_calls, mount_calls = [], []
|
|
74
|
+
for c in calls:
|
|
75
|
+
m = c["method"]
|
|
76
|
+
keys = self.recv_keys(c)
|
|
77
|
+
if not keys:
|
|
78
|
+
continue
|
|
79
|
+
if m in VERBS or (m == "on" and len(c.get("args") or []) >= 3) or (m == "route" and len(c["args"]) == 1 and obj(c["args"][0]).get("method")):
|
|
80
|
+
route_calls.append((c, keys))
|
|
81
|
+
elif m in ("use", "lazyUse", "mount", "register", "route", "group"):
|
|
82
|
+
mount_calls.append((c, keys))
|
|
83
|
+
elif m == "prefix" and c.get("args") and sval(c["args"][0]):
|
|
84
|
+
for k, _ in keys:
|
|
85
|
+
self.own_prefix[k] = sval(c["args"][0])
|
|
86
|
+
elif m == "addHook" and len(c.get("args") or []) >= 2 and sval(c["args"][0]) in HOOKS:
|
|
87
|
+
for k, kp in keys: # fastify hooks apply to the instance's routes and its child plugins
|
|
88
|
+
self.router_mw[k].append((kp, self.mw_of(c["args"][1]), c["file"], c["line"]))
|
|
89
|
+
for k, inst in self.I.items(): # new Router({ prefix: '/x' }) (koa-router)
|
|
90
|
+
init = (inst or {}).get("init") or {}
|
|
91
|
+
if last_name(init.get("new") or init.get("call") or "") in ("Router", "KoaRouter") and init.get("args"):
|
|
92
|
+
pfx = sval(obj(init["args"][0]).get("prefix"))
|
|
93
|
+
if pfx:
|
|
94
|
+
self.own_prefix[k] = pfx
|
|
95
|
+
# routerish = known factories + keys that receive route calls (and the apps mounting them)
|
|
96
|
+
for c, keys in route_calls:
|
|
97
|
+
for k, _ in keys:
|
|
98
|
+
if k not in self.kinds:
|
|
99
|
+
inst = self.I.get(k) or {}
|
|
100
|
+
if inst.get("kind") == "param":
|
|
101
|
+
self.kinds[k] = ("?", "param")
|
|
102
|
+
elif NAME_HINT.search(inst.get("name") or c.get("recv_text") or ""):
|
|
103
|
+
self.kinds[k] = ("?", "name")
|
|
104
|
+
for c, keys in mount_calls:
|
|
105
|
+
self.mount(c, keys, st)
|
|
106
|
+
for bc in F.get("bind_calls") or []:
|
|
107
|
+
for k, inst in self.I.items():
|
|
108
|
+
if inst and inst.get("kind") == "param" and inst.get("fn") == bc["fn"] and inst.get("index") is not None and inst["index"] < len(bc["args"]):
|
|
109
|
+
for pk, pfx in self.inst_keys(bc["args"][inst["index"]]):
|
|
110
|
+
if pk in self.kinds and pk != k:
|
|
111
|
+
self.mounts[k].append((pk, pfx, "resolved", [], f"{bc['file']}:{bc['line']}"))
|
|
112
|
+
st["mounts"] += 1
|
|
113
|
+
st["apps"] = sum(1 for v in self.kinds.values() if v[1] == "app")
|
|
114
|
+
st["routers"] = len({k for k, v in self.kinds.items() if v[1] in ("router", "derived", "param", "name")})
|
|
115
|
+
for c, keys in route_calls:
|
|
116
|
+
self.route(c, keys, st)
|
|
117
|
+
st["frameworks"] = sorted({v[0] for v in self.kinds.values() if v[0] != "?"})
|
|
118
|
+
st.update(finish(project, b, ctx, self.name))
|
|
119
|
+
return st
|
|
120
|
+
|
|
121
|
+
# ------------------------------------------------------------ instances
|
|
122
|
+
def classify(self, k):
|
|
123
|
+
inst = self.I.get(k) or {}
|
|
124
|
+
init = inst.get("init") or {}
|
|
125
|
+
if inst.get("kind") == "param":
|
|
126
|
+
t = inst.get("type") or ""
|
|
127
|
+
for n, fw in (("FastifyInstance", "fastify"), ("Hono", "hono"), ("Router", "express"), ("Express", "express"), ("Application", "express")):
|
|
128
|
+
if re.search(rf"\b{n}\b", t):
|
|
129
|
+
return (fw, "param")
|
|
130
|
+
return None
|
|
131
|
+
return self._factory(init)
|
|
132
|
+
|
|
133
|
+
def _factory(self, d, depth=0):
|
|
134
|
+
if not isinstance(d, dict) or depth > 4:
|
|
135
|
+
return None
|
|
136
|
+
callee = d.get("call") or d.get("new")
|
|
137
|
+
if callee is None:
|
|
138
|
+
if d.get("cond"):
|
|
139
|
+
for x in d["cond"]:
|
|
140
|
+
r = self._factory(x, depth + 1)
|
|
141
|
+
if r:
|
|
142
|
+
return r
|
|
143
|
+
return None
|
|
144
|
+
ln = last_name(callee)
|
|
145
|
+
mod = d.get("mod") or ""
|
|
146
|
+
fw = MODS.get(mod) or (MODS.get(mod.split("/")[0]) if mod.startswith("hono/") else None)
|
|
147
|
+
if ln in ("basePath", "route", "use", "get", "post", "put", "patch", "delete", "on", "prefix") and d.get("recv"):
|
|
148
|
+
parent = self.inst_keys(d["recv"])
|
|
149
|
+
if parent:
|
|
150
|
+
return (self.kinds.get(parent[0][0], ("?", ""))[0], "derived")
|
|
151
|
+
inner = self._factory(d["recv"], depth + 1) # new Hono().basePath('/api')
|
|
152
|
+
if inner:
|
|
153
|
+
return inner
|
|
154
|
+
if ln in APP_FACTORIES and (fw or "new" in d or ln in ("express", "fastify", "Fastify", "polka")):
|
|
155
|
+
return (fw or APP_FACTORIES[ln], "app" if ln not in ("Router",) else "router")
|
|
156
|
+
if ln == "Router" or ln in ROUTER_FACTORIES:
|
|
157
|
+
if "new" in d:
|
|
158
|
+
return ("koa-router" if fw in (None, "koa-router", "koa") else fw, "router")
|
|
159
|
+
return (fw or ("express" if callee.startswith("express") or not mod else MODS.get(mod, "express")), "router")
|
|
160
|
+
if fw and ln not in ("json", "urlencoded", "static", "text", "raw"):
|
|
161
|
+
return (fw, "app")
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
def recv_keys(self, c):
|
|
165
|
+
d = c.get("recv") or {}
|
|
166
|
+
keys = self.inst_keys(d)
|
|
167
|
+
if not keys and c.get("owner") and ("new" in d or "call" in d) and self._factory(d):
|
|
168
|
+
k = c["owner"] # chain owned by a declaration: const api = Router().use(...)
|
|
169
|
+
self.kinds.setdefault(k, self._factory(d))
|
|
170
|
+
keys = [(k, "")]
|
|
171
|
+
if not keys and ("new" in d or "call" in d) and self._factory(d):
|
|
172
|
+
k = f"inline:{c['file']}:{c['line']}"
|
|
173
|
+
self.kinds.setdefault(k, self._factory(d))
|
|
174
|
+
keys = [(k, "")]
|
|
175
|
+
out = []
|
|
176
|
+
for k, pfx in keys:
|
|
177
|
+
for ch in c.get("chain") or []:
|
|
178
|
+
if ch["method"] in ("basePath", "prefix") and ch.get("args") and sval(ch["args"][0]):
|
|
179
|
+
pfx = join_path(pfx, sval(ch["args"][0]))
|
|
180
|
+
out.append((k, pfx))
|
|
181
|
+
return out
|
|
182
|
+
|
|
183
|
+
def inst_keys(self, d, depth=0) -> list[tuple[str, str]]:
|
|
184
|
+
"""Router instances a described value evaluates to: [(key, extra prefix)]."""
|
|
185
|
+
if not isinstance(d, dict) or depth > 6:
|
|
186
|
+
return []
|
|
187
|
+
if d.get("key"):
|
|
188
|
+
k = d["key"]
|
|
189
|
+
init = (self.I.get(k) or {}).get("init") or {}
|
|
190
|
+
if "call" in init and last_name(init["call"]) == "basePath" and init.get("recv"):
|
|
191
|
+
bp = sval((init.get("args") or [None])[0]) or ""
|
|
192
|
+
return [(pk, join_path(pp, bp)) for pk, pp in self.inst_keys(init["recv"], depth + 1)] or [(k, bp)]
|
|
193
|
+
return [(k, "")]
|
|
194
|
+
if "call" in d:
|
|
195
|
+
ln = last_name(d["call"])
|
|
196
|
+
if ln in ("routes", "middleware", "allowedMethods", "callback", "getRouter", "router", "fetch") and d.get("recv"):
|
|
197
|
+
return self.inst_keys(d["recv"], depth + 1)
|
|
198
|
+
if ln in ("basePath",) and d.get("recv"):
|
|
199
|
+
return [(k, join_path(p, sval((d.get("args") or [None])[0]) or "")) for k, p in self.inst_keys(d["recv"], depth + 1)]
|
|
200
|
+
out = []
|
|
201
|
+
for r in d.get("ret") or []:
|
|
202
|
+
out += self.inst_keys(r, depth + 1)
|
|
203
|
+
if not out and last_name(d["call"]) in ("fp", "fastifyPlugin") and d.get("args"):
|
|
204
|
+
out = self.inst_keys(d["args"][0], depth + 1)
|
|
205
|
+
return out
|
|
206
|
+
if "require" in d:
|
|
207
|
+
return self.inst_keys(d.get("exp"), depth + 1)
|
|
208
|
+
if d.get("fn"):
|
|
209
|
+
out = []
|
|
210
|
+
for r in (d.get("ret") or []) + ([d["body"]] if d.get("body") else []):
|
|
211
|
+
out += self.inst_keys(r, depth + 1)
|
|
212
|
+
return out
|
|
213
|
+
if d.get("of") and d.get("ref", "").endswith(".router"):
|
|
214
|
+
return []
|
|
215
|
+
return []
|
|
216
|
+
|
|
217
|
+
def plugin_params(self, d, depth=0) -> list[str]:
|
|
218
|
+
"""fastify.register(plugin): the plugin function's first parameter instance."""
|
|
219
|
+
if not isinstance(d, dict) or depth > 4:
|
|
220
|
+
return []
|
|
221
|
+
fns = []
|
|
222
|
+
if d.get("fn"):
|
|
223
|
+
fns.append(d["fn"])
|
|
224
|
+
if d.get("node"):
|
|
225
|
+
fns.append(d["node"])
|
|
226
|
+
if "require" in d:
|
|
227
|
+
return self.plugin_params(d.get("exp"), depth + 1)
|
|
228
|
+
if "call" in d:
|
|
229
|
+
for a in d.get("args") or []:
|
|
230
|
+
fns += self.plugin_params(a, depth + 1)
|
|
231
|
+
return fns
|
|
232
|
+
out = [k for k, inst in self.I.items() if inst and inst.get("kind") == "param" and inst.get("index") == 0 and inst.get("fn") in fns]
|
|
233
|
+
return out or [x for x in fns if x.startswith("param:")]
|
|
234
|
+
|
|
235
|
+
# ------------------------------------------------------------ mounts
|
|
236
|
+
def mw_of(self, d):
|
|
237
|
+
"""(node, name, confidence) for a described middleware argument."""
|
|
238
|
+
if not isinstance(d, dict):
|
|
239
|
+
return (None, "?", "heuristic")
|
|
240
|
+
if d.get("fn"):
|
|
241
|
+
return (d["fn"], "inline", "exact")
|
|
242
|
+
if d.get("ref") is not None:
|
|
243
|
+
return (d.get("node") or (d.get("obj_fns") or [None])[0], d["ref"], "resolved")
|
|
244
|
+
if "call" in d:
|
|
245
|
+
return (d.get("node"), d["call"] + "()", "resolved" if d.get("node") else "heuristic")
|
|
246
|
+
return (None, d.get("expr") or "?", "heuristic")
|
|
247
|
+
|
|
248
|
+
def mount(self, c, keys, st):
|
|
249
|
+
args = c.get("args") or []
|
|
250
|
+
m = c["method"]
|
|
251
|
+
if m == "register":
|
|
252
|
+
if not args:
|
|
253
|
+
return
|
|
254
|
+
pfx = sval(obj(args[1]).get("prefix")) if len(args) > 1 else None
|
|
255
|
+
for child in self.plugin_params(args[0]):
|
|
256
|
+
self.kinds.setdefault(child, (self.kinds.get(keys[0][0], ("fastify", ""))[0], "param"))
|
|
257
|
+
for k, kp in keys:
|
|
258
|
+
self.mounts[child].append((k, join_path(kp, pfx or ""), "resolved", [], f"{c['file']}:{c['line']}"))
|
|
259
|
+
st["mounts"] += 1
|
|
260
|
+
return
|
|
261
|
+
prefixes = [""]
|
|
262
|
+
rest = args
|
|
263
|
+
if args and (svals(args[0]) and "re" not in args[0]):
|
|
264
|
+
prefixes, rest = svals(args[0]), args[1:]
|
|
265
|
+
flat = []
|
|
266
|
+
for a in rest:
|
|
267
|
+
flat += a.get("arr") if isinstance(a, dict) and a.get("arr") is not None else [a]
|
|
268
|
+
children, mws = [], []
|
|
269
|
+
for a in flat:
|
|
270
|
+
ks = [x for x in self.inst_keys(a) if x[0] in self.kinds or self._has_routes(x[0])]
|
|
271
|
+
if ks:
|
|
272
|
+
children += ks
|
|
273
|
+
else:
|
|
274
|
+
mws.append(self.mw_of(a))
|
|
275
|
+
if children:
|
|
276
|
+
for ck, cp in children:
|
|
277
|
+
for k, kp in keys:
|
|
278
|
+
if ck == k:
|
|
279
|
+
continue
|
|
280
|
+
for p in prefixes:
|
|
281
|
+
self.mounts[ck].append((k, join_path(kp, p, cp), "exact" if self.kinds.get(ck, ("", ""))[1] in ("router", "app", "derived") else "resolved",
|
|
282
|
+
mws, f"{c['file']}:{c['line']}"))
|
|
283
|
+
st["mounts"] += 1
|
|
284
|
+
elif m in ("use", "lazyUse"):
|
|
285
|
+
for k, kp in keys:
|
|
286
|
+
for p in prefixes:
|
|
287
|
+
for mw in mws:
|
|
288
|
+
self.router_mw[k].append((join_path(kp, p), mw, c["file"], c["line"]))
|
|
289
|
+
|
|
290
|
+
def _has_routes(self, k) -> bool:
|
|
291
|
+
if self._route_keys is None:
|
|
292
|
+
self._route_keys = {kk for c in self.F.get("calls") or [] if c["method"] in VERBS for kk, _ in self.inst_keys(c.get("recv") or {})}
|
|
293
|
+
return k in self._route_keys
|
|
294
|
+
|
|
295
|
+
def _mw_before(self, k, at, pfx=None):
|
|
296
|
+
"""Router-level middleware of k registered before `at` (file, line) (order known only within one file)."""
|
|
297
|
+
out = []
|
|
298
|
+
for p, x, f, ln in self.router_mw.get(k, []):
|
|
299
|
+
if pfx is None and p not in ("", "/"):
|
|
300
|
+
continue
|
|
301
|
+
if pfx is not None and (p in ("", "/") or not (pfx or "/").startswith(p)):
|
|
302
|
+
continue # prefix-scoped middleware only (global ones come through the parent's own walk)
|
|
303
|
+
if at and f == at[0] and ln > at[1]:
|
|
304
|
+
continue # registered after the route / mount: does not run for it
|
|
305
|
+
if x[1] in ("?", "inline") and not x[0]:
|
|
306
|
+
continue
|
|
307
|
+
out.append(x)
|
|
308
|
+
return out
|
|
309
|
+
|
|
310
|
+
def prefixes(self, k, depth=0, seen=frozenset(), at=None):
|
|
311
|
+
"""[(prefix, confidence, middleware, mounted_from_app)] for router k (`at`: the route call's file, line)."""
|
|
312
|
+
own = self.own_prefix.get(k, "")
|
|
313
|
+
if k in seen or depth > 8:
|
|
314
|
+
return [(own, "heuristic", [], False)]
|
|
315
|
+
if k not in self.mounts:
|
|
316
|
+
kind = self.kinds.get(k, ("?", "name"))[1]
|
|
317
|
+
mw = self._mw_before(k, at)
|
|
318
|
+
return [(own, "exact" if kind == "app" else "heuristic", mw, kind == "app")]
|
|
319
|
+
out = []
|
|
320
|
+
for parent, pfx, conf, mws, ev in self.mounts[k][:8]:
|
|
321
|
+
f, _, ln = ev.rpartition(":")
|
|
322
|
+
at_ev = (f, int(ln)) if ln.isdigit() else None
|
|
323
|
+
for pp, pc, pmw, ok in self.prefixes(parent, depth + 1, seen | {k}, at=at_ev):
|
|
324
|
+
inherited = self._mw_before(parent, at_ev, pfx)
|
|
325
|
+
out.append((join_path(pp, pfx, own), _weaker(conf, pc), pmw + inherited + mws + self._mw_before(k, at), ok))
|
|
326
|
+
if len(out) >= 8:
|
|
327
|
+
return out
|
|
328
|
+
return out
|
|
329
|
+
|
|
330
|
+
# ------------------------------------------------------------ routes
|
|
331
|
+
def route(self, c, keys, st):
|
|
332
|
+
b = self.b
|
|
333
|
+
args = list(c.get("args") or [])
|
|
334
|
+
m = c["method"]
|
|
335
|
+
methods, paths, opts = [], [], {}
|
|
336
|
+
if m == "route": # fastify.route({ method, url, handler })
|
|
337
|
+
o = obj(args[0])
|
|
338
|
+
methods = [x.upper() for x in svals(o.get("method"))] or ["GET"]
|
|
339
|
+
paths = svals(o.get("url")) or svals(o.get("path"))
|
|
340
|
+
opts = o
|
|
341
|
+
handlers = [o.get("handler")]
|
|
342
|
+
mids = []
|
|
343
|
+
else:
|
|
344
|
+
if m == "on":
|
|
345
|
+
methods = [x.upper() for x in svals(args[0])] or ["ANY"]
|
|
346
|
+
args = args[1:]
|
|
347
|
+
else:
|
|
348
|
+
methods = [VERBS[m]]
|
|
349
|
+
chain_path = None
|
|
350
|
+
for ch in c.get("chain") or []:
|
|
351
|
+
if ch["method"] == "route" and ch.get("args") and svals(ch["args"][0]):
|
|
352
|
+
chain_path = svals(ch["args"][0])
|
|
353
|
+
if args and (svals(args[0]) or (isinstance(args[0], dict) and "re" in args[0])) and not (chain_path is not None and not svals(args[0])):
|
|
354
|
+
a0 = args.pop(0)
|
|
355
|
+
paths = svals(a0) or ["{regex}"]
|
|
356
|
+
elif chain_path is not None:
|
|
357
|
+
paths = chain_path
|
|
358
|
+
elif args and isinstance(args[0], dict) and args[0].get("ref") and not args[0].get("node"):
|
|
359
|
+
args.pop(0)
|
|
360
|
+
paths = ["{" + re.sub(r"\W", "_", args and "path" or "path") + "}"]
|
|
361
|
+
else:
|
|
362
|
+
paths = [""] if chain_path is None else chain_path
|
|
363
|
+
flat = []
|
|
364
|
+
for a in args:
|
|
365
|
+
flat += a.get("arr") if isinstance(a, dict) and a.get("arr") is not None else [a]
|
|
366
|
+
opt_objs = [a for a in flat if isinstance(a, dict) and a.get("obj") is not None]
|
|
367
|
+
for o in opt_objs:
|
|
368
|
+
opts.update(o["obj"])
|
|
369
|
+
fns = [a for a in flat if not (isinstance(a, dict) and a.get("obj") is not None)]
|
|
370
|
+
handlers = fns[-1:] or [opts.get("handler")]
|
|
371
|
+
mids = fns[:-1]
|
|
372
|
+
mw = [self.mw_of(x) for x in mids]
|
|
373
|
+
for k in MW_OPTS:
|
|
374
|
+
v = opts.get(k)
|
|
375
|
+
for x in (v.get("arr") if isinstance(v, dict) and v.get("arr") is not None else [v] if v else []):
|
|
376
|
+
mw.append(self.mw_of(x))
|
|
377
|
+
handler_nodes, hconf = [], "exact"
|
|
378
|
+
for h in handlers:
|
|
379
|
+
if not isinstance(h, dict):
|
|
380
|
+
continue
|
|
381
|
+
ns = [n for n in ref_nodes(h) if b.has(n)]
|
|
382
|
+
if not ns and h.get("node") and b.has(h["node"]):
|
|
383
|
+
ns = [h["node"]]
|
|
384
|
+
handler_nodes += ns
|
|
385
|
+
if not h.get("fn") and not (h.get("ref") is not None and h.get("node")):
|
|
386
|
+
hconf = "resolved"
|
|
387
|
+
attrs = {}
|
|
388
|
+
schema = obj(opts.get("schema"))
|
|
389
|
+
if schema:
|
|
390
|
+
props = obj(obj(schema.get("body")).get("properties"))
|
|
391
|
+
if props:
|
|
392
|
+
attrs["body_fields"] = sorted(props)
|
|
393
|
+
q = obj(obj(schema.get("querystring")).get("properties"))
|
|
394
|
+
if q:
|
|
395
|
+
attrs["query_fields"] = sorted(q)
|
|
396
|
+
if not handler_nodes:
|
|
397
|
+
attrs["handler_unresolved"] = True
|
|
398
|
+
counted = False
|
|
399
|
+
literal = any(p == "" or p.startswith(("/", "*")) for p in paths if "{" not in p[:1])
|
|
400
|
+
for k, kp in keys:
|
|
401
|
+
fw, kind = self.kinds.get(k, ("?", "name"))
|
|
402
|
+
if kind == "name" and not handler_nodes:
|
|
403
|
+
continue
|
|
404
|
+
if kind in ("name", "param") and fw == "?" and not literal:
|
|
405
|
+
continue # untyped receiver + non-literal first argument: a map/cache/http-client .get(key), not a route
|
|
406
|
+
for pfx, pconf, pmw, mounted in self.prefixes(k, at=(c["file"], c["line"])):
|
|
407
|
+
for p in paths:
|
|
408
|
+
uri = express_path(join_path(pfx, kp, p))
|
|
409
|
+
conf = _weaker(hconf, pconf if mounted else "heuristic")
|
|
410
|
+
if kind == "name":
|
|
411
|
+
conf = "heuristic"
|
|
412
|
+
if any("{" in s and not re.fullmatch(r"\{\w+\*?\??\}", s) for s in uri.split("/") if s) or "{regex}" in uri:
|
|
413
|
+
conf = "heuristic"
|
|
414
|
+
ra = {**attrs, "router": (self.I.get(k) or {}).get("name") or c.get("recv_text"), "router_framework": fw}
|
|
415
|
+
if not mounted:
|
|
416
|
+
ra["unmounted"] = True
|
|
417
|
+
st["routes_unmounted"] += 1
|
|
418
|
+
for method in methods:
|
|
419
|
+
add_route(b, method, uri, handler_nodes, c["file"], c["line"], fw if fw != "?" else "express", conf, ra, pmw + mw)
|
|
420
|
+
st["routes"] += 1
|
|
421
|
+
if not handler_nodes and not counted:
|
|
422
|
+
st["routes_unresolved_handler"] += 1 # once per route call that produced routes
|
|
423
|
+
counted = True
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def _weaker(a, b):
|
|
427
|
+
order = {"exact": 3, "resolved": 2, "heuristic": 1}
|
|
428
|
+
return a if order[a] <= order[b] else b
|
|
File without changes
|