code2llm 0.5.176__tar.gz → 0.5.180__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.
- {code2llm-0.5.176 → code2llm-0.5.180}/MANIFEST.in +1 -0
- {code2llm-0.5.176/code2llm.egg-info → code2llm-0.5.180}/PKG-INFO +18 -4
- {code2llm-0.5.176 → code2llm-0.5.180}/README.md +4 -0
- code2llm-0.5.180/VERSION +1 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/__init__.py +1 -1
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/analysis/__init__.py +8 -0
- code2llm-0.5.180/code2llm/analysis/call_graph.py +16 -0
- code2llm-0.5.180/code2llm/analysis/call_graph_engine.py +448 -0
- code2llm-0.5.180/code2llm/analysis/complexity.py +285 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/analysis/coupling.py +17 -1
- code2llm-0.5.180/code2llm/analysis/native_bridge.py +285 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/analysis/pipeline_detector.py +21 -6
- code2llm-0.5.180/code2llm/analysis/smell_engine.py +363 -0
- code2llm-0.5.180/code2llm/analysis/smells.py +89 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/cli_exports/orchestrator.py +24 -4
- code2llm-0.5.180/code2llm/cli_exports/orchestrator_constants.py +126 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/analyzer.py +51 -13
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/file_analyzer.py +2 -27
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/file_cache.py +10 -5
- code2llm-0.5.180/code2llm/core/lang/_calls.py +20 -0
- code2llm-0.5.180/code2llm/core/lang/_complexity.py +30 -0
- code2llm-0.5.180/code2llm/core/lang/js_analysis.py +201 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/lang/typescript.py +3 -19
- code2llm-0.5.180/code2llm/core/models.py +227 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/persistent_cache.py +58 -17
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/refactoring.py +69 -32
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/toon/renderer.py +16 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/parsers/toon_parser.py +10 -4
- code2llm-0.5.180/code2llm/templates/extract_method.md +22 -0
- code2llm-0.5.180/code2llm/templates/move_method.md +19 -0
- {code2llm-0.5.176 → code2llm-0.5.180/code2llm.egg-info}/PKG-INFO +18 -4
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm.egg-info/SOURCES.txt +14 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm.egg-info/requires.txt +17 -3
- code2llm-0.5.180/code2llm.egg-info/top_level.txt +3 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/pyproject.toml +32 -21
- code2llm-0.5.180/tests/test_analysis_cache_version.py +122 -0
- code2llm-0.5.180/tests/test_call_graph_engine.py +130 -0
- code2llm-0.5.180/tests/test_complexity.py +204 -0
- code2llm-0.5.180/tests/test_export_cache_ownership.py +143 -0
- code2llm-0.5.180/tests/test_js_complexity_boundaries.py +115 -0
- code2llm-0.5.180/tests/test_native_rust_bridge.py +357 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_orchestrator_cache_mtime.py +3 -2
- code2llm-0.5.180/tests/test_smell_engine.py +167 -0
- code2llm-0.5.176/VERSION +0 -1
- code2llm-0.5.176/code2llm/analysis/call_graph.py +0 -208
- code2llm-0.5.176/code2llm/analysis/smells.py +0 -284
- code2llm-0.5.176/code2llm/cli_exports/orchestrator_constants.py +0 -55
- code2llm-0.5.176/code2llm/core/lang/_calls.py +0 -96
- code2llm-0.5.176/code2llm/core/lang/_complexity.py +0 -73
- code2llm-0.5.176/code2llm/core/models.py +0 -207
- code2llm-0.5.176/code2llm.egg-info/top_level.txt +0 -1
- {code2llm-0.5.176 → code2llm-0.5.180}/LICENSE +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/__main__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/analysis/_data_impl.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/analysis/cfg.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/analysis/data_analysis.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/analysis/dfg.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/analysis/pipeline_classifier.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/analysis/pipeline_resolver.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/analysis/side_effects.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/analysis/type_inference.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/analysis/utils/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/analysis/utils/ast_helpers.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/api.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/cli.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/cli_analysis.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/cli_commands.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/cli_exports/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/cli_exports/code2logic.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/cli_exports/formats.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/cli_exports/orchestrator_chunked.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/cli_exports/orchestrator_handlers.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/cli_exports/prompt.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/cli_parser.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/ast_registry.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/config.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/export_pipeline.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/file_filter.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/gitignore.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/incremental.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/lang/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/lang/_c_parser.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/lang/base.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/lang/cpp.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/lang/csharp.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/lang/generic.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/lang/go_lang.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/lang/java.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/lang/php.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/lang/ruby.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/lang/rust.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/lang/ts_extractors.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/lang/ts_parser.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/large_repo.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/repo_files.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/source_classifier.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/streaming/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/streaming/cache.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/streaming/incremental.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/streaming/prioritizer.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/streaming/scanner.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/streaming/strategies.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/streaming_analyzer.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/core/toon_size_manager.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/article_view.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/base.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/context_exporter.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/context_view.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/dashboard_data.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/dashboard_renderer.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/evolution/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/evolution/computation.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/evolution/constants.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/evolution/exclusion.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/evolution/render.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/evolution/yaml_export.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/evolution_exporter.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/flow_constants.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/flow_exporter.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/flow_renderer.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/html_dashboard.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/index_generator/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/index_generator/renderer.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/index_generator/scanner.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/index_generator.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/json_exporter.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/llm_exporter.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/map/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/map/alerts.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/map/details.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/map/header.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/map/module_list.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/map/utils.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/map/yaml_export.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/map_exporter.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/mermaid/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/mermaid/calls.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/mermaid/classic.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/mermaid/compact.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/mermaid/flow_compact.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/mermaid/flow_detailed.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/mermaid/flow_full.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/mermaid/utils.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/mermaid_exporter.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/mermaid_flow_helpers.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/planfile_tickets.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/project_yaml/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/project_yaml/constants.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/project_yaml/core.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/project_yaml/evolution.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/project_yaml/health.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/project_yaml/hotspots.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/project_yaml/modules.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/project_yaml_exporter.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/readme/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/readme/content.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/readme/files.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/readme/insights.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/readme/sections.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/readme_exporter.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/report_generators.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/toon/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/toon/_render_coupling_helpers.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/toon/_render_section_helpers.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/toon/constants.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/toon/helpers.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/toon/metrics.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/toon/metrics_core.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/toon/metrics_duplicates.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/toon/metrics_health.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/toon/module_detail.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/toon.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/toon_view.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/validate_project.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/exporters/yaml_exporter.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/generators/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/generators/_utils.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/generators/llm_flow/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/generators/llm_flow/analysis.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/generators/llm_flow/cli.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/generators/llm_flow/generator.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/generators/llm_flow/nodes.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/generators/llm_flow/parsing.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/generators/llm_flow/utils.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/generators/llm_flow.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/generators/llm_task.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/generators/mermaid/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/generators/mermaid/fix.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/generators/mermaid/png.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/generators/mermaid/validation.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/generators/mermaid.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/nlp/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/nlp/config.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/nlp/entity_resolution.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/nlp/intent_matching.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/nlp/normalization.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/nlp/pipeline.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/patterns/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/patterns/detector.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/refactor/__init__.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm/refactor/prompt_engine.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm.egg-info/dependency_links.txt +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/code2llm.egg-info/entry_points.txt +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/setup.cfg +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/setup.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_advanced_analysis.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_analyzer.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_cache_invalidation_e2e.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_calls_toon_export.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_declarative_collection.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_deep_analysis.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_dup_groups.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_edge_cases.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_export_cache_flags.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_export_profile_contract.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_file_analyzer_tagging.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_flow_exporter.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_format_quality.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_mermaid_png.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_multilanguage_e2e.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_nlp_pipeline.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_nonpython_cc_calls.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_persistent_cache.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_pipeline_detector.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_planfile_tickets_exporter.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_project_toon_export.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_prompt_engine.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_prompt_txt.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_refactoring_engine.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_smells_data_clumps.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_smells_shotgun_surgery.py +0 -0
- {code2llm-0.5.176 → code2llm-0.5.180}/tests/test_toon_v2.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code2llm
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.180
|
|
4
4
|
Summary: High-performance Python code flow analysis with optimized TOON format - CFG, DFG, call graphs, and intelligent code queries
|
|
5
5
|
Home-page: https://github.com/wronai/stts
|
|
6
6
|
Author: Tom Sapletta
|
|
@@ -36,7 +36,7 @@ Requires-Dist: psutil>=5.9
|
|
|
36
36
|
Requires-Dist: code2logic
|
|
37
37
|
Requires-Dist: vulture>=2.10
|
|
38
38
|
Requires-Dist: tiktoken>=0.5
|
|
39
|
-
Requires-Dist: tree-sitter>=0.
|
|
39
|
+
Requires-Dist: tree-sitter>=0.25
|
|
40
40
|
Requires-Dist: tree-sitter-python>=0.21
|
|
41
41
|
Requires-Dist: tree-sitter-javascript>=0.21
|
|
42
42
|
Requires-Dist: tree-sitter-typescript>=0.21
|
|
@@ -48,6 +48,14 @@ Requires-Dist: tree-sitter-cpp>=0.22
|
|
|
48
48
|
Requires-Dist: tree-sitter-c-sharp>=0.21
|
|
49
49
|
Requires-Dist: tree-sitter-php>=0.22
|
|
50
50
|
Requires-Dist: tree-sitter-ruby>=0.21
|
|
51
|
+
Provides-Extra: native
|
|
52
|
+
Requires-Dist: code2llm-rust>=0.1.0; extra == "native"
|
|
53
|
+
Provides-Extra: graph
|
|
54
|
+
Requires-Dist: code2graph>=0.1.0; extra == "graph"
|
|
55
|
+
Provides-Extra: toon
|
|
56
|
+
Requires-Dist: code2toon>=0.1.0; extra == "toon"
|
|
57
|
+
Provides-Extra: diagrams
|
|
58
|
+
Requires-Dist: code2flow>=0.1.0; extra == "diagrams"
|
|
51
59
|
Provides-Extra: dev
|
|
52
60
|
Requires-Dist: pytest>=6.2; extra == "dev"
|
|
53
61
|
Requires-Dist: pytest-cov>=2.12; extra == "dev"
|
|
@@ -55,9 +63,11 @@ Requires-Dist: pytest-xdist>=3.0; extra == "dev"
|
|
|
55
63
|
Requires-Dist: black>=21.0; extra == "dev"
|
|
56
64
|
Requires-Dist: flake8>=3.9; extra == "dev"
|
|
57
65
|
Requires-Dist: mypy>=0.910; extra == "dev"
|
|
58
|
-
Requires-Dist: goal>=2.1.218; extra == "dev"
|
|
59
66
|
Requires-Dist: costs>=0.1.20; extra == "dev"
|
|
60
|
-
Requires-Dist:
|
|
67
|
+
Requires-Dist: code2llm-rust>=0.1.0; extra == "dev"
|
|
68
|
+
Requires-Dist: code2graph>=0.1.0; extra == "dev"
|
|
69
|
+
Requires-Dist: code2toon>=0.1.0; extra == "dev"
|
|
70
|
+
Requires-Dist: code2flow>=0.1.0; extra == "dev"
|
|
61
71
|
Dynamic: author
|
|
62
72
|
Dynamic: home-page
|
|
63
73
|
Dynamic: license-file
|
|
@@ -418,3 +428,7 @@ For more information about code2llm, visit: https://github.com/tom-sapletta/code
|
|
|
418
428
|
## License
|
|
419
429
|
|
|
420
430
|
Licensed under Apache-2.0.
|
|
431
|
+
|
|
432
|
+
## Dependency maintenance
|
|
433
|
+
|
|
434
|
+
See [dependency updates and Python tool groups](docs/dependencies.md) for locked tests, daily updates and freshness checks.
|
|
@@ -353,3 +353,7 @@ For more information about code2llm, visit: https://github.com/tom-sapletta/code
|
|
|
353
353
|
## License
|
|
354
354
|
|
|
355
355
|
Licensed under Apache-2.0.
|
|
356
|
+
|
|
357
|
+
## Dependency maintenance
|
|
358
|
+
|
|
359
|
+
See [dependency updates and Python tool groups](docs/dependencies.md) for locked tests, daily updates and freshness checks.
|
code2llm-0.5.180/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.5.180
|
|
@@ -8,7 +8,7 @@ Includes NLP Processing Pipeline for query normalization, intent matching,
|
|
|
8
8
|
and entity resolution with multilingual support.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
__version__ = "0.5.
|
|
11
|
+
__version__ = "0.5.179"
|
|
12
12
|
__author__ = "STTS Project"
|
|
13
13
|
|
|
14
14
|
# Core analysis components (lightweight, always needed)
|
|
@@ -12,6 +12,10 @@ __all__ = [
|
|
|
12
12
|
"PipelineDetector",
|
|
13
13
|
"PipelineResolver",
|
|
14
14
|
"PipelineClassifier",
|
|
15
|
+
"calculate_complexity_regex",
|
|
16
|
+
"calculate_python_complexity",
|
|
17
|
+
"compute_cyclomatic_complexity",
|
|
18
|
+
"extract_function_body",
|
|
15
19
|
]
|
|
16
20
|
|
|
17
21
|
|
|
@@ -29,6 +33,10 @@ def __getattr__(name):
|
|
|
29
33
|
"PipelineDetector": ".pipeline_detector",
|
|
30
34
|
"PipelineResolver": ".pipeline_resolver",
|
|
31
35
|
"PipelineClassifier": ".pipeline_classifier",
|
|
36
|
+
"calculate_complexity_regex": ".complexity",
|
|
37
|
+
"calculate_python_complexity": ".complexity",
|
|
38
|
+
"compute_cyclomatic_complexity": ".complexity",
|
|
39
|
+
"extract_function_body": ".complexity",
|
|
32
40
|
}
|
|
33
41
|
if name in _imports:
|
|
34
42
|
import importlib
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Call graph extractor using AST.
|
|
2
|
+
|
|
3
|
+
Backward-compatibility facade delegating to code2llm.analysis.call_graph_engine.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from code2llm.analysis.call_graph_engine import (
|
|
9
|
+
CallGraphExtractor,
|
|
10
|
+
calculate_call_metrics,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"CallGraphExtractor",
|
|
15
|
+
"calculate_call_metrics",
|
|
16
|
+
]
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
"""Call graph extraction and symbol call resolution engine.
|
|
2
|
+
|
|
3
|
+
Atomized module supporting:
|
|
4
|
+
- Multi-language call extraction via regex and native Rust acceleration (code2llm-fast-core)
|
|
5
|
+
- Python AST call graph extraction with caller/callee resolution
|
|
6
|
+
- Call graph metrics computation (fan-in, fan-out, called_by, call edges)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import ast
|
|
12
|
+
import json
|
|
13
|
+
import re
|
|
14
|
+
import subprocess
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Any, Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
from code2llm.analysis.complexity import _find_rust_binary, extract_function_body
|
|
19
|
+
from code2llm.analysis.utils import ast_unparse, qualified_name
|
|
20
|
+
from code2llm.core.config import Config
|
|
21
|
+
from code2llm.core.models import AnalysisResult, FlowEdge
|
|
22
|
+
|
|
23
|
+
CALL_PATTERN_C_FAMILY = re.compile(
|
|
24
|
+
r"(?<!\bfunction\b\s)"
|
|
25
|
+
r"(?<!\bclass\b\s)"
|
|
26
|
+
r"\b([a-zA-Z_]\w*)\s*\("
|
|
27
|
+
r"|"
|
|
28
|
+
r"(?:this|self)\s*\.\s*(\w+)\s*\("
|
|
29
|
+
r"|"
|
|
30
|
+
r"\b(\w+)\s*\.\s*(\w+)\s*\("
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
_CALL_KEYWORDS = frozenset(
|
|
34
|
+
{
|
|
35
|
+
"if",
|
|
36
|
+
"for",
|
|
37
|
+
"while",
|
|
38
|
+
"switch",
|
|
39
|
+
"catch",
|
|
40
|
+
"return",
|
|
41
|
+
"throw",
|
|
42
|
+
"new",
|
|
43
|
+
"typeof",
|
|
44
|
+
"instanceof",
|
|
45
|
+
"import",
|
|
46
|
+
"export",
|
|
47
|
+
"require",
|
|
48
|
+
"console",
|
|
49
|
+
"super",
|
|
50
|
+
"class",
|
|
51
|
+
"function",
|
|
52
|
+
"async",
|
|
53
|
+
"await",
|
|
54
|
+
"delete",
|
|
55
|
+
"void",
|
|
56
|
+
"case",
|
|
57
|
+
"default",
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _resolve_call(
|
|
63
|
+
simple_call: str,
|
|
64
|
+
func_qname: str,
|
|
65
|
+
module_name: str,
|
|
66
|
+
known_simple: Dict[str, List[str]],
|
|
67
|
+
calls_seen: set[str],
|
|
68
|
+
func_info: Any,
|
|
69
|
+
) -> None:
|
|
70
|
+
"""Resolve a single call name and append to func_info.calls if novel."""
|
|
71
|
+
if simple_call in known_simple:
|
|
72
|
+
candidates = known_simple[simple_call]
|
|
73
|
+
my_module = func_qname.rsplit(".", 1)[0]
|
|
74
|
+
resolved = next(
|
|
75
|
+
(c for c in candidates if c.rsplit(".", 1)[0] == my_module),
|
|
76
|
+
candidates[0],
|
|
77
|
+
)
|
|
78
|
+
if resolved != func_qname and resolved not in calls_seen:
|
|
79
|
+
func_info.calls.append(resolved)
|
|
80
|
+
calls_seen.add(resolved)
|
|
81
|
+
else:
|
|
82
|
+
ext_name = f"{module_name}.{simple_call}"
|
|
83
|
+
if ext_name not in calls_seen:
|
|
84
|
+
func_info.calls.append(ext_name)
|
|
85
|
+
calls_seen.add(ext_name)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _rust_batch_calls(
|
|
89
|
+
lines: list[int],
|
|
90
|
+
file_path: str | None = None,
|
|
91
|
+
content: str | None = None,
|
|
92
|
+
) -> dict[int, list[str]] | None:
|
|
93
|
+
"""Call native Rust extension or binary for fast batch call extraction."""
|
|
94
|
+
if not lines:
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
# 1. Native PyO3 extension (zero-subprocess in-memory execution)
|
|
98
|
+
from code2llm.analysis.native_bridge import native_batch_calls
|
|
99
|
+
|
|
100
|
+
resolved_content = content
|
|
101
|
+
if resolved_content is None and file_path and Path(file_path).is_file():
|
|
102
|
+
try:
|
|
103
|
+
resolved_content = Path(file_path).read_text(encoding="utf-8", errors="ignore")
|
|
104
|
+
except Exception:
|
|
105
|
+
resolved_content = None
|
|
106
|
+
|
|
107
|
+
if resolved_content is not None:
|
|
108
|
+
native_res = native_batch_calls(resolved_content, lines)
|
|
109
|
+
if native_res is not None:
|
|
110
|
+
return native_res
|
|
111
|
+
|
|
112
|
+
# 2. Secondary fallback: CLI binary
|
|
113
|
+
binary = _find_rust_binary()
|
|
114
|
+
if not binary:
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
lines_arg = ",".join(str(l) for l in lines)
|
|
118
|
+
cmd = [str(binary), "--calls", "--lines", lines_arg]
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
if file_path and Path(file_path).is_file():
|
|
122
|
+
cmd.extend(["--file", str(file_path)])
|
|
123
|
+
proc = subprocess.run(
|
|
124
|
+
cmd,
|
|
125
|
+
capture_output=True,
|
|
126
|
+
text=True,
|
|
127
|
+
check=True,
|
|
128
|
+
timeout=5,
|
|
129
|
+
)
|
|
130
|
+
elif content is not None:
|
|
131
|
+
proc = subprocess.run(
|
|
132
|
+
cmd,
|
|
133
|
+
input=content,
|
|
134
|
+
capture_output=True,
|
|
135
|
+
text=True,
|
|
136
|
+
check=True,
|
|
137
|
+
timeout=5,
|
|
138
|
+
)
|
|
139
|
+
else:
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
data = json.loads(proc.stdout.strip())
|
|
143
|
+
result: dict[int, list[str]] = {}
|
|
144
|
+
for item in data:
|
|
145
|
+
result[item["line"]] = item.get("calls", [])
|
|
146
|
+
return result
|
|
147
|
+
except Exception:
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def extract_calls_regex(
|
|
152
|
+
content: str,
|
|
153
|
+
module_name: str,
|
|
154
|
+
result: Dict[str, Any],
|
|
155
|
+
file_path: str | None = None,
|
|
156
|
+
) -> None:
|
|
157
|
+
"""Extract function calls from function bodies using regex or native Rust acceleration."""
|
|
158
|
+
functions = result.get("functions", {})
|
|
159
|
+
if not functions:
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
known_simple: Dict[str, List[str]] = {}
|
|
163
|
+
for qname in functions:
|
|
164
|
+
simple = qname.rsplit(".", 1)[-1]
|
|
165
|
+
known_simple.setdefault(simple, []).append(qname)
|
|
166
|
+
|
|
167
|
+
lines = [f.line for f in functions.values() if getattr(f, "line", None)]
|
|
168
|
+
rust_calls = _rust_batch_calls(lines, file_path=file_path, content=content)
|
|
169
|
+
|
|
170
|
+
for func_qname, func_info in functions.items():
|
|
171
|
+
line = getattr(func_info, "line", None)
|
|
172
|
+
body = extract_function_body(content, line or 1)
|
|
173
|
+
if not body:
|
|
174
|
+
continue
|
|
175
|
+
calls_seen: set[str] = set()
|
|
176
|
+
|
|
177
|
+
if rust_calls and line in rust_calls:
|
|
178
|
+
for simple_call in rust_calls[line]:
|
|
179
|
+
_resolve_call(
|
|
180
|
+
simple_call,
|
|
181
|
+
func_qname,
|
|
182
|
+
module_name,
|
|
183
|
+
known_simple,
|
|
184
|
+
calls_seen,
|
|
185
|
+
func_info,
|
|
186
|
+
)
|
|
187
|
+
else:
|
|
188
|
+
for m in CALL_PATTERN_C_FAMILY.finditer(body):
|
|
189
|
+
simple_call = m.group(1) or m.group(2) or m.group(4)
|
|
190
|
+
if not simple_call or simple_call in _CALL_KEYWORDS:
|
|
191
|
+
continue
|
|
192
|
+
_resolve_call(
|
|
193
|
+
simple_call,
|
|
194
|
+
func_qname,
|
|
195
|
+
module_name,
|
|
196
|
+
known_simple,
|
|
197
|
+
calls_seen,
|
|
198
|
+
func_info,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def calculate_call_metrics(
|
|
203
|
+
functions: Dict[str, Any],
|
|
204
|
+
metrics: Dict[str, Any] | None = None,
|
|
205
|
+
) -> Dict[str, Dict[str, Any]]:
|
|
206
|
+
"""Calculate fan-in, fan-out and populate called_by for all functions."""
|
|
207
|
+
from code2llm.analysis.native_bridge import native_calculate_call_metrics
|
|
208
|
+
|
|
209
|
+
res_metrics = metrics if metrics is not None else {}
|
|
210
|
+
|
|
211
|
+
# Fast path: native Rust
|
|
212
|
+
fn_tuples = []
|
|
213
|
+
for name, fi in functions.items():
|
|
214
|
+
calls = list(getattr(fi, "calls", []))
|
|
215
|
+
called_by = list(getattr(fi, "called_by", []))
|
|
216
|
+
raw_cc = getattr(fi, "complexity", 1.0)
|
|
217
|
+
cc_val = float(raw_cc.get("cyclomatic", 1.0) if isinstance(raw_cc, dict) else (raw_cc or 1.0))
|
|
218
|
+
fn_tuples.append((name, calls, called_by, cc_val))
|
|
219
|
+
|
|
220
|
+
native_res = native_calculate_call_metrics(fn_tuples)
|
|
221
|
+
if native_res is not None:
|
|
222
|
+
computed_metrics, called_by_map = native_res
|
|
223
|
+
for name, callers in called_by_map.items():
|
|
224
|
+
if name in functions:
|
|
225
|
+
fi = functions[name]
|
|
226
|
+
if hasattr(fi, "called_by"):
|
|
227
|
+
fi.called_by = callers
|
|
228
|
+
for name, (fan_in, fan_out, cc) in computed_metrics.items():
|
|
229
|
+
res_metrics[name] = {
|
|
230
|
+
"fan_in": fan_in,
|
|
231
|
+
"fan_out": fan_out,
|
|
232
|
+
"complexity": cc,
|
|
233
|
+
}
|
|
234
|
+
return res_metrics
|
|
235
|
+
|
|
236
|
+
# Fallback: Python implementation
|
|
237
|
+
for caller_name, caller_info in functions.items():
|
|
238
|
+
calls = getattr(caller_info, "calls", [])
|
|
239
|
+
for callee_name in calls:
|
|
240
|
+
if callee_name in functions:
|
|
241
|
+
callee_info = functions[callee_name]
|
|
242
|
+
called_by = getattr(callee_info, "called_by", None)
|
|
243
|
+
if called_by is not None and caller_name not in called_by:
|
|
244
|
+
called_by.append(caller_name)
|
|
245
|
+
|
|
246
|
+
for func_name, func_info in functions.items():
|
|
247
|
+
calls = getattr(func_info, "calls", [])
|
|
248
|
+
called_by = getattr(func_info, "called_by", [])
|
|
249
|
+
fan_out = len(set(calls))
|
|
250
|
+
fan_in = len(set(called_by))
|
|
251
|
+
res_metrics[func_name] = {
|
|
252
|
+
"fan_in": fan_in,
|
|
253
|
+
"fan_out": fan_out,
|
|
254
|
+
"complexity": getattr(func_info, "complexity", 1),
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return res_metrics
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class CallGraphExtractor(ast.NodeVisitor):
|
|
261
|
+
"""Extract call graph from AST with fast resolution and optional astroid inference."""
|
|
262
|
+
|
|
263
|
+
def __init__(self, config: Config):
|
|
264
|
+
self.config = config
|
|
265
|
+
self.result = AnalysisResult()
|
|
266
|
+
self.module_name = ""
|
|
267
|
+
self.file_path = ""
|
|
268
|
+
|
|
269
|
+
# Context
|
|
270
|
+
self.function_stack: List[str] = []
|
|
271
|
+
self.class_stack: List[str] = []
|
|
272
|
+
self.imports: Dict[str, str] = {}
|
|
273
|
+
self.astroid_tree = None
|
|
274
|
+
|
|
275
|
+
def extract(
|
|
276
|
+
self, tree: ast.AST, module_name: str, file_path: str
|
|
277
|
+
) -> AnalysisResult:
|
|
278
|
+
"""Extract call graph from AST."""
|
|
279
|
+
self.result = AnalysisResult()
|
|
280
|
+
self.module_name = module_name
|
|
281
|
+
self.file_path = file_path
|
|
282
|
+
self.function_stack = []
|
|
283
|
+
self.class_stack = []
|
|
284
|
+
self.imports = {}
|
|
285
|
+
|
|
286
|
+
# Lazy astroid tree import (only when not skipped)
|
|
287
|
+
if not getattr(self.config.performance, "skip_astroid_inference", True):
|
|
288
|
+
try:
|
|
289
|
+
import astroid as _astroid
|
|
290
|
+
|
|
291
|
+
self.astroid_tree = _astroid.MANAGER.ast_from_file(file_path)
|
|
292
|
+
except Exception:
|
|
293
|
+
self.astroid_tree = None
|
|
294
|
+
else:
|
|
295
|
+
self.astroid_tree = None
|
|
296
|
+
|
|
297
|
+
self.visit(tree)
|
|
298
|
+
self._calculate_metrics()
|
|
299
|
+
return self.result
|
|
300
|
+
|
|
301
|
+
def _calculate_metrics(self) -> None:
|
|
302
|
+
"""Calculate fan-in and fan-out metrics (backward compatibility)."""
|
|
303
|
+
calculate_call_metrics(self.result.functions, self.result.metrics)
|
|
304
|
+
|
|
305
|
+
def visit_Import(self, node: ast.Import) -> None:
|
|
306
|
+
"""Track imports."""
|
|
307
|
+
for alias in node.names:
|
|
308
|
+
name = alias.asname if alias.asname else alias.name
|
|
309
|
+
self.imports[name] = alias.name
|
|
310
|
+
if hasattr(self.result, "imports"):
|
|
311
|
+
self.result.imports[name] = alias.name
|
|
312
|
+
|
|
313
|
+
def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
|
|
314
|
+
"""Track from imports."""
|
|
315
|
+
module = node.module or ""
|
|
316
|
+
for alias in node.names:
|
|
317
|
+
name = alias.asname if alias.asname else alias.name
|
|
318
|
+
full_name = f"{module}.{alias.name}" if module else alias.name
|
|
319
|
+
self.imports[name] = full_name
|
|
320
|
+
if hasattr(self.result, "imports"):
|
|
321
|
+
self.result.imports[name] = full_name
|
|
322
|
+
|
|
323
|
+
def visit_ClassDef(self, node: ast.ClassDef) -> None:
|
|
324
|
+
"""Visit class definition."""
|
|
325
|
+
self.class_stack.append(node.name)
|
|
326
|
+
|
|
327
|
+
self.result.classes[node.name] = {
|
|
328
|
+
"file": self.file_path,
|
|
329
|
+
"line": node.lineno,
|
|
330
|
+
"methods": [m.name for m in node.body if isinstance(m, ast.FunctionDef)],
|
|
331
|
+
"bases": [self._expr_to_str(b) for b in node.bases],
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
for stmt in node.body:
|
|
335
|
+
self.visit(stmt)
|
|
336
|
+
|
|
337
|
+
self.class_stack.pop()
|
|
338
|
+
|
|
339
|
+
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
|
|
340
|
+
"""Visit function definition and track calls within it."""
|
|
341
|
+
func_name = qualified_name(self.module_name, self.class_stack, node.name)
|
|
342
|
+
self.function_stack.append(func_name)
|
|
343
|
+
|
|
344
|
+
for stmt in node.body:
|
|
345
|
+
self.visit(stmt)
|
|
346
|
+
|
|
347
|
+
self.function_stack.pop()
|
|
348
|
+
|
|
349
|
+
def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
|
|
350
|
+
"""Visit async function."""
|
|
351
|
+
self.visit_FunctionDef(node)
|
|
352
|
+
|
|
353
|
+
def visit_Call(self, node: ast.Call) -> None:
|
|
354
|
+
"""Track function calls."""
|
|
355
|
+
if not self.function_stack:
|
|
356
|
+
self.generic_visit(node)
|
|
357
|
+
return
|
|
358
|
+
|
|
359
|
+
caller = self.function_stack[-1]
|
|
360
|
+
callee = self._resolve_call(node.func)
|
|
361
|
+
|
|
362
|
+
# If ast-based resolution returned None or None.sth, try astroid if loaded
|
|
363
|
+
if (not callee or "None." in callee) and self.astroid_tree:
|
|
364
|
+
astroid_callee = self._resolve_with_astroid(node)
|
|
365
|
+
if astroid_callee:
|
|
366
|
+
callee = astroid_callee
|
|
367
|
+
|
|
368
|
+
if callee and caller in self.result.functions:
|
|
369
|
+
self.result.functions[caller].calls.append(callee)
|
|
370
|
+
|
|
371
|
+
edge = FlowEdge(
|
|
372
|
+
source=str(-1),
|
|
373
|
+
target=str(-1),
|
|
374
|
+
edge_type="call",
|
|
375
|
+
label=f"{caller}->{callee}",
|
|
376
|
+
)
|
|
377
|
+
if hasattr(self.result, "call_edges"):
|
|
378
|
+
self.result.call_edges.append(edge)
|
|
379
|
+
else:
|
|
380
|
+
self.result.edges.append(edge)
|
|
381
|
+
|
|
382
|
+
self.generic_visit(node)
|
|
383
|
+
|
|
384
|
+
def _resolve_call(self, node: ast.AST) -> Optional[str]:
|
|
385
|
+
"""Resolve a call to its full name."""
|
|
386
|
+
if isinstance(node, ast.Name):
|
|
387
|
+
if node.id in self.imports:
|
|
388
|
+
return self.imports[node.id]
|
|
389
|
+
return f"{self.module_name}.{node.id}"
|
|
390
|
+
|
|
391
|
+
if isinstance(node, ast.Attribute):
|
|
392
|
+
parts: List[str] = []
|
|
393
|
+
current = node
|
|
394
|
+
|
|
395
|
+
while isinstance(current, ast.Attribute):
|
|
396
|
+
parts.append(current.attr)
|
|
397
|
+
current = current.value
|
|
398
|
+
|
|
399
|
+
if isinstance(current, ast.Name):
|
|
400
|
+
parts.append(current.id)
|
|
401
|
+
parts.reverse()
|
|
402
|
+
|
|
403
|
+
root = parts[0]
|
|
404
|
+
if root in self.imports:
|
|
405
|
+
return f"{self.imports[root]}.{'.'.join(parts[1:])}"
|
|
406
|
+
|
|
407
|
+
if root in ("self", "cls") and self.class_stack:
|
|
408
|
+
return f"{self.module_name}.{self.class_stack[-1]}.{'.'.join(parts[1:])}"
|
|
409
|
+
|
|
410
|
+
return f"{self.module_name}.{'.'.join(parts)}"
|
|
411
|
+
|
|
412
|
+
return None
|
|
413
|
+
|
|
414
|
+
def _resolve_with_astroid(self, node: ast.Call) -> Optional[str]:
|
|
415
|
+
"""Use astroid to infer the call target."""
|
|
416
|
+
if not self.astroid_tree:
|
|
417
|
+
return None
|
|
418
|
+
|
|
419
|
+
try:
|
|
420
|
+
import astroid as _astroid
|
|
421
|
+
|
|
422
|
+
for astroid_node in self.astroid_tree.nodes_of_class(_astroid.Call):
|
|
423
|
+
if (
|
|
424
|
+
astroid_node.lineno == node.lineno
|
|
425
|
+
and astroid_node.col_offset == node.col_offset
|
|
426
|
+
):
|
|
427
|
+
inferred = astroid_node.func.infer()
|
|
428
|
+
for target in inferred:
|
|
429
|
+
if hasattr(target, "qname"):
|
|
430
|
+
return target.qname()
|
|
431
|
+
break
|
|
432
|
+
except Exception:
|
|
433
|
+
pass
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
def _expr_to_str(self, node: ast.AST) -> str:
|
|
437
|
+
"""Unparse an AST expression node to a source string."""
|
|
438
|
+
return ast_unparse(node, default_none="")
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
__all__ = [
|
|
442
|
+
"CALL_PATTERN_C_FAMILY",
|
|
443
|
+
"CallGraphExtractor",
|
|
444
|
+
"calculate_call_metrics",
|
|
445
|
+
"extract_calls_regex",
|
|
446
|
+
"_resolve_call",
|
|
447
|
+
"_rust_batch_calls",
|
|
448
|
+
]
|