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/dart.py ADDED
@@ -0,0 +1,278 @@
1
+ """Dart language adapter 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 DartLanguageAnalyzer(LanguageAnalyzer):
14
+ """Dart language adapter extracting classes, mixins, enums, functions, and properties.
15
+ Example:
16
+ >>> analyzer = DartLanguageAnalyzer()
17
+ >>> res = analyzer.parse(Path("main.dart"), "class User { final int id; User(this.id); }")
18
+ >>> syms = list(analyzer.extract_symbols(res))
19
+ >>> any(s.kind == SymbolKind.CLASS for s in syms)
20
+ True
21
+ """
22
+ language: ClassVar[str] = "dart"
23
+ extensions: ClassVar[tuple[str, ...]] = (".dart",)
24
+
25
+ def parse(self, path: Path, source: str) -> ParseResult:
26
+ """Parse Dart source into AST-emulating representation.
27
+ Example:
28
+ >>> analyzer = DartLanguageAnalyzer()
29
+ >>> res = analyzer.parse(Path("app.dart"), "void main() {}")
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 Dart types, functions, methods, properties, and imports.
44
+ Example:
45
+ >>> analyzer = DartLanguageAnalyzer()
46
+ >>> res = analyzer.parse(Path("service.dart"), "class Service { Future<void> init() async {} }")
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|export)\s+[\'"]([^\'"]+)[\'"](?:\s+as\s+([a-zA-Z0-9_]+))?')
73
+ type_pattern = re.compile(r'^\s*(?:(abstract|sealed|base|interface|final)\s+)?(class|mixin|enum|extension)\s+([a-zA-Z0-9_]+)(?:<[^>]+>)?(?:\s+extends\s+([a-zA-Z0-9_.,\s<>]+))?(?:\s+with\s+([a-zA-Z0-9_.,\s<>]+))?(?:\s+implements\s+([a-zA-Z0-9_.,\s<>]+))?')
74
+ func_pattern = re.compile(r'^\s*(?:(static|override|factory)\s+)*(?:([a-zA-Z0-9_?<>]+)\s+)?([a-zA-Z0-9_]+)\s*\(([^)]*)\)(?:\s*async\s*)?(?:\s*\{|\s*=>)')
75
+ prop_pattern = re.compile(r'^\s*(?:(static|final|const|late)\s+)*(?:([a-zA-Z0-9_?<>]+)\s+)?([a-zA-Z0-9_]+)(?:\s*=\s*[^;]+)?\s*;')
76
+
77
+ current_type: str | None = None
78
+ brace_depth = 0
79
+ type_depth = 0
80
+
81
+ for line_idx, line in enumerate(lines, start=1):
82
+ stripped = line.strip()
83
+ if not stripped or stripped.startswith("//") or stripped.startswith("/*") or stripped.startswith("*"):
84
+ continue
85
+
86
+ imp_match = import_pattern.match(line)
87
+ if imp_match:
88
+ imp_uri = imp_match.group(1)
89
+ alias = imp_match.group(2) or imp_uri.split("/")[-1].replace(".dart", "")
90
+ symbols.append(Symbol(
91
+ id=f"{file_path}::import::{imp_uri}",
92
+ name=alias,
93
+ qualified_name=imp_uri,
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 '{imp_uri}'",
99
+ visibility=Visibility.PUBLIC,
100
+ modifiers=(),
101
+ content_hash=hashlib.sha256(line.encode("utf-8")).hexdigest()[:16],
102
+ ))
103
+ continue
104
+
105
+ type_match = type_pattern.match(line)
106
+ if type_match:
107
+ mod_str = type_match.group(1)
108
+ type_kind = type_match.group(2)
109
+ type_name = type_match.group(3)
110
+ vis = Visibility.PRIVATE if type_name.startswith("_") else Visibility.PUBLIC
111
+ kind = SymbolKind.INTERFACE if type_kind in ("mixin", "interface") else SymbolKind.CLASS
112
+ modifiers = tuple(filter(None, [mod_str, type_kind]))
113
+ sym_id = f"{file_path}::{type_name}"
114
+ current_type = sym_id
115
+ type_depth = brace_depth
116
+
117
+ symbols.append(Symbol(
118
+ id=sym_id,
119
+ name=type_name,
120
+ qualified_name=type_name,
121
+ kind=kind,
122
+ language=self.language,
123
+ location=Location(file_path, line_idx, 0, line_idx, len(line)),
124
+ parent_id=file_path,
125
+ signature=f"{type_kind} {type_name}",
126
+ visibility=vis,
127
+ modifiers=modifiers,
128
+ content_hash=hashlib.sha256(line.encode("utf-8")).hexdigest()[:16],
129
+ ))
130
+
131
+ func_match = func_pattern.match(line)
132
+ if func_match:
133
+ func_mod = func_match.group(1)
134
+ func_ret = func_match.group(2) or "void"
135
+ func_name = func_match.group(3)
136
+ func_args = func_match.group(4) or ""
137
+ if func_name not in ("if", "for", "while", "switch", "catch"):
138
+ vis = Visibility.PRIVATE if func_name.startswith("_") else Visibility.PUBLIC
139
+ parent_id = current_type if current_type and brace_depth > type_depth else file_path
140
+ sym_id = f"{parent_id}::{func_name}"
141
+ symbols.append(Symbol(
142
+ id=sym_id,
143
+ name=func_name,
144
+ qualified_name=func_name,
145
+ kind=SymbolKind.FUNCTION,
146
+ language=self.language,
147
+ location=Location(file_path, line_idx, 0, line_idx, len(line)),
148
+ parent_id=parent_id,
149
+ signature=f"{func_ret} {func_name}({func_args})",
150
+ visibility=vis,
151
+ modifiers=tuple(filter(None, [func_mod])),
152
+ content_hash=hashlib.sha256(line.encode("utf-8")).hexdigest()[:16],
153
+ ))
154
+
155
+ prop_match = prop_pattern.match(line)
156
+ if prop_match and not line.strip().startswith("return") and not line.strip().startswith("import"):
157
+ prop_mod = prop_match.group(1)
158
+ prop_type = prop_match.group(2) or "dynamic"
159
+ prop_name = prop_match.group(3)
160
+ if prop_name and prop_name not in ("return", "break", "continue", "throw"):
161
+ vis = Visibility.PRIVATE if prop_name.startswith("_") else Visibility.PUBLIC
162
+ parent_id = current_type if current_type and brace_depth > type_depth else file_path
163
+ sym_id = f"{parent_id}::{prop_name}"
164
+ symbols.append(Symbol(
165
+ id=sym_id,
166
+ name=prop_name,
167
+ qualified_name=prop_name,
168
+ kind=SymbolKind.VARIABLE,
169
+ language=self.language,
170
+ location=Location(file_path, line_idx, 0, line_idx, len(line)),
171
+ parent_id=parent_id,
172
+ signature=f"{prop_type} {prop_name}",
173
+ visibility=vis,
174
+ modifiers=tuple(filter(None, [prop_mod])),
175
+ content_hash=hashlib.sha256(line.encode("utf-8")).hexdigest()[:16],
176
+ ))
177
+
178
+ brace_depth += line.count("{") - line.count("}")
179
+ if current_type and brace_depth <= type_depth:
180
+ current_type = None
181
+
182
+ return sorted(symbols, key=lambda s: (s.location.start_line if s.location else 0, s.name))
183
+
184
+ def extract_edges(self, parse: ParseResult, symbols: Sequence[Symbol]) -> Iterable[Edge]:
185
+ """Extract containment, inheritance, mixin, and import edges for Dart.
186
+ Example:
187
+ >>> analyzer = DartLanguageAnalyzer()
188
+ >>> res = analyzer.parse(Path("App.dart"), "class Child extends Parent with Logger implements IService {}")
189
+ >>> syms = list(analyzer.extract_symbols(res))
190
+ >>> edges = list(analyzer.extract_edges(res, syms))
191
+ >>> len(edges) >= 1
192
+ True
193
+ """
194
+ edges: List[Edge] = []
195
+ file_path = parse.file_path
196
+ symbol_map = {s.name: s for s in symbols}
197
+ type_inherit_pattern = re.compile(r'^\s*(?:[a-zA-Z0-9_\s<>]+)?(?:class|mixin|enum|extension)\s+([a-zA-Z0-9_]+)(?:<[^>]+>)?(?:\s+extends\s+([a-zA-Z0-9_.,\s<>]+))?(?:\s+with\s+([a-zA-Z0-9_.,\s<>]+))?(?:\s+implements\s+([a-zA-Z0-9_.,\s<>]+))?')
198
+
199
+ for s in symbols:
200
+ if s.parent_id and s.parent_id != s.id:
201
+ edges.append(Edge(
202
+ source_id=s.parent_id,
203
+ target_id=s.id,
204
+ kind=EdgeKind.CONTAINS,
205
+ weight=1.0,
206
+ confidence=1.0,
207
+ location=s.location,
208
+ ))
209
+
210
+ for line_idx, line in enumerate(parse.source.splitlines(), start=1):
211
+ type_match = type_inherit_pattern.match(line)
212
+ if type_match:
213
+ type_name = type_match.group(1)
214
+ extends_raw = type_match.group(2)
215
+ with_raw = type_match.group(3)
216
+ implements_raw = type_match.group(4)
217
+ type_sym = symbol_map.get(type_name)
218
+ if type_sym:
219
+ super_candidates = []
220
+ if extends_raw:
221
+ super_candidates.append(extends_raw.strip().split("<")[0].strip())
222
+ if with_raw:
223
+ for w in with_raw.split(","):
224
+ clean_w = w.strip().split("<")[0].strip()
225
+ if clean_w:
226
+ super_candidates.append(clean_w)
227
+ if implements_raw:
228
+ for imp in implements_raw.split(","):
229
+ clean_imp = imp.strip().split("<")[0].strip()
230
+ if clean_imp:
231
+ super_candidates.append(clean_imp)
232
+ for sup in super_candidates:
233
+ if sup and sup not in ("Object", "Widget", "StatelessWidget", "StatefulWidget"):
234
+ target_id = symbol_map[sup].id if sup in symbol_map else sup
235
+ edges.append(Edge(
236
+ source_id=type_sym.id,
237
+ target_id=target_id,
238
+ kind=EdgeKind.INHERITS,
239
+ weight=1.0,
240
+ confidence=0.9,
241
+ location=Location(file_path, line_idx, 0, line_idx, len(line)),
242
+ ))
243
+
244
+ if line.strip().startswith("import ") or line.strip().startswith("export "):
245
+ imp_match = re.search(r'[\'"]([^\'"]+)[\'"]', line)
246
+ if imp_match:
247
+ target_uri = imp_match.group(1)
248
+ edges.append(Edge(
249
+ source_id=file_path,
250
+ target_id=target_uri,
251
+ kind=EdgeKind.IMPORTS,
252
+ weight=1.0,
253
+ confidence=1.0,
254
+ location=Location(file_path, line_idx, 0, line_idx, len(line)),
255
+ ))
256
+
257
+ return sorted(edges, key=lambda e: (e.source_id, e.target_id, e.kind.value))
258
+
259
+ def resolve_import(self, ref: ImportRef, ctx: ResolveContext) -> Iterable[str]:
260
+ """Resolve Dart imports (relative or package:) against workspace files.
261
+ Example:
262
+ >>> analyzer = DartLanguageAnalyzer()
263
+ >>> from codeui.core.resolver import ResolveContext
264
+ >>> ctx = ResolveContext(Path("."))
265
+ >>> ref = ImportRef("lib/main.dart", "package:app/service.dart", None, False)
266
+ >>> list(analyzer.resolve_import(ref, ctx))
267
+ []
268
+ """
269
+ uri = ref.module_name
270
+ if uri.startswith("package:"):
271
+ parts = uri[len("package:"):].split("/", 1)
272
+ if len(parts) > 1:
273
+ pkg_sub = f"lib/{parts[1]}"
274
+ cands = ctx.resolve_path(ref.from_file, pkg_sub, "dart")
275
+ if cands:
276
+ return cands
277
+ return ctx.resolve_path(ref.from_file, parts[1], "dart")
278
+ return ctx.resolve_path(ref.from_file, uri, "dart")
codeui/lang/generic.py CHANGED
@@ -1,11 +1,14 @@
1
1
  """Generic markup, config, and fallback language analyzer for codeui."""
2
+ from __future__ import annotations
2
3
  import hashlib
3
4
  from pathlib import Path
4
- from typing import ClassVar, Iterable, List, Sequence
5
+ from typing import ClassVar, Iterable, List, Sequence, TYPE_CHECKING
5
6
  from codeui.core.ir import Edge, EdgeKind, Location, Symbol, SymbolKind, Visibility
6
- from codeui.core.resolver import ResolveContext
7
7
  from codeui.lang.base import ImportRef, LanguageAnalyzer, ParseResult
8
8
 
9
+ if TYPE_CHECKING:
10
+ from codeui.core.resolver import ResolveContext
11
+
9
12
  class GenericLanguageAnalyzer(LanguageAnalyzer):
10
13
  """Fallback analyzer for configuration, markup, and generic source files.
11
14
  Example:
@@ -19,8 +22,7 @@ class GenericLanguageAnalyzer(LanguageAnalyzer):
19
22
  has_ast_support: ClassVar[bool] = False
20
23
  is_supported: ClassVar[bool] = False
21
24
  extensions: ClassVar[tuple[str, ...]] = (
22
- ".java", ".kt", ".cs", ".rb", ".php", ".swift", ".vue", ".svelte",
23
- ".c", ".cpp", ".cc", ".cxx", ".h", ".hpp", ".m", ".mm", ".scala"
25
+ ".vue", ".svelte", ".m", ".mm", ".xml", ".html"
24
26
  )
25
27
 
26
28
  def parse(self, path: Path, source: str) -> ParseResult:
codeui/lang/go.py CHANGED
@@ -1,12 +1,15 @@
1
1
  """Go 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 GoLanguageAnalyzer(LanguageAnalyzer):
11
14
  """Go language analyzer.
12
15
  Example:
codeui/lang/java.py ADDED
@@ -0,0 +1,320 @@
1
+ """Java and Kotlin 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 JavaLanguageAnalyzer(LanguageAnalyzer):
14
+ """Java and Kotlin language analyzer.
15
+ Example:
16
+ >>> analyzer = JavaLanguageAnalyzer()
17
+ >>> res = analyzer.parse(Path("Main.java"), 'public class Main { public static void main(String[] args) {} }')
18
+ >>> syms = list(analyzer.extract_symbols(res))
19
+ >>> any(s.name == "Main" for s in syms)
20
+ True
21
+ """
22
+ language: ClassVar[str] = "java"
23
+ extensions: ClassVar[tuple[str, ...]] = (".java", ".kt")
24
+ has_ast_support: ClassVar[bool] = True
25
+ is_supported: ClassVar[bool] = True
26
+
27
+ def parse(self, path: Path, source: str) -> ParseResult:
28
+ """Parse Java or Kotlin source code into ParseResult.
29
+ Example:
30
+ >>> analyzer = JavaLanguageAnalyzer()
31
+ >>> res = analyzer.parse(Path("App.java"), "package com.example;")
32
+ >>> res.errors
33
+ []
34
+ """
35
+ content_hash = hashlib.sha256(source.encode("utf-8")).hexdigest()
36
+ return ParseResult(
37
+ file_path=str(path),
38
+ source=source,
39
+ ast=source,
40
+ content_hash=content_hash,
41
+ errors=[],
42
+ )
43
+
44
+ def extract_symbols(self, parse: ParseResult) -> Iterable[Symbol]:
45
+ """Extract classes, interfaces, records, enums, and methods from Java/Kotlin source.
46
+ Example:
47
+ >>> analyzer = JavaLanguageAnalyzer()
48
+ >>> res = analyzer.parse(Path("User.java"), "public class User { public String getName() { return name; } }")
49
+ >>> syms = list(analyzer.extract_symbols(res))
50
+ >>> len(syms) >= 2
51
+ True
52
+ """
53
+ source = parse.source
54
+ file_path = parse.file_path
55
+ lines = source.splitlines()
56
+ max_line = len(lines) if lines else 1
57
+ symbols: List[Symbol] = []
58
+
59
+ file_sym = Symbol(
60
+ id=file_path,
61
+ name=Path(file_path).name,
62
+ qualified_name=file_path,
63
+ kind=SymbolKind.FILE,
64
+ language=self.language,
65
+ location=Location(file_path, 1, 0, max_line, 0),
66
+ parent_id=None,
67
+ signature=None,
68
+ visibility=Visibility.PUBLIC,
69
+ modifiers=(),
70
+ content_hash=parse.content_hash,
71
+ )
72
+ symbols.append(file_sym)
73
+
74
+ import_pattern = re.compile(r'import\s+(?:static\s+)?([A-Za-z0-9_.*]+)\s*;')
75
+ class_pattern = re.compile(r'(?:public|protected|private)?\s*(?:abstract|final|static)?\s*(?:class|interface|record|enum)\s+([A-Za-z0-9_]+)(?:<[^>]+>)?(?:\s+extends\s+([A-Za-z0-9_$.]+))?(?:\s+implements\s+([A-Za-z0-9_$,\s]+))?')
76
+ method_pattern = re.compile(r'(?:public|protected|private|static|final|synchronized|abstract|\s)+[\w<>\[\]]+\s+([A-Za-z0-9_]+)\s*\(([^)]*)\)')
77
+ kt_fun_pattern = re.compile(r'(?:public|private|protected|internal)?\s*fun\s+(?:<[^>]+>\s+)?([A-Za-z0-9_]+)\s*\(([^)]*)\)')
78
+
79
+ js_keywords = {"if", "for", "while", "switch", "catch", "return", "throw", "new", "class", "interface", "package", "import", "else", "try", "finally"}
80
+ class_ranges: List[tuple[str, str, int, int, str, str]] = []
81
+
82
+ for idx, line in enumerate(lines, start=1):
83
+ line_str = line.strip()
84
+ if not line_str or line_str.startswith("//") or line_str.startswith("*"):
85
+ continue
86
+
87
+ for match in import_pattern.finditer(line_str):
88
+ imp_path = match.group(1)
89
+ sym_id = f"import:{imp_path}"
90
+ symbols.append(Symbol(
91
+ id=sym_id,
92
+ name=imp_path.split(".")[-1],
93
+ qualified_name=f"import:{imp_path}",
94
+ kind=SymbolKind.IMPORT,
95
+ language=self.language,
96
+ location=Location(file_path, idx, line.find(imp_path), idx, line.find(imp_path) + len(imp_path)),
97
+ parent_id=file_path,
98
+ signature=f"import {imp_path}",
99
+ visibility=Visibility.PUBLIC,
100
+ modifiers=(),
101
+ content_hash=self._extract_body_hash(lines, idx - 1),
102
+ ))
103
+
104
+ for match in class_pattern.finditer(line_str):
105
+ cls_name = match.group(1)
106
+ if cls_name in js_keywords:
107
+ continue
108
+ ext_name = match.group(2) or ""
109
+ impl_names = match.group(3) or ""
110
+ sym_id = f"{file_path}::{cls_name}"
111
+ end_line = self._find_block_end(lines, idx - 1)
112
+ class_ranges.append((cls_name, sym_id, idx, end_line, ext_name, impl_names))
113
+ loc = Location(file_path, idx, line.find(cls_name), end_line, line.find(cls_name) + len(cls_name))
114
+ vis = Visibility.PUBLIC if "public" in line_str else (Visibility.PRIVATE if "private" in line_str else Visibility.INTERNAL)
115
+ symbols.append(Symbol(
116
+ id=sym_id,
117
+ name=cls_name,
118
+ qualified_name=cls_name,
119
+ kind=SymbolKind.CLASS,
120
+ language=self.language,
121
+ location=loc,
122
+ parent_id=file_path,
123
+ signature=f"class {cls_name}",
124
+ visibility=vis,
125
+ modifiers=("public",) if "public" in line_str else (),
126
+ content_hash=self._extract_body_hash(lines, idx - 1),
127
+ ))
128
+
129
+ for match in method_pattern.finditer(line_str):
130
+ m_name = match.group(1)
131
+ params = match.group(2)
132
+ if m_name in js_keywords or m_name in ("if", "for", "while", "switch", "catch", "return"):
133
+ continue
134
+ enclosing = next((c for c in reversed(class_ranges) if c[2] <= idx <= c[3]), None)
135
+ if enclosing:
136
+ cls_name, cls_sym_id, _, _, _, _ = enclosing
137
+ sym_id = f"{file_path}::{cls_name}::{m_name}"
138
+ qual_name = f"{cls_name}.{m_name}"
139
+ parent_sym_id = cls_sym_id
140
+ else:
141
+ sym_id = f"{file_path}::{m_name}"
142
+ qual_name = m_name
143
+ parent_sym_id = file_path
144
+ end_line = self._find_block_end(lines, idx - 1)
145
+ loc = Location(file_path, idx, line.find(m_name), end_line, line.find(m_name) + len(m_name))
146
+ vis = Visibility.PUBLIC if "public" in line_str else (Visibility.PRIVATE if "private" in line_str else Visibility.INTERNAL)
147
+ symbols.append(Symbol(
148
+ id=sym_id,
149
+ name=m_name,
150
+ qualified_name=qual_name,
151
+ kind=SymbolKind.METHOD,
152
+ language=self.language,
153
+ location=loc,
154
+ parent_id=parent_sym_id,
155
+ signature=f"{m_name}({params})",
156
+ visibility=vis,
157
+ modifiers=("public",) if "public" in line_str else (),
158
+ content_hash=self._extract_body_hash(lines, idx - 1),
159
+ ))
160
+
161
+ for match in kt_fun_pattern.finditer(line_str):
162
+ m_name = match.group(1)
163
+ params = match.group(2)
164
+ if m_name in js_keywords:
165
+ continue
166
+ enclosing = next((c for c in reversed(class_ranges) if c[2] <= idx <= c[3]), None)
167
+ if enclosing:
168
+ cls_name, cls_sym_id, _, _, _, _ = enclosing
169
+ sym_id = f"{file_path}::{cls_name}::{m_name}"
170
+ qual_name = f"{cls_name}.{m_name}"
171
+ parent_sym_id = cls_sym_id
172
+ else:
173
+ sym_id = f"{file_path}::{m_name}"
174
+ qual_name = m_name
175
+ parent_sym_id = file_path
176
+ end_line = self._find_block_end(lines, idx - 1)
177
+ loc = Location(file_path, idx, line.find(m_name), end_line, line.find(m_name) + len(m_name))
178
+ symbols.append(Symbol(
179
+ id=sym_id,
180
+ name=m_name,
181
+ qualified_name=qual_name,
182
+ kind=SymbolKind.FUNCTION,
183
+ language=self.language,
184
+ location=loc,
185
+ parent_id=parent_sym_id,
186
+ signature=f"fun {m_name}({params})",
187
+ visibility=Visibility.PUBLIC,
188
+ modifiers=(),
189
+ content_hash=self._extract_body_hash(lines, idx - 1),
190
+ ))
191
+
192
+ return symbols
193
+
194
+ def extract_edges(self, parse: ParseResult, symbols: Sequence[Symbol]) -> Iterable[Edge]:
195
+ """Extract relationship edges for Java / Kotlin AST.
196
+ Example:
197
+ >>> analyzer = JavaLanguageAnalyzer()
198
+ >>> res = analyzer.parse(Path("App.java"), "import com.example.Helper;")
199
+ >>> syms = list(analyzer.extract_symbols(res))
200
+ >>> edges = list(analyzer.extract_edges(res, syms))
201
+ >>> len(edges) >= 1
202
+ True
203
+ """
204
+ edges: List[Edge] = []
205
+ file_path = parse.file_path
206
+ sym_map = {s.id: s for s in symbols}
207
+ class_pattern = re.compile(r'(?:class|interface|record|enum)\s+([A-Za-z0-9_]+)(?:<[^>]+>)?(?:\s+extends\s+([A-Za-z0-9_$.]+))?(?:\s+implements\s+([A-Za-z0-9_$,\s]+))?')
208
+ call_pattern = re.compile(r'(?<![A-Za-z0-9_$-])\b([A-Za-z_][A-Za-z0-9_]*)\s*\(')
209
+ java_keywords = {"if", "for", "while", "switch", "catch", "return", "throw", "new", "super", "this"}
210
+
211
+ for s in symbols:
212
+ if s.parent_id and s.parent_id in sym_map:
213
+ edges.append(Edge(
214
+ source_id=s.parent_id,
215
+ target_id=s.id,
216
+ kind=EdgeKind.CONTAINS,
217
+ weight=1.0,
218
+ confidence=1.0,
219
+ location=s.location,
220
+ ))
221
+ if s.kind == SymbolKind.IMPORT:
222
+ target_mod = s.qualified_name.replace("import:", "")
223
+ edges.append(Edge(
224
+ source_id=file_path,
225
+ target_id=f"module::{target_mod}",
226
+ kind=EdgeKind.IMPORTS,
227
+ weight=1.0,
228
+ confidence=1.0,
229
+ location=s.location,
230
+ ))
231
+
232
+ lines = parse.source.splitlines()
233
+ for idx, line in enumerate(lines, start=1):
234
+ line_str = line.strip()
235
+ if not line_str or line_str.startswith("//") or line_str.startswith("*"):
236
+ continue
237
+ for match in class_pattern.finditer(line_str):
238
+ cls_name = match.group(1)
239
+ ext_name = match.group(2)
240
+ impl_names = match.group(3)
241
+ cls_sym_id = f"{file_path}::{cls_name}"
242
+ if ext_name:
243
+ base_clean = ext_name.strip().split("<")[0].strip()
244
+ loc = Location(file_path, idx, line.find(base_clean), idx, line.find(base_clean) + len(base_clean))
245
+ edges.append(Edge(
246
+ source_id=cls_sym_id,
247
+ target_id=f"module::{base_clean}",
248
+ kind=EdgeKind.INHERITS,
249
+ weight=1.0,
250
+ confidence=0.9,
251
+ location=loc,
252
+ ))
253
+ if impl_names:
254
+ for iface in impl_names.split(","):
255
+ iface_clean = iface.strip().split("<")[0].strip()
256
+ if iface_clean and iface_clean not in java_keywords:
257
+ loc = Location(file_path, idx, line.find(iface_clean), idx, line.find(iface_clean) + len(iface_clean))
258
+ edges.append(Edge(
259
+ source_id=cls_sym_id,
260
+ target_id=f"module::{iface_clean}",
261
+ kind=EdgeKind.IMPLEMENTS,
262
+ weight=1.0,
263
+ confidence=0.9,
264
+ location=loc,
265
+ ))
266
+
267
+ method_syms = [s for s in symbols if s.kind in (SymbolKind.METHOD, SymbolKind.FUNCTION)]
268
+ name_to_syms: dict[str, list[Symbol]] = {}
269
+ for s in method_syms:
270
+ name_to_syms.setdefault(s.name, []).append(s)
271
+
272
+ for m in method_syms:
273
+ if not m.location:
274
+ continue
275
+ m_lines = lines[m.location.start_line - 1 : m.location.end_line]
276
+ for m_line in m_lines:
277
+ for match in call_pattern.finditer(m_line):
278
+ callee_name = match.group(1)
279
+ if callee_name in java_keywords or callee_name == m.name:
280
+ continue
281
+ if callee_name in name_to_syms:
282
+ target = name_to_syms[callee_name][0]
283
+ edges.append(Edge(
284
+ source_id=m.id,
285
+ target_id=target.id,
286
+ kind=EdgeKind.CALLS,
287
+ weight=1.0,
288
+ confidence=0.85,
289
+ location=m.location,
290
+ ))
291
+
292
+ return edges
293
+
294
+ def resolve_import(self, ref: ImportRef, ctx: ResolveContext) -> Iterable[str]:
295
+ """Resolve Java import references to project files.
296
+ Example:
297
+ >>> analyzer = JavaLanguageAnalyzer()
298
+ >>> ctx = ResolveContext(Path("."))
299
+ >>> list(analyzer.resolve_import(ImportRef("App.java", "com.example.Helper", None, False), ctx))
300
+ []
301
+ """
302
+ clean_path = ref.module_name.replace(".", "/")
303
+ return ctx.resolve_path(ref.file_path, clean_path, self.language)
304
+
305
+ def _find_block_end(self, lines: List[str], start_idx: int) -> int:
306
+ open_braces = 0
307
+ found_brace = False
308
+ for i in range(start_idx, len(lines)):
309
+ line = lines[i]
310
+ open_braces += line.count("{") - line.count("}")
311
+ if "{" in line:
312
+ found_brace = True
313
+ if found_brace and open_braces <= 0:
314
+ return i + 1
315
+ return min(start_idx + 30, len(lines))
316
+
317
+ def _extract_body_hash(self, lines: List[str], start_idx: int) -> str:
318
+ end_idx = self._find_block_end(lines, start_idx)
319
+ snippet = "\n".join(lines[start_idx:end_idx])
320
+ return hashlib.sha256(snippet.encode("utf-8")).hexdigest()