aja-codeintel 0.1.6__tar.gz → 0.1.8__tar.gz
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.
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/PKG-INFO +1 -1
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/aja_codeintel.egg-info/PKG-INFO +1 -1
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/aja_codeintel.egg-info/SOURCES.txt +13 -1
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/__init__.py +36 -35
- aja_codeintel-0.1.8/codeintel_cli/commands/project/debug_cmd.py +51 -0
- aja_codeintel-0.1.8/codeintel_cli/commands/project/errors_cmd.py +222 -0
- aja_codeintel-0.1.8/codeintel_cli/commands/project/models_cmd.py +192 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/modeltree_cmd.py +213 -213
- aja_codeintel-0.1.8/codeintel_cli/commands/project/overview_cmd.py +689 -0
- aja_codeintel-0.1.8/codeintel_cli/commands/project/scan_cmd.py +95 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/servicemap_cmd.py +54 -16
- aja_codeintel-0.1.8/codeintel_cli/commands/project/types_cmd.py +134 -0
- aja_codeintel-0.1.8/codeintel_cli/context/java_service.py +471 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/core/resolve_model_target.py +133 -133
- aja_codeintel-0.1.8/codeintel_cli/endpoints/java_spring.py +472 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/frontend/server.py +37 -64
- aja_codeintel-0.1.8/codeintel_cli/lang/java/ast_engine.py +442 -0
- aja_codeintel-0.1.8/codeintel_cli/lang/java/auth.py +50 -0
- aja_codeintel-0.1.8/codeintel_cli/lang/java/base_path.py +79 -0
- aja_codeintel-0.1.8/codeintel_cli/lang/java/call_graph.py +25 -0
- aja_codeintel-0.1.8/codeintel_cli/lang/java/enums.py +54 -0
- aja_codeintel-0.1.8/codeintel_cli/lang/java/method_index.py +82 -0
- aja_codeintel-0.1.8/codeintel_cli/lang/java/models.py +114 -0
- aja_codeintel-0.1.8/codeintel_cli/lang/java/pagination.py +60 -0
- aja_codeintel-0.1.8/codeintel_cli/lang/java/types.py +294 -0
- aja_codeintel-0.1.8/codeintel_cli/lang/java/validation.py +78 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/scanner/scanner.py +49 -49
- aja_codeintel-0.1.8/codeintel_cli/terminal/__init__.py +0 -0
- aja_codeintel-0.1.8/codeintel_cli/terminal/error_parser.py +39 -0
- aja_codeintel-0.1.8/codeintel_cli/terminal/printer.py +46 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/pyproject.toml +1 -1
- aja_codeintel-0.1.6/codeintel_cli/commands/project/errors_cmd.py +0 -238
- aja_codeintel-0.1.6/codeintel_cli/commands/project/models_cmd.py +0 -151
- aja_codeintel-0.1.6/codeintel_cli/commands/project/overview_cmd.py +0 -252
- aja_codeintel-0.1.6/codeintel_cli/commands/project/scan_cmd.py +0 -46
- aja_codeintel-0.1.6/codeintel_cli/commands/project/types_cmd.py +0 -424
- aja_codeintel-0.1.6/codeintel_cli/context/java_service.py +0 -226
- aja_codeintel-0.1.6/codeintel_cli/endpoints/java_spring.py +0 -221
- aja_codeintel-0.1.6/codeintel_cli/lang/java/ast_engine.py +0 -98
- aja_codeintel-0.1.6/codeintel_cli/lang/java/models.py +0 -258
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/LICENSE +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/README.md +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/aja_codeintel.egg-info/dependency_links.txt +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/aja_codeintel.egg-info/entry_points.txt +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/aja_codeintel.egg-info/requires.txt +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/aja_codeintel.egg-info/top_level.txt +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/__init__.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/__main__.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/cli.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/__init__.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/graph/__init__.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/graph/deps_cmd.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/graph/related_cmd.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/graph/relsymbols_cmd.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/graph/reverse_related_cmd.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/nav/__init__.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/nav/copy_cmd.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/nav/open_cmd.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/nav/where_cmd.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/context_cmd.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/endpoints_cmd.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/folder_cmd.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/frontend_cmd.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/imports_cmd.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/new.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/resolve_cmd.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/tree_cmd.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/commands/project/version_cmd.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/context/java_context.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/context/java_rel.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/context/python_context.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/context/python_rel.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/context/python_service.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/core/fuzzy.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/core/opener.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/core/project.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/core/resolve_folder.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/core/resolve_target.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/core/timing.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/core/where.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/db/__init__.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/db/cache.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/db/operations.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/db/schema.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/endpoints/__init__.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/endpoints/models.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/endpoints/openapi_spec.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/endpoints/python_web.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/endpoints/scan.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/errors.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/frontend/__init__.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/graph/__init__.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/graph/builder.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/graph/query.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/graph/traverse.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/lang/__init__.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/lang/java/__init__.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/lang/java/engine.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/lang/java/resolve.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/lang/python/__init__.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/lang/python/engine.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/lang/python/models.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/lang/router.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/parser/imports.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/parser/resolve.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/parser/symbols.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/codeintel_cli/scanner/__init__.py +0 -0
- {aja_codeintel-0.1.6 → aja_codeintel-0.1.8}/setup.cfg +0 -0
|
@@ -23,6 +23,7 @@ codeintel_cli/commands/nav/open_cmd.py
|
|
|
23
23
|
codeintel_cli/commands/nav/where_cmd.py
|
|
24
24
|
codeintel_cli/commands/project/__init__.py
|
|
25
25
|
codeintel_cli/commands/project/context_cmd.py
|
|
26
|
+
codeintel_cli/commands/project/debug_cmd.py
|
|
26
27
|
codeintel_cli/commands/project/endpoints_cmd.py
|
|
27
28
|
codeintel_cli/commands/project/errors_cmd.py
|
|
28
29
|
codeintel_cli/commands/project/folder_cmd.py
|
|
@@ -72,9 +73,17 @@ codeintel_cli/lang/__init__.py
|
|
|
72
73
|
codeintel_cli/lang/router.py
|
|
73
74
|
codeintel_cli/lang/java/__init__.py
|
|
74
75
|
codeintel_cli/lang/java/ast_engine.py
|
|
76
|
+
codeintel_cli/lang/java/auth.py
|
|
77
|
+
codeintel_cli/lang/java/base_path.py
|
|
78
|
+
codeintel_cli/lang/java/call_graph.py
|
|
75
79
|
codeintel_cli/lang/java/engine.py
|
|
80
|
+
codeintel_cli/lang/java/enums.py
|
|
81
|
+
codeintel_cli/lang/java/method_index.py
|
|
76
82
|
codeintel_cli/lang/java/models.py
|
|
83
|
+
codeintel_cli/lang/java/pagination.py
|
|
77
84
|
codeintel_cli/lang/java/resolve.py
|
|
85
|
+
codeintel_cli/lang/java/types.py
|
|
86
|
+
codeintel_cli/lang/java/validation.py
|
|
78
87
|
codeintel_cli/lang/python/__init__.py
|
|
79
88
|
codeintel_cli/lang/python/engine.py
|
|
80
89
|
codeintel_cli/lang/python/models.py
|
|
@@ -82,4 +91,7 @@ codeintel_cli/parser/imports.py
|
|
|
82
91
|
codeintel_cli/parser/resolve.py
|
|
83
92
|
codeintel_cli/parser/symbols.py
|
|
84
93
|
codeintel_cli/scanner/__init__.py
|
|
85
|
-
codeintel_cli/scanner/scanner.py
|
|
94
|
+
codeintel_cli/scanner/scanner.py
|
|
95
|
+
codeintel_cli/terminal/__init__.py
|
|
96
|
+
codeintel_cli/terminal/error_parser.py
|
|
97
|
+
codeintel_cli/terminal/printer.py
|
|
@@ -1,37 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from .
|
|
8
|
-
from .
|
|
9
|
-
from .
|
|
10
|
-
from .
|
|
11
|
-
from .
|
|
12
|
-
from .
|
|
13
|
-
from .
|
|
14
|
-
from .
|
|
15
|
-
from .
|
|
16
|
-
from .
|
|
17
|
-
from .
|
|
18
|
-
from .
|
|
19
|
-
from .version_cmd import register_version
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import typer
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def register_project_commands(app: typer.Typer) -> None:
|
|
6
|
+
from .overview_cmd import register_overview
|
|
7
|
+
from .models_cmd import register_models
|
|
8
|
+
from .endpoints_cmd import register_endpoints
|
|
9
|
+
from .types_cmd import register_types_command
|
|
10
|
+
from .scan_cmd import register_scan
|
|
11
|
+
from .context_cmd import register_context
|
|
12
|
+
from .tree_cmd import register_tree
|
|
13
|
+
from .imports_cmd import register_imports
|
|
14
|
+
from .folder_cmd import register_folder
|
|
15
|
+
from .resolve_cmd import register_resolve
|
|
16
|
+
from .servicemap_cmd import register_servicemap
|
|
17
|
+
from .modeltree_cmd import register_modeltree
|
|
18
|
+
from .version_cmd import register_version
|
|
20
19
|
from .frontend_cmd import register_frontend
|
|
21
|
-
from .errors_cmd import register_errors
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
20
|
+
from .errors_cmd import register_errors
|
|
21
|
+
from .debug_cmd import register_debug
|
|
22
|
+
|
|
23
|
+
register_overview(app)
|
|
24
|
+
register_models(app)
|
|
25
|
+
register_endpoints(app)
|
|
26
|
+
register_types_command(app)
|
|
27
|
+
register_scan(app)
|
|
28
|
+
register_context(app)
|
|
29
|
+
register_tree(app)
|
|
30
|
+
register_imports(app)
|
|
31
|
+
register_folder(app)
|
|
32
|
+
register_resolve(app)
|
|
33
|
+
register_servicemap(app)
|
|
34
|
+
register_modeltree(app)
|
|
35
|
+
register_version(app)
|
|
36
36
|
register_frontend(app)
|
|
37
|
-
register_errors(app)
|
|
37
|
+
register_errors(app)
|
|
38
|
+
register_debug(app)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import time
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import typer
|
|
5
|
+
from ...core.project import find_project_root
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def register_debug(app: typer.Typer) -> None:
|
|
9
|
+
@app.command("debug", help="Show exact error code blocks + call graph (callers/callees).")
|
|
10
|
+
def debug(
|
|
11
|
+
path: str = typer.Argument(".", help="Project root"),
|
|
12
|
+
skip_compile: bool = typer.Option(False, "--skip-compile"),
|
|
13
|
+
verbose: bool = typer.Option(False, "--verbose", "-v"),
|
|
14
|
+
) -> None:
|
|
15
|
+
t0 = time.perf_counter()
|
|
16
|
+
project_root = find_project_root(Path(path).resolve())
|
|
17
|
+
|
|
18
|
+
from ...commands.project.errors_cmd import _run_mvn_compile
|
|
19
|
+
from ...lang.java.method_index import load_index, build_index, save_index, find_method_at_line
|
|
20
|
+
from ...lang.java.call_graph import build_call_graph, get_related
|
|
21
|
+
from ...terminal.printer import print_block
|
|
22
|
+
from ...terminal.error_parser import JavaError
|
|
23
|
+
|
|
24
|
+
compile_errors = []
|
|
25
|
+
if not skip_compile:
|
|
26
|
+
typer.echo("Running mvn compile...")
|
|
27
|
+
compile_errors, _, _ = _run_mvn_compile(project_root)
|
|
28
|
+
|
|
29
|
+
if not compile_errors:
|
|
30
|
+
typer.echo(typer.style("? No compile errors found.", fg=typer.colors.GREEN))
|
|
31
|
+
raise typer.Exit()
|
|
32
|
+
|
|
33
|
+
typer.echo(f"Found {len(compile_errors)} error(s). Building index + call graph...")
|
|
34
|
+
|
|
35
|
+
index = load_index(project_root)
|
|
36
|
+
if not index:
|
|
37
|
+
index = build_index(project_root)
|
|
38
|
+
save_index(project_root, index)
|
|
39
|
+
|
|
40
|
+
G = build_call_graph(index)
|
|
41
|
+
|
|
42
|
+
for e in compile_errors:
|
|
43
|
+
err = JavaError(file=e["file"], line=int(e["line"]), message=e["msg"])
|
|
44
|
+
method = find_method_at_line(index, e["file"], int(e["line"]))
|
|
45
|
+
if not method:
|
|
46
|
+
typer.echo(typer.style(f"\n[!] {e['file']}:{e['line']} — method not in index", fg=typer.colors.YELLOW))
|
|
47
|
+
continue
|
|
48
|
+
related = get_related(G, method["class"], method["method"])
|
|
49
|
+
print_block(err, method, project_root, related["callers"], related["callees"])
|
|
50
|
+
|
|
51
|
+
typer.echo(f"\nFinished in {time.perf_counter() - t0:.3f}s")
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import re
|
|
3
|
+
import subprocess
|
|
4
|
+
import time
|
|
5
|
+
import threading
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
import typer
|
|
8
|
+
from ...core.project import find_project_root
|
|
9
|
+
from ...scanner.scanner import find_all_supported_files
|
|
10
|
+
from ...db.cache import CacheManager
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _find_maven_cmd(root: Path) -> str:
|
|
14
|
+
for name in ["mvnw.cmd", "mvnw"]:
|
|
15
|
+
if (root / name).exists():
|
|
16
|
+
return str(root / name)
|
|
17
|
+
return "mvn"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _changed_files(project_root: Path) -> list[Path]:
|
|
21
|
+
try:
|
|
22
|
+
cache = CacheManager(project_root)
|
|
23
|
+
cached = {Path(p) for p in cache.get_cached_files()}
|
|
24
|
+
changed = []
|
|
25
|
+
for p in cached:
|
|
26
|
+
if not p.exists():
|
|
27
|
+
continue
|
|
28
|
+
db_mtime = cache.get_file_mtime(p)
|
|
29
|
+
if db_mtime is None or p.stat().st_mtime > db_mtime:
|
|
30
|
+
changed.append(p)
|
|
31
|
+
return changed
|
|
32
|
+
except Exception:
|
|
33
|
+
return []
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
_MVN_ERROR_RE = re.compile(r'^\[ERROR\]\s+(.+?\.java):\[(\d+),\d+\]\s+(.+)$')
|
|
37
|
+
_MVN_WARNING_RE = re.compile(r'^\[WARNING\]\s+(.+?\.java):\[(\d+),\d+\]\s+(.+)$')
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _run_mvn_compile(project_root: Path) -> tuple[list[dict], list[dict], str]:
|
|
41
|
+
mvn = _find_maven_cmd(project_root)
|
|
42
|
+
done = threading.Event()
|
|
43
|
+
|
|
44
|
+
def spinner():
|
|
45
|
+
chars = ["|", "/", "-", "\\"]
|
|
46
|
+
i = 0
|
|
47
|
+
start = time.perf_counter()
|
|
48
|
+
while not done.is_set():
|
|
49
|
+
elapsed = time.perf_counter() - start
|
|
50
|
+
typer.echo(f"\r {chars[i % 4]} mvn compile running... {elapsed:.0f}s", nl=False)
|
|
51
|
+
i += 1
|
|
52
|
+
time.sleep(0.2)
|
|
53
|
+
typer.echo("\r" + " " * 40 + "\r", nl=False)
|
|
54
|
+
|
|
55
|
+
t = threading.Thread(target=spinner, daemon=True)
|
|
56
|
+
t.start()
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
result = subprocess.run(
|
|
60
|
+
f'"{mvn}" compile -q --batch-mode',
|
|
61
|
+
cwd=str(project_root),
|
|
62
|
+
capture_output=True, text=True, shell=True, timeout=300,
|
|
63
|
+
)
|
|
64
|
+
output = result.stdout + result.stderr
|
|
65
|
+
except subprocess.TimeoutExpired:
|
|
66
|
+
done.set()
|
|
67
|
+
return [], [{"file": "?", "line": "?", "msg": "mvn compile timed out"}], ""
|
|
68
|
+
except Exception as e:
|
|
69
|
+
done.set()
|
|
70
|
+
return [], [{"file": "?", "line": "?", "msg": str(e)}], ""
|
|
71
|
+
finally:
|
|
72
|
+
done.set()
|
|
73
|
+
t.join()
|
|
74
|
+
|
|
75
|
+
errors, warnings = [], []
|
|
76
|
+
seen_e, seen_w = set(), set()
|
|
77
|
+
for line in output.splitlines():
|
|
78
|
+
m = _MVN_ERROR_RE.match(line.strip())
|
|
79
|
+
if m:
|
|
80
|
+
k = (Path(m.group(1)).name, m.group(2))
|
|
81
|
+
if k not in seen_e:
|
|
82
|
+
seen_e.add(k)
|
|
83
|
+
errors.append({"file": Path(m.group(1)).name, "line": m.group(2), "msg": m.group(3).strip()})
|
|
84
|
+
continue
|
|
85
|
+
m = _MVN_WARNING_RE.match(line.strip())
|
|
86
|
+
if m:
|
|
87
|
+
k = (Path(m.group(1)).name, m.group(2))
|
|
88
|
+
if k not in seen_w:
|
|
89
|
+
seen_w.add(k)
|
|
90
|
+
warnings.append({"file": Path(m.group(1)).name, "line": m.group(2), "msg": m.group(3).strip()})
|
|
91
|
+
return errors, warnings, output
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
_STATIC_CHECKS = [
|
|
95
|
+
(re.compile(r'catch\s*\([^)]+\)\s*\{\s*\}'), "Empty catch block", "warning"),
|
|
96
|
+
(re.compile(r'\.get\(\)\s*\.'), "Possible null dereference after .get()", "warning"),
|
|
97
|
+
(re.compile(r'System\.out\.print'), "Debug System.out.print left in code", "warning"),
|
|
98
|
+
(re.compile(r'//\s*(TODO|FIXME|HACK|XXX)\b'), "Unresolved TODO/FIXME", "info"),
|
|
99
|
+
(re.compile(r'catch\s*\(\s*Exception\s+\w+\s*\)'), "Catching raw Exception", "warning"),
|
|
100
|
+
(re.compile(r'==\s*"[^"]*"'), 'String compared with == instead of .equals()', "error"),
|
|
101
|
+
(re.compile(r'static\s+(?!final)\w+\s+\w+\s*='), "Mutable static field", "warning"),
|
|
102
|
+
(re.compile(r'throw\s+new\s+NullPointerException\s*\(\s*\)'), "NullPointerException without message", "warning"),
|
|
103
|
+
]
|
|
104
|
+
_SKIP_DIRS = {"target", "build", ".git", "test", "tests"}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _static_analyze(files: list[Path]) -> list[dict]:
|
|
108
|
+
issues = []
|
|
109
|
+
for f in files:
|
|
110
|
+
if any(part in _SKIP_DIRS for part in f.parts):
|
|
111
|
+
continue
|
|
112
|
+
try:
|
|
113
|
+
lines = f.read_text(encoding="utf-8", errors="ignore").splitlines()
|
|
114
|
+
except Exception:
|
|
115
|
+
continue
|
|
116
|
+
for lineno, line in enumerate(lines, 1):
|
|
117
|
+
for pattern, msg, severity in _STATIC_CHECKS:
|
|
118
|
+
if pattern.search(line):
|
|
119
|
+
issues.append({"file": f.name, "line": str(lineno), "msg": msg, "severity": severity})
|
|
120
|
+
break
|
|
121
|
+
return issues
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
_SEV_ORDER = {"error": 0, "warning": 1, "info": 2}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _render(compile_errors, compile_warnings, static_issues) -> list[str]:
|
|
128
|
+
out = []
|
|
129
|
+
if not compile_errors and not compile_warnings and not static_issues:
|
|
130
|
+
return ["No issues found."]
|
|
131
|
+
if compile_errors:
|
|
132
|
+
out.append(f"COMPILE ERRORS ({len(compile_errors)})")
|
|
133
|
+
out.append("-" * 60)
|
|
134
|
+
for e in compile_errors:
|
|
135
|
+
out.append(f" {e['file']:<30} line {e['line']:<6} {e['msg']}")
|
|
136
|
+
out.append("")
|
|
137
|
+
if compile_warnings:
|
|
138
|
+
out.append(f"COMPILE WARNINGS ({len(compile_warnings)})")
|
|
139
|
+
out.append("-" * 60)
|
|
140
|
+
for w in compile_warnings:
|
|
141
|
+
out.append(f" {w['file']:<30} line {w['line']:<6} {w['msg']}")
|
|
142
|
+
out.append("")
|
|
143
|
+
if static_issues:
|
|
144
|
+
static_issues.sort(key=lambda x: (_SEV_ORDER.get(x["severity"], 9), x["file"], int(x["line"])))
|
|
145
|
+
by_sev: dict[str, list] = {}
|
|
146
|
+
for i in static_issues:
|
|
147
|
+
by_sev.setdefault(i["severity"], []).append(i)
|
|
148
|
+
labels = {"error": "STATIC ERRORS", "warning": "STATIC WARNINGS", "info": "STATIC INFO"}
|
|
149
|
+
for sev in ("error", "warning", "info"):
|
|
150
|
+
group = by_sev.get(sev, [])
|
|
151
|
+
if group:
|
|
152
|
+
out.append(f"{labels[sev]} ({len(group)})")
|
|
153
|
+
out.append("-" * 60)
|
|
154
|
+
for i in group:
|
|
155
|
+
out.append(f" {i['file']:<30} line {i['line']:<6} {i['msg']}")
|
|
156
|
+
out.append("")
|
|
157
|
+
out.append("-" * 60)
|
|
158
|
+
out.append(f" Total: {len(compile_errors)} compile error(s), {len(compile_warnings)} warning(s), {len(static_issues)} static issue(s)")
|
|
159
|
+
return out
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def register_errors(app: typer.Typer) -> None:
|
|
163
|
+
@app.command("errors", help="Static analysis (instant). Add --compile to also run mvn compile.")
|
|
164
|
+
def errors(
|
|
165
|
+
path: str = typer.Argument(".", help="Project root"),
|
|
166
|
+
changed_only: bool = typer.Option(False, "--changed-only", "-c"),
|
|
167
|
+
compile: bool = typer.Option(False, "--compile", help="Also run mvn compile"),
|
|
168
|
+
skip_static: bool = typer.Option(False, "--skip-static"),
|
|
169
|
+
verbose: bool = typer.Option(False, "--verbose", "-v"),
|
|
170
|
+
block: bool = typer.Option(False, "--block", "-b", help="Show exact method code block for each compile error"),
|
|
171
|
+
) -> None:
|
|
172
|
+
t0 = time.perf_counter()
|
|
173
|
+
project_root = find_project_root(Path(path).resolve())
|
|
174
|
+
|
|
175
|
+
if changed_only:
|
|
176
|
+
java_files = _changed_files(project_root)
|
|
177
|
+
typer.echo(f"Scanning {len(java_files)} changed file(s)...")
|
|
178
|
+
else:
|
|
179
|
+
java_files = [f for f in find_all_supported_files(project_root) if f.suffix.lower() == ".java"]
|
|
180
|
+
typer.echo(f"Scanning {len(java_files)} Java file(s)...")
|
|
181
|
+
typer.echo("")
|
|
182
|
+
|
|
183
|
+
compile_errors, compile_warnings, raw_output = [], [], ""
|
|
184
|
+
|
|
185
|
+
if compile or block:
|
|
186
|
+
compile_errors, compile_warnings, raw_output = _run_mvn_compile(project_root)
|
|
187
|
+
|
|
188
|
+
static_issues = []
|
|
189
|
+
if not skip_static:
|
|
190
|
+
static_issues = _static_analyze(java_files)
|
|
191
|
+
if not verbose:
|
|
192
|
+
static_issues = [i for i in static_issues if i["severity"] != "info"]
|
|
193
|
+
|
|
194
|
+
for line in _render(compile_errors, compile_warnings, static_issues):
|
|
195
|
+
typer.echo(line)
|
|
196
|
+
|
|
197
|
+
if block and compile_errors:
|
|
198
|
+
_show_blocks(project_root, compile_errors)
|
|
199
|
+
elif block and not compile_errors:
|
|
200
|
+
typer.echo("No compile errors to show blocks for.")
|
|
201
|
+
|
|
202
|
+
typer.echo(f"\nFinished in {time.perf_counter() - t0:.3f}s")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _show_blocks(project_root: Path, compile_errors: list[dict]) -> None:
|
|
206
|
+
from ...lang.java.method_index import load_index, build_index, save_index, find_method_at_line
|
|
207
|
+
from ...terminal.printer import print_block
|
|
208
|
+
from ...terminal.error_parser import JavaError
|
|
209
|
+
|
|
210
|
+
index = load_index(project_root)
|
|
211
|
+
if not index:
|
|
212
|
+
typer.echo("\nBuilding method index...")
|
|
213
|
+
index = build_index(project_root)
|
|
214
|
+
save_index(project_root, index)
|
|
215
|
+
|
|
216
|
+
for e in compile_errors:
|
|
217
|
+
err = JavaError(file=e["file"], line=int(e["line"]), message=e["msg"])
|
|
218
|
+
method = find_method_at_line(index, e["file"], int(e["line"]))
|
|
219
|
+
if method:
|
|
220
|
+
print_block(err, method, project_root)
|
|
221
|
+
else:
|
|
222
|
+
typer.echo(f"\n[!] {e['file']}:{e['line']} - method not in index")
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from ...core.project import find_project_root
|
|
7
|
+
from ...scanner.scanner import find_all_supported_files
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
MODEL_DIRS = {
|
|
11
|
+
"model", "models",
|
|
12
|
+
"entity", "entities",
|
|
13
|
+
"domain", "domains",
|
|
14
|
+
"core",
|
|
15
|
+
"aggregate", "aggregates",
|
|
16
|
+
"persistence",
|
|
17
|
+
"db", "data", "schema",
|
|
18
|
+
"pojo", "bean", "beans",
|
|
19
|
+
"orm", "schemas",
|
|
20
|
+
"datamodel", "datamodels",
|
|
21
|
+
"table", "tables",
|
|
22
|
+
"record", "records",
|
|
23
|
+
"document", "documents",
|
|
24
|
+
"dao",
|
|
25
|
+
# petclinic-style: models live in feature packages
|
|
26
|
+
"owner", "vet", "pet", "visit",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
SKIP_DIRS = {
|
|
30
|
+
"test", "tests",
|
|
31
|
+
"controller", "controllers",
|
|
32
|
+
"service", "services",
|
|
33
|
+
"repository", "repositories",
|
|
34
|
+
"config", "configuration",
|
|
35
|
+
"util", "utils", "helper", "helpers",
|
|
36
|
+
"exception", "exceptions",
|
|
37
|
+
"security",
|
|
38
|
+
"mapper", "mappers",
|
|
39
|
+
"migration", "migrations",
|
|
40
|
+
"dto",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
SKIP_SUFFIXES = (
|
|
44
|
+
"Repository", "Mapper", "Dao",
|
|
45
|
+
"Service", "Controller",
|
|
46
|
+
"Config", "Configuration",
|
|
47
|
+
"Util", "Utils", "Helper",
|
|
48
|
+
"Exception", "Handler",
|
|
49
|
+
"Interceptor", "Filter",
|
|
50
|
+
"Listener", "Scheduler",
|
|
51
|
+
"Validator", "Converter",
|
|
52
|
+
"Serializer", "Deserializer",
|
|
53
|
+
"Factory", "Builder",
|
|
54
|
+
"Specification",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Annotations that definitively mark a class as a domain model
|
|
58
|
+
_ENTITY_ANNOTATIONS = {
|
|
59
|
+
"Entity", "MappedSuperclass", "Embeddable",
|
|
60
|
+
"Table", "Document",
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _import_extractors():
|
|
65
|
+
py = None
|
|
66
|
+
jv = None
|
|
67
|
+
try:
|
|
68
|
+
from ...lang.python.models import extract_python_models as _py
|
|
69
|
+
py = _py
|
|
70
|
+
except Exception:
|
|
71
|
+
pass
|
|
72
|
+
try:
|
|
73
|
+
from ...context.python_context import extract_python_models as _py
|
|
74
|
+
py = _py
|
|
75
|
+
except Exception:
|
|
76
|
+
pass
|
|
77
|
+
try:
|
|
78
|
+
from ...lang.java.models import extract_java_models as _jv
|
|
79
|
+
jv = _jv
|
|
80
|
+
except Exception:
|
|
81
|
+
pass
|
|
82
|
+
try:
|
|
83
|
+
from ...context.java_context import extract_java_models as _jv
|
|
84
|
+
jv = _jv
|
|
85
|
+
except Exception:
|
|
86
|
+
pass
|
|
87
|
+
return py, jv
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _is_model_path(rel: Path) -> bool:
|
|
91
|
+
parts = [p.lower() for p in rel.parts]
|
|
92
|
+
if "src" in parts and "test" in parts:
|
|
93
|
+
return False
|
|
94
|
+
if any(part in SKIP_DIRS for part in parts):
|
|
95
|
+
return False
|
|
96
|
+
stem = rel.stem
|
|
97
|
+
if any(stem.endswith(s) for s in SKIP_SUFFIXES):
|
|
98
|
+
return False
|
|
99
|
+
return any(part in MODEL_DIRS for part in parts)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _has_entity_annotation(path: Path) -> bool:
|
|
103
|
+
"""Fast check: does this .java file contain a JPA/document annotation?"""
|
|
104
|
+
try:
|
|
105
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
106
|
+
for ann in _ENTITY_ANNOTATIONS:
|
|
107
|
+
if f"@{ann}" in text:
|
|
108
|
+
return True
|
|
109
|
+
except Exception:
|
|
110
|
+
pass
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def register_models(app: typer.Typer) -> None:
|
|
115
|
+
@app.command(help="List domain models.")
|
|
116
|
+
def models(
|
|
117
|
+
root: str = typer.Argument(".", help="Project root folder"),
|
|
118
|
+
all_files: bool = typer.Option(False, "--all", help="Scan all Java files (not just model dirs)"),
|
|
119
|
+
with_inherited: bool = typer.Option(True, "--inherited/--no-inherited", help="Merge inherited fields"),
|
|
120
|
+
):
|
|
121
|
+
root_path = Path(root).resolve()
|
|
122
|
+
project_root = find_project_root(root_path)
|
|
123
|
+
extract_python_models, extract_java_models = _import_extractors()
|
|
124
|
+
files = find_all_supported_files(project_root)
|
|
125
|
+
|
|
126
|
+
pr = project_root.resolve()
|
|
127
|
+
candidates: list[Path] = []
|
|
128
|
+
|
|
129
|
+
for f in files:
|
|
130
|
+
fr = f.resolve()
|
|
131
|
+
try:
|
|
132
|
+
rel = fr.relative_to(pr)
|
|
133
|
+
except Exception:
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
ext = f.suffix.lower()
|
|
137
|
+
|
|
138
|
+
if ext == ".py":
|
|
139
|
+
if _is_model_path(rel):
|
|
140
|
+
candidates.append(fr)
|
|
141
|
+
elif ext == ".java":
|
|
142
|
+
# 1. Directory-based heuristic
|
|
143
|
+
if all_files or _is_model_path(rel):
|
|
144
|
+
candidates.append(fr)
|
|
145
|
+
# 2. Annotation-based fallback — catches petclinic-style feature packages
|
|
146
|
+
elif _has_entity_annotation(fr):
|
|
147
|
+
candidates.append(fr)
|
|
148
|
+
|
|
149
|
+
# --- collect all java models first for inheritance resolution ---
|
|
150
|
+
from ...lang.java.models import resolve_inherited_fields, ModelDef
|
|
151
|
+
|
|
152
|
+
java_models_all: list[ModelDef] = []
|
|
153
|
+
java_file_map: dict[str, list[ModelDef]] = {}
|
|
154
|
+
|
|
155
|
+
if extract_java_models:
|
|
156
|
+
for f in sorted(candidates, key=lambda x: str(x).lower()):
|
|
157
|
+
if f.suffix.lower() == ".java":
|
|
158
|
+
defs = extract_java_models(f, pr)
|
|
159
|
+
java_file_map[str(f)] = defs
|
|
160
|
+
java_models_all.extend(defs)
|
|
161
|
+
|
|
162
|
+
if with_inherited:
|
|
163
|
+
java_models_all = resolve_inherited_fields(java_models_all)
|
|
164
|
+
# rebuild map with resolved models
|
|
165
|
+
resolved_by_name = {m.name: m for m in java_models_all}
|
|
166
|
+
for k in java_file_map:
|
|
167
|
+
java_file_map[k] = [
|
|
168
|
+
resolved_by_name.get(m.name, m)
|
|
169
|
+
for m in java_file_map[k]
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
out: list[str] = []
|
|
173
|
+
for f in sorted(candidates, key=lambda x: str(x).lower()):
|
|
174
|
+
ext = f.suffix.lower()
|
|
175
|
+
if ext == ".py" and extract_python_models is not None:
|
|
176
|
+
defs = extract_python_models(f, project_root)
|
|
177
|
+
elif ext == ".java":
|
|
178
|
+
defs = java_file_map.get(str(f), [])
|
|
179
|
+
else:
|
|
180
|
+
continue
|
|
181
|
+
for d in defs:
|
|
182
|
+
inherited_marker = f" [extends: {', '.join(d.bases)}]" if d.bases else ""
|
|
183
|
+
out.append(f"{d.name} ({d.kind}){inherited_marker} {d.file}")
|
|
184
|
+
for fld in d.fields:
|
|
185
|
+
out.append(f" - {fld.name}: {fld.type}")
|
|
186
|
+
|
|
187
|
+
if not out:
|
|
188
|
+
typer.echo("No domain models found.")
|
|
189
|
+
raise typer.Exit(0)
|
|
190
|
+
|
|
191
|
+
for line in out:
|
|
192
|
+
typer.echo(line)
|