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
File without changes
@@ -0,0 +1,345 @@
1
+ """Flutter platform channels, send side: `MethodChannel('name').invokeMethod('m')` (also invokeMapMethod /
2
+ invokeListMethod, channels held in fields, top-level / static constants and locals) and
3
+ `EventChannel('name').receiveBroadcastStream()`. Each call becomes SENDS_TO endpoint:flutter:<channel>#<method> /
4
+ endpoint:flutter-event:<channel> (codegraph/bridges.py links the Kotlin / Swift handlers).
5
+
6
+ Also the Dart receiving side of native -> Dart calls: `channel.setMethodCallHandler(handler)` and the method names the
7
+ handler tests (RECEIVED_BY, side dart), and Pigeon: the @HostApi / @FlutterApi classes of the definition files, Dart
8
+ calls on host APIs (SENDS_TO endpoint:pigeon:<Api>#<method>) and the Dart classes implementing flutter APIs."""
9
+ from __future__ import annotations
10
+
11
+ import re
12
+
13
+ from ...core.model import EXACT, HEURISTIC, RESOLVED
14
+ from .program import Ctx, DartProgram, DClass, DFunc, DVar, ctor_type
15
+
16
+ METHOD_CHANNELS = {"MethodChannel", "OptionalMethodChannel"}
17
+ EVENT_CHANNELS = {"EventChannel"}
18
+ INVOKE = {"invokeMethod", "invokeMapMethod", "invokeListMethod"}
19
+
20
+
21
+ def _creation(prog: DartProgram, r, ctx: Ctx, depth: int = 0):
22
+ """The MethodChannel / EventChannel creation an expression evaluates to: (kind, creation fact, ctx) or None."""
23
+ if not isinstance(r, dict) or depth > 8:
24
+ return None
25
+ ct = ctor_type(r)
26
+ if ct:
27
+ base = ct.split(".")[-1]
28
+ if base in METHOD_CHANNELS:
29
+ return "method", r, ctx
30
+ if base in EVENT_CHANNELS:
31
+ return "event", r, ctx
32
+ return None
33
+ k = r.get("k")
34
+ if k in ("nn", "await", "as") and isinstance(r.get("e"), dict):
35
+ return _creation(prog, r["e"], ctx, depth + 1)
36
+ if k == "cond":
37
+ return _creation(prog, r.get("a"), ctx, depth + 1) or _creation(prog, r.get("b"), ctx, depth + 1)
38
+ if k == "bin" and r.get("op") == "??":
39
+ return _creation(prog, r.get("l"), ctx, depth + 1) or _creation(prog, r.get("r"), ctx, depth + 1)
40
+ var = None
41
+ if k == "id":
42
+ loc = ctx.locals.get(r["v"])
43
+ if loc:
44
+ kind, d = loc
45
+ if kind == "var" and d.get("init") is not None:
46
+ return _creation(prog, d["init"], ctx, depth + 1)
47
+ if kind == "param" and d.get("this") and ctx.cls and r["v"] in ctx.cls.fields:
48
+ var = ctx.cls.fields[r["v"]]
49
+ else:
50
+ return None
51
+ elif ctx.cls:
52
+ m, _ = prog.find_member(ctx.cls, r["v"])
53
+ if isinstance(m, DVar):
54
+ var = m
55
+ elif isinstance(m, DFunc) and m.kind == "getter":
56
+ return _getter(prog, m, depth)
57
+ if var is None:
58
+ v = prog.lookup(ctx.lib, r["v"])
59
+ if isinstance(v, DVar):
60
+ var = v
61
+ elif isinstance(v, DFunc) and v.kind == "getter":
62
+ return _getter(prog, v, depth)
63
+ elif k == "prop":
64
+ t = r.get("t") or {}
65
+ if t.get("k") == "this" and ctx.cls:
66
+ m, _ = prog.find_member(ctx.cls, r["n"])
67
+ var = m if isinstance(m, DVar) else None
68
+ elif t.get("k") == "id" and t["v"][:1].isupper(): # Class.staticField
69
+ c = prog.resolve_class(ctx.lib, t["v"])
70
+ if isinstance(c, DClass):
71
+ m, _ = prog.find_member(c, r["n"])
72
+ var = m if isinstance(m, DVar) else None
73
+ elif t.get("k") == "id" and t["v"] not in ctx.locals and prog.prefix_imports(ctx.lib, t["v"]):
74
+ v = prog.lookup_prefixed(ctx.lib, t["v"], r["n"])
75
+ var = v if isinstance(v, DVar) else None
76
+ if var is not None and var.init is not None:
77
+ return _creation(prog, var.init, Ctx(prog, var.lib, None, var.cls), depth + 1)
78
+ if var is not None and var.cls is not None:
79
+ # `late MethodChannel _channel;` assigned in initState / a constructor: `_channel = MethodChannel('x')`
80
+ for m in list(var.cls.methods.values()) + list(var.cls.ctors.values()):
81
+ for x in m.facts:
82
+ lhs = x.get("lhs") or {}
83
+ if x.get("ft") == "assign" and ((lhs.get("k") == "id" and lhs.get("v") == var.name) or
84
+ (lhs.get("k") == "prop" and lhs.get("n") == var.name
85
+ and (lhs.get("t") or {}).get("k") == "this")):
86
+ cr = _creation(prog, x.get("rhs"), prog.ctx_of(m), depth + 1)
87
+ if cr:
88
+ return cr
89
+ return None
90
+
91
+
92
+ def _getter(prog, f: DFunc, depth):
93
+ ctx = prog.ctx_of(f)
94
+ for x in f.facts:
95
+ if x["ft"] == "return":
96
+ return _creation(prog, x.get("v"), ctx, depth + 1)
97
+ return None
98
+
99
+
100
+ def _channel_name(ev, creation, ctx) -> tuple[str | None, str]:
101
+ a = creation.get("a") or []
102
+ if not a:
103
+ return None, HEURISTIC
104
+ t = ev.eval(a[0], ctx)
105
+ if t.unknown or not t.text or "{" in t.text:
106
+ return None, HEURISTIC
107
+ return t.text, (EXACT if t.conf == EXACT else RESOLVED)
108
+
109
+
110
+ _CH = {"method": "MethodChannel", "event": "EventChannel"}
111
+
112
+
113
+ def _dynamic(builder, fn, f, kind, what) -> None:
114
+ """A channel call whose channel or method name does not evaluate to a string -> the bridges `unresolved` list
115
+ (`cg bridges --unmatched`), like a dynamic React Native / Capacitor / Cordova name."""
116
+ builder.__dict__.setdefault("bridge_dynamic", []).append(
117
+ {"file": fn.file, "line": f.get("l"), "protocol": "flutter" if kind == "method" else "flutter-event", "what": what})
118
+
119
+
120
+ def emit(prog: DartProgram, ev, builder) -> dict:
121
+ from ...bridges import protocol_send
122
+ n = {"flutter": 0, "flutter-event": 0, "unresolved_channel": 0}
123
+ texts: dict = {}
124
+ for fn in list(prog.all_funcs()):
125
+ ctx = None
126
+ for f in fn.facts:
127
+ if f.get("ft") == "call" and f.get("n") == "setMethodCallHandler" and f.get("t"):
128
+ ctx = ctx or prog.ctx_of(fn)
129
+ n["dart_handlers"] = n.get("dart_handlers", 0) + _dart_handler(prog, ev, builder, fn, f, ctx, texts)
130
+ continue
131
+ if f.get("ft") != "call" or f.get("n") not in INVOKE | {"receiveBroadcastStream"} or not f.get("t"):
132
+ continue
133
+ ctx = ctx or prog.ctx_of(fn)
134
+ cr = _creation(prog, f["t"], ctx)
135
+ if not cr:
136
+ continue
137
+ kind, creation, cctx = cr
138
+ name, conf = _channel_name(ev, creation, cctx)
139
+ if not name:
140
+ n["unresolved_channel"] += 1
141
+ _dynamic(builder, fn, f, kind, f"{_CH[kind]}(<dynamic>).{f['n']}")
142
+ continue
143
+ if kind == "method" and f["n"] in INVOKE:
144
+ a = f.get("a") or []
145
+ m = ev.eval(a[0], ctx) if a else None
146
+ if m is None or m.unknown or not m.text or "{" in m.text:
147
+ n["unresolved_channel"] += 1
148
+ _dynamic(builder, fn, f, kind, f"{name}.{f['n']}(<dynamic>)")
149
+ continue
150
+ protocol_send(builder, "flutter", name, m.text, fn.id, fn.file, f.get("l"),
151
+ conf if m.conf == EXACT else RESOLVED, via=f["n"])
152
+ n["flutter"] += 1
153
+ elif kind == "event" and f["n"] == "receiveBroadcastStream":
154
+ protocol_send(builder, "flutter-event", name, None, fn.id, fn.file, f.get("l"), conf, via=f["n"])
155
+ n["flutter-event"] += 1
156
+ return n
157
+
158
+
159
+ # ------------------------------------------------------------------ Pigeon (generated BasicMessageChannels)
160
+ PIGEON_ANN = {"HostApi": "host", "FlutterApi": "flutter"}
161
+
162
+
163
+ def pigeon_apis(prog: DartProgram) -> dict:
164
+ """`@HostApi()` / `@FlutterApi()` abstract classes of the Pigeon definition files (`import
165
+ 'package:pigeon/pigeon.dart'`): name -> {kind, methods, file, line, id}. The generated code (`*.g.dart`, `.g.kt`,
166
+ `.g.swift`) is often not checked in; the definitions name the API and its methods either way."""
167
+ out = {}
168
+ for c in prog.classes.values() if isinstance(prog.classes, dict) else prog.classes:
169
+ kinds = [PIGEON_ANN[a] for a in c.ann if a in PIGEON_ANN]
170
+ if not kinds or not prog.imports_ext(c.lib, "package:pigeon/"):
171
+ continue
172
+ meths = sorted(n for n, m in c.methods.items() if m.kind == "method" and not n.startswith("_"))
173
+ out[c.name] = {"kind": kinds[0], "methods": meths, "file": c.file, "line": c.line, "id": c.id}
174
+ return out
175
+
176
+
177
+ def emit_pigeon(prog: DartProgram, builder, apis: dict) -> dict:
178
+ """Dart side of Pigeon APIs: a call of a @HostApi method on the generated class (`NativeSyncApi().m()`, a field /
179
+ provider typed `NativeSyncApi`) -> SENDS_TO endpoint:pigeon:<Api>#<method>; a class that extends / implements a
180
+ @FlutterApi (`class BgService extends BackgroundWorkerFlutterApi`) -> RECEIVED_BY from the endpoint to its method
181
+ (the native side calls it)."""
182
+ from ...bridges import protocol_receive, protocol_send
183
+ n = {"sends": 0, "dart_receivers": 0}
184
+ host = {k: v for k, v in apis.items() if v["kind"] == "host"}
185
+ defs = {v["file"] for v in apis.values()}
186
+ if host:
187
+ names = {m for v in host.values() for m in v["methods"]}
188
+ for fn in list(prog.all_funcs()):
189
+ if fn.file in defs:
190
+ continue
191
+ ctx = None
192
+ for f in fn.facts:
193
+ if f.get("ft") != "call" or f.get("n") not in names or not f.get("t"):
194
+ continue
195
+ ctx = ctx or prog.ctx_of(fn)
196
+ try:
197
+ tt = prog.infer(f["t"], ctx)
198
+ except RecursionError:
199
+ continue
200
+ api = None
201
+ if not tt or tt[0] not in ("einst", "inst"):
202
+ pt = _provider_type(prog, f["t"], ctx) # ref.read(nativeSyncApiProvider).m()
203
+ tt = ("einst", pt, []) if pt else tt
204
+ if tt and tt[0] == "einst":
205
+ api = str(tt[1]).split(".")[-1].split("<")[0]
206
+ elif tt and tt[0] == "inst" and tt[1] is not None and tt[1].name in host:
207
+ api = tt[1].name # the definition class, or a checked-in generated class of that name
208
+ if api in host and f["n"] in host[api]["methods"]:
209
+ protocol_send(builder, "pigeon", api, f["n"], fn.id, fn.file, f.get("l"), RESOLVED,
210
+ via="Pigeon HostApi", direction="to_native")
211
+ n["sends"] += 1
212
+ flutter = {k: v for k, v in apis.items() if v["kind"] == "flutter"}
213
+ if flutter:
214
+ for c in (prog.classes.values() if isinstance(prog.classes, dict) else prog.classes):
215
+ if c.file in defs or c.file.startswith(("test/", "integration_test/")) or "/test/" in c.file \
216
+ or c.file.endswith("_test.dart"):
217
+ continue # mocks / fakes of the API in tests
218
+ sup = {split_base(t) for _rel, t in c.supers}
219
+ for api in sup & set(flutter):
220
+ for m in flutter[api]["methods"]:
221
+ f = c.methods.get(m)
222
+ if f is None:
223
+ continue
224
+ protocol_receive(builder, "pigeon", api, m, f.id, f.file, f.line, RESOLVED,
225
+ via="Pigeon FlutterApi implementation", side="dart")
226
+ n["dart_receivers"] += 1
227
+ return n
228
+
229
+
230
+ def split_base(t: str) -> str:
231
+ return (t or "").split("<")[0].split(".")[-1].strip().rstrip("?")
232
+
233
+
234
+ PROVIDER_CTORS = ("Provider", "FutureProvider", "StateProvider", "AutoDisposeProvider", "ChangeNotifierProvider")
235
+
236
+
237
+ def _provider_type(prog, r, ctx):
238
+ """`ref.read(p)` / `ref.watch(p)` / `context.read<T>()` where `p = Provider<T>((ref) => ...)`: T."""
239
+ if not isinstance(r, dict) or r.get("k") != "call" or r.get("n") not in ("read", "watch"):
240
+ return None
241
+ if r.get("ta"):
242
+ return split_base(r["ta"][0])
243
+ a = r.get("a") or []
244
+ if not a or not isinstance(a[0], dict) or a[0].get("k") != "id":
245
+ return None
246
+ v = prog.lookup(ctx.lib, a[0]["v"])
247
+ init = getattr(v, "init", None)
248
+ if not isinstance(init, dict):
249
+ return None
250
+ ct = ctor_type(init) or ""
251
+ if ct.split(".")[-1].split("<")[0] not in PROVIDER_CTORS:
252
+ return None
253
+ ta = init.get("ta") or []
254
+ if ta:
255
+ return split_base(ta[0])
256
+ m = re.search(r"<\s*([\w.]+)", ct)
257
+ return split_base(m.group(1)) if m else None
258
+
259
+
260
+ # ------------------------------------------------------------------ Dart as the receiving side (native invokeMethod)
261
+ DART_SITE = re.compile(r"\.method\s*==\s*['\"]([^'\"]+)['\"]|['\"]([^'\"]+)['\"]\s*==\s*\w+\.method\b")
262
+ DART_SWITCH = re.compile(r"\bswitch\s*\(\s*\w+\.method\s*\)\s*\{")
263
+ DART_CASE = re.compile(r"\bcase\s+['\"]([^'\"]+)['\"]\s*(?::|when\b|\|\|)|\|\|\s*['\"]([^'\"]+)['\"]")
264
+
265
+
266
+ def _match(text: str, i: int, o: str, c: str) -> int:
267
+ depth = 0
268
+ for j in range(i, len(text)):
269
+ ch = text[j]
270
+ if ch == o:
271
+ depth += 1
272
+ elif ch == c:
273
+ depth -= 1
274
+ if depth == 0:
275
+ return j
276
+ return len(text)
277
+
278
+
279
+ def _line_off(text: str, line: int) -> int:
280
+ pos = 0
281
+ for _ in range(max(0, line - 1)):
282
+ pos = text.find("\n", pos) + 1
283
+ if pos == 0:
284
+ return len(text)
285
+ return pos
286
+
287
+
288
+ def _dart_handler(prog, ev, builder, fn, f, ctx, texts) -> int:
289
+ """`channel.setMethodCallHandler(handler)` on a channel with a known name: the method names the handler tests
290
+ (`call.method == 'x'`, `switch (call.method) { case 'x': }`) are received in Dart (RECEIVED_BY from
291
+ endpoint:flutter:<channel>#<x>; the native side sends them with invokeMethod)."""
292
+ from ...bridges import protocol_receive
293
+ cr = _creation(prog, f["t"], ctx)
294
+ if not cr or cr[0] != "method":
295
+ return 0
296
+ name, conf = _channel_name(ev, cr[1], cr[2])
297
+ if not name:
298
+ return 0
299
+ a = (f.get("a") or [None])[0]
300
+ target, file = fn, fn.file
301
+ if isinstance(a, dict) and a.get("k") in ("id", "prop"):
302
+ nm = a.get("v") if a.get("k") == "id" else a.get("n")
303
+ h = None
304
+ if fn.cls is not None:
305
+ h, _ = prog.find_member(fn.cls, nm)
306
+ if h is None:
307
+ h = prog.lookup(fn.lib, nm)
308
+ if not isinstance(h, DFunc):
309
+ return 0
310
+ target, file = h, h.file
311
+ root = getattr(prog, "root", None)
312
+ if file not in texts:
313
+ try:
314
+ texts[file] = (root / file).read_text(encoding="utf-8", errors="replace") if root else ""
315
+ except OSError:
316
+ texts[file] = ""
317
+ text = texts[file]
318
+ if not text:
319
+ return 0
320
+ if target is fn: # a closure: the call's argument list
321
+ lo = text.find("setMethodCallHandler", _line_off(text, f.get("l") or 1))
322
+ if lo < 0:
323
+ return 0
324
+ lo = text.find("(", lo)
325
+ hi = _match(text, lo, "(", ")")
326
+ else:
327
+ lo = _line_off(text, target.line)
328
+ b0 = text.find("{", lo)
329
+ hi = _match(text, b0, "{", "}") if b0 >= 0 else lo
330
+ region = text[lo:hi]
331
+ meths = []
332
+ for m in DART_SITE.finditer(region):
333
+ meths.append((m.group(1) or m.group(2), lo + m.start()))
334
+ for m in DART_SWITCH.finditer(region):
335
+ end = _match(region, m.end() - 1, "{", "}")
336
+ for cm in DART_CASE.finditer(region, m.end(), end):
337
+ meths.append((cm.group(1) or cm.group(2), lo + cm.start()))
338
+ seen = set()
339
+ for meth, pos in meths:
340
+ if meth in seen:
341
+ continue
342
+ seen.add(meth)
343
+ protocol_receive(builder, "flutter", name, meth, target.id, file, text.count("\n", 0, pos) + 1,
344
+ conf if conf != EXACT else RESOLVED, via="setMethodCallHandler (Dart)", side="dart")
345
+ return len(seen)