codeui-python 0.1.0__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 (52) hide show
  1. codeui/__init__.py +23 -0
  2. codeui/agent/__init__.py +15 -0
  3. codeui/agent/context.py +207 -0
  4. codeui/agent/contribution.py +224 -0
  5. codeui/agent/edit_tools.py +280 -0
  6. codeui/agent/integrations.py +243 -0
  7. codeui/agent/mcp_server.py +94 -0
  8. codeui/analysis/__init__.py +1 -0
  9. codeui/analysis/api_drift.py +79 -0
  10. codeui/analysis/base.py +42 -0
  11. codeui/analysis/circular.py +116 -0
  12. codeui/analysis/dead_code.py +63 -0
  13. codeui/analysis/duplicates.py +69 -0
  14. codeui/analysis/runner.py +57 -0
  15. codeui/analysis/shadowing.py +49 -0
  16. codeui/analysis/undefined.py +97 -0
  17. codeui/analysis/unreachable.py +28 -0
  18. codeui/analysis/unresolved_import.py +153 -0
  19. codeui/cli/__init__.py +1 -0
  20. codeui/cli/main.py +437 -0
  21. codeui/core/__init__.py +1 -0
  22. codeui/core/cache.py +135 -0
  23. codeui/core/graph.py +435 -0
  24. codeui/core/ir.py +218 -0
  25. codeui/core/override.py +230 -0
  26. codeui/core/repo.py +85 -0
  27. codeui/core/resolver.py +281 -0
  28. codeui/errors.py +98 -0
  29. codeui/lang/__init__.py +1 -0
  30. codeui/lang/base.py +117 -0
  31. codeui/lang/generic.py +90 -0
  32. codeui/lang/go.py +167 -0
  33. codeui/lang/python.py +294 -0
  34. codeui/lang/registry.py +83 -0
  35. codeui/lang/rust.py +185 -0
  36. codeui/lang/ts.py +396 -0
  37. codeui/plugins/__init__.py +1 -0
  38. codeui/plugins/registry.py +31 -0
  39. codeui/report/__init__.py +1 -0
  40. codeui/report/json_emitter.py +24 -0
  41. codeui/report/markdown_emitter.py +33 -0
  42. codeui/report/sarif_emitter.py +56 -0
  43. codeui/server/__init__.py +1 -0
  44. codeui/server/server.py +705 -0
  45. codeui/tracer/__init__.py +1 -0
  46. codeui/tracer/tracer.py +147 -0
  47. codeui_python-0.1.0.dist-info/METADATA +191 -0
  48. codeui_python-0.1.0.dist-info/RECORD +52 -0
  49. codeui_python-0.1.0.dist-info/WHEEL +5 -0
  50. codeui_python-0.1.0.dist-info/entry_points.txt +2 -0
  51. codeui_python-0.1.0.dist-info/licenses/LICENSE +21 -0
  52. codeui_python-0.1.0.dist-info/top_level.txt +1 -0
codeui/lang/rust.py ADDED
@@ -0,0 +1,185 @@
1
+ """Rust language adapter for codeui."""
2
+ import hashlib
3
+ import re
4
+ from pathlib import Path
5
+ from typing import ClassVar, Iterable, List, Sequence
6
+ from codeui.core.ir import Edge, EdgeKind, Location, Symbol, SymbolKind, Visibility
7
+ from codeui.core.resolver import ResolveContext
8
+ from codeui.lang.base import ImportRef, LanguageAnalyzer, ParseResult
9
+
10
+ class RustLanguageAnalyzer(LanguageAnalyzer):
11
+ """Rust language analyzer.
12
+ Example:
13
+ >>> analyzer = RustLanguageAnalyzer()
14
+ >>> res = analyzer.parse(Path("main.rs"), 'fn main() { println!("Hello"); }')
15
+ >>> syms = list(analyzer.extract_symbols(res))
16
+ >>> len(syms) >= 1
17
+ True
18
+ """
19
+ language: ClassVar[str] = "rust"
20
+ extensions: ClassVar[tuple[str, ...]] = (".rs",)
21
+
22
+ def parse(self, path: Path, source: str) -> ParseResult:
23
+ """Parse Rust source file.
24
+ Example:
25
+ >>> analyzer = RustLanguageAnalyzer()
26
+ >>> res = analyzer.parse(Path("lib.rs"), "pub fn add(a: i32, b: i32) -> i32 { a + b }")
27
+ >>> res.errors
28
+ []
29
+ """
30
+ content_hash = hashlib.sha256(source.encode("utf-8")).hexdigest()
31
+ return ParseResult(
32
+ file_path=str(path),
33
+ source=source,
34
+ ast=source,
35
+ content_hash=content_hash,
36
+ errors=[],
37
+ )
38
+
39
+ def extract_symbols(self, parse: ParseResult) -> Iterable[Symbol]:
40
+ """Extract fn, struct, enum, trait, and mod symbols from Rust source.
41
+ Example:
42
+ >>> analyzer = RustLanguageAnalyzer()
43
+ >>> res = analyzer.parse(Path("main.rs"), "pub struct Config { pub port: u16 }")
44
+ >>> syms = list(analyzer.extract_symbols(res))
45
+ >>> any(s.kind == SymbolKind.STRUCT for s in syms)
46
+ True
47
+ """
48
+ source = parse.source
49
+ file_path = parse.file_path
50
+ lines = source.splitlines()
51
+ max_line = len(lines) if lines else 1
52
+ symbols: List[Symbol] = []
53
+ file_sym = Symbol(
54
+ id=file_path,
55
+ name=Path(file_path).name,
56
+ qualified_name=file_path,
57
+ kind=SymbolKind.FILE,
58
+ language=self.language,
59
+ location=Location(file_path, 1, 0, max_line, 0),
60
+ parent_id=None,
61
+ signature=None,
62
+ visibility=Visibility.PUBLIC,
63
+ modifiers=(),
64
+ content_hash=parse.content_hash,
65
+ )
66
+ symbols.append(file_sym)
67
+ fn_pattern = re.compile(r'(?:pub(?:\([^)]+\))?\s+)?(?:async\s+)?fn\s+([A-Za-z0-9_]+)\s*(?:<[^>]+>)?\s*\(([^)]*)\)')
68
+ struct_pattern = re.compile(r'(?:pub(?:\([^)]+\))?\s+)?struct\s+([A-Za-z0-9_]+)(?:<[^>]+>)?')
69
+ enum_pattern = re.compile(r'(?:pub(?:\([^)]+\))?\s+)?enum\s+([A-Za-z0-9_]+)(?:<[^>]+>)?')
70
+ trait_pattern = re.compile(r'(?:pub(?:\([^)]+\))?\s+)?trait\s+([A-Za-z0-9_]+)(?:<[^>]+>)?')
71
+ for idx, line in enumerate(lines, start=1):
72
+ line_str = line.strip()
73
+ for match in fn_pattern.finditer(line_str):
74
+ name = match.group(1)
75
+ params = match.group(2)
76
+ sym_id = f"{file_path}::{name}"
77
+ end_line = self._find_block_end(lines, idx - 1)
78
+ loc = Location(file_path, idx, line.find(name), end_line, line.find(name) + len(name))
79
+ vis = Visibility.PUBLIC if "pub" in line_str else Visibility.PRIVATE
80
+ symbols.append(Symbol(
81
+ id=sym_id,
82
+ name=name,
83
+ qualified_name=name,
84
+ kind=SymbolKind.FUNCTION,
85
+ language=self.language,
86
+ location=loc,
87
+ parent_id=file_path,
88
+ signature=f"fn {name}({params})",
89
+ visibility=vis,
90
+ modifiers=("pub",) if "pub" in line_str else (),
91
+ content_hash=self._extract_body_hash(lines, idx - 1),
92
+ ))
93
+ for match in struct_pattern.finditer(line_str):
94
+ name = match.group(1)
95
+ sym_id = f"{file_path}::{name}"
96
+ end_line = self._find_block_end(lines, idx - 1)
97
+ loc = Location(file_path, idx, line.find(name), end_line, line.find(name) + len(name))
98
+ vis = Visibility.PUBLIC if "pub" in line_str else Visibility.PRIVATE
99
+ symbols.append(Symbol(
100
+ id=sym_id,
101
+ name=name,
102
+ qualified_name=name,
103
+ kind=SymbolKind.STRUCT,
104
+ language=self.language,
105
+ location=loc,
106
+ parent_id=file_path,
107
+ signature=f"struct {name}",
108
+ visibility=vis,
109
+ modifiers=("pub",) if "pub" in line_str else (),
110
+ content_hash=self._extract_body_hash(lines, idx - 1),
111
+ ))
112
+ for match in enum_pattern.finditer(line_str):
113
+ name = match.group(1)
114
+ sym_id = f"{file_path}::{name}"
115
+ end_line = self._find_block_end(lines, idx - 1)
116
+ loc = Location(file_path, idx, line.find(name), end_line, line.find(name) + len(name))
117
+ vis = Visibility.PUBLIC if "pub" in line_str else Visibility.PRIVATE
118
+ symbols.append(Symbol(
119
+ id=sym_id,
120
+ name=name,
121
+ qualified_name=name,
122
+ kind=SymbolKind.ENUM,
123
+ language=self.language,
124
+ location=loc,
125
+ parent_id=file_path,
126
+ signature=f"enum {name}",
127
+ visibility=vis,
128
+ modifiers=("pub",) if "pub" in line_str else (),
129
+ content_hash=self._extract_body_hash(lines, idx - 1),
130
+ ))
131
+ for match in trait_pattern.finditer(line_str):
132
+ name = match.group(1)
133
+ sym_id = f"{file_path}::{name}"
134
+ end_line = self._find_block_end(lines, idx - 1)
135
+ loc = Location(file_path, idx, line.find(name), end_line, line.find(name) + len(name))
136
+ vis = Visibility.PUBLIC if "pub" in line_str else Visibility.PRIVATE
137
+ symbols.append(Symbol(
138
+ id=sym_id,
139
+ name=name,
140
+ qualified_name=name,
141
+ kind=SymbolKind.INTERFACE,
142
+ language=self.language,
143
+ location=loc,
144
+ parent_id=file_path,
145
+ signature=f"trait {name}",
146
+ visibility=vis,
147
+ modifiers=("pub",) if "pub" in line_str else (),
148
+ content_hash=self._extract_body_hash(lines, idx - 1),
149
+ ))
150
+ return symbols
151
+
152
+ def extract_edges(self, parse: ParseResult, symbols: Sequence[Symbol]) -> Iterable[Edge]:
153
+ """Extract relationship edges in Rust source.
154
+ Example:
155
+ >>> analyzer = RustLanguageAnalyzer()
156
+ >>> res = analyzer.parse(Path("lib.rs"), "fn foo() {}")
157
+ >>> syms = list(analyzer.extract_symbols(res))
158
+ >>> edges = list(analyzer.extract_edges(res, syms))
159
+ >>> len(edges) >= 1
160
+ True
161
+ """
162
+ edges: List[Edge] = []
163
+ sym_map = {s.id: s for s in symbols}
164
+ for s in symbols:
165
+ if s.parent_id and s.parent_id in sym_map:
166
+ edges.append(Edge(
167
+ source_id=s.parent_id,
168
+ target_id=s.id,
169
+ kind=EdgeKind.CONTAINS,
170
+ weight=1.0,
171
+ confidence=1.0,
172
+ location=s.location,
173
+ ))
174
+ return edges
175
+
176
+ def resolve_import(self, ref: ImportRef, ctx: ResolveContext) -> Iterable[str]:
177
+ """Resolve Rust module references.
178
+ Example:
179
+ >>> analyzer = RustLanguageAnalyzer()
180
+ >>> ctx = ResolveContext(Path("."))
181
+ >>> ref = ImportRef("main.rs", "crate::utils", None, False)
182
+ >>> list(analyzer.resolve_import(ref, ctx))
183
+ []
184
+ """
185
+ return []
codeui/lang/ts.py ADDED
@@ -0,0 +1,396 @@
1
+ """TypeScript/JavaScript language adapter for codeui."""
2
+ import hashlib
3
+ import re
4
+ from pathlib import Path
5
+ from typing import ClassVar, Iterable, List, Sequence
6
+ from codeui.core.ir import Edge, EdgeKind, Location, Symbol, SymbolKind, Visibility
7
+ from codeui.core.resolver import ResolveContext
8
+ from codeui.lang.base import ImportRef, LanguageAnalyzer, ParseResult
9
+
10
+ class TSLanguageAnalyzer(LanguageAnalyzer):
11
+ """TypeScript and JavaScript language adapter.
12
+ Example:
13
+ >>> analyzer = TSLanguageAnalyzer()
14
+ >>> res = analyzer.parse(Path("app.ts"), "export function greet(name: string) { return 'hello ' + name; }")
15
+ >>> syms = list(analyzer.extract_symbols(res))
16
+ >>> len(syms) >= 2
17
+ True
18
+ """
19
+ language: ClassVar[str] = "typescript"
20
+ extensions: ClassVar[tuple[str, ...]] = (".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs")
21
+
22
+ def parse(self, path: Path, source: str) -> ParseResult:
23
+ """Parse TypeScript / JavaScript file source.
24
+ Example:
25
+ >>> analyzer = TSLanguageAnalyzer()
26
+ >>> res = analyzer.parse(Path("main.ts"), "const x = 10;")
27
+ >>> res.errors
28
+ []
29
+ """
30
+ content_hash = hashlib.sha256(source.encode("utf-8")).hexdigest()
31
+ return ParseResult(
32
+ file_path=str(path),
33
+ source=source,
34
+ ast=source,
35
+ content_hash=content_hash,
36
+ errors=[],
37
+ )
38
+
39
+ def extract_symbols(self, parse: ParseResult) -> Iterable[Symbol]:
40
+ """Extract functions, classes, interfaces, types, imports, and variables.
41
+ Example:
42
+ >>> analyzer = TSLanguageAnalyzer()
43
+ >>> res = analyzer.parse(Path("index.ts"), "class User { id: number; }")
44
+ >>> syms = list(analyzer.extract_symbols(res))
45
+ >>> any(s.kind == SymbolKind.CLASS for s in syms)
46
+ True
47
+ """
48
+ source = parse.source
49
+ file_path = parse.file_path
50
+ lines = source.splitlines()
51
+ max_line = len(lines) if lines else 1
52
+ symbols: List[Symbol] = []
53
+ file_sym = Symbol(
54
+ id=file_path,
55
+ name=Path(file_path).name,
56
+ qualified_name=file_path,
57
+ kind=SymbolKind.FILE,
58
+ language=self.language,
59
+ location=Location(file_path, 1, 0, max_line, 0),
60
+ parent_id=None,
61
+ signature=None,
62
+ visibility=Visibility.PUBLIC,
63
+ modifiers=(),
64
+ content_hash=parse.content_hash,
65
+ )
66
+ 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_$]+)(?:<[^>]+>)?')
69
+ type_pattern = re.compile(r'(?:export\s+)?type\s+([A-Za-z0-9_$]+)(?:<[^>]+>)?\s*=')
70
+ func_pattern = re.compile(r'(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+([A-Za-z0-9_$]+)\s*(?:<[^>]+>)?\s*\(')
71
+ const_func_pattern = re.compile(r'(?:export\s+)?(?:default\s+)?(?:const|let|var)\s+([A-Za-z0-9_$]+)\s*=\s*(?:async\s*)?(?:<[^>]+>)?\s*\(')
72
+ const_var_pattern = re.compile(r'(?:export\s+)?(?:const|let|var)\s+([A-Za-z0-9_$]+)\s*=')
73
+ method_pattern = re.compile(r'^\s*(?:static\s+)?(?:async\s+)?([A-Za-z0-9_$]+)\s*(?:<[^>]+>)?\s*\(([^)]*)\)\s*\{')
74
+ import_pattern = re.compile(r'import\s+(?:\{([^}]+)\}|([A-Za-z0-9_$]+))\s+from\s+[\'"]([^\'"]+)[\'"]')
75
+ require_pattern = re.compile(r'(?:const|let|var)\s+(?:\{([^}]+)\}|([A-Za-z0-9_$]+))\s*=\s*require\s*\(\s*[\'"]([^\'"]+)[\'"]\s*\)')
76
+ exports_func_pattern = re.compile(r'(?:module\.)?exports(?:\.([A-Za-z0-9_$]+))?\s*=\s*(?:async\s*)?(?:function\s*)?(?:([A-Za-z0-9_$]+)\s*)?\(([^)]*)\)')
77
+ in_multiline = False
78
+ for idx, line in enumerate(lines, start=1):
79
+ line_str = line.strip()
80
+ clean_str_line = re.sub(r"'(?:\\.|[^'\\])*'", "''", line_str)
81
+ clean_str_line = re.sub(r'"(?:\\.|[^"\\])*"', '""', clean_str_line)
82
+ clean_str_line = re.sub(r'`(?:\\.|[^`\\])*`', '``', clean_str_line)
83
+ if in_multiline:
84
+ if "*/" in clean_str_line:
85
+ in_multiline = False
86
+ line_str = line_str.split("*/", 1)[1].strip()
87
+ else:
88
+ continue
89
+ if "/*" in clean_str_line:
90
+ if "*/" in clean_str_line:
91
+ line_str = re.sub(r'/\*.*?\*/', '', line_str).strip()
92
+ else:
93
+ in_multiline = True
94
+ line_str = line_str.split("/*", 1)[0].strip()
95
+ if not line_str or line_str.startswith("//") or line_str.startswith("*"):
96
+ continue
97
+ for match in class_pattern.finditer(line_str):
98
+ name = match.group(1)
99
+ sym_id = f"{file_path}::{name}"
100
+ end_line = self._find_block_end(lines, idx - 1)
101
+ loc = Location(file_path, idx, line.find(name), end_line, line.find(name) + len(name))
102
+ symbols.append(Symbol(
103
+ id=sym_id,
104
+ name=name,
105
+ qualified_name=name,
106
+ kind=SymbolKind.CLASS,
107
+ language=self.language,
108
+ location=loc,
109
+ parent_id=file_path,
110
+ signature=f"class {name}",
111
+ visibility=Visibility.PUBLIC if "export" in line_str else Visibility.INTERNAL,
112
+ modifiers=("export",) if "export" in line_str else (),
113
+ content_hash=self._extract_body_hash(lines, idx - 1),
114
+ ))
115
+ for match in interface_pattern.finditer(line_str):
116
+ name = match.group(1)
117
+ sym_id = f"{file_path}::{name}"
118
+ end_line = self._find_block_end(lines, idx - 1)
119
+ loc = Location(file_path, idx, line.find(name), end_line, line.find(name) + len(name))
120
+ symbols.append(Symbol(
121
+ id=sym_id,
122
+ name=name,
123
+ qualified_name=name,
124
+ kind=SymbolKind.INTERFACE,
125
+ language=self.language,
126
+ location=loc,
127
+ parent_id=file_path,
128
+ signature=f"interface {name}",
129
+ visibility=Visibility.PUBLIC if "export" in line_str else Visibility.INTERNAL,
130
+ modifiers=("export",) if "export" in line_str else (),
131
+ content_hash=self._extract_body_hash(lines, idx - 1),
132
+ ))
133
+ for match in type_pattern.finditer(line_str):
134
+ name = match.group(1)
135
+ sym_id = f"{file_path}::{name}"
136
+ loc = Location(file_path, idx, line.find(name), idx, line.find(name) + len(name))
137
+ symbols.append(Symbol(
138
+ id=sym_id,
139
+ name=name,
140
+ qualified_name=name,
141
+ kind=SymbolKind.TYPE_ALIAS,
142
+ language=self.language,
143
+ location=loc,
144
+ parent_id=file_path,
145
+ signature=f"type {name}",
146
+ visibility=Visibility.PUBLIC if "export" in line_str else Visibility.INTERNAL,
147
+ modifiers=("export",) if "export" in line_str else (),
148
+ content_hash=self._extract_body_hash(lines, idx - 1),
149
+ ))
150
+ for match in func_pattern.finditer(line_str):
151
+ name = match.group(1)
152
+ if name:
153
+ sym_id = f"{file_path}::{name}"
154
+ end_line = self._find_block_end(lines, idx - 1)
155
+ loc = Location(file_path, idx, line.find(name), end_line, line.find(name) + len(name))
156
+ symbols.append(Symbol(
157
+ id=sym_id,
158
+ name=name,
159
+ qualified_name=name,
160
+ kind=SymbolKind.FUNCTION,
161
+ language=self.language,
162
+ location=loc,
163
+ parent_id=file_path,
164
+ signature=f"function {name}(...)",
165
+ visibility=Visibility.PUBLIC if "export" in line_str else Visibility.INTERNAL,
166
+ modifiers=("export",) if "export" in line_str else (),
167
+ content_hash=self._extract_body_hash(lines, idx - 1),
168
+ ))
169
+ for match in const_func_pattern.finditer(line_str):
170
+ name = match.group(1)
171
+ if name:
172
+ sym_id = f"{file_path}::{name}"
173
+ end_line = self._find_block_end(lines, idx - 1)
174
+ loc = Location(file_path, idx, line.find(name), end_line, line.find(name) + len(name))
175
+ symbols.append(Symbol(
176
+ id=sym_id,
177
+ name=name,
178
+ qualified_name=name,
179
+ kind=SymbolKind.FUNCTION,
180
+ language=self.language,
181
+ location=loc,
182
+ parent_id=file_path,
183
+ signature=f"const {name} = (...)",
184
+ visibility=Visibility.PUBLIC if "export" in line_str else Visibility.INTERNAL,
185
+ modifiers=("export",) if "export" in line_str else (),
186
+ content_hash=self._extract_body_hash(lines, idx - 1),
187
+ ))
188
+ for match in const_var_pattern.finditer(line_str):
189
+ name = match.group(1)
190
+ if name and name not in ("if", "for", "while", "switch", "catch", "function"):
191
+ sym_id = f"{file_path}::{name}"
192
+ loc = Location(file_path, idx, line.find(name), idx, line.find(name) + len(name))
193
+ symbols.append(Symbol(
194
+ id=sym_id,
195
+ name=name,
196
+ qualified_name=name,
197
+ kind=SymbolKind.VARIABLE,
198
+ language=self.language,
199
+ location=loc,
200
+ parent_id=file_path,
201
+ signature=f"var {name}",
202
+ visibility=Visibility.PUBLIC if "export" in line_str else Visibility.INTERNAL,
203
+ modifiers=("export",) if "export" in line_str else (),
204
+ content_hash=hashlib.sha256(line_str.encode()).hexdigest()[:16],
205
+ ))
206
+ for match in method_pattern.finditer(line_str):
207
+ name = match.group(1)
208
+ params = match.group(2)
209
+ if name not in ("if", "for", "while", "switch", "catch", "function", "constructor"):
210
+ sym_id = f"{file_path}::{name}"
211
+ loc = Location(file_path, idx, line.find(name), idx, line.find(name) + len(name))
212
+ symbols.append(Symbol(
213
+ id=sym_id,
214
+ name=name,
215
+ qualified_name=name,
216
+ kind=SymbolKind.METHOD,
217
+ language=self.language,
218
+ location=loc,
219
+ parent_id=file_path,
220
+ signature=f"{name}({params})",
221
+ visibility=Visibility.PUBLIC if "export" in line_str or "static" in line_str else Visibility.INTERNAL,
222
+ modifiers=("static",) if "static" in line_str else (),
223
+ content_hash=self._extract_body_hash(lines, idx - 1),
224
+ ))
225
+ for match in import_pattern.finditer(line_str):
226
+ spec = match.group(1) or match.group(2) or ""
227
+ target = match.group(3)
228
+ sym_id = f"{file_path}::import::{target}"
229
+ loc = Location(file_path, idx, 0, idx, len(line_str))
230
+ symbols.append(Symbol(
231
+ id=sym_id,
232
+ name=spec.strip(),
233
+ qualified_name=f"import:{target}",
234
+ kind=SymbolKind.IMPORT,
235
+ language=self.language,
236
+ location=loc,
237
+ parent_id=file_path,
238
+ signature=line_str,
239
+ visibility=Visibility.INTERNAL,
240
+ modifiers=(),
241
+ content_hash=hashlib.sha256(line_str.encode()).hexdigest()[:16],
242
+ ))
243
+ for match in require_pattern.finditer(line_str):
244
+ spec = match.group(1) or match.group(2) or ""
245
+ target = match.group(3)
246
+ sym_id = f"{file_path}::import::{target}"
247
+ loc = Location(file_path, idx, 0, idx, len(line_str))
248
+ symbols.append(Symbol(
249
+ id=sym_id,
250
+ name=spec.strip(),
251
+ qualified_name=f"import:{target}",
252
+ kind=SymbolKind.IMPORT,
253
+ language=self.language,
254
+ location=loc,
255
+ parent_id=file_path,
256
+ signature=line_str,
257
+ visibility=Visibility.INTERNAL,
258
+ modifiers=(),
259
+ content_hash=hashlib.sha256(line_str.encode()).hexdigest()[:16],
260
+ ))
261
+ for match in exports_func_pattern.finditer(line_str):
262
+ name = match.group(1) or match.group(2)
263
+ if name and name not in ("if", "for", "while", "switch", "catch"):
264
+ params = match.group(3) or ""
265
+ sym_id = f"{file_path}::{name}"
266
+ loc = Location(file_path, idx, line.find(name), idx, line.find(name) + len(name))
267
+ symbols.append(Symbol(
268
+ id=sym_id,
269
+ name=name,
270
+ qualified_name=name,
271
+ kind=SymbolKind.FUNCTION,
272
+ language=self.language,
273
+ location=loc,
274
+ parent_id=file_path,
275
+ signature=f"exports.{name} = ({params})",
276
+ visibility=Visibility.PUBLIC,
277
+ modifiers=("export",),
278
+ content_hash=self._extract_body_hash(lines, idx - 1),
279
+ ))
280
+ return symbols
281
+
282
+ def extract_edges(self, parse: ParseResult, symbols: Sequence[Symbol]) -> Iterable[Edge]:
283
+ """Extract relationship edges for TypeScript / JavaScript AST.
284
+ Example:
285
+ >>> analyzer = TSLanguageAnalyzer()
286
+ >>> res = analyzer.parse(Path("a.ts"), "import { b } from './b'; function a() { b(); }")
287
+ >>> syms = list(analyzer.extract_symbols(res))
288
+ >>> edges = list(analyzer.extract_edges(res, syms))
289
+ >>> len(edges) >= 1
290
+ True
291
+ """
292
+ edges: List[Edge] = []
293
+ file_path = parse.file_path
294
+ sym_map = {s.id: s for s in symbols}
295
+ for s in symbols:
296
+ if s.parent_id and s.parent_id in sym_map:
297
+ edges.append(Edge(
298
+ source_id=s.parent_id,
299
+ target_id=s.id,
300
+ kind=EdgeKind.CONTAINS,
301
+ weight=1.0,
302
+ confidence=1.0,
303
+ location=s.location,
304
+ ))
305
+ if s.kind == SymbolKind.IMPORT:
306
+ target_mod = s.qualified_name.replace("import:", "")
307
+ weight = float(self._count_symbol_usages(parse.source, s.name))
308
+ edges.append(Edge(
309
+ source_id=file_path,
310
+ target_id=f"module::{target_mod}",
311
+ kind=EdgeKind.IMPORTS,
312
+ weight=weight,
313
+ confidence=1.0,
314
+ location=s.location,
315
+ ))
316
+ call_pattern = re.compile(r'(?<![A-Za-z0-9_$-])\b([A-Za-z_$][A-Za-z0-9_$]*)\s*\(')
317
+ js_keywords = {
318
+ "if", "for", "while", "switch", "catch", "function", "var", "let", "const",
319
+ "return", "throw", "new", "typeof", "instanceof", "yield", "await", "async",
320
+ "import", "export", "constructor", "super", "class", "case", "default",
321
+ "else", "try", "finally", "break", "continue", "in", "of", "void", "delete", "require"
322
+ }
323
+ lines = parse.source.splitlines()
324
+ func_syms = [s for s in symbols if s.kind in (SymbolKind.FUNCTION, SymbolKind.METHOD, SymbolKind.CLASS)]
325
+ in_multiline_edges = False
326
+ for idx, line in enumerate(lines, start=1):
327
+ line_str = line.strip()
328
+ clean_str_line = re.sub(r"'(?:\\.|[^'\\])*'", "''", line_str)
329
+ clean_str_line = re.sub(r'"(?:\\.|[^"\\])*"', '""', clean_str_line)
330
+ clean_str_line = re.sub(r'`(?:\\.|[^`\\])*`', '``', clean_str_line)
331
+ if in_multiline_edges:
332
+ if "*/" in clean_str_line:
333
+ in_multiline_edges = False
334
+ line_str = line_str.split("*/", 1)[1].strip()
335
+ else:
336
+ continue
337
+ if "/*" in clean_str_line:
338
+ if "*/" in clean_str_line:
339
+ line_str = re.sub(r'/\*.*?\*/', '', line_str).strip()
340
+ else:
341
+ in_multiline_edges = True
342
+ line_str = line_str.split("/*", 1)[0].strip()
343
+ if not line_str or line_str.startswith("//") or line_str.startswith("*") or "var(" in line_str:
344
+ continue
345
+ enclosing = None
346
+ for s in func_syms:
347
+ if s.location and s.location.start_line <= idx <= (s.location.end_line or (s.location.start_line + 50)):
348
+ enclosing = s.id
349
+ caller_id = enclosing or file_path
350
+ clean_code_line = re.sub(r"'(?:\\.|[^'\\])*'", "''", line_str)
351
+ clean_code_line = re.sub(r'"(?:\\.|[^"\\])*"', '""', clean_code_line)
352
+ clean_code_line = re.sub(r'`(?:\\.|[^`\\])*`', '``', clean_code_line)
353
+ clean_code_line = re.sub(r'/(?:\\.|[^/\\])+/[gimsuy]*', '//', clean_code_line)
354
+ for match in call_pattern.finditer(clean_code_line):
355
+ called_name = match.group(1)
356
+ pre_str = clean_code_line[:match.start(1)].rstrip()
357
+ 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"):
359
+ continue
360
+ if called_name not in js_keywords and not is_member_call and called_name not in ("translateY", "translateX", "scale", "rotate"):
361
+ col = line.find(called_name)
362
+ weight = float(self._count_symbol_usages(parse.source, called_name))
363
+ edges.append(Edge(
364
+ source_id=caller_id,
365
+ target_id=f"{file_path}::{called_name}",
366
+ kind=EdgeKind.CALLS,
367
+ weight=weight,
368
+ confidence=0.75,
369
+ location=Location(file_path, idx, col if col >= 0 else 0, idx, col + len(called_name) if col >= 0 else len(called_name)),
370
+ ))
371
+ return edges
372
+
373
+ def _count_symbol_usages(self, source: str, symbol_name: str) -> int:
374
+ if not symbol_name or symbol_name == "*":
375
+ return 1
376
+ cleaned_symbols = [s.strip() for s in symbol_name.split(",") if s.strip()]
377
+ total = 0
378
+ for sym in (cleaned_symbols or [symbol_name]):
379
+ pattern = r"\b" + re.escape(sym) + r"\b"
380
+ for line in source.splitlines():
381
+ stripped = line.strip()
382
+ if stripped.startswith(("import ", "export ", "//", "/*")):
383
+ continue
384
+ total += len(re.findall(pattern, line))
385
+ return max(1, total)
386
+
387
+ def resolve_import(self, ref: ImportRef, ctx: ResolveContext) -> Iterable[str]:
388
+ """Resolve JS/TS import reference paths via resolve_path.
389
+ Example:
390
+ >>> analyzer = TSLanguageAnalyzer()
391
+ >>> ctx = ResolveContext(Path("."))
392
+ >>> ref = ImportRef("src/a.ts", "./b", None, True)
393
+ >>> list(analyzer.resolve_import(ref, ctx))
394
+ []
395
+ """
396
+ return ctx.resolve_path(ref.file_path, ref.module_name, "typescript")
@@ -0,0 +1 @@
1
+ """Plugin registry package for codeui."""
@@ -0,0 +1,31 @@
1
+ """Plugin entry-point registry for third-party adapters and analyzers."""
2
+ import importlib.metadata
3
+ from typing import Any, List
4
+
5
+ class PluginRegistry:
6
+ """Manages discovery and loading of entrypoint-based codeui plugins.
7
+ Example:
8
+ >>> reg = PluginRegistry()
9
+ >>> isinstance(reg.load_plugins(), list)
10
+ True
11
+ """
12
+ def load_plugins(self) -> List[Any]:
13
+ """Load external plugins registered under 'codeui.plugins' entrypoint group.
14
+ Example:
15
+ >>> reg = PluginRegistry()
16
+ >>> plugins = reg.load_plugins()
17
+ >>> isinstance(plugins, list)
18
+ True
19
+ """
20
+ loaded = []
21
+ try:
22
+ entry_points = importlib.metadata.entry_points()
23
+ if hasattr(entry_points, "select"):
24
+ eps = entry_points.select(group="codeui.plugins")
25
+ else:
26
+ eps = entry_points.get("codeui.plugins", [])
27
+ for ep in eps:
28
+ loaded.append(ep.load())
29
+ except Exception:
30
+ pass
31
+ return loaded
@@ -0,0 +1 @@
1
+ """Report emitters package for codeui."""
@@ -0,0 +1,24 @@
1
+ """Deterministic JSON report emitter."""
2
+ import json
3
+ from typing import Any, Dict
4
+ from codeui.core.graph import Graph
5
+
6
+ class JSONEmitter:
7
+ """Emits byte-identical deterministic JSON graph reports.
8
+ Example:
9
+ >>> g = Graph()
10
+ >>> emitter = JSONEmitter()
11
+ >>> report = emitter.emit(g)
12
+ >>> '"symbols": []' in report
13
+ True
14
+ """
15
+ def emit(self, graph: Graph) -> str:
16
+ """Serialize graph to byte-identical sorted JSON string.
17
+ Example:
18
+ >>> g = Graph()
19
+ >>> emitter = JSONEmitter()
20
+ >>> isinstance(emitter.emit(g), str)
21
+ True
22
+ """
23
+ data = graph.to_dict()
24
+ return json.dumps(data, sort_keys=True, indent=2)