entrygraph 0.1.26__tar.gz → 0.1.28__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.
- {entrygraph-0.1.26 → entrygraph-0.1.28}/PKG-INFO +1 -1
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/_version.py +2 -2
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/api.py +23 -3
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/cli/main.py +10 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/data/sinks/csharp.toml +23 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/data/sinks/go.toml +30 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/data/sinks/java.toml +23 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/data/sinks/php.toml +44 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/data/sinks/ruby.toml +30 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/data/sinks/rust.toml +16 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/detect/entrypoints/base.py +11 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/detect/entrypoints/csharp.py +22 -3
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/detect/entrypoints/golang.py +7 -8
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/detect/entrypoints/java.py +41 -4
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/detect/entrypoints/javascript.py +26 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/detect/entrypoints/python.py +8 -2
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/extract/javascript.py +18 -9
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/graph/adjacency.py +49 -9
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/graph/cte.py +9 -6
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/pipeline/scanner.py +61 -10
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/resolve/hierarchy.py +9 -1
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/resolve/resolver.py +18 -6
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/resolve/symbol_table.py +23 -4
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_entrypoint_expansion.py +175 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_extract_go.py +30 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_extract_java.py +6 -5
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_extract_javascript.py +27 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_indexer.py +27 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_reachability.py +34 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_resolver.py +82 -0
- entrygraph-0.1.28/tests/test_sanitizer_languages.py +56 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_taint.py +25 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/.github/workflows/ci.yml +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/.github/workflows/release.yml +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/.gitignore +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/LICENSE +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/README.md +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/RELEASING.md +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/pyproject.toml +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/__init__.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/__main__.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/cli/__init__.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/cli/render.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/data/sinks/javascript.toml +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/data/sinks/lib_javascript.toml +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/data/sinks/lib_python.toml +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/data/sinks/python.toml +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/db/__init__.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/db/engine.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/db/meta.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/db/models.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/db/queries.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/detect/__init__.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/detect/entrypoints/__init__.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/detect/entrypoints/configs.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/detect/entrypoints/php.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/detect/entrypoints/ruby.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/detect/entrypoints/rust.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/detect/frameworks.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/detect/manifests.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/detect/taint.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/errors.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/extract/__init__.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/extract/base.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/extract/csharp.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/extract/golang.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/extract/ir.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/extract/java.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/extract/php.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/extract/python.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/extract/registry.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/extract/ruby.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/extract/rust.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/fs/__init__.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/fs/hashing.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/fs/lang.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/fs/walker.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/graph/__init__.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/graph/scoring.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/kinds.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/parsing/__init__.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/parsing/parsers.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/parsing/queries.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/pipeline/__init__.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/pipeline/worker.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/pipeline/writer.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/py.typed +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/csharp/calls.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/csharp/definitions.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/csharp/imports.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/go/calls.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/go/definitions.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/go/imports.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/java/calls.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/java/definitions.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/java/imports.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/javascript/calls.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/javascript/definitions.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/javascript/imports.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/php/calls.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/php/definitions.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/php/imports.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/python/calls.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/python/definitions.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/python/imports.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/ruby/calls.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/ruby/definitions.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/ruby/imports.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/rust/calls.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/rust/definitions.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/queries/rust/imports.scm +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/resolve/__init__.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/resolve/externals.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/src/entrygraph/results.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/conftest.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/csharp/aspnet_app/Controllers/ReportsController.cs +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/csharp/aspnet_app/Program.cs +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/csharp/aspnet_app/Services/ReportService.cs +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/csharp/aspnet_app/app.csproj +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/csharp/minimalapi_app/Program.cs +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/csharp/minimalapi_app/app.csproj +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/go/gin_app/go.mod +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/go/gin_app/main.go +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/go/gin_app/service.go +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/go/nethttp_app/go.mod +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/go/nethttp_app/main.go +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/java/methodref_app/pom.xml +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/java/methodref_app/src/main/java/com/example/App.java +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/java/spring_app/pom.xml +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/java/spring_app/src/main/java/com/example/Application.java +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/java/spring_app/src/main/java/com/example/ReportRunner.java +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/java/spring_app/src/main/java/com/example/ReportService.java +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/java/spring_app/src/main/java/com/example/UserController.java +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/javascript/commonjs_app/server.js +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/javascript/express_app/package.json +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/javascript/express_app/src/routes.js +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/javascript/express_app/src/services.js +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/php/laravel_app/app/Http/Controllers/ReportController.php +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/php/laravel_app/artisan +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/php/laravel_app/composer.json +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/php/laravel_app/routes/web.php +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/python/chained_sinks/app.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/python/flask_app/app/__init__.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/python/flask_app/app/db.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/python/flask_app/app/routes.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/python/flask_app/app/services.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/python/flask_app/cli.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/python/flask_app/requirements.txt +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/python/fuzzy_sink/app.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/python/heal_fidelity/caller.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/python/heal_fidelity/worker.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/python/may_continue/app.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/python/sanitizer/app.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/python/taint_source/handler.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/ruby/sinatra_app/Gemfile +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/ruby/sinatra_app/app.rb +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/ruby/sinatra_app/services/runner.rb +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/rust/axum_app/Cargo.toml +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/rust/axum_app/src/handlers.rs +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/rust/axum_app/src/main.rs +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/rust/axum_callback_app/Cargo.toml +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/rust/axum_callback_app/src/main.rs +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/rust/scoped_sink_app/Cargo.toml +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/fixtures/rust/scoped_sink_app/src/main.rs +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_api.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_cli.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_commonjs.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_csharp_callbacks.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_cte_bounds.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_engine_pragmas.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_entrypoints.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_extract_csharp.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_extract_php.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_extract_python.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_extract_ruby.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_extract_rust.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_frameworks.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_fuzzy_sink.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_go_callbacks.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_hardening.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_heal_fidelity.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_incremental.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_java_callbacks.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_lang.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_manifests.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_may_continue.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_models.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_pool.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_registry_cache.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_render.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_rust_callbacks.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_rust_scoped_sinks.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_scoring.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_sink_catalog.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_taint_sanitizers.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_taint_sources.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/tests/test_walker.py +0 -0
- {entrygraph-0.1.26 → entrygraph-0.1.28}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: entrygraph
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.28
|
|
4
4
|
Summary: Language-agnostic code graph: query symbols, entrypoints, and source-to-sink call paths from a SQLite index
|
|
5
5
|
Project-URL: Repository, https://github.com/brettbergin/entrygraph
|
|
6
6
|
Author-email: Brett Bergin <brettberginbc@yahoo.com>
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.1.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
21
|
+
__version__ = version = '0.1.28'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 28)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -88,6 +88,17 @@ class _FilteredAdjacency:
|
|
|
88
88
|
)
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
class PathResults(list):
|
|
92
|
+
"""CallPath list that also reports whether enumeration was budget-truncated.
|
|
93
|
+
|
|
94
|
+
It is a plain list for every existing use (iteration, len, indexing); the
|
|
95
|
+
`truncated` flag lets the CLI warn when 0 paths may mean "budget spent", not
|
|
96
|
+
"no reachable sink".
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
truncated: bool = False
|
|
100
|
+
|
|
101
|
+
|
|
91
102
|
def _has_sanitizer(path: CallPath) -> bool:
|
|
92
103
|
"""True if a category sanitizer is called on the path (heuristic, no dataflow).
|
|
93
104
|
|
|
@@ -317,11 +328,15 @@ class CodeGraph:
|
|
|
317
328
|
sources = self._source_ids(session, source, source_category)
|
|
318
329
|
sinks = self._sink_ids(session, sink, sink_category)
|
|
319
330
|
if not sources or not sinks:
|
|
320
|
-
return
|
|
331
|
+
return PathResults()
|
|
321
332
|
traverser = self._traverser(session, engine, kinds, floor, include_cha)
|
|
322
333
|
raw_paths = traverser.paths(sources, sinks, max_depth=max_depth, max_paths=max_paths)
|
|
323
334
|
if not raw_paths:
|
|
324
|
-
|
|
335
|
+
# 0 paths may mean the visit budget was spent before any sink was
|
|
336
|
+
# reached — propagate the flag so this isn't mistaken for "safe".
|
|
337
|
+
empty = PathResults()
|
|
338
|
+
empty.truncated = bool(getattr(raw_paths, "truncated", False))
|
|
339
|
+
return empty
|
|
325
340
|
all_ids = {node for path in raw_paths for node, _ in path}
|
|
326
341
|
symbol_map = q.symbols_by_ids(session, all_ids)
|
|
327
342
|
registry = self._registry(session)
|
|
@@ -347,7 +362,12 @@ class CodeGraph:
|
|
|
347
362
|
results.sort(
|
|
348
363
|
key=lambda p: (-(p.risk_score or 0.0), len(p.symbols), [s.id for s in p.symbols])
|
|
349
364
|
)
|
|
350
|
-
return
|
|
365
|
+
# Enumeration collected a candidate pool; return the top max_paths by risk.
|
|
366
|
+
# Truncating after the risk rank (not during DFS) is what makes the widen
|
|
367
|
+
# flags monotonic — a wider edge set can only add candidates to rank.
|
|
368
|
+
out = PathResults(results[:max_paths])
|
|
369
|
+
out.truncated = bool(getattr(raw_paths, "truncated", False))
|
|
370
|
+
return out
|
|
351
371
|
|
|
352
372
|
def reachable(
|
|
353
373
|
self,
|
|
@@ -8,6 +8,7 @@ directly testable.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
import argparse
|
|
11
|
+
import sys
|
|
11
12
|
from dataclasses import asdict
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
|
|
@@ -277,6 +278,15 @@ def cmd_paths(args) -> int:
|
|
|
277
278
|
include_callbacks=args.include_callbacks,
|
|
278
279
|
prune_sanitized=args.prune_sanitized,
|
|
279
280
|
)
|
|
281
|
+
truncated = bool(getattr(paths, "truncated", False))
|
|
282
|
+
if truncated:
|
|
283
|
+
# A budget-truncated search may have missed paths — never let an empty or
|
|
284
|
+
# short result read as a clean "no reachable sinks" on a large graph.
|
|
285
|
+
print(
|
|
286
|
+
"warning: path search hit its work budget and may be incomplete; "
|
|
287
|
+
"narrow --source/--sink or lower --max-depth for a complete answer.",
|
|
288
|
+
file=sys.stderr,
|
|
289
|
+
)
|
|
280
290
|
if args.json:
|
|
281
291
|
print(
|
|
282
292
|
to_json(
|
|
@@ -104,3 +104,26 @@ id = "csharp.env"
|
|
|
104
104
|
category = "env_input"
|
|
105
105
|
callee = "cs:Environment.GetEnvironmentVariable"
|
|
106
106
|
description = "Environment variable input"
|
|
107
|
+
|
|
108
|
+
# ---------------- sanitizers ----------------
|
|
109
|
+
|
|
110
|
+
[[sanitizer]]
|
|
111
|
+
id = "cs.sanitize.path-getfilename"
|
|
112
|
+
category = "path_traversal"
|
|
113
|
+
callee = "cs:*.{GetFileName,GetFullPath}"
|
|
114
|
+
effect = "reduces"
|
|
115
|
+
description = "Path.GetFileName / GetFullPath normalization"
|
|
116
|
+
|
|
117
|
+
[[sanitizer]]
|
|
118
|
+
id = "cs.sanitize.uri-escape"
|
|
119
|
+
category = "ssrf"
|
|
120
|
+
callee = "cs:*.{EscapeDataString,EscapeUriString}"
|
|
121
|
+
effect = "reduces"
|
|
122
|
+
description = "Uri.EscapeDataString URL encoding"
|
|
123
|
+
|
|
124
|
+
[[sanitizer]]
|
|
125
|
+
id = "cs.sanitize.sql-parameter"
|
|
126
|
+
category = "sql"
|
|
127
|
+
callee = "cs:*.{AddWithValue}"
|
|
128
|
+
effect = "reduces"
|
|
129
|
+
description = "Parameterized query value binding (SqlParameter)"
|
|
@@ -85,3 +85,33 @@ id = "go.env"
|
|
|
85
85
|
category = "env_input"
|
|
86
86
|
callee = "go:os.Getenv"
|
|
87
87
|
description = "Environment variable input"
|
|
88
|
+
|
|
89
|
+
# ---------------- sanitizers ----------------
|
|
90
|
+
|
|
91
|
+
[[sanitizer]]
|
|
92
|
+
id = "go.sanitize.filepath-clean"
|
|
93
|
+
category = "path_traversal"
|
|
94
|
+
callee = "go:{path/filepath.Clean,path.Clean,path/filepath.Base,path.Base}"
|
|
95
|
+
effect = "reduces"
|
|
96
|
+
description = "Path normalization / basename"
|
|
97
|
+
|
|
98
|
+
[[sanitizer]]
|
|
99
|
+
id = "go.sanitize.html-escape"
|
|
100
|
+
category = "template_injection"
|
|
101
|
+
callee = "go:{html.EscapeString,template.HTMLEscapeString}"
|
|
102
|
+
effect = "reduces"
|
|
103
|
+
description = "HTML escaping"
|
|
104
|
+
|
|
105
|
+
[[sanitizer]]
|
|
106
|
+
id = "go.sanitize.url-escape"
|
|
107
|
+
category = "ssrf"
|
|
108
|
+
callee = "go:{url.QueryEscape,url.PathEscape}"
|
|
109
|
+
effect = "reduces"
|
|
110
|
+
description = "URL component escaping"
|
|
111
|
+
|
|
112
|
+
[[sanitizer]]
|
|
113
|
+
id = "go.sanitize.strconv-sql"
|
|
114
|
+
category = "sql"
|
|
115
|
+
callee = "go:{strconv.Atoi,strconv.ParseInt,strconv.ParseUint,strconv.ParseFloat}"
|
|
116
|
+
effect = "neutralizes"
|
|
117
|
+
description = "Numeric parse — a parsed number cannot carry SQL injection"
|
|
@@ -90,3 +90,26 @@ id = "java.env"
|
|
|
90
90
|
category = "env_input"
|
|
91
91
|
callee = "java:System.getenv"
|
|
92
92
|
description = "Environment variable input"
|
|
93
|
+
|
|
94
|
+
# ---------------- sanitizers ----------------
|
|
95
|
+
|
|
96
|
+
[[sanitizer]]
|
|
97
|
+
id = "java.sanitize.html-encode"
|
|
98
|
+
category = "template_injection"
|
|
99
|
+
callee = "java:*.{forHtml,forHtmlContent,forJavaScript,escapeHtml4,escapeHtml,escapeXml11,htmlEscape}"
|
|
100
|
+
effect = "reduces"
|
|
101
|
+
description = "OWASP Encoder / commons-text / Spring HTML escaping"
|
|
102
|
+
|
|
103
|
+
[[sanitizer]]
|
|
104
|
+
id = "java.sanitize.path-normalize"
|
|
105
|
+
category = "path_traversal"
|
|
106
|
+
callee = "java:*.{getFileName,normalize,getName}"
|
|
107
|
+
effect = "reduces"
|
|
108
|
+
description = "Path normalization / filename extraction (Path.getFileName, FilenameUtils.getName)"
|
|
109
|
+
|
|
110
|
+
[[sanitizer]]
|
|
111
|
+
id = "java.sanitize.url-encode"
|
|
112
|
+
category = "ssrf"
|
|
113
|
+
callee = "java:*.{urlEncode,encode}"
|
|
114
|
+
effect = "reduces"
|
|
115
|
+
description = "URL encoding (URLEncoder.encode)"
|
|
@@ -123,3 +123,47 @@ id = "php.laravel.request"
|
|
|
123
123
|
category = "http_input"
|
|
124
124
|
callee = "php:*.{input,query,post,cookie,header}"
|
|
125
125
|
description = "Laravel request accessors"
|
|
126
|
+
|
|
127
|
+
# ---------------- sanitizers ----------------
|
|
128
|
+
|
|
129
|
+
[[sanitizer]]
|
|
130
|
+
id = "php.sanitize.escapeshell"
|
|
131
|
+
category = "command_exec"
|
|
132
|
+
callee = "php:{escapeshellarg,escapeshellcmd}"
|
|
133
|
+
effect = "neutralizes"
|
|
134
|
+
description = "Shell argument/command escaping"
|
|
135
|
+
|
|
136
|
+
[[sanitizer]]
|
|
137
|
+
id = "php.sanitize.htmlspecialchars"
|
|
138
|
+
category = "template_injection"
|
|
139
|
+
callee = "php:{htmlspecialchars,htmlentities,strip_tags}"
|
|
140
|
+
effect = "reduces"
|
|
141
|
+
description = "HTML escaping"
|
|
142
|
+
|
|
143
|
+
[[sanitizer]]
|
|
144
|
+
id = "php.sanitize.sql-escape"
|
|
145
|
+
category = "sql"
|
|
146
|
+
callee = "php:{mysqli_real_escape_string,pg_escape_string,pg_escape_literal}"
|
|
147
|
+
effect = "reduces"
|
|
148
|
+
description = "SQL string escaping"
|
|
149
|
+
|
|
150
|
+
[[sanitizer]]
|
|
151
|
+
id = "php.sanitize.sql-quote"
|
|
152
|
+
category = "sql"
|
|
153
|
+
callee = "php:*.{quote,real_escape_string}"
|
|
154
|
+
effect = "reduces"
|
|
155
|
+
description = "PDO::quote / mysqli::real_escape_string"
|
|
156
|
+
|
|
157
|
+
[[sanitizer]]
|
|
158
|
+
id = "php.sanitize.intval"
|
|
159
|
+
category = "sql"
|
|
160
|
+
callee = "php:{intval,floatval}"
|
|
161
|
+
effect = "neutralizes"
|
|
162
|
+
description = "Numeric cast — a parsed number cannot carry SQL injection"
|
|
163
|
+
|
|
164
|
+
[[sanitizer]]
|
|
165
|
+
id = "php.sanitize.basename"
|
|
166
|
+
category = "path_traversal"
|
|
167
|
+
callee = "php:{basename,realpath}"
|
|
168
|
+
effect = "reduces"
|
|
169
|
+
description = "Basename / realpath resolution"
|
|
@@ -100,3 +100,33 @@ category = "env_input"
|
|
|
100
100
|
callee = "rb:*.{fetch}"
|
|
101
101
|
require_arg_hint = "ENV"
|
|
102
102
|
description = "Environment variable input"
|
|
103
|
+
|
|
104
|
+
# ---------------- sanitizers ----------------
|
|
105
|
+
|
|
106
|
+
[[sanitizer]]
|
|
107
|
+
id = "rb.sanitize.shellwords"
|
|
108
|
+
category = "command_exec"
|
|
109
|
+
callee = "rb:*.{shellescape}"
|
|
110
|
+
effect = "neutralizes"
|
|
111
|
+
description = "Shell argument quoting (Shellwords.escape / String#shellescape)"
|
|
112
|
+
|
|
113
|
+
[[sanitizer]]
|
|
114
|
+
id = "rb.sanitize.html-escape"
|
|
115
|
+
category = "template_injection"
|
|
116
|
+
callee = "rb:{CGI.escapeHTML,ERB.Util.html_escape,ERB.Util.h,Rack.Utils.escape_html}"
|
|
117
|
+
effect = "reduces"
|
|
118
|
+
description = "HTML escaping"
|
|
119
|
+
|
|
120
|
+
[[sanitizer]]
|
|
121
|
+
id = "rb.sanitize.sql"
|
|
122
|
+
category = "sql"
|
|
123
|
+
callee = "rb:*.{sanitize_sql,sanitize_sql_array,sanitize_sql_for_conditions,quote}"
|
|
124
|
+
effect = "reduces"
|
|
125
|
+
description = "ActiveRecord SQL sanitization"
|
|
126
|
+
|
|
127
|
+
[[sanitizer]]
|
|
128
|
+
id = "rb.sanitize.basename"
|
|
129
|
+
category = "path_traversal"
|
|
130
|
+
callee = "rb:{File.basename}"
|
|
131
|
+
effect = "reduces"
|
|
132
|
+
description = "Basename extraction"
|
|
@@ -66,3 +66,19 @@ id = "rust.stdin"
|
|
|
66
66
|
category = "stdin_input"
|
|
67
67
|
callee = "rs:{std.io.stdin,io.stdin}"
|
|
68
68
|
description = "Standard input"
|
|
69
|
+
|
|
70
|
+
# ---------------- sanitizers ----------------
|
|
71
|
+
|
|
72
|
+
[[sanitizer]]
|
|
73
|
+
id = "rs.sanitize.shell-escape"
|
|
74
|
+
category = "command_exec"
|
|
75
|
+
callee = "rs:{shell_escape.escape,shell_escape.unix.escape,shlex.quote,shlex.try_quote}"
|
|
76
|
+
effect = "neutralizes"
|
|
77
|
+
description = "Shell argument quoting (shell-escape / shlex crates)"
|
|
78
|
+
|
|
79
|
+
[[sanitizer]]
|
|
80
|
+
id = "rs.sanitize.path-filename"
|
|
81
|
+
category = "path_traversal"
|
|
82
|
+
callee = "rs:*.{file_name,canonicalize}"
|
|
83
|
+
effect = "reduces"
|
|
84
|
+
description = "Path::file_name / canonicalize"
|
|
@@ -74,6 +74,17 @@ def first_string_arg(decorator_text: str) -> str | None:
|
|
|
74
74
|
return match.group(1) if match else None
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
def compose_route(prefix: str | None, path: str | None) -> str:
|
|
78
|
+
"""Join a class/group-level route prefix with a method-level path.
|
|
79
|
+
|
|
80
|
+
Either side may be None/empty or carry stray leading/trailing slashes. The
|
|
81
|
+
result is a single slash-prefixed route ("/" when both are empty), matching
|
|
82
|
+
the NestJS rule so composed routes read consistently across frameworks.
|
|
83
|
+
"""
|
|
84
|
+
parts = [seg.strip("/") for seg in (prefix, path) if seg and seg.strip("/")]
|
|
85
|
+
return "/" + "/".join(parts)
|
|
86
|
+
|
|
87
|
+
|
|
77
88
|
def methods_kwarg(decorator_text: str) -> list[str]:
|
|
78
89
|
match = _METHODS_KWARG.search(decorator_text)
|
|
79
90
|
if not match:
|
|
@@ -14,6 +14,7 @@ import re
|
|
|
14
14
|
|
|
15
15
|
from entrygraph.detect.entrypoints.base import (
|
|
16
16
|
EntrypointRule,
|
|
17
|
+
compose_route,
|
|
17
18
|
first_string_arg,
|
|
18
19
|
register,
|
|
19
20
|
)
|
|
@@ -48,14 +49,32 @@ def _is_controller(t: RawSymbol) -> bool:
|
|
|
48
49
|
return any("Controller" in b for b in t.bases)
|
|
49
50
|
|
|
50
51
|
|
|
52
|
+
def _controller_token(qname: str) -> str:
|
|
53
|
+
"""The `[controller]` route-token value: the class name minus a Controller suffix."""
|
|
54
|
+
name = qname.rsplit(".", 1)[-1]
|
|
55
|
+
return name[: -len("Controller")] if name.endswith("Controller") else name
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _class_route_prefix(t: RawSymbol) -> str:
|
|
59
|
+
"""A controller's class-level [Route("...")] prefix, with [controller] expanded."""
|
|
60
|
+
route_dec = next((d for d in t.decorators if _ROUTE_ATTR.match(d)), None)
|
|
61
|
+
prefix = first_string_arg(route_dec) if route_dec else None
|
|
62
|
+
if not prefix:
|
|
63
|
+
return ""
|
|
64
|
+
return prefix.replace("[controller]", _controller_token(t.qualified_name))
|
|
65
|
+
|
|
66
|
+
|
|
51
67
|
def _aspnet_controller_routes(x: FileExtraction) -> list[EntrypointHint]:
|
|
52
|
-
|
|
68
|
+
controller_syms = [t for t in _type_symbols(x) if _is_controller(t)]
|
|
69
|
+
controllers = {t.qualified_name for t in controller_syms}
|
|
53
70
|
if not controllers:
|
|
54
71
|
return []
|
|
72
|
+
prefixes = {t.qualified_name: _class_route_prefix(t) for t in controller_syms}
|
|
55
73
|
hints: list[EntrypointHint] = []
|
|
56
74
|
for method in _methods(x):
|
|
57
75
|
if method.parent_qualified_name not in controllers:
|
|
58
76
|
continue
|
|
77
|
+
prefix = prefixes.get(method.parent_qualified_name or "", "")
|
|
59
78
|
for decorator in method.decorators:
|
|
60
79
|
m = _HTTP_ATTR.match(decorator)
|
|
61
80
|
if m:
|
|
@@ -64,7 +83,7 @@ def _aspnet_controller_routes(x: FileExtraction) -> list[EntrypointHint]:
|
|
|
64
83
|
rule_id="csharp.aspnet.controller-route",
|
|
65
84
|
kind=EntrypointKind.HTTP_ROUTE,
|
|
66
85
|
handler_qualified_name=method.qualified_name,
|
|
67
|
-
route=first_string_arg(decorator)
|
|
86
|
+
route=compose_route(prefix, first_string_arg(decorator)),
|
|
68
87
|
http_methods=[m.group(1).upper()],
|
|
69
88
|
framework="aspnetcore",
|
|
70
89
|
)
|
|
@@ -79,7 +98,7 @@ def _aspnet_controller_routes(x: FileExtraction) -> list[EntrypointHint]:
|
|
|
79
98
|
rule_id="csharp.aspnet.controller-route",
|
|
80
99
|
kind=EntrypointKind.HTTP_ROUTE,
|
|
81
100
|
handler_qualified_name=method.qualified_name,
|
|
82
|
-
route=first_string_arg(route_dec)
|
|
101
|
+
route=compose_route(prefix, first_string_arg(route_dec)),
|
|
83
102
|
http_methods=["*"],
|
|
84
103
|
framework="aspnetcore",
|
|
85
104
|
)
|
|
@@ -11,8 +11,11 @@ from entrygraph.detect.entrypoints.base import (
|
|
|
11
11
|
from entrygraph.extract.ir import EntrypointHint, FileExtraction
|
|
12
12
|
from entrygraph.kinds import EntrypointKind, SymbolKind
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
# gin exports UPPERCASE verbs (r.GET); chi/fiber use TitleCase (r.Get). Accept
|
|
15
|
+
# both so the shared _gin_style matcher recognizes chi/fiber, not just gin.
|
|
16
|
+
_HTTP_VERBS = frozenset({"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"})
|
|
17
|
+
_GIN_METHODS = (
|
|
18
|
+
_HTTP_VERBS | frozenset(v.title() for v in _HTTP_VERBS) | frozenset({"Any", "Handle"})
|
|
16
19
|
)
|
|
17
20
|
_NETHTTP_REGISTER = frozenset({"HandleFunc", "Handle"})
|
|
18
21
|
|
|
@@ -73,12 +76,8 @@ def _gin_routes(x: FileExtraction) -> list[EntrypointHint]:
|
|
|
73
76
|
):
|
|
74
77
|
route = first_string_arg("(" + ref.arg_preview.lstrip("("))
|
|
75
78
|
if route is not None and route.startswith("/"):
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if ref.callee_name
|
|
79
|
-
in {"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"}
|
|
80
|
-
else "*"
|
|
81
|
-
)
|
|
79
|
+
verb = ref.callee_name.upper()
|
|
80
|
+
method = verb if verb in _HTTP_VERBS else "*"
|
|
82
81
|
hints.append(
|
|
83
82
|
EntrypointHint(
|
|
84
83
|
rule_id="go.gin.route",
|
|
@@ -13,6 +13,7 @@ import re
|
|
|
13
13
|
|
|
14
14
|
from entrygraph.detect.entrypoints.base import (
|
|
15
15
|
EntrypointRule,
|
|
16
|
+
compose_route,
|
|
16
17
|
first_string_arg,
|
|
17
18
|
register,
|
|
18
19
|
)
|
|
@@ -38,6 +39,10 @@ def _annotation_names(decorators: list[str], pattern: re.Pattern) -> bool:
|
|
|
38
39
|
return any(pattern.match(d) for d in decorators)
|
|
39
40
|
|
|
40
41
|
|
|
42
|
+
def _class_prefix(qname: str | None, prefixes: dict[str, str]) -> str:
|
|
43
|
+
return prefixes.get(qname or "", "")
|
|
44
|
+
|
|
45
|
+
|
|
41
46
|
def _type_symbols(x: FileExtraction) -> list[RawSymbol]:
|
|
42
47
|
return [s for s in x.symbols if s.kind in (SymbolKind.CLASS, SymbolKind.INTERFACE)]
|
|
43
48
|
|
|
@@ -50,6 +55,17 @@ def _enclosing_type(method: RawSymbol, types: list[RawSymbol]) -> RawSymbol | No
|
|
|
50
55
|
return next((t for t in types if t.qualified_name == method.parent_qualified_name), None)
|
|
51
56
|
|
|
52
57
|
|
|
58
|
+
def _class_route_prefixes(types: list[RawSymbol], pattern: re.Pattern) -> dict[str, str]:
|
|
59
|
+
"""Map controller/resource qname -> its class-level route prefix (via `pattern`)."""
|
|
60
|
+
prefixes: dict[str, str] = {}
|
|
61
|
+
for t in types:
|
|
62
|
+
for decorator in t.decorators:
|
|
63
|
+
if pattern.match(decorator):
|
|
64
|
+
prefixes[t.qualified_name] = first_string_arg(decorator) or ""
|
|
65
|
+
break
|
|
66
|
+
return prefixes
|
|
67
|
+
|
|
68
|
+
|
|
53
69
|
def _spring_routes(x: FileExtraction) -> list[EntrypointHint]:
|
|
54
70
|
types = _type_symbols(x)
|
|
55
71
|
controllers = {
|
|
@@ -57,6 +73,10 @@ def _spring_routes(x: FileExtraction) -> list[EntrypointHint]:
|
|
|
57
73
|
}
|
|
58
74
|
if not controllers:
|
|
59
75
|
return []
|
|
76
|
+
# A controller's own @RequestMapping("/api") is its route prefix.
|
|
77
|
+
prefixes = _class_route_prefixes(
|
|
78
|
+
[t for t in types if t.qualified_name in controllers], _SPRING_MAPPING
|
|
79
|
+
)
|
|
60
80
|
hints = []
|
|
61
81
|
for method in _methods(x):
|
|
62
82
|
if method.parent_qualified_name not in controllers:
|
|
@@ -67,12 +87,15 @@ def _spring_routes(x: FileExtraction) -> list[EntrypointHint]:
|
|
|
67
87
|
continue
|
|
68
88
|
verb = _MAPPING_VERB.get(match.group(1))
|
|
69
89
|
methods = [verb] if verb else ["*"]
|
|
90
|
+
route = compose_route(
|
|
91
|
+
_class_prefix(method.parent_qualified_name, prefixes), first_string_arg(decorator)
|
|
92
|
+
)
|
|
70
93
|
hints.append(
|
|
71
94
|
EntrypointHint(
|
|
72
95
|
rule_id="java.spring.route",
|
|
73
96
|
kind=EntrypointKind.HTTP_ROUTE,
|
|
74
97
|
handler_qualified_name=method.qualified_name,
|
|
75
|
-
route=
|
|
98
|
+
route=route,
|
|
76
99
|
http_methods=methods,
|
|
77
100
|
framework="spring-boot",
|
|
78
101
|
)
|
|
@@ -85,6 +108,10 @@ def _jaxrs_routes(x: FileExtraction) -> list[EntrypointHint]:
|
|
|
85
108
|
resources = {t.qualified_name for t in types if _annotation_names(t.decorators, _JAXRS_PATH)}
|
|
86
109
|
if not resources:
|
|
87
110
|
return []
|
|
111
|
+
# A resource class's own @Path("/api") prefixes every sub-resource method.
|
|
112
|
+
prefixes = _class_route_prefixes(
|
|
113
|
+
[t for t in types if t.qualified_name in resources], _JAXRS_PATH
|
|
114
|
+
)
|
|
88
115
|
hints = []
|
|
89
116
|
for method in _methods(x):
|
|
90
117
|
if method.parent_qualified_name not in resources:
|
|
@@ -95,13 +122,16 @@ def _jaxrs_routes(x: FileExtraction) -> list[EntrypointHint]:
|
|
|
95
122
|
)
|
|
96
123
|
if verb is None:
|
|
97
124
|
continue
|
|
98
|
-
|
|
125
|
+
method_path = next(
|
|
126
|
+
(first_string_arg(d) for d in method.decorators if _JAXRS_PATH.match(d)), None
|
|
127
|
+
)
|
|
128
|
+
route = compose_route(_class_prefix(method.parent_qualified_name, prefixes), method_path)
|
|
99
129
|
hints.append(
|
|
100
130
|
EntrypointHint(
|
|
101
131
|
rule_id="java.jaxrs",
|
|
102
132
|
kind=EntrypointKind.HTTP_ROUTE,
|
|
103
133
|
handler_qualified_name=method.qualified_name,
|
|
104
|
-
route=route
|
|
134
|
+
route=route,
|
|
105
135
|
http_methods=[verb],
|
|
106
136
|
framework="jax-rs",
|
|
107
137
|
)
|
|
@@ -121,6 +151,9 @@ def _micronaut_routes(x: FileExtraction) -> list[EntrypointHint]:
|
|
|
121
151
|
}
|
|
122
152
|
if not controllers:
|
|
123
153
|
return []
|
|
154
|
+
prefixes = _class_route_prefixes(
|
|
155
|
+
[t for t in types if t.qualified_name in controllers], _MICRONAUT_CONTROLLER
|
|
156
|
+
)
|
|
124
157
|
hints = []
|
|
125
158
|
for method in _methods(x):
|
|
126
159
|
if method.parent_qualified_name not in controllers:
|
|
@@ -128,12 +161,16 @@ def _micronaut_routes(x: FileExtraction) -> list[EntrypointHint]:
|
|
|
128
161
|
for decorator in method.decorators:
|
|
129
162
|
m = _MICRONAUT_MAPPING.match(decorator)
|
|
130
163
|
if m:
|
|
164
|
+
route = compose_route(
|
|
165
|
+
_class_prefix(method.parent_qualified_name, prefixes),
|
|
166
|
+
first_string_arg(decorator),
|
|
167
|
+
)
|
|
131
168
|
hints.append(
|
|
132
169
|
EntrypointHint(
|
|
133
170
|
rule_id="java.micronaut.route",
|
|
134
171
|
kind=EntrypointKind.HTTP_ROUTE,
|
|
135
172
|
handler_qualified_name=method.qualified_name,
|
|
136
|
-
route=
|
|
173
|
+
route=route,
|
|
137
174
|
http_methods=[m.group(1).upper()],
|
|
138
175
|
framework="micronaut",
|
|
139
176
|
)
|
|
@@ -14,6 +14,31 @@ _NEST_DECORATOR = re.compile(r"^@(Get|Post|Put|Delete|Patch|All)\b")
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
_TRAILING_IDENT = re.compile(r",\s*([A-Za-z_$][\w$]*)\s*\)?\s*$")
|
|
17
|
+
_RECEIVER_ROOT = re.compile(r"^\s*([A-Za-z_$][\w$]*)")
|
|
18
|
+
# HTTP-client and test-harness roots whose .get()/.post() are *requests*, not
|
|
19
|
+
# route registrations: supertest/nock/chai/superagent and Node/browser clients.
|
|
20
|
+
# Excludes them so test suites don't flood the route table (ghost 72%, strapi 99%).
|
|
21
|
+
_CLIENT_ROOTS = frozenset(
|
|
22
|
+
{
|
|
23
|
+
"nock",
|
|
24
|
+
"supertest",
|
|
25
|
+
"request",
|
|
26
|
+
"superagent",
|
|
27
|
+
"agent",
|
|
28
|
+
"chai",
|
|
29
|
+
"axios",
|
|
30
|
+
"got",
|
|
31
|
+
"fetch",
|
|
32
|
+
"http",
|
|
33
|
+
"https",
|
|
34
|
+
"fastify",
|
|
35
|
+
} # fastify.inject(...).get is a test call
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _receiver_root(receiver_text: str) -> str | None:
|
|
40
|
+
m = _RECEIVER_ROOT.match(receiver_text)
|
|
41
|
+
return m.group(1) if m else None
|
|
17
42
|
|
|
18
43
|
|
|
19
44
|
def _router_routes(framework: str):
|
|
@@ -29,6 +54,7 @@ def _router_routes(framework: str):
|
|
|
29
54
|
if (
|
|
30
55
|
ref.kind == "call"
|
|
31
56
|
and ref.receiver_text is not None
|
|
57
|
+
and _receiver_root(ref.receiver_text) not in _CLIENT_ROOTS
|
|
32
58
|
and ref.callee_name in _HTTP_METHODS
|
|
33
59
|
and ref.arg_preview
|
|
34
60
|
):
|
|
@@ -53,8 +53,14 @@ def _flask_routes(x: FileExtraction) -> list[EntrypointHint]:
|
|
|
53
53
|
else:
|
|
54
54
|
verb = _HTTP_VERB.match(decorator)
|
|
55
55
|
if verb:
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
candidate = first_string_arg(decorator)
|
|
57
|
+
# The @app.get/@router.post shorthand shares its shape with
|
|
58
|
+
# @mock.patch("dotted.target"), which the verb `patch` matches.
|
|
59
|
+
# A real Flask/FastAPI route is a URL path ("/..."); a mock target
|
|
60
|
+
# is a dotted import path. Require the URL shape to exclude mocks.
|
|
61
|
+
if candidate is not None and candidate.startswith("/"):
|
|
62
|
+
route = candidate
|
|
63
|
+
methods = [verb.group(2).upper()]
|
|
58
64
|
if route is not None:
|
|
59
65
|
hints.append(
|
|
60
66
|
EntrypointHint(
|
|
@@ -36,6 +36,22 @@ _SCOPE_TYPES = frozenset(
|
|
|
36
36
|
_NAMED_SCOPES = frozenset({"function_declaration", "method_definition", "class_declaration"})
|
|
37
37
|
_SRC_ROOTS = ("src", "lib", "app")
|
|
38
38
|
_EXPORT_PARENTS = frozenset({"export_statement"})
|
|
39
|
+
_JS_EXTS = (".d.ts", ".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs", ".jsx")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _atomic_segment(filename: str) -> str:
|
|
43
|
+
"""A filename -> one module-path segment: strip a JS/TS extension, then map any
|
|
44
|
+
remaining internal dots to `_`.
|
|
45
|
+
|
|
46
|
+
A dotted stem like ``article.service`` must NOT become two dotted segments, or
|
|
47
|
+
``.split(".")`` in relative-import resolution mis-counts package depth and
|
|
48
|
+
duplicates a directory name (``article.article.article.service``) — issue #42.
|
|
49
|
+
"""
|
|
50
|
+
for ext in _JS_EXTS:
|
|
51
|
+
if filename.endswith(ext):
|
|
52
|
+
filename = filename[: -len(ext)]
|
|
53
|
+
break
|
|
54
|
+
return filename.replace(".", "_")
|
|
39
55
|
|
|
40
56
|
|
|
41
57
|
class JavaScriptExtractor:
|
|
@@ -45,11 +61,7 @@ class JavaScriptExtractor:
|
|
|
45
61
|
parts = repo_relative_path.split("/")
|
|
46
62
|
if parts[0] in _SRC_ROOTS and len(parts) > 1:
|
|
47
63
|
parts = parts[1:]
|
|
48
|
-
stem = parts[-1]
|
|
49
|
-
for ext in (".d.ts", ".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs", ".jsx"):
|
|
50
|
-
if stem.endswith(ext):
|
|
51
|
-
stem = stem[: -len(ext)]
|
|
52
|
-
break
|
|
64
|
+
stem = _atomic_segment(parts[-1])
|
|
53
65
|
is_package = stem in ("index", "mod")
|
|
54
66
|
parts = parts[:-1] if is_package else [*parts[:-1], stem]
|
|
55
67
|
return ".".join(p for p in parts if p) or "_root", is_package
|
|
@@ -358,10 +370,7 @@ class JavaScriptExtractor:
|
|
|
358
370
|
if segment == "..":
|
|
359
371
|
parts = parts[:-1]
|
|
360
372
|
else:
|
|
361
|
-
seg = segment
|
|
362
|
-
for ext in (".ts", ".tsx", ".js", ".jsx"):
|
|
363
|
-
if seg.endswith(ext):
|
|
364
|
-
seg = seg[: -len(ext)]
|
|
373
|
+
seg = _atomic_segment(segment)
|
|
365
374
|
if seg not in ("index", "mod"):
|
|
366
375
|
parts.append(seg)
|
|
367
376
|
return ".".join(p for p in parts if p) or "_root"
|