codeui-python 0.1.0__py3-none-any.whl → 0.1.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 (49) hide show
  1. codeui/__init__.py +1 -1
  2. codeui/agent/context.py +19 -3
  3. codeui/analysis/dead_code.py +13 -2
  4. codeui/analysis/undefined.py +70 -0
  5. codeui/analysis/unresolved_import.py +34 -4
  6. codeui/cli/main.py +101 -7
  7. codeui/core/cache.py +54 -0
  8. codeui/core/graph.py +233 -3
  9. codeui/core/override.py +1 -0
  10. codeui/core/resolver.py +51 -37
  11. codeui/errors.py +10 -0
  12. codeui/lang/base.py +5 -2
  13. codeui/lang/cpp.py +271 -0
  14. codeui/lang/csharp.py +262 -0
  15. codeui/lang/dart.py +278 -0
  16. codeui/lang/generic.py +6 -4
  17. codeui/lang/go.py +5 -2
  18. codeui/lang/java.py +320 -0
  19. codeui/lang/kotlin.py +303 -0
  20. codeui/lang/php.py +275 -0
  21. codeui/lang/python.py +49 -7
  22. codeui/lang/registry.py +22 -2
  23. codeui/lang/ruby.py +270 -0
  24. codeui/lang/rust.py +5 -2
  25. codeui/lang/scala.py +290 -0
  26. codeui/lang/swift.py +276 -0
  27. codeui/lang/ts.py +99 -8
  28. codeui/report/sarif_emitter.py +2 -1
  29. codeui/server/server.py +156 -85
  30. codeui/server/static/css/styles.css +1609 -0
  31. codeui/server/static/index.html +199 -0
  32. codeui/server/static/js/api.js +485 -0
  33. codeui/server/static/js/app.js +1044 -0
  34. codeui/server/static/js/graph_canvas.js +1485 -0
  35. codeui/server/static/js/minimap.js +383 -0
  36. codeui/server/static/js/sidebar.js +268 -0
  37. codeui/server/static/js/state.js +83 -0
  38. codeui/server/static/js/subgraph_window.js +1208 -0
  39. codeui/server/static/js/theme.js +332 -0
  40. codeui/tracer/__init__.py +4 -1
  41. codeui/tracer/tracer.py +109 -118
  42. codeui_python-0.1.1.dist-info/METADATA +171 -0
  43. codeui_python-0.1.1.dist-info/RECORD +71 -0
  44. {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/licenses/LICENSE +1 -1
  45. codeui_python-0.1.0.dist-info/METADATA +0 -191
  46. codeui_python-0.1.0.dist-info/RECORD +0 -52
  47. {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/WHEEL +0 -0
  48. {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/entry_points.txt +0 -0
  49. {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/top_level.txt +0 -0
codeui/lang/swift.py ADDED
@@ -0,0 +1,276 @@
1
+ """Swift language analyzer for codeui."""
2
+ from __future__ import annotations
3
+ import hashlib
4
+ import re
5
+ from pathlib import Path
6
+ from typing import ClassVar, Iterable, List, Sequence, TYPE_CHECKING
7
+ from codeui.core.ir import Edge, EdgeKind, Location, Symbol, SymbolKind, Visibility
8
+ from codeui.lang.base import ImportRef, LanguageAnalyzer, ParseResult
9
+
10
+ if TYPE_CHECKING:
11
+ from codeui.core.resolver import ResolveContext
12
+
13
+ class SwiftLanguageAnalyzer(LanguageAnalyzer):
14
+ """Swift language adapter extracting protocols, classes, structs, enums, actors, funcs, and vars.
15
+ Example:
16
+ >>> analyzer = SwiftLanguageAnalyzer()
17
+ >>> res = analyzer.parse(Path("Model.swift"), "struct User: Codable { let id: Int }")
18
+ >>> syms = list(analyzer.extract_symbols(res))
19
+ >>> any(s.kind == SymbolKind.CLASS for s in syms)
20
+ True
21
+ """
22
+ language: ClassVar[str] = "swift"
23
+ extensions: ClassVar[tuple[str, ...]] = (".swift",)
24
+
25
+ def parse(self, path: Path, source: str) -> ParseResult:
26
+ """Parse Swift source into AST-emulating representation.
27
+ Example:
28
+ >>> analyzer = SwiftLanguageAnalyzer()
29
+ >>> res = analyzer.parse(Path("App.swift"), "func run() {}")
30
+ >>> res.errors
31
+ []
32
+ """
33
+ content_hash = hashlib.sha256(source.encode("utf-8")).hexdigest()
34
+ return ParseResult(
35
+ file_path=str(path),
36
+ source=source,
37
+ ast=source,
38
+ content_hash=content_hash,
39
+ errors=[],
40
+ )
41
+
42
+ def extract_symbols(self, parse: ParseResult) -> Iterable[Symbol]:
43
+ """Extract Swift types, functions, properties, extensions, and imports.
44
+ Example:
45
+ >>> analyzer = SwiftLanguageAnalyzer()
46
+ >>> res = analyzer.parse(Path("Service.swift"), "class Service { func fetch() {} }")
47
+ >>> syms = list(analyzer.extract_symbols(res))
48
+ >>> len(syms) >= 2
49
+ True
50
+ """
51
+ source = parse.source
52
+ file_path = parse.file_path
53
+ lines = source.splitlines()
54
+ max_line = len(lines) if lines else 1
55
+ symbols: List[Symbol] = []
56
+
57
+ file_sym = Symbol(
58
+ id=file_path,
59
+ name=Path(file_path).name,
60
+ qualified_name=file_path,
61
+ kind=SymbolKind.FILE,
62
+ language=self.language,
63
+ location=Location(file_path, 1, 0, max_line, 0),
64
+ parent_id=None,
65
+ signature=None,
66
+ visibility=Visibility.PUBLIC,
67
+ modifiers=(),
68
+ content_hash=parse.content_hash,
69
+ )
70
+ symbols.append(file_sym)
71
+
72
+ import_pattern = re.compile(r'^\s*import\s+([a-zA-Z0-9_]+)')
73
+ type_pattern = re.compile(r'^\s*(?:(public|private|fileprivate|internal|open)\s+)?(?:(final)\s+)?(class|struct|protocol|enum|actor)\s+([a-zA-Z0-9_]+)(?:<[^>]+>)?(?:\s*:\s*([a-zA-Z0-9_.,\s<>]+))?')
74
+ extension_pattern = re.compile(r'^\s*extension\s+([a-zA-Z0-9_]+)(?:\s*:\s*([a-zA-Z0-9_.,\s<>]+))?')
75
+ func_pattern = re.compile(r'^\s*(?:(public|private|fileprivate|internal|open)\s+)?(?:(static|class|override|mutating|async|throws)\s+)*(func|init)\s*(?:<[^>]+>\s*)?([a-zA-Z0-9_]+)?\s*\(([^)]*)\)(?:\s*(?:async\s*)?(?:throws\s*)?->\s*([a-zA-Z0-9_?<>,\s\[\]:]+))?')
76
+ var_pattern = re.compile(r'^\s*(?:(public|private|fileprivate|internal|open)\s+)?(?:(static|class|weak|unowned)\s+)*(let|var)\s+([a-zA-Z0-9_]+)(?:\s*:\s*([a-zA-Z0-9_?<>,\s\[\]:]+))?')
77
+
78
+ current_type: str | None = None
79
+ brace_depth = 0
80
+ type_depth = 0
81
+
82
+ for line_idx, line in enumerate(lines, start=1):
83
+ stripped = line.strip()
84
+ if not stripped or stripped.startswith("//") or stripped.startswith("/*") or stripped.startswith("*"):
85
+ continue
86
+
87
+ imp_match = import_pattern.match(line)
88
+ if imp_match:
89
+ mod_name = imp_match.group(1)
90
+ symbols.append(Symbol(
91
+ id=f"{file_path}::import::{mod_name}",
92
+ name=mod_name,
93
+ qualified_name=mod_name,
94
+ kind=SymbolKind.IMPORT,
95
+ language=self.language,
96
+ location=Location(file_path, line_idx, 0, line_idx, len(line)),
97
+ parent_id=file_path,
98
+ signature=f"import {mod_name}",
99
+ visibility=Visibility.PUBLIC,
100
+ modifiers=(),
101
+ content_hash=hashlib.sha256(line.encode("utf-8")).hexdigest()[:16],
102
+ ))
103
+ continue
104
+
105
+ ext_match = extension_pattern.match(line)
106
+ if ext_match:
107
+ ext_name = ext_match.group(1)
108
+ sym_id = f"{file_path}::extension_{ext_name}_{line_idx}"
109
+ current_type = sym_id
110
+ type_depth = brace_depth
111
+ symbols.append(Symbol(
112
+ id=sym_id,
113
+ name=ext_name,
114
+ qualified_name=ext_name,
115
+ kind=SymbolKind.CLASS,
116
+ language=self.language,
117
+ location=Location(file_path, line_idx, 0, line_idx, len(line)),
118
+ parent_id=file_path,
119
+ signature=f"extension {ext_name}",
120
+ visibility=Visibility.PUBLIC,
121
+ modifiers=("extension",),
122
+ content_hash=hashlib.sha256(line.encode("utf-8")).hexdigest()[:16],
123
+ ))
124
+
125
+ type_match = type_pattern.match(line)
126
+ if type_match:
127
+ vis_str = type_match.group(1) or "internal"
128
+ final_mod = type_match.group(2)
129
+ type_kind = type_match.group(3)
130
+ type_name = type_match.group(4)
131
+ vis = Visibility.PRIVATE if vis_str in ("private", "fileprivate") else (Visibility.PROTECTED if vis_str == "internal" else Visibility.PUBLIC)
132
+ kind = SymbolKind.INTERFACE if type_kind == "protocol" else SymbolKind.CLASS
133
+ modifiers = tuple(filter(None, [vis_str, final_mod, type_kind]))
134
+ sym_id = f"{file_path}::{type_name}"
135
+ current_type = sym_id
136
+ type_depth = brace_depth
137
+
138
+ symbols.append(Symbol(
139
+ id=sym_id,
140
+ name=type_name,
141
+ qualified_name=type_name,
142
+ kind=kind,
143
+ language=self.language,
144
+ location=Location(file_path, line_idx, 0, line_idx, len(line)),
145
+ parent_id=file_path,
146
+ signature=f"{vis_str} {type_kind} {type_name}",
147
+ visibility=vis,
148
+ modifiers=modifiers,
149
+ content_hash=hashlib.sha256(line.encode("utf-8")).hexdigest()[:16],
150
+ ))
151
+
152
+ func_match = func_pattern.match(line)
153
+ if func_match:
154
+ vis_str = func_match.group(1) or "internal"
155
+ func_mod = func_match.group(2)
156
+ is_init = func_match.group(3) == "init"
157
+ func_name = "init" if is_init else (func_match.group(4) or "anonymous")
158
+ func_args = func_match.group(5) or ""
159
+ func_ret = func_match.group(6) or "Void"
160
+ vis = Visibility.PRIVATE if vis_str in ("private", "fileprivate") else (Visibility.PROTECTED if vis_str == "internal" else Visibility.PUBLIC)
161
+ parent_id = current_type if current_type and brace_depth > type_depth else file_path
162
+ sym_id = f"{parent_id}::{func_name}"
163
+ symbols.append(Symbol(
164
+ id=sym_id,
165
+ name=func_name,
166
+ qualified_name=func_name,
167
+ kind=SymbolKind.FUNCTION,
168
+ language=self.language,
169
+ location=Location(file_path, line_idx, 0, line_idx, len(line)),
170
+ parent_id=parent_id,
171
+ signature=f"func {func_name}({func_args}) -> {func_ret}" if not is_init else f"init({func_args})",
172
+ visibility=vis,
173
+ modifiers=tuple(filter(None, [vis_str, func_mod])),
174
+ content_hash=hashlib.sha256(line.encode("utf-8")).hexdigest()[:16],
175
+ ))
176
+
177
+ var_match = var_pattern.match(line)
178
+ if var_match:
179
+ vis_str = var_match.group(1) or "internal"
180
+ var_mod = var_match.group(2)
181
+ let_or_var = var_match.group(3)
182
+ var_name = var_match.group(4)
183
+ var_type = var_match.group(5) or "Any"
184
+ vis = Visibility.PRIVATE if vis_str in ("private", "fileprivate") else (Visibility.PROTECTED if vis_str == "internal" else Visibility.PUBLIC)
185
+ parent_id = current_type if current_type and brace_depth > type_depth else file_path
186
+ sym_id = f"{parent_id}::{var_name}"
187
+ symbols.append(Symbol(
188
+ id=sym_id,
189
+ name=var_name,
190
+ qualified_name=var_name,
191
+ kind=SymbolKind.VARIABLE,
192
+ language=self.language,
193
+ location=Location(file_path, line_idx, 0, line_idx, len(line)),
194
+ parent_id=parent_id,
195
+ signature=f"{let_or_var} {var_name}: {var_type}",
196
+ visibility=vis,
197
+ modifiers=tuple(filter(None, [vis_str, var_mod, let_or_var])),
198
+ content_hash=hashlib.sha256(line.encode("utf-8")).hexdigest()[:16],
199
+ ))
200
+
201
+ brace_depth += line.count("{") - line.count("}")
202
+ if current_type and brace_depth <= type_depth:
203
+ current_type = None
204
+
205
+ return sorted(symbols, key=lambda s: (s.location.start_line if s.location else 0, s.name))
206
+
207
+ def extract_edges(self, parse: ParseResult, symbols: Sequence[Symbol]) -> Iterable[Edge]:
208
+ """Extract containment, protocol adoption, class inheritance, and import edges for Swift.
209
+ Example:
210
+ >>> analyzer = SwiftLanguageAnalyzer()
211
+ >>> res = analyzer.parse(Path("App.swift"), "class Child: Parent {}")
212
+ >>> syms = list(analyzer.extract_symbols(res))
213
+ >>> edges = list(analyzer.extract_edges(res, syms))
214
+ >>> len(edges) >= 1
215
+ True
216
+ """
217
+ edges: List[Edge] = []
218
+ file_path = parse.file_path
219
+ symbol_map = {s.name: s for s in symbols}
220
+ type_inherit_pattern = re.compile(r'^\s*(?:[a-zA-Z0-9_\s<>]+)?(?:class|struct|protocol|enum|actor|extension)\s+([a-zA-Z0-9_]+)(?:<[^>]+>)?\s*:\s*([a-zA-Z0-9_.,\s<>]+)')
221
+
222
+ for s in symbols:
223
+ if s.parent_id and s.parent_id != s.id:
224
+ edges.append(Edge(
225
+ source_id=s.parent_id,
226
+ target_id=s.id,
227
+ kind=EdgeKind.CONTAINS,
228
+ weight=1.0,
229
+ confidence=1.0,
230
+ location=s.location,
231
+ ))
232
+
233
+ for line_idx, line in enumerate(parse.source.splitlines(), start=1):
234
+ type_match = type_inherit_pattern.match(line)
235
+ if type_match:
236
+ type_name = type_match.group(1)
237
+ super_types_raw = type_match.group(2)
238
+ type_sym = symbol_map.get(type_name)
239
+ if type_sym:
240
+ for super_raw in super_types_raw.split(","):
241
+ clean_super = super_raw.strip().split("<")[0].strip()
242
+ if clean_super and clean_super not in ("AnyObject", "Codable", "Equatable", "Hashable"):
243
+ target_id = symbol_map[clean_super].id if clean_super in symbol_map else clean_super
244
+ edges.append(Edge(
245
+ source_id=type_sym.id,
246
+ target_id=target_id,
247
+ kind=EdgeKind.INHERITS,
248
+ weight=1.0,
249
+ confidence=0.9,
250
+ location=Location(file_path, line_idx, 0, line_idx, len(line)),
251
+ ))
252
+
253
+ if line.strip().startswith("import "):
254
+ imp_mod = line.strip().replace("import ", "").split()[0]
255
+ edges.append(Edge(
256
+ source_id=file_path,
257
+ target_id=imp_mod,
258
+ kind=EdgeKind.IMPORTS,
259
+ weight=1.0,
260
+ confidence=1.0,
261
+ location=Location(file_path, line_idx, 0, line_idx, len(line)),
262
+ ))
263
+
264
+ return sorted(edges, key=lambda e: (e.source_id, e.target_id, e.kind.value))
265
+
266
+ def resolve_import(self, ref: ImportRef, ctx: ResolveContext) -> Iterable[str]:
267
+ """Resolve Swift module imports against workspace packages or files.
268
+ Example:
269
+ >>> analyzer = SwiftLanguageAnalyzer()
270
+ >>> from codeui.core.resolver import ResolveContext
271
+ >>> ctx = ResolveContext(Path("."))
272
+ >>> ref = ImportRef("Sources/App/main.swift", "SharedKit", None, False)
273
+ >>> list(analyzer.resolve_import(ref, ctx))
274
+ []
275
+ """
276
+ return ctx.resolve_path(ref.from_file, ref.module_name, "swift")
codeui/lang/ts.py CHANGED
@@ -1,12 +1,15 @@
1
1
  """TypeScript/JavaScript language adapter for codeui."""
2
+ from __future__ import annotations
2
3
  import hashlib
3
4
  import re
4
5
  from pathlib import Path
5
- from typing import ClassVar, Iterable, List, Sequence
6
+ from typing import ClassVar, Iterable, List, Sequence, TYPE_CHECKING
6
7
  from codeui.core.ir import Edge, EdgeKind, Location, Symbol, SymbolKind, Visibility
7
- from codeui.core.resolver import ResolveContext
8
8
  from codeui.lang.base import ImportRef, LanguageAnalyzer, ParseResult
9
9
 
10
+ if TYPE_CHECKING:
11
+ from codeui.core.resolver import ResolveContext
12
+
10
13
  class TSLanguageAnalyzer(LanguageAnalyzer):
11
14
  """TypeScript and JavaScript language adapter.
12
15
  Example:
@@ -64,8 +67,8 @@ class TSLanguageAnalyzer(LanguageAnalyzer):
64
67
  content_hash=parse.content_hash,
65
68
  )
66
69
  symbols.append(file_sym)
67
- class_pattern = re.compile(r'(?:export\s+)?(?:default\s+)?class\s+([A-Za-z0-9_$]+)(?:<[^>]+>)?(?:\s+extends\s+([A-Za-z0-9_$]+))?')
68
- interface_pattern = re.compile(r'(?:export\s+)?interface\s+([A-Za-z0-9_$]+)(?:<[^>]+>)?')
70
+ class_pattern = re.compile(r'(?:export\s+)?(?:default\s+)?class\s+([A-Za-z0-9_$]+)(?:<[^>]+>)?(?:\s+extends\s+([A-Za-z0-9_$.]+))?(?:\s+implements\s+([A-Za-z0-9_$,\s]+))?')
71
+ interface_pattern = re.compile(r'(?:export\s+)?interface\s+([A-Za-z0-9_$]+)(?:<[^>]+>)?(?:\s+extends\s+([A-Za-z0-9_$,\s]+))?')
69
72
  type_pattern = re.compile(r'(?:export\s+)?type\s+([A-Za-z0-9_$]+)(?:<[^>]+>)?\s*=')
70
73
  func_pattern = re.compile(r'(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+([A-Za-z0-9_$]+)\s*(?:<[^>]+>)?\s*\(')
71
74
  const_func_pattern = re.compile(r'(?:export\s+)?(?:default\s+)?(?:const|let|var)\s+([A-Za-z0-9_$]+)\s*=\s*(?:async\s*)?(?:<[^>]+>)?\s*\(')
@@ -292,6 +295,8 @@ class TSLanguageAnalyzer(LanguageAnalyzer):
292
295
  edges: List[Edge] = []
293
296
  file_path = parse.file_path
294
297
  sym_map = {s.id: s for s in symbols}
298
+ import_map: dict[str, str] = {}
299
+
295
300
  for s in symbols:
296
301
  if s.parent_id and s.parent_id in sym_map:
297
302
  edges.append(Edge(
@@ -304,6 +309,21 @@ class TSLanguageAnalyzer(LanguageAnalyzer):
304
309
  ))
305
310
  if s.kind == SymbolKind.IMPORT:
306
311
  target_mod = s.qualified_name.replace("import:", "")
312
+ raw_spec = s.name.strip()
313
+ if raw_spec:
314
+ for part in raw_spec.split(","):
315
+ part_clean = part.strip()
316
+ if not part_clean:
317
+ continue
318
+ if " as " in part_clean:
319
+ orig, alias = part_clean.split(" as ", 1)
320
+ import_map[alias.strip()] = f"module::{target_mod}::{orig.strip()}"
321
+ elif part_clean.startswith("*"):
322
+ alias_name = part_clean.replace("*", "").replace("as", "").strip()
323
+ if alias_name:
324
+ import_map[alias_name] = f"module::{target_mod}"
325
+ else:
326
+ import_map[part_clean] = f"module::{target_mod}::{part_clean}"
307
327
  weight = float(self._count_symbol_usages(parse.source, s.name))
308
328
  edges.append(Edge(
309
329
  source_id=file_path,
@@ -313,7 +333,11 @@ class TSLanguageAnalyzer(LanguageAnalyzer):
313
333
  confidence=1.0,
314
334
  location=s.location,
315
335
  ))
336
+
337
+ class_inherit_pattern = re.compile(r'(?:export\s+)?(?:default\s+)?class\s+([A-Za-z0-9_$]+)(?:<[^>]+>)?(?:\s+extends\s+([A-Za-z0-9_$.]+))?(?:\s+implements\s+([A-Za-z0-9_$,\s]+))?')
338
+ interface_inherit_pattern = re.compile(r'(?:export\s+)?interface\s+([A-Za-z0-9_$]+)(?:<[^>]+>)?(?:\s+extends\s+([A-Za-z0-9_$,\s]+))?')
316
339
  call_pattern = re.compile(r'(?<![A-Za-z0-9_$-])\b([A-Za-z_$][A-Za-z0-9_$]*)\s*\(')
340
+ jsx_pattern = re.compile(r'<([A-Za-z_$][A-Za-z0-9_$]*)(?:[\s/>]|\.[A-Za-z0-9_$]+)')
317
341
  js_keywords = {
318
342
  "if", "for", "while", "switch", "catch", "function", "var", "let", "const",
319
343
  "return", "throw", "new", "typeof", "instanceof", "yield", "await", "async",
@@ -321,8 +345,9 @@ class TSLanguageAnalyzer(LanguageAnalyzer):
321
345
  "else", "try", "finally", "break", "continue", "in", "of", "void", "delete", "require"
322
346
  }
323
347
  lines = parse.source.splitlines()
324
- func_syms = [s for s in symbols if s.kind in (SymbolKind.FUNCTION, SymbolKind.METHOD, SymbolKind.CLASS)]
348
+ func_syms = [s for s in symbols if s.kind in (SymbolKind.FUNCTION, SymbolKind.METHOD, SymbolKind.CLASS, SymbolKind.INTERFACE)]
325
349
  in_multiline_edges = False
350
+
326
351
  for idx, line in enumerate(lines, start=1):
327
352
  line_str = line.strip()
328
353
  clean_str_line = re.sub(r"'(?:\\.|[^'\\])*'", "''", line_str)
@@ -342,6 +367,54 @@ class TSLanguageAnalyzer(LanguageAnalyzer):
342
367
  line_str = line_str.split("/*", 1)[0].strip()
343
368
  if not line_str or line_str.startswith("//") or line_str.startswith("*") or "var(" in line_str:
344
369
  continue
370
+
371
+ for match in class_inherit_pattern.finditer(line_str):
372
+ cls_name = match.group(1)
373
+ base_name = match.group(2)
374
+ impl_names = match.group(3)
375
+ cls_sym_id = f"{file_path}::{cls_name}"
376
+ if base_name:
377
+ target_id = import_map.get(base_name, f"{file_path}::{base_name}")
378
+ edges.append(Edge(
379
+ source_id=cls_sym_id,
380
+ target_id=target_id,
381
+ kind=EdgeKind.INHERITS,
382
+ weight=1.0,
383
+ confidence=1.0,
384
+ location=Location(file_path, idx, line.find(base_name), idx, line.find(base_name) + len(base_name)),
385
+ ))
386
+ if impl_names:
387
+ for iface in impl_names.split(","):
388
+ iface_clean = iface.strip()
389
+ if iface_clean and iface_clean not in js_keywords:
390
+ target_id = import_map.get(iface_clean, f"{file_path}::{iface_clean}")
391
+ edges.append(Edge(
392
+ source_id=cls_sym_id,
393
+ target_id=target_id,
394
+ kind=EdgeKind.INHERITS,
395
+ weight=1.0,
396
+ confidence=0.9,
397
+ location=Location(file_path, idx, line.find(iface_clean), idx, line.find(iface_clean) + len(iface_clean)),
398
+ ))
399
+
400
+ for match in interface_inherit_pattern.finditer(line_str):
401
+ iface_name = match.group(1)
402
+ ext_names = match.group(2)
403
+ iface_sym_id = f"{file_path}::{iface_name}"
404
+ if ext_names:
405
+ for ext_target in ext_names.split(","):
406
+ ext_clean = ext_target.strip()
407
+ if ext_clean and ext_clean not in js_keywords:
408
+ target_id = import_map.get(ext_clean, f"{file_path}::{ext_clean}")
409
+ edges.append(Edge(
410
+ source_id=iface_sym_id,
411
+ target_id=target_id,
412
+ kind=EdgeKind.INHERITS,
413
+ weight=1.0,
414
+ confidence=0.9,
415
+ location=Location(file_path, idx, line.find(ext_clean), idx, line.find(ext_clean) + len(ext_clean)),
416
+ ))
417
+
345
418
  enclosing = None
346
419
  for s in func_syms:
347
420
  if s.location and s.location.start_line <= idx <= (s.location.end_line or (s.location.start_line + 50)):
@@ -351,23 +424,41 @@ class TSLanguageAnalyzer(LanguageAnalyzer):
351
424
  clean_code_line = re.sub(r'"(?:\\.|[^"\\])*"', '""', clean_code_line)
352
425
  clean_code_line = re.sub(r'`(?:\\.|[^`\\])*`', '``', clean_code_line)
353
426
  clean_code_line = re.sub(r'/(?:\\.|[^/\\])+/[gimsuy]*', '//', clean_code_line)
427
+
354
428
  for match in call_pattern.finditer(clean_code_line):
355
429
  called_name = match.group(1)
356
430
  pre_str = clean_code_line[:match.start(1)].rstrip()
357
431
  is_member_call = pre_str.endswith(".") or pre_str.endswith("?.")
358
- if called_name[0].isupper() and not pre_str.endswith("new") and not pre_str.endswith("function"):
432
+ if is_member_call:
359
433
  continue
360
- if called_name not in js_keywords and not is_member_call and called_name not in ("translateY", "translateX", "scale", "rotate"):
434
+ if called_name not in js_keywords and called_name not in ("translateY", "translateX", "scale", "rotate"):
361
435
  col = line.find(called_name)
362
436
  weight = float(self._count_symbol_usages(parse.source, called_name))
437
+ target_id = import_map.get(called_name, f"{file_path}::{called_name}")
363
438
  edges.append(Edge(
364
439
  source_id=caller_id,
365
- target_id=f"{file_path}::{called_name}",
440
+ target_id=target_id,
366
441
  kind=EdgeKind.CALLS,
367
442
  weight=weight,
368
443
  confidence=0.75,
369
444
  location=Location(file_path, idx, col if col >= 0 else 0, idx, col + len(called_name) if col >= 0 else len(called_name)),
370
445
  ))
446
+
447
+ for match in jsx_pattern.finditer(clean_code_line):
448
+ tag_name = match.group(1)
449
+ if tag_name and tag_name[0].isupper() and tag_name not in js_keywords:
450
+ col = line.find(tag_name)
451
+ weight = float(self._count_symbol_usages(parse.source, tag_name))
452
+ target_id = import_map.get(tag_name, f"{file_path}::{tag_name}")
453
+ edges.append(Edge(
454
+ source_id=caller_id,
455
+ target_id=target_id,
456
+ kind=EdgeKind.CALLS,
457
+ weight=weight,
458
+ confidence=0.85,
459
+ location=Location(file_path, idx, col if col >= 0 else 0, idx, col + len(tag_name) if col >= 0 else len(tag_name)),
460
+ ))
461
+
371
462
  return edges
372
463
 
373
464
  def _count_symbol_usages(self, source: str, symbol_name: str) -> int:
@@ -1,6 +1,7 @@
1
1
  """SARIF 2.1.0 format report emitter."""
2
2
  import json
3
3
  from typing import Any, Dict
4
+ import codeui
4
5
  from codeui.core.graph import Graph
5
6
 
6
7
  class SARIFEmitter:
@@ -46,7 +47,7 @@ class SARIFEmitter:
46
47
  "tool": {
47
48
  "driver": {
48
49
  "name": "codeui",
49
- "version": "0.1.0",
50
+ "version": codeui.__version__,
50
51
  "informationUri": "https://codeui.dev",
51
52
  }
52
53
  },