codegraph-engine 3.0.0__2-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 (130) hide show
  1. codegraph/__init__.py +37 -0
  2. codegraph/agent.py +26 -0
  3. codegraph/agent_brain.py +2212 -0
  4. codegraph/agent_capabilities.py +4058 -0
  5. codegraph/agent_rules.py +531 -0
  6. codegraph/api_drift.py +455 -0
  7. codegraph/architecture.py +371 -0
  8. codegraph/async_queue/__init__.py +50 -0
  9. codegraph/async_queue/detectors/__init__.py +48 -0
  10. codegraph/async_queue/detectors/celery.py +160 -0
  11. codegraph/async_queue/detectors/kafka.py +168 -0
  12. codegraph/async_queue/detectors/rabbitmq.py +116 -0
  13. codegraph/async_queue/detectors/redis.py +130 -0
  14. codegraph/async_queue/detectors/sqs.py +135 -0
  15. codegraph/async_queue/models.py +161 -0
  16. codegraph/async_queue/otel_models.py +74 -0
  17. codegraph/async_queue/otel_parser.py +337 -0
  18. codegraph/async_queue/reconciler.py +427 -0
  19. codegraph/async_queue/scanner.py +291 -0
  20. codegraph/async_queue/traversal.py +447 -0
  21. codegraph/audit.py +106 -0
  22. codegraph/binding_resolver.py +616 -0
  23. codegraph/branch_comparison.py +197 -0
  24. codegraph/cache.py +95 -0
  25. codegraph/change_impact.py +547 -0
  26. codegraph/cli.py +2436 -0
  27. codegraph/cli_output.py +205 -0
  28. codegraph/config.py +45 -0
  29. codegraph/constraints.py +238 -0
  30. codegraph/context.py +2182 -0
  31. codegraph/context_freshness.py +351 -0
  32. codegraph/database/__init__.py +72 -0
  33. codegraph/database/extractor.py +3651 -0
  34. codegraph/database/interrogation.py +1494 -0
  35. codegraph/database/models.py +310 -0
  36. codegraph/database/schema_drift.py +862 -0
  37. codegraph/epistemic.py +200 -0
  38. codegraph/errors.py +282 -0
  39. codegraph/evidence/__init__.py +15 -0
  40. codegraph/evidence/citations.py +418 -0
  41. codegraph/evidence_contract.py +486 -0
  42. codegraph/frameworks.py +960 -0
  43. codegraph/frameworks_nestjs.py +502 -0
  44. codegraph/frameworks_nextjs.py +337 -0
  45. codegraph/frameworks_react.py +457 -0
  46. codegraph/freshness.py +335 -0
  47. codegraph/frontend_html.py +109 -0
  48. codegraph/git.py +362 -0
  49. codegraph/git_state.py +545 -0
  50. codegraph/graph/__init__.py +46 -0
  51. codegraph/graph/models.py +56 -0
  52. codegraph/graph/traversal.py +1993 -0
  53. codegraph/indexing/__init__.py +24 -0
  54. codegraph/indexing/classifier.py +564 -0
  55. codegraph/indexing/indexer.py +2578 -0
  56. codegraph/indexing/models.py +427 -0
  57. codegraph/indexing/parser.py +3404 -0
  58. codegraph/indexing/pool.py +136 -0
  59. codegraph/indexing/scanner.py +211 -0
  60. codegraph/indexing/telemetry.py +326 -0
  61. codegraph/indexing/templates.py +418 -0
  62. codegraph/indexing/test_framework.py +116 -0
  63. codegraph/installer.py +1702 -0
  64. codegraph/interrogation.py +2355 -0
  65. codegraph/llm/__init__.py +3 -0
  66. codegraph/llm/base.py +15 -0
  67. codegraph/llm/context.py +20 -0
  68. codegraph/mcp/__init__.py +3 -0
  69. codegraph/mcp/server.py +1476 -0
  70. codegraph/mcp_diagnostics.py +882 -0
  71. codegraph/memory/__init__.py +3 -0
  72. codegraph/memory/store.py +46 -0
  73. codegraph/models.py +310 -0
  74. codegraph/monorepo.py +925 -0
  75. codegraph/observability.py +262 -0
  76. codegraph/optimizer.py +1014 -0
  77. codegraph/planner.py +417 -0
  78. codegraph/process_lifecycle.py +1153 -0
  79. codegraph/py.typed +1 -0
  80. codegraph/query_expansion.py +199 -0
  81. codegraph/ranking.py +408 -0
  82. codegraph/refactor/__init__.py +36 -0
  83. codegraph/refactor/models.py +114 -0
  84. codegraph/refactor/renamer.py +844 -0
  85. codegraph/refactor/transactions.py +414 -0
  86. codegraph/resolver.py +3968 -0
  87. codegraph/resources/__init__.py +46 -0
  88. codegraph/resources/cache.py +117 -0
  89. codegraph/resources/coalescer.py +83 -0
  90. codegraph/resources/debouncer.py +98 -0
  91. codegraph/resources/governor.py +219 -0
  92. codegraph/resources/monitor.py +102 -0
  93. codegraph/resources/policy.py +123 -0
  94. codegraph/retrieval_policy.py +320 -0
  95. codegraph/roblox.py +2174 -0
  96. codegraph/route_composer.py +363 -0
  97. codegraph/route_topology.py +172 -0
  98. codegraph/runtime/__init__.py +51 -0
  99. codegraph/runtime/collector.py +155 -0
  100. codegraph/runtime/db_watcher.py +191 -0
  101. codegraph/runtime/ingestor.py +692 -0
  102. codegraph/runtime/models.py +130 -0
  103. codegraph/runtime/port_inspector.py +304 -0
  104. codegraph/runtime/reconciliation.py +492 -0
  105. codegraph/runtime/runner.py +353 -0
  106. codegraph/runtime/trace_linker.py +175 -0
  107. codegraph/search/__init__.py +25 -0
  108. codegraph/search/hybrid.py +846 -0
  109. codegraph/search/semantic.py +28 -0
  110. codegraph/security/__init__.py +51 -0
  111. codegraph/security/paths.py +189 -0
  112. codegraph/security/redaction.py +633 -0
  113. codegraph/semantic_conflicts.py +270 -0
  114. codegraph/semantic_decorators.py +372 -0
  115. codegraph/structural_diff.py +542 -0
  116. codegraph/styles.py +144 -0
  117. codegraph/symbol_history.py +277 -0
  118. codegraph/target_resolver.py +348 -0
  119. codegraph/task.py +637 -0
  120. codegraph/tool_selection_eval.py +1158 -0
  121. codegraph/tsconfig.py +236 -0
  122. codegraph/ui/__init__.py +6 -0
  123. codegraph/ui/server.py +755 -0
  124. codegraph/watcher.py +323 -0
  125. codegraph_engine-3.0.0.dist-info/METADATA +868 -0
  126. codegraph_engine-3.0.0.dist-info/RECORD +130 -0
  127. codegraph_engine-3.0.0.dist-info/WHEEL +6 -0
  128. codegraph_engine-3.0.0.dist-info/entry_points.txt +2 -0
  129. codegraph_engine-3.0.0.dist-info/licenses/LICENSE +21 -0
  130. codegraph_engine-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,457 @@
1
+ """React and JSX/TSX Component Architecture and Composition Analyzer (Pillar 4).
2
+
3
+ Extracts deterministic facts from React components:
4
+ - Component definitions (Functional components, React.FC, memo, forwardRef)
5
+ - Custom hooks (useAuth, useInvoices)
6
+ - JSX component composition hierarchy (ParentComponent -> RENDERS -> ChildComponent)
7
+ - Hook invocations (Component -> USES_HOOK -> Hook)
8
+ - Context provider mountings (Component -> PROVIDES -> Context)
9
+ - Client-side data fetching (Component/Hook -> FETCHES_ROUTE -> RouteEndpoint)
10
+ - Stylesheet imports and CSS class usages (IMPORTS_STYLE, USES_STYLE_CLASS)
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import re
15
+ from dataclasses import dataclass
16
+
17
+ from codegraph.indexing.models import BindingRef, Symbol
18
+
19
+ _REACT_BUILTINS = frozenset({
20
+ "Fragment",
21
+ "React.Fragment",
22
+ "Suspense",
23
+ "React.Suspense",
24
+ "StrictMode",
25
+ "React.StrictMode",
26
+ "Profiler",
27
+ "React.Profiler",
28
+ })
29
+
30
+ # React component function / arrow definition patterns
31
+ _REACT_FUNC_COMP = re.compile(
32
+ r"^(?:export\s+(?:default\s+)?)?(?:async\s+)?function\s+([A-Z][A-Za-z0-9_$]*)\s*\("
33
+ )
34
+ _REACT_CONST_COMP = re.compile(
35
+ r"^(?:export\s+)?(?:const|let)\s+([A-Z][A-Za-z0-9_$]*)\s*(?::\s*(?:React\.)?(?:FC|FunctionComponent)(?:<[^>]+>)?)?\s*=\s*"
36
+ r"(?:(?:\([^)]*\)|[A-Za-z0-9_$]+)\s*=>|function\b|(?:React\.)?(?:memo|forwardRef)\s*\()"
37
+ )
38
+
39
+ # Custom hook definition patterns (starts with 'use' followed by uppercase letter)
40
+ _REACT_HOOK_DEF = re.compile(
41
+ r"^(?:export\s+(?:default\s+)?)?(?:async\s+)?function\s+(use[A-Z][A-Za-z0-9_$]*)\s*\("
42
+ r"|^(?:export\s+)?(?:const|let)\s+(use[A-Z][A-Za-z0-9_$]*)\s*(?::\s*[^=]+)?=\s*"
43
+ )
44
+
45
+ # JSX element tag opening: <ComponentName ... or <ComponentName/> or <Namespace.Component
46
+ _JSX_ELEMENT_TAG = re.compile(
47
+ r"<([A-Z][A-Za-z0-9_$]*(?:\.[A-Z][A-Za-z0-9_$]*)?)[\s/>]"
48
+ )
49
+
50
+ # Context provider: <AuthContext.Provider
51
+ _CONTEXT_PROVIDER_TAG = re.compile(
52
+ r"<([A-Za-z0-9_$]+Context)\.Provider[\s>]"
53
+ )
54
+
55
+ # Hook invocation call: useAuth(), useInvoices(), useState()
56
+ _HOOK_CALL = re.compile(
57
+ r"\b(use[A-Z][A-Za-z0-9_$]*)\s*\("
58
+ )
59
+
60
+ # Client-side data fetching calls across all major modern libraries (fetch, axios, useSWR, useQuery, etc.)
61
+ _FETCH_CALL_PATTERNS = (
62
+ # fetch<ResponseType>('/api/...') or fetch(`/api/...`)
63
+ re.compile(
64
+ r"""\bfetch\s*(?:<([A-Za-z0-9_$,\s\[\]]+)>)?\s*\(\s*[`'"](/[^`'"]+)[`'"]"""
65
+ ),
66
+ # (axios|api|apiClient|client|http|httpClient|request).verb<ResponseType>('/api/...')
67
+ re.compile(
68
+ r"""\b(?:axios|api|apiClient|client|http|httpClient|request)\.(get|post|put|delete|patch)\s*(?:<([A-Za-z0-9_$,\s\[\]]+)>)?\s*\(\s*[`'"](/[^`'"]+)[`'"]"""
69
+ ),
70
+ # axios<ResponseType>('/api/...') or axios({ url: '/api/...' })
71
+ re.compile(
72
+ r"""\baxios\s*(?:<([A-Za-z0-9_$,\s\[\]]+)>)?\s*\(\s*(?:\{\s*url:\s*)?[`'"](/[^`'"]+)[`'"]"""
73
+ ),
74
+ # useSWR<ResponseType>('/api/...')
75
+ re.compile(
76
+ r"""\buseSWR\s*(?:<([A-Za-z0-9_$,\s\[\]]+)>)?\s*\(\s*(?:\[\s*)?[`'"](/[^`'"]+)[`'"]"""
77
+ ),
78
+ # useQuery<ResponseType>(['/api/...']) or useQuery({ queryKey: ['/api/...'] })
79
+ re.compile(
80
+ r"""\buseQuery\s*(?:<([A-Za-z0-9_$,\s\[\]]+)>)?\s*\(\s*(?:\[\s*|\{\s*queryKey:\s*\[\s*)?[`'"](/[^`'"]+)[`'"]"""
81
+ ),
82
+ # $fetch<ResponseType>('/api/...') or useFetch<ResponseType>('/api/...') (Vue / Nuxt)
83
+ re.compile(
84
+ r"""\b(?:\$fetch|useFetch)\s*(?:<([A-Za-z0-9_$,\s\[\]]+)>)?\s*\(\s*[`'"](/[^`'"]+)[`'"]"""
85
+ ),
86
+ )
87
+
88
+ _TS_INTERFACE_DEF = re.compile(
89
+ r"""(?:export\s+)?(?:interface|type)\s+([A-Z][A-Za-z0-9_$]*)(?:\s+extends\s+[^{]+)?\s*(?:=\s*)?\{([^}]+)\}""",
90
+ re.MULTILINE | re.DOTALL,
91
+ )
92
+ _TS_FIELD_DEF = re.compile(r"""([a-zA-Z0-9_$]+)(\?)?\s*:\s*([^;,\n]+)""")
93
+
94
+
95
+ # CSS / SCSS style imports
96
+ _STYLE_IMPORT_NAMED = re.compile(
97
+ r"""import\s+([A-Za-z0-9_$]+)\s+from\s+['"]([^'"]+\.(?:module\.(?:css|scss|sass|less)|css|scss|sass|less))['"]"""
98
+ )
99
+ _STYLE_IMPORT_SIDE_EFFECT = re.compile(
100
+ r"""import\s+['"]([^'"]+\.(?:module\.(?:css|scss|sass|less)|css|scss|sass|less))['"]"""
101
+ )
102
+
103
+ # CSS class usage: styles.headerTitle or styles['header-title']
104
+ _MODULE_CLASS_USAGE = re.compile(
105
+ r"""\b([A-Za-z0-9_$]+)\.([A-Za-z0-9_$]+)|\b([A-Za-z0-9_$]+)\['([^']+)'\]"""
106
+ )
107
+ # className="btn-primary flex items-center"
108
+ _CLASSNAME_LITERAL = re.compile(
109
+ r"""className\s*=\s*['"]([^'"]+)['"]"""
110
+ )
111
+
112
+
113
+ @dataclass(frozen=True)
114
+ class ReactElementRender:
115
+ parent_component: str
116
+ child_component: str
117
+ file_path: str
118
+ line: int
119
+
120
+
121
+ @dataclass(frozen=True)
122
+ class ReactHookCall:
123
+ caller_symbol: str
124
+ hook_name: str
125
+ file_path: str
126
+ line: int
127
+
128
+
129
+ @dataclass(frozen=True)
130
+ class ReactAnalysisResult:
131
+ components: tuple[Symbol, ...] = ()
132
+ hooks: tuple[Symbol, ...] = ()
133
+ bindings: tuple[BindingRef, ...] = ()
134
+ renders: tuple[ReactElementRender, ...] = ()
135
+ hook_calls: tuple[ReactHookCall, ...] = ()
136
+
137
+
138
+ def _estimate_block_end(lines: list[str], start_idx: int) -> int:
139
+ depth = 0
140
+ opened = False
141
+ for i, line in enumerate(lines[start_idx:], start=start_idx):
142
+ opens = line.count("{")
143
+ closes = line.count("}")
144
+ if opens > 0:
145
+ opened = True
146
+ depth += opens - closes
147
+ if opened and depth <= 0:
148
+ return i + 1
149
+ if not opened and i == start_idx and ";" in line:
150
+ return i + 1
151
+ return len(lines)
152
+
153
+
154
+ def is_react_or_ui_file(file_path: str, content: str) -> bool:
155
+ """Check if file is likely a React or JSX/TSX component file."""
156
+ if file_path.endswith((".jsx", ".tsx")):
157
+ return True
158
+ if any(k in content for k in ("React", "useState", "useEffect", "export default function", "className=")):
159
+ return True
160
+ return False
161
+
162
+
163
+ def analyze_react_file(
164
+ content: str,
165
+ file_path: str,
166
+ module: str,
167
+ ) -> ReactAnalysisResult:
168
+ """Parse React components, JSX renders, custom hooks, style links, and data fetches."""
169
+ if not is_react_or_ui_file(file_path, content):
170
+ return ReactAnalysisResult()
171
+
172
+ lines = content.splitlines()
173
+ components: list[Symbol] = []
174
+ hooks: list[Symbol] = []
175
+ bindings: list[BindingRef] = []
176
+ renders: list[ReactElementRender] = []
177
+ hook_calls: list[ReactHookCall] = []
178
+
179
+ # Map line number intervals to active component/hook name
180
+ scope_intervals: list[tuple[int, int, str]] = []
181
+
182
+ # 1. First pass: Discover component definitions and custom hooks
183
+ for idx, line in enumerate(lines):
184
+ lineno = idx + 1
185
+ stripped = line.strip()
186
+
187
+ # Component definition
188
+ m_comp = _REACT_FUNC_COMP.match(stripped) or _REACT_CONST_COMP.match(stripped)
189
+ if m_comp:
190
+ c_name = m_comp.group(1)
191
+ end_ln = _estimate_block_end(lines, idx)
192
+ scope_intervals.append((lineno, end_ln, c_name))
193
+ canon_id = f"{module}.{c_name}" if module else c_name
194
+ components.append(
195
+ Symbol(
196
+ id=canon_id,
197
+ canonical_id=canon_id,
198
+ name=c_name,
199
+ qualified_name=c_name,
200
+ kind="component",
201
+ language="typescript" if file_path.endswith((".ts", ".tsx")) else "javascript",
202
+ module=module,
203
+ path=file_path,
204
+ file_path=file_path,
205
+ scope="",
206
+ start_line=lineno,
207
+ end_line=end_ln,
208
+ )
209
+ )
210
+ continue
211
+
212
+ # Hook definition
213
+ m_hook = _REACT_HOOK_DEF.match(stripped)
214
+ if m_hook:
215
+ h_name = m_hook.group(1) or m_hook.group(2)
216
+ end_ln = _estimate_block_end(lines, idx)
217
+ scope_intervals.append((lineno, end_ln, h_name))
218
+ canon_id = f"{module}.{h_name}" if module else h_name
219
+ hooks.append(
220
+ Symbol(
221
+ id=canon_id,
222
+ canonical_id=canon_id,
223
+ name=h_name,
224
+ qualified_name=h_name,
225
+ kind="hook",
226
+ language="typescript" if file_path.endswith((".ts", ".tsx")) else "javascript",
227
+ module=module,
228
+ path=file_path,
229
+ file_path=file_path,
230
+ scope="",
231
+ start_line=lineno,
232
+ end_line=end_ln,
233
+ )
234
+ )
235
+
236
+ # 2. Extract style imports at file level
237
+ style_bindings: dict[str, str] = {} # binding variable -> stylesheet path
238
+ for idx, line in enumerate(lines):
239
+ lineno = idx + 1
240
+ m_style = _STYLE_IMPORT_NAMED.search(line)
241
+ if m_style:
242
+ var_name, style_path = m_style.group(1), m_style.group(2)
243
+ style_bindings[var_name] = style_path
244
+ bindings.append(
245
+ BindingRef(
246
+ target_name=style_path,
247
+ file_path=file_path,
248
+ line=lineno,
249
+ scope="",
250
+ expr_kind="REACT_IMPORTS_STYLE",
251
+ source_expr=var_name,
252
+ base_expr="IMPORTS_STYLE",
253
+ )
254
+ )
255
+ else:
256
+ m_side = _STYLE_IMPORT_SIDE_EFFECT.search(line)
257
+ if m_side:
258
+ style_path = m_side.group(1)
259
+ bindings.append(
260
+ BindingRef(
261
+ target_name=style_path,
262
+ file_path=file_path,
263
+ line=lineno,
264
+ scope="",
265
+ expr_kind="REACT_IMPORTS_STYLE",
266
+ source_expr="",
267
+ base_expr="IMPORTS_STYLE",
268
+ )
269
+ )
270
+
271
+ def find_active_scope(ln: int) -> str:
272
+ for start, end, name in scope_intervals:
273
+ if start <= ln <= end:
274
+ return name
275
+ return ""
276
+
277
+ # 3. Second pass: Scan for JSX renders, hook invocations, context providers, fetch calls, class usages
278
+ seen_renders: set[tuple[str, str, int]] = set()
279
+
280
+ for idx, line in enumerate(lines):
281
+ lineno = idx + 1
282
+ active_sym = find_active_scope(lineno)
283
+
284
+ # JSX Elements (<Button ...>)
285
+ for m_jsx in _JSX_ELEMENT_TAG.finditer(line):
286
+ child_name = m_jsx.group(1)
287
+ if child_name not in _REACT_BUILTINS:
288
+ if active_sym and (active_sym, child_name, lineno) not in seen_renders:
289
+ seen_renders.add((active_sym, child_name, lineno))
290
+ renders.append(
291
+ ReactElementRender(
292
+ parent_component=active_sym,
293
+ child_component=child_name,
294
+ file_path=file_path,
295
+ line=lineno,
296
+ )
297
+ )
298
+ bindings.append(
299
+ BindingRef(
300
+ target_name=child_name,
301
+ file_path=file_path,
302
+ line=lineno,
303
+ scope=active_sym,
304
+ expr_kind="REACT_RENDERS",
305
+ source_expr=active_sym,
306
+ base_expr="RENDERS",
307
+ )
308
+ )
309
+
310
+ # Context Providers (<AuthContext.Provider ...>)
311
+ for m_ctx in _CONTEXT_PROVIDER_TAG.finditer(line):
312
+ ctx_name = m_ctx.group(1)
313
+ bindings.append(
314
+ BindingRef(
315
+ target_name=ctx_name,
316
+ file_path=file_path,
317
+ line=lineno,
318
+ scope=active_sym,
319
+ expr_kind="REACT_CONTEXT_PROVIDER",
320
+ source_expr=active_sym,
321
+ base_expr="PROVIDES",
322
+ )
323
+ )
324
+
325
+ # Hook invocations (useAuth(), useQuery())
326
+ for m_hcall in _HOOK_CALL.finditer(line):
327
+ hook_name = m_hcall.group(1)
328
+ # Avoid self-reference if inside definition line
329
+ if active_sym and active_sym != hook_name:
330
+ hook_calls.append(
331
+ ReactHookCall(
332
+ caller_symbol=active_sym,
333
+ hook_name=hook_name,
334
+ file_path=file_path,
335
+ line=lineno,
336
+ )
337
+ )
338
+ bindings.append(
339
+ BindingRef(
340
+ target_name=hook_name,
341
+ file_path=file_path,
342
+ line=lineno,
343
+ scope=active_sym,
344
+ expr_kind="REACT_USES_HOOK",
345
+ source_expr=active_sym,
346
+ base_expr="USES_HOOK",
347
+ )
348
+ )
349
+
350
+ # Client-side API fetch calls across all modern libraries
351
+ for p in _FETCH_CALL_PATTERNS:
352
+ for m_fetch in p.finditer(line):
353
+ groups = m_fetch.groups()
354
+ method = "GET"
355
+ resp_type = ""
356
+ raw_path = ""
357
+
358
+ if len(groups) == 3:
359
+ method = (groups[0] or "GET").upper()
360
+ resp_type = (groups[1] or "").strip()
361
+ raw_path = groups[2] or ""
362
+ elif len(groups) == 2:
363
+ resp_type = (groups[0] or "").strip()
364
+ raw_path = groups[1] or ""
365
+ elif len(groups) == 1:
366
+ raw_path = groups[0] or ""
367
+
368
+ if raw_path:
369
+ m_method = re.search(r"""\bmethod\s*:\s*['"]([A-Za-z]+)['"]""", line, re.IGNORECASE)
370
+ if m_method:
371
+ method = m_method.group(1).upper()
372
+
373
+ bindings.append(
374
+ BindingRef(
375
+ target_name=raw_path,
376
+ file_path=file_path,
377
+ line=lineno,
378
+ scope=active_sym,
379
+ expr_kind="REACT_FETCH_ROUTE",
380
+ source_expr=active_sym or file_path,
381
+ base_expr=method,
382
+ attr_name=resp_type,
383
+ )
384
+ )
385
+
386
+
387
+ # CSS Class references via styles object (e.g. styles.invoiceHeader)
388
+ for m_mod in _MODULE_CLASS_USAGE.finditer(line):
389
+ mod_obj = m_mod.group(1) or m_mod.group(3)
390
+ cls_name = m_mod.group(2) or m_mod.group(4)
391
+ if mod_obj in style_bindings and cls_name:
392
+ bindings.append(
393
+ BindingRef(
394
+ target_name=cls_name,
395
+ file_path=file_path,
396
+ line=lineno,
397
+ scope=active_sym,
398
+ expr_kind="REACT_USES_STYLE_CLASS",
399
+ source_expr=active_sym or file_path,
400
+ base_expr=style_bindings[mod_obj],
401
+ attr_name=cls_name,
402
+ )
403
+ )
404
+
405
+ # Plain className strings: className="btn-primary active"
406
+ for m_cls in _CLASSNAME_LITERAL.finditer(line):
407
+ classes_str = m_cls.group(1)
408
+ for single_cls in classes_str.split():
409
+ clean_cls = single_cls.strip()
410
+ if clean_cls and not clean_cls.startswith("{"):
411
+ bindings.append(
412
+ BindingRef(
413
+ target_name=clean_cls,
414
+ file_path=file_path,
415
+ line=lineno,
416
+ scope=active_sym,
417
+ expr_kind="REACT_USES_STYLE_CLASS",
418
+ source_expr=active_sym or file_path,
419
+ base_expr="CLASSNAME",
420
+ )
421
+ )
422
+
423
+ # 4. Extract TypeScript interfaces and types for cross-language contract verification
424
+ if file_path.endswith((".ts", ".tsx")):
425
+ for m_iface in _TS_INTERFACE_DEF.finditer(content):
426
+ iface_name = m_iface.group(1)
427
+ raw_body = m_iface.group(2)
428
+ clean_body = re.sub(r"/\*.*?\*/", "", raw_body, flags=re.DOTALL)
429
+ clean_body = re.sub(r"//.*$", "", clean_body, flags=re.MULTILINE)
430
+ fields: list[tuple[str, str]] = []
431
+ for f in _TS_FIELD_DEF.finditer(clean_body):
432
+ fname = f.group(1).strip()
433
+ is_opt = bool(f.group(2))
434
+ ftype = f.group(3).strip().rstrip(";,").strip()
435
+ fields.append((fname, f"{ftype}?" if is_opt else ftype))
436
+ if fields:
437
+ start_line = content[: m_iface.start()].count("\n") + 1
438
+ bindings.append(
439
+ BindingRef(
440
+ target_name=iface_name,
441
+ file_path=file_path,
442
+ line=start_line,
443
+ scope="",
444
+ expr_kind="TS_INTERFACE",
445
+ source_expr=iface_name,
446
+ dict_entries=tuple(fields),
447
+ )
448
+ )
449
+
450
+ return ReactAnalysisResult(
451
+
452
+ components=tuple(components),
453
+ hooks=tuple(hooks),
454
+ bindings=tuple(bindings),
455
+ renders=tuple(renders),
456
+ hook_calls=tuple(hook_calls),
457
+ )