graphlint 0.2.0__tar.gz → 0.3.0__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.
- {graphlint-0.2.0 → graphlint-0.3.0}/CHANGELOG.md +11 -0
- {graphlint-0.2.0/graphlint.egg-info → graphlint-0.3.0}/PKG-INFO +35 -9
- {graphlint-0.2.0 → graphlint-0.3.0}/README.md +31 -8
- {graphlint-0.2.0 → graphlint-0.3.0}/README_PYPI.md +31 -8
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/en/architecture/overview.md +3 -3
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/en/cli/usage.md +1 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/en/guide/configuration.md +36 -12
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/en/guide/entry-detection.md +126 -10
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/en/guide/getting-started.md +7 -1
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/zh/README.md +30 -7
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/zh/architecture/overview.md +4 -4
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/zh/cli/usage.md +1 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/zh/guide/configuration.md +36 -12
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/zh/guide/entry-detection.md +126 -10
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/zh/guide/getting-started.md +7 -1
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/__init__.py +1 -1
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/analyzer/_graph_algo.py +37 -22
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/analyzer/graph.py +15 -7
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/analyzer/language/python/__init__.py +5 -1
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/analyzer/language/python/visitor.py +28 -6
- graphlint-0.3.0/graphlint/analyzer/language/rust/__init__.py +72 -0
- graphlint-0.3.0/graphlint/analyzer/language/rust/constants.py +258 -0
- graphlint-0.3.0/graphlint/analyzer/language/rust/entry.py +332 -0
- graphlint-0.3.0/graphlint/analyzer/language/rust/imports.py +171 -0
- graphlint-0.3.0/graphlint/analyzer/language/rust/parser.py +191 -0
- graphlint-0.3.0/graphlint/analyzer/language/rust/visitor.py +871 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/api.py +55 -8
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/config/defaults.py +65 -1
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/i18n/en.py +1 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/i18n/zh_CN.py +1 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/incremental/indexer.py +43 -6
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/params.py +9 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/storage/hashing.py +4 -1
- {graphlint-0.2.0 → graphlint-0.3.0/graphlint.egg-info}/PKG-INFO +35 -9
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint.egg-info/SOURCES.txt +6 -0
- graphlint-0.3.0/graphlint.egg-info/requires.txt +8 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/pyproject.toml +5 -1
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_auto_build.py +1 -1
- graphlint-0.2.0/graphlint.egg-info/requires.txt +0 -4
- {graphlint-0.2.0 → graphlint-0.3.0}/CONTRIBUTING.md +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/LICENSE +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/MANIFEST.in +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/en/api/build.md +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/en/api/configure.md +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/en/api/exceptions.md +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/en/api/query.md +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/en/guide/agent-integration.md +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/en/guide/warnings.md +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/en/index.md +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/zh/api/build.md +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/zh/api/configure.md +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/zh/api/exceptions.md +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/zh/api/query.md +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/zh/guide/agent-integration.md +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/zh/guide/warnings.md +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/docs/zh/index.md +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/agent_tools.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/analyzer/__init__.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/analyzer/_types.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/analyzer/language/__init__.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/analyzer/language/base.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/analyzer/language/python/constants.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/analyzer/language/python/decorators.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/analyzer/language/python/entry.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/analyzer/language/python/imports.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/analyzer/language/python/parser.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/analyzer/language/registry.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/analyzer/warnings.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/cli.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/config/__init__.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/config/manager.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/exceptions.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/i18n/__init__.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/incremental/__init__.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/incremental/_db_ops.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/query/__init__.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/query/engine.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/query/formatter.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/query/volume.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/storage/__init__.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/storage/db.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint/storage/schema.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint.egg-info/dependency_links.txt +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint.egg-info/entry_points.txt +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/graphlint.egg-info/top_level.txt +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/setup.cfg +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/__init__.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/integration/__init__.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/integration/test_api.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/integration/test_cli.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/integration/test_data_transfer.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/integration/test_dead_code_query.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/integration/test_full_pipeline.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/integration/test_incremental.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/integration/test_json_output.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/performance/__init__.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/performance/test_large_codebase.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/__init__.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_config.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_db.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_db_ops.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_decorators.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_engine.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_entry_detect.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_exceptions.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_formatter.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_graph.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_graph_algo.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_hashing.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_i18n.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_imports.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_mock_boundary.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_params.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_parser.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_schema.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_volume.py +0 -0
- {graphlint-0.2.0 → graphlint-0.3.0}/tests/unit/test_warnings.py +0 -0
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.3.0] - 2026-07-23
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Experimental Rust language analysis via `tree-sitter-rust` (opt-in via `[rust]`
|
|
9
|
+
optional dependency group)
|
|
10
|
+
- `query --public-as-entry` option: treats all public functions/methods as entry
|
|
11
|
+
points for dead-code reachability analysis
|
|
12
|
+
- `pub_api` entry rule template (disabled by default) for projects that want
|
|
13
|
+
public API surface as the primary entry
|
|
14
|
+
|
|
15
|
+
|
|
5
16
|
## [0.2.0] - 2026-07-22
|
|
6
17
|
|
|
7
18
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphlint
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A static analysis tool for identifying dead code in a codebase. It provides Python API and CLI for use by agents.
|
|
5
5
|
Author-email: Yutong Zou <yutong.zou.24@alumni.ucl.ac.uk>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -27,6 +27,9 @@ License-File: LICENSE
|
|
|
27
27
|
Provides-Extra: dev
|
|
28
28
|
Requires-Dist: pytest>=7; extra == "dev"
|
|
29
29
|
Requires-Dist: pytest-cov>=4; extra == "dev"
|
|
30
|
+
Provides-Extra: rust
|
|
31
|
+
Requires-Dist: tree-sitter<0.25,>=0.22; extra == "rust"
|
|
32
|
+
Requires-Dist: tree-sitter-rust<0.25,>=0.22; extra == "rust"
|
|
30
33
|
Dynamic: license-file
|
|
31
34
|
|
|
32
35
|
# graphlint
|
|
@@ -35,20 +38,31 @@ Dynamic: license-file
|
|
|
35
38
|
[](https://pypi.org/project/graphlint/)
|
|
36
39
|
[](https://github.com/AngelosZou/graphlint/blob/main/LICENSE)
|
|
37
40
|
|
|
38
|
-
[
|
|
39
|
-
[](https://github.com/AngelosZou/graphlint/blob/main/docs/zh/README.md)
|
|
41
|
+
[English](https://github.com/AngelosZou/graphlint/blob/main/README.md) | [简体中文](https://github.com/AngelosZou/graphlint/blob/main/docs/zh/README.md)
|
|
40
42
|
|
|
41
|
-
**Dead code detection for AI-generated
|
|
43
|
+
**Dead code detection for AI-generated codebases.**
|
|
42
44
|
|
|
43
|
-
AI agents generate code rapidly, leaving behind dead and redundant code that pollutes the LLM's context window and dilutes attention. Graphlint analyzes your
|
|
45
|
+
AI agents generate code rapidly, leaving behind dead and redundant code that pollutes the LLM's context window and dilutes attention. Graphlint analyzes your codebase's dependency graph to identify entry points and **detect dead code** — components unreachable from any entry point — so agents can self-clean and keep the codebase lean.
|
|
46
|
+
|
|
47
|
+
## Supported Languages
|
|
48
|
+
|
|
49
|
+
| Language | Status | Parser | Features |
|
|
50
|
+
|----------|--------|--------|----------|
|
|
51
|
+
| **Python** (`.py`) | Built-in | `ast` (stdlib) | Decorators, type annotations, dynamic imports, framework-aware entry detection |
|
|
52
|
+
| **Rust** (`.rs`) | Built-in (opt-in deps) | `tree-sitter` | Attribute macros, traits, `pub` visibility, `macro_rules!` |
|
|
53
|
+
|
|
54
|
+
Install with Rust support: `pip install graphlint[rust]` (adds `tree-sitter` and `tree-sitter-rust`).
|
|
44
55
|
|
|
45
56
|
## Features
|
|
46
57
|
|
|
47
58
|
- **Dead code detection** — finds components unreachable from any entry point via graph traversal
|
|
48
|
-
- **
|
|
59
|
+
- **Multi-language support** — Python and Rust backends via a language adapter abstraction; Python uses stdlib `ast`, Rust uses `tree-sitter`
|
|
60
|
+
- **Language-specific awareness** — Python decorators, Rust attribute macros (`#[tokio::main]`, `#[test]`), trait implementations, `pub` visibility, and more
|
|
61
|
+
- **AST/CST parsing** — extracts functions, methods, structs, enums, traits, impls, macros, variables, and fields; aware of type annotations and unpacked variables
|
|
49
62
|
- **Dependency graph** — builds directed edges: `read`, `write`, `call`, `inherit`, `decorate`
|
|
50
|
-
- **Entry point detection** —
|
|
51
|
-
- **
|
|
63
|
+
- **Entry point detection** — 17 built-in rules covering Python frameworks (FastAPI, Flask, Django, Click, Typer, Celery, pytest) and Rust conventions (main, async runtimes, WASM, proc macros, FFI, tests, pub API) plus custom rules
|
|
64
|
+
- **Configurable entry templates** — add custom entry rules via `ast_pattern` prefixes including `function_call:`, `function_def:`, `decorator:`, `file_match:`, `visibility:pub` (Rust), `trait_impl:` (Rust), `macro_def:` (Rust), and more
|
|
65
|
+
- **`--public-as-entry` flag** — treat all public items (Rust `pub`) as entry points for library-crate analysis
|
|
52
66
|
- **Warning detection** — 11 warning types including circular references, unused imports, write-only variables, and more
|
|
53
67
|
- **Python API + CLI** — integrate into any Tool, CI pipeline, or let agents self-analyze and self-clean
|
|
54
68
|
|
|
@@ -60,6 +74,12 @@ pip install graphlint
|
|
|
60
74
|
|
|
61
75
|
**Requirements:** Python >= 3.9
|
|
62
76
|
|
|
77
|
+
For Rust support (`.rs` files), install the optional `tree-sitter` dependencies:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install graphlint[rust]
|
|
81
|
+
```
|
|
82
|
+
|
|
63
83
|
## Quick Start
|
|
64
84
|
|
|
65
85
|
### Agent Integration
|
|
@@ -96,6 +116,9 @@ graphlint query -g 1 --detail full
|
|
|
96
116
|
# Exit non-zero when dead code or circular refs found (for CI)
|
|
97
117
|
graphlint query --json --fail-on dead_code,circular_ref
|
|
98
118
|
|
|
119
|
+
# Treat all public items as entry points (library-crate mode)
|
|
120
|
+
graphlint query --public-as-entry
|
|
121
|
+
|
|
99
122
|
# Rebuild index
|
|
100
123
|
graphlint build --force
|
|
101
124
|
|
|
@@ -193,7 +216,10 @@ Full documentation is available in the [docs/](https://github.com/AngelosZou/gra
|
|
|
193
216
|
|
|
194
217
|
## Limitations
|
|
195
218
|
|
|
196
|
-
- **Static analysis only** — graphlint performs static analysis and cannot detect runtime linkage such as `getattr`, `importlib`, or dynamic dispatch patterns, which may result in false positives. **Mitigation:** add custom entry rules matching your codebase's conventions. For example, graphlint's own codebase uses `function_def:_detect_*` and `function_def:visit_*` patterns to prevent functions discovered via `getattr` from being flagged as dead.
|
|
219
|
+
- **Static analysis only** — graphlint performs static analysis and cannot detect runtime linkage such as `getattr`, `importlib`, or dynamic dispatch patterns, which may result in false positives. This primarily affects Python; Rust's static dispatch model produces fewer false positives. **Mitigation:** add custom entry rules matching your codebase's conventions. For example, graphlint's own codebase uses `function_def:_detect_*` and `function_def:visit_*` patterns to prevent functions discovered via `getattr` from being flagged as dead.
|
|
220
|
+
- **Python dynamic imports** — due to Python's dynamic import mechanisms (`importlib`, `getattr`, metaclasses, etc.), the default entry templates may produce false positives in codebases that rely heavily on runtime dispatch. Users should tune the `entry_rules` configuration to match their project's conventions.
|
|
221
|
+
- **Rust macro expansion** — tree-sitter parses unexpanded source; procedural macros and `macro_rules!` bodies appear as opaque token trees. Some macro-generated call paths may be missed. `#[derive]` attributes are partially recognized via implicit `inherit` edges.
|
|
222
|
+
- **`--public-as-entry` scope** — this flag only applies to languages with `public` visibility declarations (Rust `pub`). It has no effect on Python files. Toggling this flag triggers a full re-index. For long-term library-crate analysis, prefer enabling the `rust_pub_api` entry rule via `graphlint config` to persist the setting.
|
|
197
223
|
- **Large codebase build time** — on a large codebase with 700+ `.py` files, 1,000+ classes, and 14,000+ functions, a full rebuild takes approximately 200 seconds (actual performance depends on hardware). Small projects (~60 files) complete in ~1 second. **Best practice:** run `query` before making changes to plan your work, and avoid invoking `query` during refactoring to prevent unnecessary index rebuilds.
|
|
198
224
|
|
|
199
225
|
## License
|
|
@@ -4,20 +4,31 @@
|
|
|
4
4
|
[](https://pypi.org/project/graphlint/)
|
|
5
5
|
[](https://github.com/AngelosZou/graphlint/blob/main/LICENSE)
|
|
6
6
|
|
|
7
|
-
[
|
|
8
|
-
[](docs/zh/README.md)
|
|
7
|
+
[English](README.md) | [简体中文](docs/zh/README.md)
|
|
9
8
|
|
|
10
|
-
**Dead code detection for AI-generated
|
|
9
|
+
**Dead code detection for AI-generated codebases.**
|
|
11
10
|
|
|
12
|
-
AI agents generate code rapidly, leaving behind dead and redundant code that pollutes the LLM's context window and dilutes attention. Graphlint analyzes your
|
|
11
|
+
AI agents generate code rapidly, leaving behind dead and redundant code that pollutes the LLM's context window and dilutes attention. Graphlint analyzes your codebase's dependency graph to identify entry points and **detect dead code** — components unreachable from any entry point — so agents can self-clean and keep the codebase lean.
|
|
12
|
+
|
|
13
|
+
## Supported Languages
|
|
14
|
+
|
|
15
|
+
| Language | Status | Parser | Features |
|
|
16
|
+
|----------|--------|--------|----------|
|
|
17
|
+
| **Python** (`.py`) | Built-in | `ast` (stdlib) | Decorators, type annotations, dynamic imports, framework-aware entry detection |
|
|
18
|
+
| **Rust** (`.rs`) | Built-in (opt-in deps) | `tree-sitter` | Attribute macros, traits, `pub` visibility, `macro_rules!` |
|
|
19
|
+
|
|
20
|
+
Install with Rust support: `pip install graphlint[rust]` (adds `tree-sitter` and `tree-sitter-rust`).
|
|
13
21
|
|
|
14
22
|
## Features
|
|
15
23
|
|
|
16
24
|
- **Dead code detection** — finds components unreachable from any entry point via graph traversal
|
|
17
|
-
- **
|
|
25
|
+
- **Multi-language support** — Python and Rust backends via a language adapter abstraction; Python uses stdlib `ast`, Rust uses `tree-sitter`
|
|
26
|
+
- **Language-specific awareness** — Python decorators, Rust attribute macros (`#[tokio::main]`, `#[test]`), trait implementations, `pub` visibility, and more
|
|
27
|
+
- **AST/CST parsing** — extracts functions, methods, structs, enums, traits, impls, macros, variables, and fields; aware of type annotations and unpacked variables
|
|
18
28
|
- **Dependency graph** — builds directed edges: `read`, `write`, `call`, `inherit`, `decorate`
|
|
19
|
-
- **Entry point detection** —
|
|
20
|
-
- **
|
|
29
|
+
- **Entry point detection** — 17 built-in rules covering Python frameworks (FastAPI, Flask, Django, Click, Typer, Celery, pytest) and Rust conventions (main, async runtimes, WASM, proc macros, FFI, tests, pub API) plus custom rules
|
|
30
|
+
- **Configurable entry templates** — add custom entry rules via `ast_pattern` prefixes including `function_call:`, `function_def:`, `decorator:`, `file_match:`, `visibility:pub` (Rust), `trait_impl:` (Rust), `macro_def:` (Rust), and more
|
|
31
|
+
- **`--public-as-entry` flag** — treat all public items (Rust `pub`) as entry points for library-crate analysis
|
|
21
32
|
- **Warning detection** — 11 warning types including circular references, unused imports, write-only variables, and more
|
|
22
33
|
- **Python API + CLI** — integrate into any Tool, CI pipeline, or let agents self-analyze and self-clean
|
|
23
34
|
|
|
@@ -29,6 +40,12 @@ pip install graphlint
|
|
|
29
40
|
|
|
30
41
|
**Requirements:** Python >= 3.9
|
|
31
42
|
|
|
43
|
+
For Rust support (`.rs` files), install the optional `tree-sitter` dependencies:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install graphlint[rust]
|
|
47
|
+
```
|
|
48
|
+
|
|
32
49
|
## Quick Start
|
|
33
50
|
|
|
34
51
|
### Agent Integration
|
|
@@ -65,6 +82,9 @@ graphlint query -g 1 --detail full
|
|
|
65
82
|
# Exit non-zero when dead code or circular refs found (for CI)
|
|
66
83
|
graphlint query --json --fail-on dead_code,circular_ref
|
|
67
84
|
|
|
85
|
+
# Treat all public items as entry points (library-crate mode)
|
|
86
|
+
graphlint query --public-as-entry
|
|
87
|
+
|
|
68
88
|
# Rebuild index
|
|
69
89
|
graphlint build --force
|
|
70
90
|
|
|
@@ -162,7 +182,10 @@ Full documentation is available in the [docs/](docs/) directory:
|
|
|
162
182
|
|
|
163
183
|
## Limitations
|
|
164
184
|
|
|
165
|
-
- **Static analysis only** — graphlint performs static analysis and cannot detect runtime linkage such as `getattr`, `importlib`, or dynamic dispatch patterns, which may result in false positives. **Mitigation:** add custom entry rules matching your codebase's conventions. For example, graphlint's own codebase uses `function_def:_detect_*` and `function_def:visit_*` patterns to prevent functions discovered via `getattr` from being flagged as dead.
|
|
185
|
+
- **Static analysis only** — graphlint performs static analysis and cannot detect runtime linkage such as `getattr`, `importlib`, or dynamic dispatch patterns, which may result in false positives. This primarily affects Python; Rust's static dispatch model produces fewer false positives. **Mitigation:** add custom entry rules matching your codebase's conventions. For example, graphlint's own codebase uses `function_def:_detect_*` and `function_def:visit_*` patterns to prevent functions discovered via `getattr` from being flagged as dead.
|
|
186
|
+
- **Python dynamic imports** — due to Python's dynamic import mechanisms (`importlib`, `getattr`, metaclasses, etc.), the default entry templates may produce false positives in codebases that rely heavily on runtime dispatch. Users should tune the `entry_rules` configuration to match their project's conventions.
|
|
187
|
+
- **Rust macro expansion** — tree-sitter parses unexpanded source; procedural macros and `macro_rules!` bodies appear as opaque token trees. Some macro-generated call paths may be missed. `#[derive]` attributes are partially recognized via implicit `inherit` edges.
|
|
188
|
+
- **`--public-as-entry` scope** — this flag only applies to languages with `public` visibility declarations (Rust `pub`). It has no effect on Python files. Toggling this flag triggers a full re-index. For long-term library-crate analysis, prefer enabling the `rust_pub_api` entry rule via `graphlint config` to persist the setting.
|
|
166
189
|
- **Large codebase build time** — on a large codebase with 700+ `.py` files, 1,000+ classes, and 14,000+ functions, a full rebuild takes approximately 200 seconds (actual performance depends on hardware). Small projects (~60 files) complete in ~1 second. **Best practice:** run `query` before making changes to plan your work, and avoid invoking `query` during refactoring to prevent unnecessary index rebuilds.
|
|
167
190
|
|
|
168
191
|
## License
|
|
@@ -4,20 +4,31 @@
|
|
|
4
4
|
[](https://pypi.org/project/graphlint/)
|
|
5
5
|
[](https://github.com/AngelosZou/graphlint/blob/main/LICENSE)
|
|
6
6
|
|
|
7
|
-
[
|
|
8
|
-
[](https://github.com/AngelosZou/graphlint/blob/main/docs/zh/README.md)
|
|
7
|
+
[English](https://github.com/AngelosZou/graphlint/blob/main/README.md) | [简体中文](https://github.com/AngelosZou/graphlint/blob/main/docs/zh/README.md)
|
|
9
8
|
|
|
10
|
-
**Dead code detection for AI-generated
|
|
9
|
+
**Dead code detection for AI-generated codebases.**
|
|
11
10
|
|
|
12
|
-
AI agents generate code rapidly, leaving behind dead and redundant code that pollutes the LLM's context window and dilutes attention. Graphlint analyzes your
|
|
11
|
+
AI agents generate code rapidly, leaving behind dead and redundant code that pollutes the LLM's context window and dilutes attention. Graphlint analyzes your codebase's dependency graph to identify entry points and **detect dead code** — components unreachable from any entry point — so agents can self-clean and keep the codebase lean.
|
|
12
|
+
|
|
13
|
+
## Supported Languages
|
|
14
|
+
|
|
15
|
+
| Language | Status | Parser | Features |
|
|
16
|
+
|----------|--------|--------|----------|
|
|
17
|
+
| **Python** (`.py`) | Built-in | `ast` (stdlib) | Decorators, type annotations, dynamic imports, framework-aware entry detection |
|
|
18
|
+
| **Rust** (`.rs`) | Built-in (opt-in deps) | `tree-sitter` | Attribute macros, traits, `pub` visibility, `macro_rules!` |
|
|
19
|
+
|
|
20
|
+
Install with Rust support: `pip install graphlint[rust]` (adds `tree-sitter` and `tree-sitter-rust`).
|
|
13
21
|
|
|
14
22
|
## Features
|
|
15
23
|
|
|
16
24
|
- **Dead code detection** — finds components unreachable from any entry point via graph traversal
|
|
17
|
-
- **
|
|
25
|
+
- **Multi-language support** — Python and Rust backends via a language adapter abstraction; Python uses stdlib `ast`, Rust uses `tree-sitter`
|
|
26
|
+
- **Language-specific awareness** — Python decorators, Rust attribute macros (`#[tokio::main]`, `#[test]`), trait implementations, `pub` visibility, and more
|
|
27
|
+
- **AST/CST parsing** — extracts functions, methods, structs, enums, traits, impls, macros, variables, and fields; aware of type annotations and unpacked variables
|
|
18
28
|
- **Dependency graph** — builds directed edges: `read`, `write`, `call`, `inherit`, `decorate`
|
|
19
|
-
- **Entry point detection** —
|
|
20
|
-
- **
|
|
29
|
+
- **Entry point detection** — 17 built-in rules covering Python frameworks (FastAPI, Flask, Django, Click, Typer, Celery, pytest) and Rust conventions (main, async runtimes, WASM, proc macros, FFI, tests, pub API) plus custom rules
|
|
30
|
+
- **Configurable entry templates** — add custom entry rules via `ast_pattern` prefixes including `function_call:`, `function_def:`, `decorator:`, `file_match:`, `visibility:pub` (Rust), `trait_impl:` (Rust), `macro_def:` (Rust), and more
|
|
31
|
+
- **`--public-as-entry` flag** — treat all public items (Rust `pub`) as entry points for library-crate analysis
|
|
21
32
|
- **Warning detection** — 11 warning types including circular references, unused imports, write-only variables, and more
|
|
22
33
|
- **Python API + CLI** — integrate into any Tool, CI pipeline, or let agents self-analyze and self-clean
|
|
23
34
|
|
|
@@ -29,6 +40,12 @@ pip install graphlint
|
|
|
29
40
|
|
|
30
41
|
**Requirements:** Python >= 3.9
|
|
31
42
|
|
|
43
|
+
For Rust support (`.rs` files), install the optional `tree-sitter` dependencies:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install graphlint[rust]
|
|
47
|
+
```
|
|
48
|
+
|
|
32
49
|
## Quick Start
|
|
33
50
|
|
|
34
51
|
### Agent Integration
|
|
@@ -65,6 +82,9 @@ graphlint query -g 1 --detail full
|
|
|
65
82
|
# Exit non-zero when dead code or circular refs found (for CI)
|
|
66
83
|
graphlint query --json --fail-on dead_code,circular_ref
|
|
67
84
|
|
|
85
|
+
# Treat all public items as entry points (library-crate mode)
|
|
86
|
+
graphlint query --public-as-entry
|
|
87
|
+
|
|
68
88
|
# Rebuild index
|
|
69
89
|
graphlint build --force
|
|
70
90
|
|
|
@@ -162,7 +182,10 @@ Full documentation is available in the [docs/](https://github.com/AngelosZou/gra
|
|
|
162
182
|
|
|
163
183
|
## Limitations
|
|
164
184
|
|
|
165
|
-
- **Static analysis only** — graphlint performs static analysis and cannot detect runtime linkage such as `getattr`, `importlib`, or dynamic dispatch patterns, which may result in false positives. **Mitigation:** add custom entry rules matching your codebase's conventions. For example, graphlint's own codebase uses `function_def:_detect_*` and `function_def:visit_*` patterns to prevent functions discovered via `getattr` from being flagged as dead.
|
|
185
|
+
- **Static analysis only** — graphlint performs static analysis and cannot detect runtime linkage such as `getattr`, `importlib`, or dynamic dispatch patterns, which may result in false positives. This primarily affects Python; Rust's static dispatch model produces fewer false positives. **Mitigation:** add custom entry rules matching your codebase's conventions. For example, graphlint's own codebase uses `function_def:_detect_*` and `function_def:visit_*` patterns to prevent functions discovered via `getattr` from being flagged as dead.
|
|
186
|
+
- **Python dynamic imports** — due to Python's dynamic import mechanisms (`importlib`, `getattr`, metaclasses, etc.), the default entry templates may produce false positives in codebases that rely heavily on runtime dispatch. Users should tune the `entry_rules` configuration to match their project's conventions.
|
|
187
|
+
- **Rust macro expansion** — tree-sitter parses unexpanded source; procedural macros and `macro_rules!` bodies appear as opaque token trees. Some macro-generated call paths may be missed. `#[derive]` attributes are partially recognized via implicit `inherit` edges.
|
|
188
|
+
- **`--public-as-entry` scope** — this flag only applies to languages with `public` visibility declarations (Rust `pub`). It has no effect on Python files. Toggling this flag triggers a full re-index. For long-term library-crate analysis, prefer enabling the `rust_pub_api` entry rule via `graphlint config` to persist the setting.
|
|
166
189
|
- **Large codebase build time** — on a large codebase with 700+ `.py` files, 1,000+ classes, and 14,000+ functions, a full rebuild takes approximately 200 seconds (actual performance depends on hardware). Small projects (~60 files) complete in ~1 second. **Best practice:** run `query` before making changes to plan your work, and avoid invoking `query` during refactoring to prevent unnecessary index rebuilds.
|
|
167
190
|
|
|
168
191
|
## License
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Architecture Overview
|
|
2
2
|
|
|
3
|
-
graphlint is positioned as a dead code detection tool for AI-generated codebases. It analyzes the dependency graph to find components unreachable from all entry points (dead code), helping agents clean redundant code and reduce context pollution and attention dilution.
|
|
3
|
+
graphlint is positioned as a dead code detection tool for AI-generated codebases (Python, Rust, and more in the future). It analyzes the dependency graph to find components unreachable from all entry points (dead code), helping agents clean redundant code and reduce context pollution and attention dilution.
|
|
4
4
|
|
|
5
5
|
## Overall Architecture
|
|
6
6
|
|
|
@@ -97,8 +97,8 @@ AST Parse (ast.parse / other language parser)
|
|
|
97
97
|
│
|
|
98
98
|
▼
|
|
99
99
|
ASTVisitor Traversal (single pass)
|
|
100
|
-
├── Extract nodes (classes/functions/methods/variables/fields)
|
|
101
|
-
├── Parse import statements
|
|
100
|
+
├── Extract nodes (classes/functions/methods/structs/enums/traits/impls/variables/fields)
|
|
101
|
+
├── Parse import statements (Python) / use declarations (Rust)
|
|
102
102
|
├── Collect name usage
|
|
103
103
|
└── Collect structured references (ReferenceInfo — read/write/call/inherit/decorate)
|
|
104
104
|
│
|
|
@@ -45,6 +45,7 @@ graphlint query -j # JSON format output
|
|
|
45
45
|
| `--file-limit` | — | int | `10` | Max files shown in graph detail (0=unlimited) |
|
|
46
46
|
| `--node-limit` | — | int | `30` | Max nodes shown in graph detail (0=unlimited) |
|
|
47
47
|
| `--no-scan` | — | flag | `false` | Skip auto-scan/build, query existing index only |
|
|
48
|
+
| `--public-as-entry` | — | flag | `false` | Treat public methods (Rust `pub`) as execution entry points — see [Entry Detection](../guide/entry-detection.md#-public-as-entry-flag) |
|
|
48
49
|
| `--fail-on` | — | string | — | Exit non-zero if matching warning types found (comma-separated) |
|
|
49
50
|
|
|
50
51
|
### Examples
|
|
@@ -16,10 +16,17 @@ graphlint's configuration file is located at `.graphlint/config.json` in the pro
|
|
|
16
16
|
{"name": "typer_app", "ast_pattern": "class_instantiation:typer.Typer | decorator:*.command", "file_pattern": "**/*.py", "enabled": true},
|
|
17
17
|
{"name": "celery_app", "ast_pattern": "class_instantiation:Celery | class_instantiation:celery.Celery", "file_pattern": "**/*.py", "enabled": true},
|
|
18
18
|
{"name": "pytest_plugin", "ast_pattern": "function_def:pytest_addoption | decorator:pytest.fixture", "file_pattern": "**/conftest.py", "enabled": true},
|
|
19
|
-
{"name": "pytest_test", "ast_pattern": "test_file", "file_pattern": "**/*.py", "enabled": true, "no_propagate": true}
|
|
19
|
+
{"name": "pytest_test", "ast_pattern": "test_file", "file_pattern": "**/*.py", "enabled": true, "no_propagate": true},
|
|
20
|
+
{"name": "rust_main", "ast_pattern": "function_def:main", "file_pattern": "**/*.rs", "enabled": true},
|
|
21
|
+
{"name": "rust_async_main", "ast_pattern": "decorator:tokio::main | decorator:actix_rt::main | decorator:actix_web::main | decorator:async_std::main | decorator:rocket::main | decorator:rocket::launch | decorator:main", "file_pattern": "**/*.rs", "enabled": true},
|
|
22
|
+
{"name": "rust_wasm_entry", "ast_pattern": "decorator:wasm_bindgen", "file_pattern": "**/*.rs", "enabled": true},
|
|
23
|
+
{"name": "rust_proc_macro", "ast_pattern": "decorator:proc_macro | decorator:proc_macro_derive | decorator:proc_macro_attribute", "file_pattern": "**/*.rs", "enabled": true},
|
|
24
|
+
{"name": "rust_ffi_export", "ast_pattern": "decorator:no_mangle | decorator:export_name", "file_pattern": "**/*.rs", "enabled": true},
|
|
25
|
+
{"name": "rust_test", "ast_pattern": "test_file", "file_pattern": "**/*.rs", "enabled": true, "no_propagate": true},
|
|
26
|
+
{"name": "rust_pub_api", "ast_pattern": "visibility:pub", "file_pattern": "**/*.rs", "enabled": false}
|
|
20
27
|
],
|
|
21
28
|
"exclude_patterns": {
|
|
22
|
-
"always_exclude": ["__pycache__/", ".mypy_cache/", ".pytest_cache/", ".tox/", ".venv/", "venv/", "env/", "virtualenv/", ".env/", "node_modules/", ".git/", ".svn/", ".hg/", ".idea/", ".vscode/", ".vs/", ".graphlint/", "build/", "dist/", "*.egg-info/", "*.pyc", "*.pyo"],
|
|
29
|
+
"always_exclude": ["__pycache__/", ".mypy_cache/", ".pytest_cache/", ".tox/", ".venv/", "venv/", "env/", "virtualenv/", ".env/", "node_modules/", ".git/", ".svn/", ".hg/", ".idea/", ".vscode/", ".vs/", ".graphlint/", "build/", "dist/", "target/", ".cargo/", "*.egg-info/", "*.pyc", "*.pyo"],
|
|
23
30
|
"user_exclude": []
|
|
24
31
|
},
|
|
25
32
|
"lang": "system",
|
|
@@ -36,7 +43,7 @@ graphlint's configuration file is located at `.graphlint/config.json` in the pro
|
|
|
36
43
|
"test_patterns": {
|
|
37
44
|
"config_files": ["conftest.py"],
|
|
38
45
|
"dir_patterns": ["tests/", "test/", "__tests__/"],
|
|
39
|
-
"file_patterns": ["test_*.py", "*_test.py"],
|
|
46
|
+
"file_patterns": ["test_*.py", "*_test.py", "test_*.rs", "*_test.rs"],
|
|
40
47
|
"function_patterns": ["test_*"]
|
|
41
48
|
},
|
|
42
49
|
"version": 1
|
|
@@ -58,19 +65,36 @@ Defines how code entry points are auto-detected. Each rule contains:
|
|
|
58
65
|
| `enabled` | `boolean` | Whether the rule is enabled |
|
|
59
66
|
| `no_propagate` | `boolean` | Entry does not propagate reachability (default `false`, e.g. pytest test rules set to `true`) |
|
|
60
67
|
|
|
68
|
+
**Note:** The `rust_pub_api` rule (pattern `visibility:pub`, Rust only) is **disabled by default** (`"enabled": false`). Use `--public-as-entry` at query time, or manually set `"enabled": true` in the config for persistent library-crate analysis.
|
|
69
|
+
|
|
70
|
+
### --public-as-entry Flag
|
|
71
|
+
|
|
72
|
+
The `--public-as-entry` CLI flag (or `public_as_entry=True` API parameter) treats all public items as execution entry points:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
graphlint query --public-as-entry
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- **Scope:** Only affects languages with `public` visibility declarations (Rust `pub`). Has no effect on Python files.
|
|
79
|
+
- **Re-indexing:** Toggling the flag triggers a full rebuild (the flag value is stored in the scan stamp). For long-term use, prefer enabling the `rust_pub_api` config rule or adding custom `visibility:pub` entry rules to avoid repeated rebuilds.
|
|
80
|
+
- **Config interaction:** Independent of the `rust_pub_api` config rule — the two mechanisms are orthogonal and do not interfere.
|
|
81
|
+
|
|
61
82
|
#### AST Pattern Prefixes
|
|
62
83
|
|
|
63
84
|
All rules use the same prefix pattern syntax, supporting OR combinations with ` | `:
|
|
64
85
|
|
|
65
|
-
| Prefix | Match Target | Example |
|
|
66
|
-
|
|
67
|
-
| `function_call:` | Function call | `"function_call:my_entry"` |
|
|
68
|
-
| `function_def:` | Function definition name (supports glob) | `"function_def:run_*"` |
|
|
69
|
-
| `decorator:` | Decorator | `"decorator:app.route"` |
|
|
70
|
-
| `class_instantiation:` | Class instantiation | `"class_instantiation:MyApp"` |
|
|
71
|
-
| `file_match:` | Filename match | `"file_match:**/main.py"` |
|
|
72
|
-
| `if_name_main` | `if __name__ == '__main__'` check | `"if_name_main"` |
|
|
73
|
-
| `test_file` | Test file detection (uses `test_patterns` config) | `"test_file"` |
|
|
86
|
+
| Prefix | Match Target | Example | Languages |
|
|
87
|
+
|--------|-------------|---------|-----------|
|
|
88
|
+
| `function_call:` | Function call | `"function_call:my_entry"` | Python |
|
|
89
|
+
| `function_def:` | Function definition name (supports glob) | `"function_def:run_*"` | Python, Rust |
|
|
90
|
+
| `decorator:` | Decorator or attribute macro (`#[...]`) | `"decorator:app.route"` / `"decorator:tokio::main"` | Python, Rust |
|
|
91
|
+
| `class_instantiation:` | Class instantiation | `"class_instantiation:MyApp"` | Python |
|
|
92
|
+
| `file_match:` | Filename match | `"file_match:**/main.py"` | Python, Rust |
|
|
93
|
+
| `if_name_main` | `if __name__ == '__main__'` check | `"if_name_main"` | Python |
|
|
94
|
+
| `test_file` | Test file detection (uses `test_patterns` config) | `"test_file"` | Python, Rust |
|
|
95
|
+
| `visibility:pub` | Items with `pub` visibility modifier | `"visibility:pub"` | Rust |
|
|
96
|
+
| `trait_impl:` | Trait implementation block | `"trait_impl:Default"` | Rust |
|
|
97
|
+
| `macro_def:` | `macro_rules!` definition | `"macro_def:my_macro"` | Rust |
|
|
74
98
|
|
|
75
99
|
### exclude_patterns — Exclude Patterns
|
|
76
100
|
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Entry Point Detection Reference
|
|
2
2
|
|
|
3
|
-
graphlint has
|
|
3
|
+
graphlint has 17 built-in entry point detection rules (10 for Python, 7 for Rust) and supports custom rule extension. On first `graphlint build`, these rules are written as a template into `.graphlint/config.json`; thereafter the config file is the single source of truth — you may add, remove, or modify rules via `graphlint config` or by editing the file directly.
|
|
4
4
|
|
|
5
5
|
## Built-in Rules
|
|
6
6
|
|
|
7
|
+
### Python Rules
|
|
8
|
+
|
|
7
9
|
### 1. python_main
|
|
8
10
|
|
|
9
11
|
Detects the standard Python entry point `if __name__ == '__main__':`.
|
|
@@ -167,6 +169,117 @@ Detects Pytest test cases as entry points.
|
|
|
167
169
|
pass
|
|
168
170
|
```
|
|
169
171
|
|
|
172
|
+
### Rust Rules
|
|
173
|
+
|
|
174
|
+
### 11. rust_main
|
|
175
|
+
|
|
176
|
+
Detects the Rust binary crate entry point `fn main()`.
|
|
177
|
+
|
|
178
|
+
- **Match Pattern**: `function_def:main`
|
|
179
|
+
- **Match Files**: `**/*.rs`
|
|
180
|
+
- **Example**:
|
|
181
|
+
```rust
|
|
182
|
+
fn main() {
|
|
183
|
+
println!("Hello, world!");
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### 12. rust_async_main
|
|
188
|
+
|
|
189
|
+
Detects async runtime `#[...]` attributes that decorate `main`.
|
|
190
|
+
|
|
191
|
+
- **Match Pattern**: `decorator:tokio::main | decorator:actix_rt::main | decorator:actix_web::main | decorator:async_std::main | decorator:rocket::main | decorator:rocket::launch | decorator:main`
|
|
192
|
+
- **Match Files**: `**/*.rs`
|
|
193
|
+
- **Note**: Rust attribute macros (`#[...]`) are compile-time and do **not** create `decorate` edges in the dependency graph (unlike Python decorators).
|
|
194
|
+
- **Example**:
|
|
195
|
+
```rust
|
|
196
|
+
#[tokio::main]
|
|
197
|
+
async fn main() {
|
|
198
|
+
// ...
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### 13. rust_wasm_entry
|
|
203
|
+
|
|
204
|
+
Detects WebAssembly exports.
|
|
205
|
+
|
|
206
|
+
- **Match Pattern**: `decorator:wasm_bindgen`
|
|
207
|
+
- **Match Files**: `**/*.rs`
|
|
208
|
+
- **Example**:
|
|
209
|
+
```rust
|
|
210
|
+
use wasm_bindgen::prelude::*;
|
|
211
|
+
|
|
212
|
+
#[wasm_bindgen]
|
|
213
|
+
pub fn greet(name: &str) {
|
|
214
|
+
// ...
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### 14. rust_proc_macro
|
|
219
|
+
|
|
220
|
+
Detects Rust procedural macro entry points called by the compiler.
|
|
221
|
+
|
|
222
|
+
- **Match Pattern**: `decorator:proc_macro | decorator:proc_macro_derive | decorator:proc_macro_attribute`
|
|
223
|
+
- **Match Files**: `**/*.rs`
|
|
224
|
+
- **Example**:
|
|
225
|
+
```rust
|
|
226
|
+
#[proc_macro]
|
|
227
|
+
pub fn my_macro(input: TokenStream) -> TokenStream {
|
|
228
|
+
// ...
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### 15. rust_ffi_export
|
|
233
|
+
|
|
234
|
+
Detects FFI exports via `#[no_mangle]` or `#[export_name]`.
|
|
235
|
+
|
|
236
|
+
- **Match Pattern**: `decorator:no_mangle | decorator:export_name`
|
|
237
|
+
- **Match Files**: `**/*.rs`
|
|
238
|
+
- **Example**:
|
|
239
|
+
```rust
|
|
240
|
+
#[no_mangle]
|
|
241
|
+
pub extern "C" fn my_export() {
|
|
242
|
+
// ...
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 16. rust_test
|
|
247
|
+
|
|
248
|
+
Detects Rust test files and `#[test]` functions.
|
|
249
|
+
|
|
250
|
+
- **Match Pattern**: `test_file`
|
|
251
|
+
- **Match Files**: `**/*.rs`
|
|
252
|
+
- **Note**: Test entry points do not propagate reachability to non-test code under test.
|
|
253
|
+
- **Example**:
|
|
254
|
+
```rust
|
|
255
|
+
#[test]
|
|
256
|
+
fn test_addition() {
|
|
257
|
+
assert_eq!(2 + 2, 4);
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### 17. rust_pub_api
|
|
262
|
+
|
|
263
|
+
Treats all `pub` items in Rust library crates as entry points.
|
|
264
|
+
|
|
265
|
+
- **Match Pattern**: `visibility:pub`
|
|
266
|
+
- **Match Files**: `**/*.rs`
|
|
267
|
+
- **Note**: Disabled by default (`"enabled": false`). Use `--public-as-entry` to activate it at query time, or enable the rule in config for persistent library-crate analysis.
|
|
268
|
+
|
|
269
|
+
## `--public-as-entry` Flag
|
|
270
|
+
|
|
271
|
+
The `--public-as-entry` flag provides an alternative way to treat public items as entry points without modifying the config:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
graphlint query --public-as-entry
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
This flag:
|
|
278
|
+
- Only applies to languages with `public` visibility declarations (Rust `pub`); has no effect on Python files.
|
|
279
|
+
- Toggles independently from the `rust_pub_api` config entry rule — the two are orthogonal mechanisms.
|
|
280
|
+
- **Triggers a full re-index** when switched on or off (detected via the scan stamp).
|
|
281
|
+
- For long-term library-crate analysis, prefer enabling `rust_pub_api` in `.graphlint/config.json` to persist the setting and avoid repeated rebuilds.
|
|
282
|
+
|
|
170
283
|
## Custom Rules
|
|
171
284
|
|
|
172
285
|
Add custom entry detection rules via the `entry_rules` configuration.
|
|
@@ -175,15 +288,18 @@ Add custom entry detection rules via the `entry_rules` configuration.
|
|
|
175
288
|
|
|
176
289
|
All rules (built-in and custom) use the same prefix syntax, supporting OR combinations with ` | `.
|
|
177
290
|
|
|
178
|
-
| Prefix | Description | Example |
|
|
179
|
-
|
|
180
|
-
| `function_call:<name>` | Match a function call by name | `"function_call:start_app"` |
|
|
181
|
-
| `function_def:<pattern>` | Match a function definition by name (supports glob) | `"function_def:run_*"` |
|
|
182
|
-
| `decorator:<name>` | Match a decorator by name | `"decorator:app.route"` |
|
|
183
|
-
| `class_instantiation:<name>` | Match a class instantiation by name | `"class_instantiation:MyApp"` |
|
|
184
|
-
| `file_match:<pattern>` | Match a filename pattern | `"file_match:**/startup.py"` |
|
|
185
|
-
| `if_name_main` | Match `if __name__ == '__main__'` | `"if_name_main"` |
|
|
186
|
-
| `test_file` | Match test files (uses `test_patterns` config) | `"test_file"` |
|
|
291
|
+
| Prefix | Description | Example | Applicable |
|
|
292
|
+
|--------|-------------|---------|------------|
|
|
293
|
+
| `function_call:<name>` | Match a function call by name | `"function_call:start_app"` | Python |
|
|
294
|
+
| `function_def:<pattern>` | Match a function definition by name (supports glob) | `"function_def:run_*"` | Python, Rust |
|
|
295
|
+
| `decorator:<name>` | Match a decorator (Python) or attribute macro (Rust `#[...]`) by name | `"decorator:app.route"` / `"decorator:tokio::main"` | Python, Rust |
|
|
296
|
+
| `class_instantiation:<name>` | Match a class instantiation by name | `"class_instantiation:MyApp"` | Python |
|
|
297
|
+
| `file_match:<pattern>` | Match a filename pattern | `"file_match:**/startup.py"` | Python, Rust |
|
|
298
|
+
| `if_name_main` | Match `if __name__ == '__main__'` | `"if_name_main"` | Python |
|
|
299
|
+
| `test_file` | Match test files (uses `test_patterns` config) | `"test_file"` | Python, Rust |
|
|
300
|
+
| `visibility:pub` | Match items with `pub` visibility modifier | `"visibility:pub"` | Rust |
|
|
301
|
+
| `trait_impl:<pattern>` | Match `impl Trait for Type` blocks | `"trait_impl:Default"` | Rust |
|
|
302
|
+
| `macro_def:<pattern>` | Match `macro_rules!` definitions | `"macro_def:my_macro"` | Rust |
|
|
187
303
|
|
|
188
304
|
### Custom Rule Examples
|
|
189
305
|
|
|
@@ -10,7 +10,13 @@ graphlint provides a **Python API** (for integration into any Tool development)
|
|
|
10
10
|
pip install graphlint
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Requirements: Python >= 3.9
|
|
13
|
+
Requirements: Python >= 3.9
|
|
14
|
+
|
|
15
|
+
For Rust support (`.rs` files), install the optional `tree-sitter` dependencies:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install graphlint[rust]
|
|
19
|
+
```
|
|
14
20
|
|
|
15
21
|
## Agent Integration
|
|
16
22
|
|
|
@@ -4,20 +4,31 @@
|
|
|
4
4
|
[](https://pypi.org/project/graphlint/)
|
|
5
5
|
[](https://github.com/AngelosZou/graphlint/blob/main/LICENSE)
|
|
6
6
|
|
|
7
|
-
[
|
|
8
|
-
[](README.md)
|
|
7
|
+
[English](../../README.md) | [简体中文](README.md)
|
|
9
8
|
|
|
10
|
-
**面向 AI
|
|
9
|
+
**面向 AI 生成代码库的死代码检测工具。**
|
|
11
10
|
|
|
12
11
|
AI Agent 在快速生成代码的同时,也会留下大量冗余和死代码。这些代码污染了 LLM 的上下文窗口,稀释了注意力。Graphlint 通过分析代码库的依赖关系图,识别入口点并**检测死代码**(从任何入口点均不可达的组件),让 Agent 能够自行清理,保持代码库的简洁高效。
|
|
13
12
|
|
|
13
|
+
## 支持的语言
|
|
14
|
+
|
|
15
|
+
| 语言 | 状态 | 解析器 | 特性 |
|
|
16
|
+
|------|------|--------|------|
|
|
17
|
+
| **Python** (`.py`) | 内置 | `ast`(标准库) | 装饰器、类型注解、动态导入、框架感知入口检测 |
|
|
18
|
+
| **Rust** (`.rs`) | 内置(可选依赖) | `tree-sitter` | 属性宏、Trait、`pub` 可见性、`macro_rules!` |
|
|
19
|
+
|
|
20
|
+
安装含 Rust 支持的版本:`pip install graphlint[rust]`(额外引入 `tree-sitter` 和 `tree-sitter-rust`)。
|
|
21
|
+
|
|
14
22
|
## 特性
|
|
15
23
|
|
|
16
24
|
- **死代码检测** — 通过图遍历找出所有入口点不可达的组件
|
|
17
|
-
-
|
|
25
|
+
- **多语言支持** — Python 和 Rust 后端,通过语言适配器抽象层实现;Python 使用标准库 `ast`,Rust 使用 `tree-sitter`
|
|
26
|
+
- **语言专有特性感知** — Python 装饰器、Rust 属性宏(`#[tokio::main]`、`#[test]`)、Trait 实现、`pub` 可见性等
|
|
27
|
+
- **AST/CST 解析** — 提取函数、方法、结构体、枚举、Trait、实现块、宏、变量、字段;感知类型声明,自动处理循环解包等变量绑定
|
|
18
28
|
- **依赖图构建** — 有向边:`read`、`write`、`call`、`inherit`、`decorate`
|
|
19
|
-
- **入口点检测** —
|
|
20
|
-
-
|
|
29
|
+
- **入口点检测** — 17 种内置规则,覆盖 Python 框架(FastAPI、Flask、Django、Click、Typer、Celery、pytest)和 Rust 惯例(main、异步运行时、WASM、proc 宏、FFI、测试、pub API)及自定义规则
|
|
30
|
+
- **可配置入口模板** — 通过 `ast_pattern` 前缀添加自定义入口规则,包括 `function_call:`、`function_def:`、`decorator:`、`file_match:`、`visibility:pub`(Rust)、`trait_impl:`(Rust)、`macro_def:`(Rust)等
|
|
31
|
+
- **`--public-as-entry` 标志** — 将所有公开项(Rust `pub`)视为入口点,用于库代码分析
|
|
21
32
|
- **警告检测** — 11 种警告类型:循环引用、未使用 import、只写变量、死代码等
|
|
22
33
|
- **Python API + CLI** — 可集成到任何 Tool 开发、CI 流程,或直接供 Agent 自行分析和清理
|
|
23
34
|
|
|
@@ -29,6 +40,12 @@ pip install graphlint
|
|
|
29
40
|
|
|
30
41
|
**要求:** Python >= 3.9
|
|
31
42
|
|
|
43
|
+
如需支持 Rust(`.rs` 文件),安装可选的 `tree-sitter` 依赖:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install graphlint[rust]
|
|
47
|
+
```
|
|
48
|
+
|
|
32
49
|
## 快速开始
|
|
33
50
|
|
|
34
51
|
### Agent 集成
|
|
@@ -65,6 +82,9 @@ graphlint query -g 1 --detail full
|
|
|
65
82
|
# 检测到死代码或循环引用时返回非零退出码(用于 CI)
|
|
66
83
|
graphlint query --json --fail-on dead_code,circular_ref
|
|
67
84
|
|
|
85
|
+
# 将所有公开项视为入口点(库模式分析)
|
|
86
|
+
graphlint query --public-as-entry
|
|
87
|
+
|
|
68
88
|
# 重建索引
|
|
69
89
|
graphlint build --force
|
|
70
90
|
|
|
@@ -162,7 +182,10 @@ Graphlint 将配置存储在分析目录下的 `.graphlint/config.json` 中。
|
|
|
162
182
|
|
|
163
183
|
## 局限性
|
|
164
184
|
|
|
165
|
-
- **纯静态分析** — graphlint 基于静态分析执行,无法检测 `getattr`、`importlib`
|
|
185
|
+
- **纯静态分析** — graphlint 基于静态分析执行,无法检测 `getattr`、`importlib` 等运行时动态链接或动态分发模式,这可能会导致假阳性。此问题主要影响 Python;Rust 的静态分发模型产生的假阳性较少。**缓解方案:** 根据代码库的实际约定添加自定义入口规则。例如,graphlint 自身的代码库使用了 `function_def:_detect_*` 和 `function_def:visit_*` 两种 ast_pattern,防止通过 `getattr` 发现的函数被误报为死代码。
|
|
186
|
+
- **Python 动态导入** — 由于 Python 的动态导入机制(`importlib`、`getattr`、元类等),默认入口模板在重度依赖运行时调度的代码库中可能产生假阳性。用户应根据项目约定调整 `entry_rules` 配置。
|
|
187
|
+
- **Rust 宏展开** — tree-sitter 解析未展开的源代码;过程宏和 `macro_rules!` 体显示为不透明标记树。部分宏生成的调用路径可能遗漏。`#[derive]` 属性通过隐式 `inherit` 边部分识别。
|
|
188
|
+
- **`--public-as-entry` 适用范围** — 此标志仅适用于具有 `public` 可见性声明的语言(Rust `pub`),对 Python 文件无效。开关此标志会触发全量重新索引。对于长期库代码分析,建议通过 `graphlint config` 启用 `rust_pub_api` 入口规则以持久化设置。
|
|
166
189
|
- **大规模代码库构建耗时** — 在包含 700+ 个 Python 文件、1,000+ 个类定义和 14,000+ 个函数的大规模代码库上,完整重构需要约 200 秒(实际时间与设备性能有关)。小型项目(~60 个文件)约 1 秒完成。**最佳实践:** 在改动前运行 `query` 了解现状并规划方案,改动期间避免执行 `query` 以防止不必要地触发索引重构。
|
|
167
190
|
|
|
168
191
|
## 许可证
|