codegraph-engine 3.0.0__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.
- codegraph/__init__.py +37 -0
- codegraph/agent.py +26 -0
- codegraph/agent_brain.py +2212 -0
- codegraph/agent_capabilities.py +4058 -0
- codegraph/agent_rules.py +531 -0
- codegraph/api_drift.py +455 -0
- codegraph/architecture.py +371 -0
- codegraph/async_queue/__init__.py +50 -0
- codegraph/async_queue/detectors/__init__.py +48 -0
- codegraph/async_queue/detectors/celery.py +160 -0
- codegraph/async_queue/detectors/kafka.py +168 -0
- codegraph/async_queue/detectors/rabbitmq.py +116 -0
- codegraph/async_queue/detectors/redis.py +130 -0
- codegraph/async_queue/detectors/sqs.py +135 -0
- codegraph/async_queue/models.py +161 -0
- codegraph/async_queue/otel_models.py +74 -0
- codegraph/async_queue/otel_parser.py +337 -0
- codegraph/async_queue/reconciler.py +427 -0
- codegraph/async_queue/scanner.py +291 -0
- codegraph/async_queue/traversal.py +447 -0
- codegraph/audit.py +106 -0
- codegraph/binding_resolver.py +616 -0
- codegraph/branch_comparison.py +197 -0
- codegraph/cache.py +95 -0
- codegraph/change_impact.py +547 -0
- codegraph/cli.py +2436 -0
- codegraph/cli_output.py +205 -0
- codegraph/config.py +45 -0
- codegraph/constraints.py +238 -0
- codegraph/context.py +2182 -0
- codegraph/context_freshness.py +351 -0
- codegraph/database/__init__.py +72 -0
- codegraph/database/extractor.py +3651 -0
- codegraph/database/interrogation.py +1494 -0
- codegraph/database/models.py +310 -0
- codegraph/database/schema_drift.py +862 -0
- codegraph/epistemic.py +200 -0
- codegraph/errors.py +282 -0
- codegraph/evidence/__init__.py +15 -0
- codegraph/evidence/citations.py +418 -0
- codegraph/evidence_contract.py +486 -0
- codegraph/frameworks.py +960 -0
- codegraph/frameworks_nestjs.py +502 -0
- codegraph/frameworks_nextjs.py +337 -0
- codegraph/frameworks_react.py +457 -0
- codegraph/freshness.py +335 -0
- codegraph/frontend_html.py +109 -0
- codegraph/git.py +362 -0
- codegraph/git_state.py +545 -0
- codegraph/graph/__init__.py +46 -0
- codegraph/graph/models.py +56 -0
- codegraph/graph/traversal.py +1993 -0
- codegraph/indexing/__init__.py +24 -0
- codegraph/indexing/classifier.py +564 -0
- codegraph/indexing/indexer.py +2578 -0
- codegraph/indexing/models.py +427 -0
- codegraph/indexing/parser.py +3404 -0
- codegraph/indexing/pool.py +136 -0
- codegraph/indexing/scanner.py +211 -0
- codegraph/indexing/telemetry.py +326 -0
- codegraph/indexing/templates.py +418 -0
- codegraph/indexing/test_framework.py +116 -0
- codegraph/installer.py +1702 -0
- codegraph/interrogation.py +2355 -0
- codegraph/llm/__init__.py +3 -0
- codegraph/llm/base.py +15 -0
- codegraph/llm/context.py +20 -0
- codegraph/mcp/__init__.py +3 -0
- codegraph/mcp/server.py +1476 -0
- codegraph/mcp_diagnostics.py +882 -0
- codegraph/memory/__init__.py +3 -0
- codegraph/memory/store.py +46 -0
- codegraph/models.py +310 -0
- codegraph/monorepo.py +925 -0
- codegraph/observability.py +262 -0
- codegraph/optimizer.py +1014 -0
- codegraph/planner.py +417 -0
- codegraph/process_lifecycle.py +1153 -0
- codegraph/py.typed +1 -0
- codegraph/query_expansion.py +199 -0
- codegraph/ranking.py +408 -0
- codegraph/refactor/__init__.py +36 -0
- codegraph/refactor/models.py +114 -0
- codegraph/refactor/renamer.py +844 -0
- codegraph/refactor/transactions.py +414 -0
- codegraph/resolver.py +3968 -0
- codegraph/resources/__init__.py +46 -0
- codegraph/resources/cache.py +117 -0
- codegraph/resources/coalescer.py +83 -0
- codegraph/resources/debouncer.py +98 -0
- codegraph/resources/governor.py +219 -0
- codegraph/resources/monitor.py +102 -0
- codegraph/resources/policy.py +123 -0
- codegraph/retrieval_policy.py +320 -0
- codegraph/roblox.py +2174 -0
- codegraph/route_composer.py +363 -0
- codegraph/route_topology.py +172 -0
- codegraph/runtime/__init__.py +51 -0
- codegraph/runtime/collector.py +155 -0
- codegraph/runtime/db_watcher.py +191 -0
- codegraph/runtime/ingestor.py +692 -0
- codegraph/runtime/models.py +130 -0
- codegraph/runtime/port_inspector.py +304 -0
- codegraph/runtime/reconciliation.py +492 -0
- codegraph/runtime/runner.py +353 -0
- codegraph/runtime/trace_linker.py +175 -0
- codegraph/search/__init__.py +25 -0
- codegraph/search/hybrid.py +846 -0
- codegraph/search/semantic.py +28 -0
- codegraph/security/__init__.py +51 -0
- codegraph/security/paths.py +189 -0
- codegraph/security/redaction.py +633 -0
- codegraph/semantic_conflicts.py +270 -0
- codegraph/semantic_decorators.py +372 -0
- codegraph/structural_diff.py +542 -0
- codegraph/styles.py +144 -0
- codegraph/symbol_history.py +277 -0
- codegraph/target_resolver.py +348 -0
- codegraph/task.py +637 -0
- codegraph/tool_selection_eval.py +1158 -0
- codegraph/tsconfig.py +236 -0
- codegraph/ui/__init__.py +6 -0
- codegraph/ui/server.py +755 -0
- codegraph/watcher.py +323 -0
- codegraph_engine-3.0.0.dist-info/METADATA +859 -0
- codegraph_engine-3.0.0.dist-info/RECORD +130 -0
- codegraph_engine-3.0.0.dist-info/WHEEL +6 -0
- codegraph_engine-3.0.0.dist-info/entry_points.txt +2 -0
- codegraph_engine-3.0.0.dist-info/licenses/LICENSE +21 -0
- 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
|
+
)
|