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.
- codeui/__init__.py +1 -1
- codeui/agent/context.py +19 -3
- codeui/analysis/dead_code.py +13 -2
- codeui/analysis/undefined.py +70 -0
- codeui/analysis/unresolved_import.py +34 -4
- codeui/cli/main.py +101 -7
- codeui/core/cache.py +54 -0
- codeui/core/graph.py +233 -3
- codeui/core/override.py +1 -0
- codeui/core/resolver.py +51 -37
- codeui/errors.py +10 -0
- codeui/lang/base.py +5 -2
- codeui/lang/cpp.py +271 -0
- codeui/lang/csharp.py +262 -0
- codeui/lang/dart.py +278 -0
- codeui/lang/generic.py +6 -4
- codeui/lang/go.py +5 -2
- codeui/lang/java.py +320 -0
- codeui/lang/kotlin.py +303 -0
- codeui/lang/php.py +275 -0
- codeui/lang/python.py +49 -7
- codeui/lang/registry.py +22 -2
- codeui/lang/ruby.py +270 -0
- codeui/lang/rust.py +5 -2
- codeui/lang/scala.py +290 -0
- codeui/lang/swift.py +276 -0
- codeui/lang/ts.py +99 -8
- codeui/report/sarif_emitter.py +2 -1
- codeui/server/server.py +156 -85
- codeui/server/static/css/styles.css +1609 -0
- codeui/server/static/index.html +199 -0
- codeui/server/static/js/api.js +485 -0
- codeui/server/static/js/app.js +1044 -0
- codeui/server/static/js/graph_canvas.js +1485 -0
- codeui/server/static/js/minimap.js +383 -0
- codeui/server/static/js/sidebar.js +268 -0
- codeui/server/static/js/state.js +83 -0
- codeui/server/static/js/subgraph_window.js +1208 -0
- codeui/server/static/js/theme.js +332 -0
- codeui/tracer/__init__.py +4 -1
- codeui/tracer/tracer.py +109 -118
- codeui_python-0.1.1.dist-info/METADATA +171 -0
- codeui_python-0.1.1.dist-info/RECORD +71 -0
- {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/licenses/LICENSE +1 -1
- codeui_python-0.1.0.dist-info/METADATA +0 -191
- codeui_python-0.1.0.dist-info/RECORD +0 -52
- {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/WHEEL +0 -0
- {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/entry_points.txt +0 -0
- {codeui_python-0.1.0.dist-info → codeui_python-0.1.1.dist-info}/top_level.txt +0 -0
codeui/core/graph.py
CHANGED
|
@@ -88,7 +88,7 @@ def classify_file(file_path: str, entry_points: Optional[Iterable[str]] = None,
|
|
|
88
88
|
shared_kw = {"utils", "util", "common", "shared", "helpers", "helper", "types", "constants", "errors", "exceptions"}
|
|
89
89
|
hooks_kw = {"hooks", "composables"}
|
|
90
90
|
frontend_kw = {"components", "screens", "views", "pages", "features", "ui", "frontend", "styles", "widgets", "assets", "static", "public", "templates", "client", "web"}
|
|
91
|
-
backend_kw = {"server", "services", "service", "api", "backend", "domain", "data", "db", "models", "model", "controllers", "controller", "routes", "route", "handler", "handlers", "repository", "queries", "libraries", "srv", "analysis", "report", "plugins", "lang", "tracer", "agent"}
|
|
91
|
+
backend_kw = {"server", "services", "service", "api", "backend", "domain", "data", "db", "models", "model", "controllers", "controller", "routes", "route", "handler", "handlers", "repository", "queries", "libraries", "srv", "analysis", "report", "plugins", "lang", "tracer", "agent","lib"}
|
|
92
92
|
|
|
93
93
|
is_hook_stem = (stem.startswith("use") and len(stem) > 3 and stem[3].isupper() and name_lower.endswith((".ts", ".tsx", ".js", ".jsx", ".vue", ".svelte"))) or (stem.startswith("use_") and name_lower.endswith(".py") and any(p in ("hooks", "composables") for p in parts_lower))
|
|
94
94
|
|
|
@@ -293,6 +293,19 @@ class Graph:
|
|
|
293
293
|
symbols = [self._symbols[sid] for sid in sym_ids if sid in self._symbols]
|
|
294
294
|
return sorted(symbols, key=lambda s: (s.location.start_line if s.location else 0, s.id))
|
|
295
295
|
|
|
296
|
+
def has_file(self, file_id: str) -> bool:
|
|
297
|
+
"""Check if file exists in the code graph.
|
|
298
|
+
Example:
|
|
299
|
+
>>> g = Graph()
|
|
300
|
+
>>> g.has_file("main.py")
|
|
301
|
+
False
|
|
302
|
+
"""
|
|
303
|
+
clean = file_id.replace("\\", "/").lstrip("./")
|
|
304
|
+
for f in self._file_symbols:
|
|
305
|
+
if f.replace("\\", "/").lstrip("./") == clean or f == file_id:
|
|
306
|
+
return True
|
|
307
|
+
return False
|
|
308
|
+
|
|
296
309
|
def get_all_files(self) -> List[str]:
|
|
297
310
|
"""Return list of all analyzed file paths sorted.
|
|
298
311
|
Example:
|
|
@@ -311,6 +324,15 @@ class Graph:
|
|
|
311
324
|
"""
|
|
312
325
|
return sorted(self._edges, key=lambda e: (e.source_id, e.target_id, str(e.kind)))
|
|
313
326
|
|
|
327
|
+
def get_all_edges(self) -> List[Edge]:
|
|
328
|
+
"""Return all graph edges sorted deterministically (alias for get_edges).
|
|
329
|
+
Example:
|
|
330
|
+
>>> g = Graph()
|
|
331
|
+
>>> g.get_all_edges()
|
|
332
|
+
[]
|
|
333
|
+
"""
|
|
334
|
+
return self.get_edges()
|
|
335
|
+
|
|
314
336
|
def get_outgoing_edges(self, source_id: str, kind: EdgeKind | None = None) -> List[Edge]:
|
|
315
337
|
"""Return outgoing edges from source symbol.
|
|
316
338
|
Example:
|
|
@@ -335,6 +357,203 @@ class Graph:
|
|
|
335
357
|
edges = [e for e in edges if e.kind == kind]
|
|
336
358
|
return sorted(edges, key=lambda e: (e.source_id, e.target_id, str(e.kind)))
|
|
337
359
|
|
|
360
|
+
def get_file_subgraph(self, target_id: str, include_cross: bool = True, max_hops: Optional[int] = None) -> Dict[str, Any]:
|
|
361
|
+
"""Extract a sub-graph for a file or external symbol containing its symbols and related cross-file connections.
|
|
362
|
+
Example:
|
|
363
|
+
>>> g = Graph()
|
|
364
|
+
>>> loc = Location("app.py", 1, 0, 5, 0)
|
|
365
|
+
>>> g.add_symbol(Symbol("app.py::main", "main", "main", SymbolKind.FUNCTION, "python", loc, None, "def main()", Visibility.PUBLIC, (), "h1"))
|
|
366
|
+
>>> res = g.get_file_subgraph("app.py")
|
|
367
|
+
>>> res["file_id"]
|
|
368
|
+
'app.py'
|
|
369
|
+
"""
|
|
370
|
+
clean_target = target_id.replace("module::", "").replace("\\", "/").strip().lstrip("./")
|
|
371
|
+
target_file: Optional[str] = None
|
|
372
|
+
for f in self._file_symbols:
|
|
373
|
+
if f.replace("\\", "/").lstrip("./") == clean_target or f == target_id:
|
|
374
|
+
target_file = f
|
|
375
|
+
break
|
|
376
|
+
|
|
377
|
+
if not target_file and target_id in self._symbols:
|
|
378
|
+
sym = self._symbols[target_id]
|
|
379
|
+
if sym.location and sym.location.file_id:
|
|
380
|
+
target_file = sym.location.file_id
|
|
381
|
+
|
|
382
|
+
if not target_file:
|
|
383
|
+
for s in self._symbols.values():
|
|
384
|
+
if s.id == target_id or s.name == clean_target or s.qualified_name == clean_target or s.id.endswith("::" + clean_target):
|
|
385
|
+
if s.location and s.location.file_id:
|
|
386
|
+
target_file = s.location.file_id
|
|
387
|
+
break
|
|
388
|
+
|
|
389
|
+
if target_file and target_file in self._file_symbols:
|
|
390
|
+
raw_symbols = self.get_symbols_by_file(target_file)
|
|
391
|
+
symbols = [s for s in raw_symbols if s.kind not in (SymbolKind.FILE, SymbolKind.VARIABLE)]
|
|
392
|
+
if not symbols:
|
|
393
|
+
symbols = [s for s in raw_symbols if s.kind != SymbolKind.FILE]
|
|
394
|
+
if not symbols:
|
|
395
|
+
symbols = raw_symbols
|
|
396
|
+
sym_ids = {s.id for s in symbols}
|
|
397
|
+
cls = self.get_file_classification(target_file)
|
|
398
|
+
|
|
399
|
+
intra_edges = [
|
|
400
|
+
e for e in self._edges
|
|
401
|
+
if (e.source_id in sym_ids or e.source_id == target_file) and (e.target_id in sym_ids or e.target_id == target_file)
|
|
402
|
+
]
|
|
403
|
+
|
|
404
|
+
related_symbols_dict: Dict[str, Dict[str, Any]] = {}
|
|
405
|
+
collected_cross_edges: Set[Tuple[str, str, str]] = set()
|
|
406
|
+
ext_edges: List[Edge] = []
|
|
407
|
+
|
|
408
|
+
if include_cross:
|
|
409
|
+
frontier: Set[str] = set(sym_ids)
|
|
410
|
+
visited_symbols: Set[str] = set(sym_ids)
|
|
411
|
+
hops = 0
|
|
412
|
+
max_traversal_hops = max_hops if max_hops is not None else 1000
|
|
413
|
+
|
|
414
|
+
file_outgoing = self.get_outgoing_edges(target_file)
|
|
415
|
+
file_incoming = self.get_incoming_edges(target_file)
|
|
416
|
+
for e in file_outgoing + file_incoming:
|
|
417
|
+
endpoint = e.target_id if e.source_id == target_file else e.source_id
|
|
418
|
+
if endpoint in self._file_symbols and endpoint != target_file:
|
|
419
|
+
edge_key = (e.source_id, e.target_id, str(e.kind))
|
|
420
|
+
if edge_key not in collected_cross_edges:
|
|
421
|
+
collected_cross_edges.add(edge_key)
|
|
422
|
+
ext_edges.append(e)
|
|
423
|
+
if endpoint not in related_symbols_dict and endpoint not in sym_ids:
|
|
424
|
+
endpoint_name = endpoint.split("/")[-1]
|
|
425
|
+
cls_end = self.get_file_classification(endpoint)
|
|
426
|
+
related_symbols_dict[endpoint] = {
|
|
427
|
+
"id": endpoint,
|
|
428
|
+
"name": endpoint_name,
|
|
429
|
+
"qualified_name": endpoint,
|
|
430
|
+
"kind": "file",
|
|
431
|
+
"file_id": endpoint,
|
|
432
|
+
"layer": str(cls_end.layer.value if hasattr(cls_end.layer, "value") else cls_end.layer) if cls_end else "shared",
|
|
433
|
+
"feature": cls_end.feature if cls_end else "external",
|
|
434
|
+
"is_external": True,
|
|
435
|
+
"location": None,
|
|
436
|
+
"signature": None,
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
while frontier and hops < max_traversal_hops:
|
|
440
|
+
next_frontier: Set[str] = set()
|
|
441
|
+
for current_id in frontier:
|
|
442
|
+
for e in self.get_outgoing_edges(current_id):
|
|
443
|
+
edge_key = (e.source_id, e.target_id, str(e.kind))
|
|
444
|
+
if e.target_id in self._symbols:
|
|
445
|
+
other_sym = self._symbols[e.target_id]
|
|
446
|
+
if other_sym.kind in (SymbolKind.FILE, SymbolKind.VARIABLE):
|
|
447
|
+
continue
|
|
448
|
+
other_file = other_sym.location.file_id if other_sym.location else ""
|
|
449
|
+
if other_file and other_file in self._file_symbols:
|
|
450
|
+
if other_file != target_file:
|
|
451
|
+
if edge_key not in collected_cross_edges:
|
|
452
|
+
collected_cross_edges.add(edge_key)
|
|
453
|
+
ext_edges.append(e)
|
|
454
|
+
if other_sym.id not in related_symbols_dict:
|
|
455
|
+
other_cls = self.get_file_classification(other_file)
|
|
456
|
+
related_symbols_dict[other_sym.id] = {
|
|
457
|
+
"id": other_sym.id,
|
|
458
|
+
"name": other_sym.name,
|
|
459
|
+
"qualified_name": other_sym.qualified_name,
|
|
460
|
+
"kind": str(other_sym.kind.value if hasattr(other_sym.kind, "value") else other_sym.kind),
|
|
461
|
+
"file_id": other_file,
|
|
462
|
+
"layer": str(other_cls.layer.value if hasattr(other_cls.layer, "value") else other_cls.layer) if other_cls else "shared",
|
|
463
|
+
"feature": other_cls.feature if other_cls else "external",
|
|
464
|
+
"is_external": True,
|
|
465
|
+
"location": other_sym.location.to_dict() if other_sym.location else None,
|
|
466
|
+
"signature": other_sym.signature,
|
|
467
|
+
}
|
|
468
|
+
if other_sym.id not in visited_symbols:
|
|
469
|
+
visited_symbols.add(other_sym.id)
|
|
470
|
+
next_frontier.add(other_sym.id)
|
|
471
|
+
|
|
472
|
+
for e in self.get_incoming_edges(current_id):
|
|
473
|
+
edge_key = (e.source_id, e.target_id, str(e.kind))
|
|
474
|
+
if e.source_id in self._symbols:
|
|
475
|
+
other_sym = self._symbols[e.source_id]
|
|
476
|
+
if other_sym.kind in (SymbolKind.FILE, SymbolKind.VARIABLE):
|
|
477
|
+
continue
|
|
478
|
+
other_file = other_sym.location.file_id if other_sym.location else ""
|
|
479
|
+
if other_file and other_file in self._file_symbols:
|
|
480
|
+
if other_file != target_file:
|
|
481
|
+
if edge_key not in collected_cross_edges:
|
|
482
|
+
collected_cross_edges.add(edge_key)
|
|
483
|
+
ext_edges.append(e)
|
|
484
|
+
if other_sym.id not in related_symbols_dict:
|
|
485
|
+
other_cls = self.get_file_classification(other_file)
|
|
486
|
+
related_symbols_dict[other_sym.id] = {
|
|
487
|
+
"id": other_sym.id,
|
|
488
|
+
"name": other_sym.name,
|
|
489
|
+
"qualified_name": other_sym.qualified_name,
|
|
490
|
+
"kind": str(other_sym.kind.value if hasattr(other_sym.kind, "value") else other_sym.kind),
|
|
491
|
+
"file_id": other_file,
|
|
492
|
+
"layer": str(other_cls.layer.value if hasattr(other_cls.layer, "value") else other_cls.layer) if other_cls else "shared",
|
|
493
|
+
"feature": other_cls.feature if other_cls else "external",
|
|
494
|
+
"is_external": True,
|
|
495
|
+
"location": other_sym.location.to_dict() if other_sym.location else None,
|
|
496
|
+
"signature": other_sym.signature,
|
|
497
|
+
}
|
|
498
|
+
if other_sym.id not in visited_symbols:
|
|
499
|
+
visited_symbols.add(other_sym.id)
|
|
500
|
+
next_frontier.add(other_sym.id)
|
|
501
|
+
frontier = next_frontier
|
|
502
|
+
hops += 1
|
|
503
|
+
|
|
504
|
+
all_known_ids = sym_ids | set(related_symbols_dict.keys()) | {target_file}
|
|
505
|
+
for e in self._edges:
|
|
506
|
+
edge_key = (e.source_id, e.target_id, str(e.kind))
|
|
507
|
+
if e.source_id in all_known_ids and e.target_id in all_known_ids:
|
|
508
|
+
if edge_key not in collected_cross_edges and (e.source_id not in sym_ids or e.target_id not in sym_ids):
|
|
509
|
+
collected_cross_edges.add(edge_key)
|
|
510
|
+
ext_edges.append(e)
|
|
511
|
+
|
|
512
|
+
return {
|
|
513
|
+
"file_id": target_file,
|
|
514
|
+
"classification": cls.to_dict(),
|
|
515
|
+
"symbols": [s.to_dict() for s in symbols],
|
|
516
|
+
"related_symbols": list(related_symbols_dict.values()),
|
|
517
|
+
"edges": [e.to_dict() for e in intra_edges + ext_edges],
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
ext_id = target_id
|
|
521
|
+
sym_name = ext_id.split("::")[-1].split(".")[-1]
|
|
522
|
+
incoming = [e for e in self._edges if e.target_id == ext_id or e.target_id.endswith("." + sym_name)]
|
|
523
|
+
outgoing = [e for e in self._edges if e.source_id == ext_id or e.source_id.endswith("." + sym_name)]
|
|
524
|
+
ext_edges = incoming + outgoing
|
|
525
|
+
related_symbols = {}
|
|
526
|
+
for e in ext_edges:
|
|
527
|
+
endpoint = e.source_id if e.target_id == ext_id or e.target_id.endswith("." + sym_name) else e.target_id
|
|
528
|
+
if endpoint in self._symbols:
|
|
529
|
+
s = self._symbols[endpoint]
|
|
530
|
+
related_symbols[endpoint] = {
|
|
531
|
+
"id": s.id,
|
|
532
|
+
"name": s.name,
|
|
533
|
+
"qualified_name": s.qualified_name,
|
|
534
|
+
"kind": str(s.kind.value if hasattr(s.kind, "value") else s.kind),
|
|
535
|
+
"file_id": s.location.file_id if s.location else "",
|
|
536
|
+
"location": s.location.to_dict() if s.location else None,
|
|
537
|
+
"signature": s.signature,
|
|
538
|
+
"is_external": False,
|
|
539
|
+
}
|
|
540
|
+
is_class = bool(sym_name and sym_name[0].isupper())
|
|
541
|
+
return {
|
|
542
|
+
"file_id": ext_id,
|
|
543
|
+
"classification": {"layer": "shared", "feature": "external", "module": "external"},
|
|
544
|
+
"is_external": True,
|
|
545
|
+
"symbols": [{
|
|
546
|
+
"id": ext_id,
|
|
547
|
+
"name": sym_name,
|
|
548
|
+
"qualified_name": ext_id,
|
|
549
|
+
"kind": "class" if is_class else "function",
|
|
550
|
+
"signature": f"{sym_name}(...)",
|
|
551
|
+
"is_external": True,
|
|
552
|
+
}],
|
|
553
|
+
"related_symbols": list(related_symbols.values()),
|
|
554
|
+
"edges": [e.to_dict() for e in ext_edges],
|
|
555
|
+
}
|
|
556
|
+
|
|
338
557
|
def get_subgraph(self, symbol_id: str, depth: int | None = None) -> Tuple[List[Symbol], List[Edge]]:
|
|
339
558
|
"""Extract a sub-graph surrounding a target symbol up to specified depth or all reachable nodes if depth is None.
|
|
340
559
|
Example:
|
|
@@ -398,14 +617,25 @@ class Graph:
|
|
|
398
617
|
for file_id in self.get_all_files():
|
|
399
618
|
classification = self.get_file_classification(file_id)
|
|
400
619
|
syms = self.get_symbols_by_file(file_id)
|
|
620
|
+
detected_symbols = [s for s in syms if s.kind not in (SymbolKind.FILE, SymbolKind.VARIABLE)]
|
|
621
|
+
clean_path = file_id.replace("\\", "/").lstrip("./")
|
|
622
|
+
parts = [p for p in clean_path.split("/") if p]
|
|
623
|
+
folder = "/".join(parts[:-1]) if len(parts) > 1 else ""
|
|
624
|
+
main_folder = parts[0] if len(parts) > 1 else "root"
|
|
625
|
+
subfolder = parts[-2] if len(parts) > 2 else (parts[0] if len(parts) > 1 else "root")
|
|
401
626
|
result[file_id] = {
|
|
402
627
|
"path": file_id,
|
|
403
628
|
"layer": str(classification.layer),
|
|
404
629
|
"feature": classification.feature,
|
|
405
630
|
"module": classification.module,
|
|
406
|
-
"
|
|
407
|
-
"
|
|
631
|
+
"folder": folder,
|
|
632
|
+
"main_folder": main_folder,
|
|
633
|
+
"subfolder": subfolder,
|
|
634
|
+
"symbol_count": len(detected_symbols),
|
|
635
|
+
"symbols_count": len(detected_symbols),
|
|
636
|
+
"total_symbols_count": len(syms),
|
|
408
637
|
"symbol_ids": [s.id for s in syms],
|
|
638
|
+
"detected_symbol_ids": [s.id for s in detected_symbols],
|
|
409
639
|
}
|
|
410
640
|
return result
|
|
411
641
|
|
codeui/core/override.py
CHANGED
codeui/core/resolver.py
CHANGED
|
@@ -20,8 +20,21 @@ class ResolveContext:
|
|
|
20
20
|
self._resolve_cache: Dict[Tuple[str, str, str], List[str]] = {}
|
|
21
21
|
self._known_files: Optional[Set[str]] = None
|
|
22
22
|
self._search_roots: List[Path] = [self.root_path]
|
|
23
|
+
self.known_extensions: Set[str] = {
|
|
24
|
+
".py", ".pyi", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".go",
|
|
25
|
+
".rs", ".java", ".kt", ".kts", ".c", ".cpp", ".cc", ".cxx", ".h", ".hpp",
|
|
26
|
+
".cs", ".php", ".rb", ".swift", ".scala", ".sc", ".dart", ".json", ".yaml", ".yml"
|
|
27
|
+
}
|
|
23
28
|
self._load_configs()
|
|
24
29
|
|
|
30
|
+
def set_supported_extensions(self, exts: Iterable[str]) -> None:
|
|
31
|
+
"""Configure file extensions considered during import resolution.
|
|
32
|
+
Example:
|
|
33
|
+
>>> ctx = ResolveContext(Path("."))
|
|
34
|
+
>>> ctx.set_supported_extensions([".py", ".ts"])
|
|
35
|
+
"""
|
|
36
|
+
self.known_extensions = {e.lower() if e.startswith(".") else f".{e.lower()}" for e in exts}
|
|
37
|
+
|
|
25
38
|
def set_known_files(self, files: Iterable[str]) -> None:
|
|
26
39
|
"""Register known project files to accelerate import resolution without disk checks.
|
|
27
40
|
Example:
|
|
@@ -133,6 +146,9 @@ class ResolveContext:
|
|
|
133
146
|
if cache_key in self._resolve_cache:
|
|
134
147
|
return self._resolve_cache[cache_key]
|
|
135
148
|
|
|
149
|
+
all_exts = sorted(list(self.known_extensions))
|
|
150
|
+
index_basenames = ["index", "__init__", "mod", "lib", "main"]
|
|
151
|
+
|
|
136
152
|
resolved: List[str] = []
|
|
137
153
|
current_dir = (self.root_path / Path(current_file).parent).resolve() if current_file else self.root_path
|
|
138
154
|
clean_mod = import_path.replace("module::", "").replace("import:", "").strip()
|
|
@@ -145,27 +161,28 @@ class ResolveContext:
|
|
|
145
161
|
if target_dir.parent and target_dir.parent != target_dir:
|
|
146
162
|
target_dir = target_dir.parent
|
|
147
163
|
|
|
148
|
-
parts = [p for p in rest.
|
|
164
|
+
parts = [p for p in rest.replace("\\", "/").split("/") if p]
|
|
165
|
+
if not parts and "." in rest:
|
|
166
|
+
parts = [p for p in rest.split(".") if p]
|
|
167
|
+
|
|
168
|
+
candidate_paths: List[Path] = []
|
|
149
169
|
if not parts:
|
|
150
|
-
|
|
170
|
+
for idx_base in index_basenames:
|
|
171
|
+
for ext in all_exts:
|
|
172
|
+
candidate_paths.append(target_dir / f"{idx_base}{ext}")
|
|
151
173
|
else:
|
|
152
174
|
sub_str = "/".join(parts)
|
|
153
175
|
parent_sub = "/".join(parts[:-1]) if len(parts) > 1 else ""
|
|
154
|
-
candidate_paths
|
|
155
|
-
|
|
156
|
-
target_dir / f"{sub_str}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
target_dir / f"{parent_sub}.py",
|
|
165
|
-
target_dir / f"{parent_sub}.ts",
|
|
166
|
-
target_dir / f"{parent_sub}.js",
|
|
167
|
-
target_dir / parent_sub / "__init__.py",
|
|
168
|
-
])
|
|
176
|
+
candidate_paths.append(target_dir / sub_str)
|
|
177
|
+
for ext in all_exts:
|
|
178
|
+
candidate_paths.append(target_dir / f"{sub_str}{ext}")
|
|
179
|
+
if parent_sub:
|
|
180
|
+
candidate_paths.append(target_dir / f"{parent_sub}{ext}")
|
|
181
|
+
for idx_base in index_basenames:
|
|
182
|
+
candidate_paths.append(target_dir / sub_str / f"{idx_base}{ext}")
|
|
183
|
+
if parent_sub:
|
|
184
|
+
candidate_paths.append(target_dir / parent_sub / f"{idx_base}{ext}")
|
|
185
|
+
|
|
169
186
|
for cp in candidate_paths:
|
|
170
187
|
if self._known_files is not None:
|
|
171
188
|
try:
|
|
@@ -185,9 +202,7 @@ class ResolveContext:
|
|
|
185
202
|
if import_path.startswith(alias):
|
|
186
203
|
rel = import_path[len(alias):].lstrip("/")
|
|
187
204
|
base = Path(target) / rel
|
|
188
|
-
|
|
189
|
-
extensions = sorted(list(LanguageRegistry().get_supported_extensions() | {".ts", ".tsx", ".js", ".jsx", ".py"}))
|
|
190
|
-
for ext in extensions:
|
|
205
|
+
for ext in all_exts:
|
|
191
206
|
p = base.with_suffix(ext)
|
|
192
207
|
if self._known_files is not None:
|
|
193
208
|
try:
|
|
@@ -225,9 +240,6 @@ class ResolveContext:
|
|
|
225
240
|
if not clean_mod.startswith("."):
|
|
226
241
|
mod_path_str = clean_mod.replace(".", "/")
|
|
227
242
|
parts = [p for p in mod_path_str.split("/") if p]
|
|
228
|
-
from codeui.lang.registry import LanguageRegistry
|
|
229
|
-
code_extensions = sorted(list(LanguageRegistry().get_supported_extensions() | {".py", ".ts", ".tsx", ".js", ".jsx", ".go", ".rs", ".mjs", ".cjs"}))
|
|
230
|
-
index_filenames = ["__init__.py", "index.ts", "index.tsx", "index.js", "index.jsx", "mod.rs", "lib.rs"]
|
|
231
243
|
|
|
232
244
|
if self._known_files is not None:
|
|
233
245
|
search_root_prefixes = [""] + [f"{r.name}/" for r in self._search_roots if r != self.root_path]
|
|
@@ -235,7 +247,7 @@ class ResolveContext:
|
|
|
235
247
|
sub_str = "/".join(parts[i:])
|
|
236
248
|
parent_sub = "/".join(parts[i:-1]) if len(parts[i:]) > 1 else ""
|
|
237
249
|
for prefix in search_root_prefixes:
|
|
238
|
-
for ext in
|
|
250
|
+
for ext in all_exts:
|
|
239
251
|
cand1 = f"{prefix}{sub_str}{ext}"
|
|
240
252
|
if cand1 in self._known_files:
|
|
241
253
|
resolved.append(cand1)
|
|
@@ -243,14 +255,15 @@ class ResolveContext:
|
|
|
243
255
|
cand2 = f"{prefix}{parent_sub}{ext}"
|
|
244
256
|
if cand2 in self._known_files:
|
|
245
257
|
resolved.append(cand2)
|
|
246
|
-
for
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
258
|
+
for idx_base in index_basenames:
|
|
259
|
+
for ext in all_exts:
|
|
260
|
+
cand3 = f"{prefix}{sub_str}/{idx_base}{ext}"
|
|
261
|
+
if cand3 in self._known_files:
|
|
262
|
+
resolved.append(cand3)
|
|
263
|
+
if parent_sub:
|
|
264
|
+
cand4 = f"{prefix}{parent_sub}/{idx_base}{ext}"
|
|
265
|
+
if cand4 in self._known_files:
|
|
266
|
+
resolved.append(cand4)
|
|
254
267
|
if resolved:
|
|
255
268
|
break
|
|
256
269
|
else:
|
|
@@ -260,14 +273,15 @@ class ResolveContext:
|
|
|
260
273
|
sub_str = "/".join(sub_parts)
|
|
261
274
|
parent_sub = "/".join(sub_parts[:-1]) if len(sub_parts) > 1 else ""
|
|
262
275
|
for root in self._search_roots:
|
|
263
|
-
for ext in
|
|
276
|
+
for ext in all_exts:
|
|
264
277
|
candidate_paths.append(root / f"{sub_str}{ext}")
|
|
265
278
|
if parent_sub:
|
|
266
279
|
candidate_paths.append(root / f"{parent_sub}{ext}")
|
|
267
|
-
for
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
280
|
+
for idx_base in index_basenames:
|
|
281
|
+
for ext in all_exts:
|
|
282
|
+
candidate_paths.append(root / sub_str / f"{idx_base}{ext}")
|
|
283
|
+
if parent_sub:
|
|
284
|
+
candidate_paths.append(root / parent_sub / f"{idx_base}{ext}")
|
|
271
285
|
|
|
272
286
|
for cp in candidate_paths:
|
|
273
287
|
if cp.exists() and cp.is_file():
|
codeui/errors.py
CHANGED
|
@@ -96,3 +96,13 @@ class ContributionError(CodeUIError):
|
|
|
96
96
|
'Failed to push changes'
|
|
97
97
|
"""
|
|
98
98
|
pass
|
|
99
|
+
|
|
100
|
+
class ExportError(CodeUIError):
|
|
101
|
+
"""Raised when exporting project graph or pages fails.
|
|
102
|
+
Example:
|
|
103
|
+
>>> err = ExportError("Output directory is not writable")
|
|
104
|
+
>>> str(err)
|
|
105
|
+
'Output directory is not writable'
|
|
106
|
+
"""
|
|
107
|
+
pass
|
|
108
|
+
|
codeui/lang/base.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"""Base interface for language AST adapters."""
|
|
2
|
+
from __future__ import annotations
|
|
2
3
|
import hashlib
|
|
3
4
|
from abc import ABC, abstractmethod
|
|
4
5
|
from dataclasses import dataclass
|
|
5
6
|
from pathlib import Path
|
|
6
|
-
from typing import Any, ClassVar, Iterable, List, Sequence
|
|
7
|
+
from typing import Any, ClassVar, Iterable, List, Sequence, TYPE_CHECKING
|
|
7
8
|
from codeui.core.ir import Edge, Location, Symbol
|
|
8
|
-
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from codeui.core.resolver import ResolveContext
|
|
9
12
|
|
|
10
13
|
@dataclass
|
|
11
14
|
class ParseResult:
|