aja-codeintel 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 (68) hide show
  1. aja_codeintel-0.1.0.dist-info/METADATA +436 -0
  2. aja_codeintel-0.1.0.dist-info/RECORD +68 -0
  3. aja_codeintel-0.1.0.dist-info/WHEEL +5 -0
  4. aja_codeintel-0.1.0.dist-info/entry_points.txt +3 -0
  5. aja_codeintel-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. aja_codeintel-0.1.0.dist-info/top_level.txt +1 -0
  7. codeintel_cli/__init__.py +1 -0
  8. codeintel_cli/__main__.py +4 -0
  9. codeintel_cli/cli.py +41 -0
  10. codeintel_cli/commands/__init__.py +1 -0
  11. codeintel_cli/commands/graph/__init__.py +18 -0
  12. codeintel_cli/commands/graph/deps_cmd.py +35 -0
  13. codeintel_cli/commands/graph/related_cmd.py +121 -0
  14. codeintel_cli/commands/graph/relsymbols_cmd.py +347 -0
  15. codeintel_cli/commands/graph/reverse_related_cmd.py +54 -0
  16. codeintel_cli/commands/nav/__init__.py +12 -0
  17. codeintel_cli/commands/nav/copy_cmd.py +101 -0
  18. codeintel_cli/commands/nav/open_cmd.py +18 -0
  19. codeintel_cli/commands/nav/where_cmd.py +21 -0
  20. codeintel_cli/commands/project/__init__.py +26 -0
  21. codeintel_cli/commands/project/context_cmd.py +326 -0
  22. codeintel_cli/commands/project/folder_cmd.py +51 -0
  23. codeintel_cli/commands/project/imports_cmd.py +90 -0
  24. codeintel_cli/commands/project/models_cmd.py +98 -0
  25. codeintel_cli/commands/project/modeltree_cmd.py +476 -0
  26. codeintel_cli/commands/project/new.py +0 -0
  27. codeintel_cli/commands/project/resolve_cmd.py +29 -0
  28. codeintel_cli/commands/project/scan_cmd.py +51 -0
  29. codeintel_cli/commands/project/servicemap_cmd.py +180 -0
  30. codeintel_cli/commands/project/tree_cmd.py +203 -0
  31. codeintel_cli/commands/project/version_cmd.py +14 -0
  32. codeintel_cli/context/java_context.py +180 -0
  33. codeintel_cli/context/java_rel.py +299 -0
  34. codeintel_cli/context/java_service.py +291 -0
  35. codeintel_cli/context/python_context.py +91 -0
  36. codeintel_cli/context/python_rel.py +251 -0
  37. codeintel_cli/context/python_service.py +205 -0
  38. codeintel_cli/core/fuzzy.py +72 -0
  39. codeintel_cli/core/opener.py +37 -0
  40. codeintel_cli/core/project.py +34 -0
  41. codeintel_cli/core/resolve_folder.py +68 -0
  42. codeintel_cli/core/resolve_model_target.py +92 -0
  43. codeintel_cli/core/resolve_target.py +53 -0
  44. codeintel_cli/core/timing.py +13 -0
  45. codeintel_cli/core/where.py +77 -0
  46. codeintel_cli/db/__init__.py +7 -0
  47. codeintel_cli/db/cache.py +224 -0
  48. codeintel_cli/db/operations.py +333 -0
  49. codeintel_cli/db/schema.py +102 -0
  50. codeintel_cli/errors.py +78 -0
  51. codeintel_cli/graph/__init__.py +1 -0
  52. codeintel_cli/graph/builder.py +149 -0
  53. codeintel_cli/graph/query.py +30 -0
  54. codeintel_cli/graph/traverse.py +49 -0
  55. codeintel_cli/lang/__init__.py +0 -0
  56. codeintel_cli/lang/java/__init__.py +0 -0
  57. codeintel_cli/lang/java/engine.py +18 -0
  58. codeintel_cli/lang/java/models.py +105 -0
  59. codeintel_cli/lang/java/resolve.py +49 -0
  60. codeintel_cli/lang/python/__init__.py +0 -0
  61. codeintel_cli/lang/python/engine.py +8 -0
  62. codeintel_cli/lang/python/models.py +86 -0
  63. codeintel_cli/lang/router.py +24 -0
  64. codeintel_cli/parser/imports.py +26 -0
  65. codeintel_cli/parser/resolve.py +49 -0
  66. codeintel_cli/parser/symbols.py +92 -0
  67. codeintel_cli/scanner/__init__.py +0 -0
  68. codeintel_cli/scanner/scanner.py +41 -0
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from collections import deque
5
+
6
+
7
+ def build_reverse_graph(graph: dict[Path, set[Path]]) -> dict[Path, set[Path]]:
8
+ rev: dict[Path, set[Path]] = {}
9
+
10
+ for src, targets in graph.items():
11
+ rev.setdefault(src, set())
12
+ for t in targets:
13
+ rev.setdefault(t, set()).add(src)
14
+
15
+ return rev
16
+
17
+
18
+ def bfs_related(
19
+ graph: dict[Path, set[Path]],
20
+ start: Path,
21
+ max_depth: int,
22
+ skip: set[Path] | None = None,
23
+ ) -> set[Path]:
24
+ if max_depth <= 0:
25
+ return set()
26
+
27
+ skip = skip or set()
28
+ seen: set[Path] = set()
29
+ q = deque([(start, 0)])
30
+
31
+ while q:
32
+ node, depth = q.popleft()
33
+
34
+ expand = node not in skip
35
+
36
+ for nxt in graph.get(node, set()):
37
+ if nxt == start or nxt in seen:
38
+ continue
39
+
40
+ next_depth = depth + 1
41
+ if next_depth > max_depth:
42
+ continue
43
+
44
+ seen.add(nxt)
45
+
46
+ if expand:
47
+ q.append((nxt, next_depth))
48
+
49
+ return seen
File without changes
File without changes
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from pathlib import Path
5
+
6
+ IMP = re.compile(r"^\s*import\s+([\w\.]+)\s*;")
7
+
8
+
9
+ def java_extract_imports(path: Path):
10
+ out: list[str] = []
11
+ try:
12
+ for line in path.read_text(errors="ignore").splitlines():
13
+ m = IMP.match(line)
14
+ if m:
15
+ out.append(m.group(1))
16
+ except Exception:
17
+ pass
18
+ return out
@@ -0,0 +1,105 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ import re
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class ModelField:
10
+ name: str
11
+ type: str
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class ModelDef:
16
+ name: str
17
+ file: str
18
+ kind: str
19
+ bases: list[str]
20
+ fields: list[ModelField]
21
+
22
+
23
+ TYPE_RE = re.compile(r"\b(class|interface|enum|record)\s+([A-Za-z_][A-Za-z0-9_]*)\b")
24
+ RECORD_RE = re.compile(r"\brecord\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)")
25
+ FIELD_RE = re.compile(
26
+ r"^\s*(?:@\w+(?:\([^)]*\))?\s*)*"
27
+ r"(?:(?:public|protected|private)\s+)?"
28
+ r"(?:(?:static|final)\s+)*"
29
+ r"([A-Za-z0-9_<>\[\].,?]+)\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:=.*)?;"
30
+ )
31
+
32
+ _DENY_TYPES = {"return", "throw", "new", "super", "this", "break", "continue"}
33
+ _DENY_NAMES = {"return", "throw", "new", "super", "this", "true", "false", "null"}
34
+
35
+
36
+ def _split_record_params(sig: str) -> list[tuple[str, str]]:
37
+ s = sig.strip()
38
+ if not s:
39
+ return []
40
+ parts: list[str] = []
41
+ cur = []
42
+ depth = 0
43
+ for ch in s:
44
+ if ch == "<":
45
+ depth += 1
46
+ elif ch == ">":
47
+ depth = max(0, depth - 1)
48
+ elif ch == "," and depth == 0:
49
+ parts.append("".join(cur).strip())
50
+ cur = []
51
+ continue
52
+ cur.append(ch)
53
+ if cur:
54
+ parts.append("".join(cur).strip())
55
+
56
+ out: list[tuple[str, str]] = []
57
+ for p in parts:
58
+ toks = p.split()
59
+ if len(toks) >= 2:
60
+ t = " ".join(toks[:-1]).strip()
61
+ n = toks[-1].strip()
62
+ if t and n:
63
+ out.append((n, t))
64
+ return out
65
+
66
+
67
+ def extract_java_models(path: Path, project_root: Path) -> list[ModelDef]:
68
+ try:
69
+ text = path.read_text(encoding="utf-8", errors="ignore")
70
+ except Exception:
71
+ return []
72
+
73
+ rel = str(path.relative_to(project_root))
74
+
75
+ mrec = RECORD_RE.search(text)
76
+ if mrec:
77
+ name = mrec.group(1)
78
+ params = mrec.group(2)
79
+ fields = [ModelField(n, t) for (n, t) in _split_record_params(params)]
80
+ return [ModelDef(name, rel, "java", [], fields)]
81
+
82
+ mtype = TYPE_RE.search(text)
83
+ if not mtype:
84
+ return []
85
+
86
+ name = mtype.group(2)
87
+
88
+ fields: list[ModelField] = []
89
+ for line in text.splitlines():
90
+ s = line.strip()
91
+ if not s or s.startswith("return ") or s.startswith("throw "):
92
+ continue
93
+ fm = FIELD_RE.match(line)
94
+ if fm:
95
+ ftype = fm.group(1).strip()
96
+ fname = fm.group(2).strip()
97
+ if not ftype or not fname:
98
+ continue
99
+ if ftype.lower() in _DENY_TYPES:
100
+ continue
101
+ if fname.lower() in _DENY_NAMES:
102
+ continue
103
+ fields.append(ModelField(fname, ftype))
104
+
105
+ return [ModelDef(name, rel, "java", [], fields)]
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ def _possible_java_source_roots(project_root: Path) -> list[Path]:
7
+ out: list[Path] = []
8
+ seen: set[Path] = set()
9
+
10
+ def add(p: Path) -> None:
11
+ try:
12
+ pr = p.resolve()
13
+ except Exception:
14
+ return
15
+ if pr in seen:
16
+ return
17
+ if pr.exists() and pr.is_dir():
18
+ seen.add(pr)
19
+ out.append(pr)
20
+
21
+ add(project_root / "src" / "main" / "java")
22
+ add(project_root / "src" / "test" / "java")
23
+
24
+ for p in project_root.rglob("src"):
25
+ try:
26
+ if not p.is_dir():
27
+ continue
28
+ except Exception:
29
+ continue
30
+ add(p / "main" / "java")
31
+ add(p / "test" / "java")
32
+
33
+ add(project_root)
34
+
35
+ return out
36
+
37
+
38
+ def resolve_java_import_to_file(module: str, project_root: Path) -> Path | None:
39
+ if not module:
40
+ return None
41
+
42
+ rel = Path(*module.split("."))
43
+
44
+ for base in _possible_java_source_roots(project_root):
45
+ cand = (base / rel).with_suffix(".java")
46
+ if cand.exists() and cand.is_file():
47
+ return cand.resolve()
48
+
49
+ return None
File without changes
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from ...parser.imports import extract_imports_from_file
5
+
6
+
7
+ def py_extract_imports(path: Path):
8
+ return extract_imports_from_file(path)
@@ -0,0 +1,86 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ import ast
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class ModelField:
10
+ name: str
11
+ type: str
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class ModelDef:
16
+ name: str
17
+ file: str
18
+ kind: str
19
+ bases: list[str]
20
+ fields: list[ModelField]
21
+
22
+
23
+ def _unparse(node: ast.AST | None) -> str:
24
+ if node is None:
25
+ return "Any"
26
+ try:
27
+ return ast.unparse(node)
28
+ except Exception:
29
+ return node.__class__.__name__
30
+
31
+
32
+ def _base_names(cls: ast.ClassDef) -> list[str]:
33
+ return [_unparse(b) for b in cls.bases]
34
+
35
+
36
+ def _has_decorator(cls: ast.ClassDef, name: str) -> bool:
37
+ for d in cls.decorator_list:
38
+ if isinstance(d, ast.Name) and d.id == name:
39
+ return True
40
+ if isinstance(d, ast.Attribute) and d.attr == name:
41
+ return True
42
+ return False
43
+
44
+
45
+ def _looks_like_model(cls: ast.ClassDef) -> bool:
46
+ bases = _base_names(cls)
47
+ if _has_decorator(cls, "dataclass"):
48
+ return True
49
+ if any("BaseModel" in b for b in bases):
50
+ return True
51
+ if any(b.endswith("Base") for b in bases):
52
+ return True
53
+ return False
54
+
55
+
56
+ def extract_python_models(path: Path, project_root: Path) -> list[ModelDef]:
57
+ try:
58
+ tree = ast.parse(path.read_text(encoding="utf-8", errors="ignore"))
59
+ except Exception:
60
+ return []
61
+
62
+ rel = str(path.relative_to(project_root))
63
+ out: list[ModelDef] = []
64
+
65
+ for node in tree.body:
66
+ if not isinstance(node, ast.ClassDef):
67
+ continue
68
+ if not _looks_like_model(node):
69
+ continue
70
+
71
+ fields: list[ModelField] = []
72
+ for st in node.body:
73
+ if isinstance(st, ast.AnnAssign) and isinstance(st.target, ast.Name):
74
+ fields.append(ModelField(st.target.id, _unparse(st.annotation)))
75
+
76
+ out.append(
77
+ ModelDef(
78
+ name=node.name,
79
+ file=rel,
80
+ kind="python",
81
+ bases=_base_names(node),
82
+ fields=fields,
83
+ )
84
+ )
85
+
86
+ return out
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from .python.engine import py_extract_imports
6
+ from .java.engine import java_extract_imports
7
+
8
+ def extract_imports(path: Path, lang: str):
9
+ path = path.resolve()
10
+ if lang == "java" or path.suffix.lower() == ".java":
11
+ return java_extract_imports(path)
12
+ return py_extract_imports(path)
13
+
14
+ def extract_models_for_file(path: Path, project_root: Path):
15
+ path = path.resolve()
16
+ if path.suffix.lower() == ".py":
17
+ from .python.models import extract_python_models
18
+ return extract_python_models(path, project_root)
19
+
20
+ if path.suffix.lower() == ".java":
21
+ from .java.models import extract_java_models
22
+ return extract_java_models(path, project_root)
23
+
24
+ return []
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ import ast
4
+ from pathlib import Path
5
+
6
+
7
+ def extract_imports_from_code(code: str) -> list[tuple[str, int]]:
8
+ code = code.lstrip("\ufeff")
9
+ tree = ast.parse(code)
10
+
11
+ out: list[tuple[str, int]] = []
12
+
13
+ for node in ast.walk(tree):
14
+ if isinstance(node, ast.Import):
15
+ for n in node.names:
16
+ if n.name:
17
+ out.append((n.name, 0))
18
+ elif isinstance(node, ast.ImportFrom):
19
+ out.append((node.module or "", node.level))
20
+
21
+ return out
22
+
23
+
24
+ def extract_imports_from_file(path: Path) -> list[tuple[str, int]]:
25
+ text = path.read_text(encoding="utf-8-sig")
26
+ return extract_imports_from_code(text)
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+ from pathlib import Path
3
+
4
+
5
+ def resolve_module_to_file(module: str, root: Path) -> Path | None:
6
+ """
7
+ Absolute import resolution (backward compatible).
8
+ Example: 'codeintel_cli.graph.builder' -> root/codeintel_cli/graph/builder.py
9
+ """
10
+ if not module:
11
+ return None
12
+
13
+ cand = root / (module.replace(".", "/") + ".py")
14
+ if cand.exists():
15
+ return cand.resolve()
16
+
17
+ init = root / module.replace(".", "/") / "__init__.py"
18
+ if init.exists():
19
+ return init.resolve()
20
+
21
+ return None
22
+
23
+
24
+ def resolve_import_to_file(module: str, level: int, current_file: Path, root: Path) -> Path | None:
25
+ """
26
+ Supports absolute + relative imports.
27
+ - level=0: absolute import (uses resolve_module_to_file)
28
+ - level>0: relative import (from . / .. / ...)
29
+ """
30
+ # absolute
31
+ if level == 0:
32
+ return resolve_module_to_file(module, root)
33
+
34
+ # relative
35
+ base = current_file.parent
36
+ for _ in range(level - 1):
37
+ base = base.parent
38
+
39
+ target_base = base if module == "" else base / module.replace(".", "/")
40
+
41
+ py = target_base.with_suffix(".py")
42
+ if py.exists():
43
+ return py.resolve()
44
+
45
+ init = target_base / "__init__.py"
46
+ if init.exists():
47
+ return init.resolve()
48
+
49
+ return None
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ import ast
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class ClassInfo:
10
+ name: str
11
+ lineno: int
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class FuncInfo:
16
+ name: str
17
+ lineno: int
18
+ signature: str
19
+
20
+
21
+ def _format_args(a: ast.arguments) -> str:
22
+ parts: list[str] = []
23
+
24
+ if getattr(a, "posonlyargs", None):
25
+ for x in a.posonlyargs:
26
+ parts.append(x.arg)
27
+ parts.append("/")
28
+
29
+ for x in a.args:
30
+ parts.append(x.arg)
31
+
32
+ if a.vararg:
33
+ parts.append(f"*{a.vararg.arg}")
34
+ elif a.kwonlyargs:
35
+ parts.append("*")
36
+
37
+ for x in a.kwonlyargs:
38
+ parts.append(x.arg)
39
+
40
+ if a.kwarg:
41
+ parts.append(f"**{a.kwarg.arg}")
42
+
43
+ return ", ".join(parts)
44
+
45
+
46
+ def _read_source_text(path: Path) -> str:
47
+ try:
48
+ raw = path.read_bytes()
49
+ except Exception:
50
+ return ""
51
+
52
+ text = raw.decode("utf-8-sig", errors="ignore")
53
+
54
+ if "\x00" in text:
55
+ text = text.replace("\x00", "")
56
+ if text.startswith("\ufeff"):
57
+ text = text.lstrip("\ufeff")
58
+
59
+ return text
60
+
61
+
62
+ def extract_classes_from_file(path: Path) -> list[ClassInfo]:
63
+ try:
64
+ text = _read_source_text(path)
65
+ if not text.strip():
66
+ return []
67
+ tree = ast.parse(text)
68
+ except Exception:
69
+ return []
70
+
71
+ out: list[ClassInfo] = []
72
+ for node in tree.body:
73
+ if isinstance(node, ast.ClassDef):
74
+ out.append(ClassInfo(node.name, getattr(node, "lineno", 1)))
75
+ return out
76
+
77
+
78
+ def extract_funcs_from_file(path: Path) -> list[FuncInfo]:
79
+ try:
80
+ text = _read_source_text(path)
81
+ if not text.strip():
82
+ return []
83
+ tree = ast.parse(text)
84
+ except Exception:
85
+ return []
86
+
87
+ out: list[FuncInfo] = []
88
+ for node in tree.body:
89
+ if isinstance(node, ast.FunctionDef):
90
+ sig = f"{node.name}({_format_args(node.args)})"
91
+ out.append(FuncInfo(node.name, getattr(node, "lineno", 1), sig))
92
+ return out
File without changes
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from ..core.project import is_skipped_path
6
+
7
+
8
+ def find_source_files(root: Path, exts: tuple[str, ...]) -> list[Path]:
9
+ root = root.resolve()
10
+ results: list[Path] = []
11
+
12
+ for ext in exts:
13
+ for p in root.rglob(f"*{ext}"):
14
+ if not p.is_file():
15
+ continue
16
+ if is_skipped_path(p):
17
+ continue
18
+ results.append(p.resolve())
19
+
20
+ return sorted(set(results))
21
+
22
+
23
+ def find_python_files(root: Path) -> list[Path]:
24
+ return find_source_files(root, (".py",))
25
+
26
+
27
+ def find_java_files(root: Path) -> list[Path]:
28
+ return find_source_files(root, (".java",))
29
+
30
+
31
+ def find_all_supported_files(root: Path) -> list[Path]:
32
+ root = root.resolve()
33
+ results: list[Path] = []
34
+ for p in root.rglob("*"):
35
+ if not p.is_file():
36
+ continue
37
+ if is_skipped_path(p):
38
+ continue
39
+ if p.suffix.lower() in {".py", ".java"}:
40
+ results.append(p.resolve())
41
+ return sorted(set(results))