From fafcb56c2c43c42691c7080b76730ce0b4923c71 Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 08:45:30 +0100 Subject: [PATCH 01/17] Restructure report and unit modules --- codra/__init__.py | 8 +-- codra/bps_analyzer.py | 2 +- codra/bps_result.py | 2 +- codra/cli.py | 58 +++++++++++++------ codra/cli_args.py | 13 +++++ codra/cli_dependencies.py | 12 ++++ codra/csa_analyzer.py | 2 +- codra/csa_result.py | 2 +- codra/directory_scanner.py | 2 +- codra/function_extractor.py | 2 +- codra/indirection_analyzer.py | 2 +- codra/indirection_result.py | 2 +- codra/python_file_scanner.py | 2 +- codra/report/__init__.py | 0 .../{report_builder.py => report/builder.py} | 30 ++++++---- codra/{file_report.py => report/file.py} | 2 +- codra/{report.py => report/model.py} | 4 +- .../serializer.py} | 2 +- .../{report_summary.py => report/summary.py} | 0 codra/unit/__init__.py | 0 .../definition.py} | 0 codra/{unit_key.py => unit/key.py} | 0 codra/{unit_metrics.py => unit/metrics.py} | 0 codra/{unit_node.py => unit/node.py} | 2 +- .../node_collector.py} | 4 +- codra/{unit_report.py => unit/overview.py} | 4 +- 26 files changed, 105 insertions(+), 52 deletions(-) create mode 100644 codra/cli_args.py create mode 100644 codra/cli_dependencies.py create mode 100644 codra/report/__init__.py rename codra/{report_builder.py => report/builder.py} (87%) rename codra/{file_report.py => report/file.py} (80%) rename codra/{report.py => report/model.py} (72%) rename codra/{report_serializer.py => report/serializer.py} (90%) rename codra/{report_summary.py => report/summary.py} (100%) create mode 100644 codra/unit/__init__.py rename codra/{unit_definition.py => unit/definition.py} (100%) rename codra/{unit_key.py => unit/key.py} (100%) rename codra/{unit_metrics.py => unit/metrics.py} (100%) rename codra/{unit_node.py => unit/node.py} (79%) rename codra/{unit_node_collector.py => unit/node_collector.py} (95%) rename codra/{unit_report.py => unit/overview.py} (66%) diff --git a/codra/__init__.py b/codra/__init__.py index 59736ce..0e000f1 100644 --- a/codra/__init__.py +++ b/codra/__init__.py @@ -5,10 +5,10 @@ from .directory_scanner import DirectoryScanner from .indirection_analyzer import IndirectionAnalyzer from .indirection_result import IndirectionResult -from .report import Report -from .report_builder import ReportBuilder -from .report_serializer import ReportSerializer -from .unit_definition import UnitDefinition +from .report.builder import ReportBuilder +from .report.model import Report +from .report.serializer import ReportSerializer +from .unit.definition import UnitDefinition __all__ = [ "BpsAnalyzer", diff --git a/codra/bps_analyzer.py b/codra/bps_analyzer.py index 98f720f..be362b3 100644 --- a/codra/bps_analyzer.py +++ b/codra/bps_analyzer.py @@ -7,7 +7,7 @@ from .condition_metrics_collector import ConditionMetricsCollector from .function_symbol_collector import FunctionSymbolCollector from .if_collector import IfCollector -from .unit_node_collector import UnitNodeCollector +from .unit.node_collector import UnitNodeCollector @dataclass diff --git a/codra/bps_result.py b/codra/bps_result.py index 24cffef..d8450e1 100644 --- a/codra/bps_result.py +++ b/codra/bps_result.py @@ -2,7 +2,7 @@ from dataclasses import dataclass -from .unit_definition import UnitDefinition +from .unit.definition import UnitDefinition @dataclass(frozen=True) diff --git a/codra/cli.py b/codra/cli.py index 1c2efae..210d802 100644 --- a/codra/cli.py +++ b/codra/cli.py @@ -1,30 +1,21 @@ from __future__ import annotations import argparse +import logging import sys -from dataclasses import dataclass from .bps_analyzer import BpsAnalyzer from .csa_analyzer import CsaAnalyzer +from .cli_args import CliArgs +from .cli_dependencies import CliDependencies from .file_path_collector import FilePathCollector from .indirection_analyzer import IndirectionAnalyzer -from .report_builder import ReportBuilder -from .report_serializer import ReportSerializer +from .report.builder import ReportBuilder +from .report.serializer import ReportSerializer from .threshold_config import ThresholdConfig -@dataclass(frozen=True) -class CliArgs: - path: str - threshold_csa: int | None - threshold_id: int | None - threshold_bps: float | None - - -@dataclass(frozen=True) -class CliDependencies: - builder: ReportBuilder - serializer: ReportSerializer +logger = logging.getLogger(__name__) def parse_args(argv: list[str]) -> CliArgs: @@ -33,12 +24,24 @@ def parse_args(argv: list[str]) -> CliArgs: parser.add_argument("--threshold-csa", type=int, default=None) parser.add_argument("--threshold-id", type=int, default=None) parser.add_argument("--threshold-bps", type=float, default=None) + parser.add_argument( + "--log-level", + default="INFO", + help="Logging level (e.g. DEBUG, INFO, WARNING).", + ) + parser.add_argument( + "--output", + default=None, + help="Output file path. Defaults to stdout when omitted.", + ) o = parser.parse_args(argv) return CliArgs( path=o.path, threshold_csa=o.threshold_csa, threshold_id=o.threshold_id, threshold_bps=o.threshold_bps, + log_level=o.log_level, + output=o.output, ) @@ -60,15 +63,34 @@ def build_dependencies() -> CliDependencies: return CliDependencies(builder=builder, serializer=ReportSerializer()) +def configure_logging(log_level: str) -> None: + level = logging._nameToLevel.get(log_level.upper(), logging.INFO) + logging.basicConfig( + level=level, + format="%(levelname)s:%(name)s:%(message)s", + ) + + def run_cli(args: CliArgs, deps: CliDependencies) -> int: + logger.info("Starting analysis for %s", args.path) report = deps.builder.build(args.path) - sys.stdout.write(deps.serializer.to_json(report)) - sys.stdout.write("\n") + payload = deps.serializer.to_json(report) + if args.output: + logger.info("Writing report to %s", args.output) + with open(args.output, "w", encoding="utf-8") as handle: + handle.write(payload) + handle.write("\n") + else: + logger.info("Writing report to stdout") + sys.stdout.write(payload) + sys.stdout.write("\n") return 0 if deps.builder.check_thresholds(report, build_thresholds(args)) else 1 def main() -> None: - raise SystemExit(run_cli(parse_args(sys.argv[1:]), build_dependencies())) + args = parse_args(sys.argv[1:]) + configure_logging(args.log_level) + raise SystemExit(run_cli(args, build_dependencies())) if __name__ == "__main__": diff --git a/codra/cli_args.py b/codra/cli_args.py new file mode 100644 index 0000000..93c9cb8 --- /dev/null +++ b/codra/cli_args.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class CliArgs: + path: str + threshold_csa: int | None + threshold_id: int | None + threshold_bps: float | None + log_level: str + output: str | None diff --git a/codra/cli_dependencies.py b/codra/cli_dependencies.py new file mode 100644 index 0000000..ef0dedc --- /dev/null +++ b/codra/cli_dependencies.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from .report.builder import ReportBuilder +from .report.serializer import ReportSerializer + + +@dataclass(frozen=True) +class CliDependencies: + builder: ReportBuilder + serializer: ReportSerializer diff --git a/codra/csa_analyzer.py b/codra/csa_analyzer.py index 0ed94a2..7f6dc84 100644 --- a/codra/csa_analyzer.py +++ b/codra/csa_analyzer.py @@ -6,7 +6,7 @@ from .csa_result import CsaResult from .function_symbol_collector import FunctionSymbolCollector from .module_symbol_collector import ModuleSymbolCollector -from .unit_node_collector import UnitNodeCollector +from .unit.node_collector import UnitNodeCollector @dataclass diff --git a/codra/csa_result.py b/codra/csa_result.py index 2e600e8..2b0c18a 100644 --- a/codra/csa_result.py +++ b/codra/csa_result.py @@ -2,7 +2,7 @@ from dataclasses import dataclass -from .unit_definition import UnitDefinition +from .unit.definition import UnitDefinition @dataclass(frozen=True) diff --git a/codra/directory_scanner.py b/codra/directory_scanner.py index 9b9413b..a87acc8 100644 --- a/codra/directory_scanner.py +++ b/codra/directory_scanner.py @@ -4,7 +4,7 @@ from dataclasses import dataclass, field from .python_file_scanner import PythonFileScanner -from .unit_definition import UnitDefinition +from .unit.definition import UnitDefinition @dataclass diff --git a/codra/function_extractor.py b/codra/function_extractor.py index 5ebf5df..3091ceb 100644 --- a/codra/function_extractor.py +++ b/codra/function_extractor.py @@ -3,7 +3,7 @@ import ast from dataclasses import dataclass, field -from .unit_definition import UnitDefinition +from .unit.definition import UnitDefinition @dataclass diff --git a/codra/indirection_analyzer.py b/codra/indirection_analyzer.py index 66974b0..61f2107 100644 --- a/codra/indirection_analyzer.py +++ b/codra/indirection_analyzer.py @@ -7,7 +7,7 @@ from .call_collector import CallCollector from .function_definition_collector import FunctionDefinitionCollector from .indirection_result import IndirectionResult -from .unit_node_collector import UnitNodeCollector +from .unit.node_collector import UnitNodeCollector @dataclass diff --git a/codra/indirection_result.py b/codra/indirection_result.py index 5deaf47..b6521f2 100644 --- a/codra/indirection_result.py +++ b/codra/indirection_result.py @@ -2,7 +2,7 @@ from dataclasses import dataclass -from .unit_definition import UnitDefinition +from .unit.definition import UnitDefinition @dataclass(frozen=True) diff --git a/codra/python_file_scanner.py b/codra/python_file_scanner.py index a836078..46a9e7b 100644 --- a/codra/python_file_scanner.py +++ b/codra/python_file_scanner.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from .function_extractor import FunctionExtractor -from .unit_definition import UnitDefinition +from .unit.definition import UnitDefinition @dataclass diff --git a/codra/report/__init__.py b/codra/report/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codra/report_builder.py b/codra/report/builder.py similarity index 87% rename from codra/report_builder.py rename to codra/report/builder.py index d3f1832..eb98fc8 100644 --- a/codra/report_builder.py +++ b/codra/report/builder.py @@ -1,19 +1,23 @@ from __future__ import annotations +import logging from dataclasses import dataclass -from .bps_analyzer import BpsAnalyzer -from .csa_analyzer import CsaAnalyzer -from .file_path_collector import FilePathCollector -from .file_report import FileReport -from .indirection_analyzer import IndirectionAnalyzer -from .report import Report -from .report_summary import ReportSummary -from .threshold_config import ThresholdConfig -from .unit_definition import UnitDefinition -from .unit_key import UnitKey -from .unit_metrics import UnitMetrics -from .unit_report import UnitReport +from ..bps_analyzer import BpsAnalyzer +from ..csa_analyzer import CsaAnalyzer +from ..file_path_collector import FilePathCollector +from ..indirection_analyzer import IndirectionAnalyzer +from ..threshold_config import ThresholdConfig +from ..unit.definition import UnitDefinition +from ..unit.key import UnitKey +from ..unit.metrics import UnitMetrics +from ..unit.overview import UnitReport +from .file import FileReport +from .model import Report +from .summary import ReportSummary + + +logger = logging.getLogger(__name__) @dataclass @@ -24,10 +28,12 @@ class ReportBuilder: path_collector: FilePathCollector def build(self, root_path: str) -> Report: + logger.info("Collecting files from %s", root_path) file_paths = self.path_collector.collect(root_path) files: list[FileReport] = [] total_units = 0 for file_path in file_paths: + logger.info("Analyzing %s", file_path) csa_results = self.csa_analyzer.analyze_file(file_path) indirection_results = self.indirection_analyzer.analyze_file(file_path) bps_results = self.bps_analyzer.analyze_file(file_path) diff --git a/codra/file_report.py b/codra/report/file.py similarity index 80% rename from codra/file_report.py rename to codra/report/file.py index fc6717c..039496f 100644 --- a/codra/file_report.py +++ b/codra/report/file.py @@ -2,7 +2,7 @@ from dataclasses import dataclass -from .unit_report import UnitReport +from ..unit.overview import UnitReport @dataclass(frozen=True) diff --git a/codra/report.py b/codra/report/model.py similarity index 72% rename from codra/report.py rename to codra/report/model.py index d19d2a1..4c0701c 100644 --- a/codra/report.py +++ b/codra/report/model.py @@ -2,8 +2,8 @@ from dataclasses import dataclass -from .file_report import FileReport -from .report_summary import ReportSummary +from .file import FileReport +from .summary import ReportSummary @dataclass(frozen=True) diff --git a/codra/report_serializer.py b/codra/report/serializer.py similarity index 90% rename from codra/report_serializer.py rename to codra/report/serializer.py index 59d8673..ca458f3 100644 --- a/codra/report_serializer.py +++ b/codra/report/serializer.py @@ -4,7 +4,7 @@ from dataclasses import asdict from dataclasses import dataclass -from .report import Report +from .model import Report @dataclass diff --git a/codra/report_summary.py b/codra/report/summary.py similarity index 100% rename from codra/report_summary.py rename to codra/report/summary.py diff --git a/codra/unit/__init__.py b/codra/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codra/unit_definition.py b/codra/unit/definition.py similarity index 100% rename from codra/unit_definition.py rename to codra/unit/definition.py diff --git a/codra/unit_key.py b/codra/unit/key.py similarity index 100% rename from codra/unit_key.py rename to codra/unit/key.py diff --git a/codra/unit_metrics.py b/codra/unit/metrics.py similarity index 100% rename from codra/unit_metrics.py rename to codra/unit/metrics.py diff --git a/codra/unit_node.py b/codra/unit/node.py similarity index 79% rename from codra/unit_node.py rename to codra/unit/node.py index 0c38fef..b872c23 100644 --- a/codra/unit_node.py +++ b/codra/unit/node.py @@ -3,7 +3,7 @@ import ast from dataclasses import dataclass -from .unit_definition import UnitDefinition +from .definition import UnitDefinition @dataclass(frozen=True) diff --git a/codra/unit_node_collector.py b/codra/unit/node_collector.py similarity index 95% rename from codra/unit_node_collector.py rename to codra/unit/node_collector.py index 56e1b64..c279c8d 100644 --- a/codra/unit_node_collector.py +++ b/codra/unit/node_collector.py @@ -3,8 +3,8 @@ import ast from dataclasses import dataclass, field -from .unit_definition import UnitDefinition -from .unit_node import UnitNode +from .definition import UnitDefinition +from .node import UnitNode @dataclass diff --git a/codra/unit_report.py b/codra/unit/overview.py similarity index 66% rename from codra/unit_report.py rename to codra/unit/overview.py index 208e656..1143617 100644 --- a/codra/unit_report.py +++ b/codra/unit/overview.py @@ -2,8 +2,8 @@ from dataclasses import dataclass -from .unit_definition import UnitDefinition -from .unit_metrics import UnitMetrics +from .definition import UnitDefinition +from .metrics import UnitMetrics @dataclass(frozen=True) From af9f93bb586d863b321fad6ce80cc294df63f9b3 Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 14:46:09 +0100 Subject: [PATCH 02/17] Group modules into scoped packages --- codra/__init__.py | 14 +++++++------- codra/alias/__init__.py | 0 .../{alias_collector.py => alias/collector.py} | 0 codra/bps/__init__.py | 0 codra/{bps_analyzer.py => bps/analyzer.py} | 10 +++++----- codra/{bps_result.py => bps/result.py} | 2 +- codra/call/__init__.py | 0 codra/{call_collector.py => call/collector.py} | 0 codra/cli/__init__.py | 0 codra/{cli_args.py => cli/args.py} | 0 .../dependencies.py} | 4 ++-- codra/{cli.py => cli/main.py} | 18 +++++++++--------- codra/condition/__init__.py | 0 .../collector.py} | 2 +- .../metrics.py} | 0 codra/csa/__init__.py | 0 codra/{csa_analyzer.py => csa/analyzer.py} | 8 ++++---- codra/{csa_result.py => csa/result.py} | 2 +- codra/directory/__init__.py | 0 .../scanner.py} | 4 ++-- codra/file/__init__.py | 0 .../path_collector.py} | 0 codra/function/__init__.py | 0 .../definition_collector.py} | 0 .../extractor.py} | 2 +- .../symbol_collector.py} | 2 +- .../symbol_usage.py} | 0 codra/if_/__init__.py | 0 codra/{if_collector.py => if_/collector.py} | 0 codra/indirection/__init__.py | 0 .../analyzer.py} | 10 +++++----- .../result.py} | 2 +- codra/module/__init__.py | 0 .../symbol_collector.py} | 0 codra/python/__init__.py | 0 .../file_scanner.py} | 4 ++-- codra/report/builder.py | 10 +++++----- codra/threshold/__init__.py | 0 .../config.py} | 0 39 files changed, 47 insertions(+), 47 deletions(-) create mode 100644 codra/alias/__init__.py rename codra/{alias_collector.py => alias/collector.py} (100%) create mode 100644 codra/bps/__init__.py rename codra/{bps_analyzer.py => bps/analyzer.py} (84%) rename codra/{bps_result.py => bps/result.py} (77%) create mode 100644 codra/call/__init__.py rename codra/{call_collector.py => call/collector.py} (100%) create mode 100644 codra/cli/__init__.py rename codra/{cli_args.py => cli/args.py} (100%) rename codra/{cli_dependencies.py => cli/dependencies.py} (66%) rename codra/{cli.py => cli/main.py} (86%) create mode 100644 codra/condition/__init__.py rename codra/{condition_metrics_collector.py => condition/collector.py} (95%) rename codra/{condition_metrics.py => condition/metrics.py} (100%) create mode 100644 codra/csa/__init__.py rename codra/{csa_analyzer.py => csa/analyzer.py} (86%) rename codra/{csa_result.py => csa/result.py} (83%) create mode 100644 codra/directory/__init__.py rename codra/{directory_scanner.py => directory/scanner.py} (87%) create mode 100644 codra/file/__init__.py rename codra/{file_path_collector.py => file/path_collector.py} (100%) create mode 100644 codra/function/__init__.py rename codra/{function_definition_collector.py => function/definition_collector.py} (100%) rename codra/{function_extractor.py => function/extractor.py} (97%) rename codra/{function_symbol_collector.py => function/symbol_collector.py} (97%) rename codra/{function_symbol_usage.py => function/symbol_usage.py} (100%) create mode 100644 codra/if_/__init__.py rename codra/{if_collector.py => if_/collector.py} (100%) create mode 100644 codra/indirection/__init__.py rename codra/{indirection_analyzer.py => indirection/analyzer.py} (93%) rename codra/{indirection_result.py => indirection/result.py} (82%) create mode 100644 codra/module/__init__.py rename codra/{module_symbol_collector.py => module/symbol_collector.py} (100%) create mode 100644 codra/python/__init__.py rename codra/{python_file_scanner.py => python/file_scanner.py} (82%) create mode 100644 codra/threshold/__init__.py rename codra/{threshold_config.py => threshold/config.py} (100%) diff --git a/codra/__init__.py b/codra/__init__.py index 0e000f1..33de38a 100644 --- a/codra/__init__.py +++ b/codra/__init__.py @@ -1,10 +1,10 @@ -from .bps_analyzer import BpsAnalyzer -from .bps_result import BpsResult -from .csa_analyzer import CsaAnalyzer -from .csa_result import CsaResult -from .directory_scanner import DirectoryScanner -from .indirection_analyzer import IndirectionAnalyzer -from .indirection_result import IndirectionResult +from .bps.analyzer import BpsAnalyzer +from .bps.result import BpsResult +from .csa.analyzer import CsaAnalyzer +from .csa.result import CsaResult +from .directory.scanner import DirectoryScanner +from .indirection.analyzer import IndirectionAnalyzer +from .indirection.result import IndirectionResult from .report.builder import ReportBuilder from .report.model import Report from .report.serializer import ReportSerializer diff --git a/codra/alias/__init__.py b/codra/alias/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codra/alias_collector.py b/codra/alias/collector.py similarity index 100% rename from codra/alias_collector.py rename to codra/alias/collector.py diff --git a/codra/bps/__init__.py b/codra/bps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codra/bps_analyzer.py b/codra/bps/analyzer.py similarity index 84% rename from codra/bps_analyzer.py rename to codra/bps/analyzer.py index be362b3..b2a4ca9 100644 --- a/codra/bps_analyzer.py +++ b/codra/bps/analyzer.py @@ -3,11 +3,11 @@ import ast from dataclasses import dataclass -from .bps_result import BpsResult -from .condition_metrics_collector import ConditionMetricsCollector -from .function_symbol_collector import FunctionSymbolCollector -from .if_collector import IfCollector -from .unit.node_collector import UnitNodeCollector +from ..condition.collector import ConditionMetricsCollector +from ..function.symbol_collector import FunctionSymbolCollector +from ..if_.collector import IfCollector +from ..unit.node_collector import UnitNodeCollector +from .result import BpsResult @dataclass diff --git a/codra/bps_result.py b/codra/bps/result.py similarity index 77% rename from codra/bps_result.py rename to codra/bps/result.py index d8450e1..9009d56 100644 --- a/codra/bps_result.py +++ b/codra/bps/result.py @@ -2,7 +2,7 @@ from dataclasses import dataclass -from .unit.definition import UnitDefinition +from ..unit.definition import UnitDefinition @dataclass(frozen=True) diff --git a/codra/call/__init__.py b/codra/call/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codra/call_collector.py b/codra/call/collector.py similarity index 100% rename from codra/call_collector.py rename to codra/call/collector.py diff --git a/codra/cli/__init__.py b/codra/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codra/cli_args.py b/codra/cli/args.py similarity index 100% rename from codra/cli_args.py rename to codra/cli/args.py diff --git a/codra/cli_dependencies.py b/codra/cli/dependencies.py similarity index 66% rename from codra/cli_dependencies.py rename to codra/cli/dependencies.py index ef0dedc..0b339ef 100644 --- a/codra/cli_dependencies.py +++ b/codra/cli/dependencies.py @@ -2,8 +2,8 @@ from dataclasses import dataclass -from .report.builder import ReportBuilder -from .report.serializer import ReportSerializer +from ..report.builder import ReportBuilder +from ..report.serializer import ReportSerializer @dataclass(frozen=True) diff --git a/codra/cli.py b/codra/cli/main.py similarity index 86% rename from codra/cli.py rename to codra/cli/main.py index 210d802..1471eef 100644 --- a/codra/cli.py +++ b/codra/cli/main.py @@ -4,15 +4,15 @@ import logging import sys -from .bps_analyzer import BpsAnalyzer -from .csa_analyzer import CsaAnalyzer -from .cli_args import CliArgs -from .cli_dependencies import CliDependencies -from .file_path_collector import FilePathCollector -from .indirection_analyzer import IndirectionAnalyzer -from .report.builder import ReportBuilder -from .report.serializer import ReportSerializer -from .threshold_config import ThresholdConfig +from ..bps.analyzer import BpsAnalyzer +from ..csa.analyzer import CsaAnalyzer +from ..file.path_collector import FilePathCollector +from ..indirection.analyzer import IndirectionAnalyzer +from ..report.builder import ReportBuilder +from ..report.serializer import ReportSerializer +from ..threshold.config import ThresholdConfig +from .args import CliArgs +from .dependencies import CliDependencies logger = logging.getLogger(__name__) diff --git a/codra/condition/__init__.py b/codra/condition/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codra/condition_metrics_collector.py b/codra/condition/collector.py similarity index 95% rename from codra/condition_metrics_collector.py rename to codra/condition/collector.py index a2bcc0a..0893384 100644 --- a/codra/condition_metrics_collector.py +++ b/codra/condition/collector.py @@ -3,7 +3,7 @@ import ast from dataclasses import dataclass -from .condition_metrics import ConditionMetrics +from .metrics import ConditionMetrics @dataclass diff --git a/codra/condition_metrics.py b/codra/condition/metrics.py similarity index 100% rename from codra/condition_metrics.py rename to codra/condition/metrics.py diff --git a/codra/csa/__init__.py b/codra/csa/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codra/csa_analyzer.py b/codra/csa/analyzer.py similarity index 86% rename from codra/csa_analyzer.py rename to codra/csa/analyzer.py index 7f6dc84..595787f 100644 --- a/codra/csa_analyzer.py +++ b/codra/csa/analyzer.py @@ -3,10 +3,10 @@ import ast from dataclasses import dataclass -from .csa_result import CsaResult -from .function_symbol_collector import FunctionSymbolCollector -from .module_symbol_collector import ModuleSymbolCollector -from .unit.node_collector import UnitNodeCollector +from ..function.symbol_collector import FunctionSymbolCollector +from ..module.symbol_collector import ModuleSymbolCollector +from ..unit.node_collector import UnitNodeCollector +from .result import CsaResult @dataclass diff --git a/codra/csa_result.py b/codra/csa/result.py similarity index 83% rename from codra/csa_result.py rename to codra/csa/result.py index 2b0c18a..39fc8a7 100644 --- a/codra/csa_result.py +++ b/codra/csa/result.py @@ -2,7 +2,7 @@ from dataclasses import dataclass -from .unit.definition import UnitDefinition +from ..unit.definition import UnitDefinition @dataclass(frozen=True) diff --git a/codra/directory/__init__.py b/codra/directory/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codra/directory_scanner.py b/codra/directory/scanner.py similarity index 87% rename from codra/directory_scanner.py rename to codra/directory/scanner.py index a87acc8..63b6500 100644 --- a/codra/directory_scanner.py +++ b/codra/directory/scanner.py @@ -3,8 +3,8 @@ import os from dataclasses import dataclass, field -from .python_file_scanner import PythonFileScanner -from .unit.definition import UnitDefinition +from ..python.file_scanner import PythonFileScanner +from ..unit.definition import UnitDefinition @dataclass diff --git a/codra/file/__init__.py b/codra/file/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codra/file_path_collector.py b/codra/file/path_collector.py similarity index 100% rename from codra/file_path_collector.py rename to codra/file/path_collector.py diff --git a/codra/function/__init__.py b/codra/function/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codra/function_definition_collector.py b/codra/function/definition_collector.py similarity index 100% rename from codra/function_definition_collector.py rename to codra/function/definition_collector.py diff --git a/codra/function_extractor.py b/codra/function/extractor.py similarity index 97% rename from codra/function_extractor.py rename to codra/function/extractor.py index 3091ceb..6443fa5 100644 --- a/codra/function_extractor.py +++ b/codra/function/extractor.py @@ -3,7 +3,7 @@ import ast from dataclasses import dataclass, field -from .unit.definition import UnitDefinition +from ..unit.definition import UnitDefinition @dataclass diff --git a/codra/function_symbol_collector.py b/codra/function/symbol_collector.py similarity index 97% rename from codra/function_symbol_collector.py rename to codra/function/symbol_collector.py index f765130..db81046 100644 --- a/codra/function_symbol_collector.py +++ b/codra/function/symbol_collector.py @@ -3,7 +3,7 @@ import ast from dataclasses import dataclass, field -from .function_symbol_usage import FunctionSymbolUsage +from .symbol_usage import FunctionSymbolUsage @dataclass diff --git a/codra/function_symbol_usage.py b/codra/function/symbol_usage.py similarity index 100% rename from codra/function_symbol_usage.py rename to codra/function/symbol_usage.py diff --git a/codra/if_/__init__.py b/codra/if_/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codra/if_collector.py b/codra/if_/collector.py similarity index 100% rename from codra/if_collector.py rename to codra/if_/collector.py diff --git a/codra/indirection/__init__.py b/codra/indirection/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codra/indirection_analyzer.py b/codra/indirection/analyzer.py similarity index 93% rename from codra/indirection_analyzer.py rename to codra/indirection/analyzer.py index 61f2107..b25a412 100644 --- a/codra/indirection_analyzer.py +++ b/codra/indirection/analyzer.py @@ -3,11 +3,11 @@ import ast from dataclasses import dataclass -from .alias_collector import AliasCollector -from .call_collector import CallCollector -from .function_definition_collector import FunctionDefinitionCollector -from .indirection_result import IndirectionResult -from .unit.node_collector import UnitNodeCollector +from ..alias.collector import AliasCollector +from ..call.collector import CallCollector +from ..function.definition_collector import FunctionDefinitionCollector +from ..unit.node_collector import UnitNodeCollector +from .result import IndirectionResult @dataclass diff --git a/codra/indirection_result.py b/codra/indirection/result.py similarity index 82% rename from codra/indirection_result.py rename to codra/indirection/result.py index b6521f2..4200b75 100644 --- a/codra/indirection_result.py +++ b/codra/indirection/result.py @@ -2,7 +2,7 @@ from dataclasses import dataclass -from .unit.definition import UnitDefinition +from ..unit.definition import UnitDefinition @dataclass(frozen=True) diff --git a/codra/module/__init__.py b/codra/module/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codra/module_symbol_collector.py b/codra/module/symbol_collector.py similarity index 100% rename from codra/module_symbol_collector.py rename to codra/module/symbol_collector.py diff --git a/codra/python/__init__.py b/codra/python/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codra/python_file_scanner.py b/codra/python/file_scanner.py similarity index 82% rename from codra/python_file_scanner.py rename to codra/python/file_scanner.py index 46a9e7b..c276682 100644 --- a/codra/python_file_scanner.py +++ b/codra/python/file_scanner.py @@ -3,8 +3,8 @@ import ast from dataclasses import dataclass -from .function_extractor import FunctionExtractor -from .unit.definition import UnitDefinition +from ..function.extractor import FunctionExtractor +from ..unit.definition import UnitDefinition @dataclass diff --git a/codra/report/builder.py b/codra/report/builder.py index eb98fc8..6de5556 100644 --- a/codra/report/builder.py +++ b/codra/report/builder.py @@ -3,11 +3,11 @@ import logging from dataclasses import dataclass -from ..bps_analyzer import BpsAnalyzer -from ..csa_analyzer import CsaAnalyzer -from ..file_path_collector import FilePathCollector -from ..indirection_analyzer import IndirectionAnalyzer -from ..threshold_config import ThresholdConfig +from ..bps.analyzer import BpsAnalyzer +from ..csa.analyzer import CsaAnalyzer +from ..file.path_collector import FilePathCollector +from ..indirection.analyzer import IndirectionAnalyzer +from ..threshold.config import ThresholdConfig from ..unit.definition import UnitDefinition from ..unit.key import UnitKey from ..unit.metrics import UnitMetrics diff --git a/codra/threshold/__init__.py b/codra/threshold/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/codra/threshold_config.py b/codra/threshold/config.py similarity index 100% rename from codra/threshold_config.py rename to codra/threshold/config.py From 1da4f98e07f2b91737e16a22981d47d58661f992 Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 14:51:56 +0100 Subject: [PATCH 03/17] Update docs and launch config for new CLI --- .vscode/launch.json | 6 ++++-- README.md | 10 ++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 9130c5b..2654712 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,12 +9,14 @@ "env": { "PYTHONPATH": "${workspaceFolder}" }, - "module": "codra.cli", + "module": "codra.cli.main", "args": [ "${workspaceFolder}/codra", "--threshold-csa", "5", "--threshold-id", "2", - "--threshold-bps", "0.7" + "--threshold-bps", "0.7", + "--log-level", "DEBUG", + "--output", "${workspaceFolder}/report.json" ], "console": "integratedTerminal", "justMyCode": false diff --git a/README.md b/README.md index 1e706e6..fca4f71 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,19 @@ pip install -e . Analizza un percorso e stampa il report JSON su stdout: ```bash -python3 -m codra.cli /percorso/progetto +python3 -m codra.cli.main /percorso/progetto ``` Esempio con soglie: ```bash -python3 -m codra.cli /percorso/progetto --threshold-csa 10 --threshold-id 2 --threshold-bps 0.7 +python3 -m codra.cli.main /percorso/progetto --threshold-csa 10 --threshold-id 2 --threshold-bps 0.7 +``` + +Esempio con log e file di output: + +```bash +python3 -m codra.cli.main /percorso/progetto --log-level DEBUG --output report.json ``` ## Viewer React (opzionale) From 4bf6267971041d233a6f285e4ad2e32929281924 Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 15:05:35 +0100 Subject: [PATCH 04/17] Add status filters and issue indicators to viewer --- viewer/src/App.tsx | 80 +++++++++++++++++++++++++++++++++++-- viewer/src/report_table.tsx | 61 ++++++++++++++++++++++++---- viewer/src/report_utils.ts | 72 +++++++++++++++++++++++++++++++++ viewer/src/styles.css | 68 ++++++++++++++++++++++++++++++- 4 files changed, 270 insertions(+), 11 deletions(-) create mode 100644 viewer/src/report_utils.ts diff --git a/viewer/src/App.tsx b/viewer/src/App.tsx index c60d7c3..7a18998 100644 --- a/viewer/src/App.tsx +++ b/viewer/src/App.tsx @@ -1,11 +1,14 @@ -import { useState } from "react" +import { useMemo, useState } from "react" import type { Report } from "./report_types" import { ReportTable } from "./report_table" +import { evaluateMetrics, type StatusLevel } from "./report_utils" export function App() { const [report, setReport] = useState(null) const [error, setError] = useState(null) const [filterText, setFilterText] = useState("") + const [statusFilter, setStatusFilter] = useState("all") + const [onlyIssues, setOnlyIssues] = useState(false) const handleFile = (event: React.ChangeEvent) => { const file = event.target.files?.[0] @@ -30,6 +33,27 @@ export function App() { reader.readAsText(file) } + const globalStats = useMemo(() => { + if (!report) { + return null + } + const stats = { + ok: 0, + warning: 0, + critical: 0 + } + for (const file of report.files) { + for (const unit of file.units) { + const evaluation = evaluateMetrics(unit.metrics) + stats[evaluation.level] += 1 + } + } + const issues = stats.warning + stats.critical + const overall: StatusLevel = + stats.critical > 0 ? "critical" : stats.warning > 0 ? "warning" : "ok" + return { ...stats, issues, overall } + }, [report]) + return (
@@ -47,8 +71,30 @@ export function App() { type="text" value={filterText} onChange={(event) => setFilterText(event.target.value)} - placeholder="Search by file, unit, or kind" + placeholder="Search by file, unit, kind, or issue" + /> + + + {error ?
{error}
: null} @@ -71,8 +117,36 @@ export function App() { Units {report.summary.total_units}
+
+ Critical + {globalStats?.critical ?? 0} +
+
+ Warning + {globalStats?.warning ?? 0} +
+
+ OK + {globalStats?.ok ?? 0} +
+
+ Issues + {globalStats?.issues ?? 0} +
+
+ Health + + + {globalStats?.overall ?? "ok"} + +
- + ) : (
diff --git a/viewer/src/report_table.tsx b/viewer/src/report_table.tsx index 907ab64..d2eddf2 100644 --- a/viewer/src/report_table.tsx +++ b/viewer/src/report_table.tsx @@ -1,24 +1,35 @@ import { useMemo, useState } from "react" import type { FileReport, UnitReport } from "./report_types" +import { evaluateMetrics, type StatusLevel } from "./report_utils" -export type SortKey = "file" | "unit" | "kind" | "csa" | "id" | "bps" +export type SortKey = "file" | "unit" | "kind" | "status" | "csa" | "id" | "bps" type ReportTableProps = { files: FileReport[] filterText: string + statusFilter: StatusLevel | "all" + onlyIssues: boolean } type Row = { file: string unit: string kind: string + status: StatusLevel + statusScore: number + issues: string[] csa: number idMax: number bps: number raw: UnitReport } -export function ReportTable({ files, filterText }: ReportTableProps) { +export function ReportTable({ + files, + filterText, + statusFilter, + onlyIssues +}: ReportTableProps) { const [sortKey, setSortKey] = useState("file") const [direction, setDirection] = useState<"asc" | "desc">("asc") @@ -26,10 +37,14 @@ export function ReportTable({ files, filterText }: ReportTableProps) { const entries: Row[] = [] for (const file of files) { for (const unit of file.units) { + const evaluation = evaluateMetrics(unit.metrics) entries.push({ file: file.file_path, unit: unit.unit.qualified_id, kind: unit.unit.kind, + status: evaluation.level, + statusScore: evaluation.score, + issues: evaluation.issues.map((issue) => issue.label), csa: unit.metrics.csa_main, idMax: unit.metrics.id_max, bps: unit.metrics.bps, @@ -38,14 +53,18 @@ export function ReportTable({ files, filterText }: ReportTableProps) { } } const needle = filterText.trim().toLowerCase() - const filtered = needle - ? entries.filter((row) => - [row.file, row.unit, row.kind] + const filtered = entries.filter((row) => { + const matchesText = needle + ? [row.file, row.unit, row.kind, row.issues.join(" ")] .join(" ") .toLowerCase() .includes(needle) - ) - : entries + : true + const matchesStatus = + statusFilter === "all" ? true : row.status === statusFilter + const matchesIssues = onlyIssues ? row.status !== "ok" : true + return matchesText && matchesStatus && matchesIssues + }) const sorted = [...filtered].sort((left, right) => { const factor = direction === "asc" ? 1 : -1 if (sortKey === "file") { @@ -57,6 +76,9 @@ export function ReportTable({ files, filterText }: ReportTableProps) { if (sortKey === "kind") { return left.kind.localeCompare(right.kind) * factor } + if (sortKey === "status") { + return (left.statusScore - right.statusScore) * factor + } if (sortKey === "csa") { return (left.csa - right.csa) * factor } @@ -96,6 +118,11 @@ export function ReportTable({ files, filterText }: ReportTableProps) { Kind + + + + Issues @@ -119,9 +147,28 @@ export function ReportTable({ files, filterText }: ReportTableProps) { {row.file} {row.unit} {row.kind} + + + + {row.status} + + {row.csa} {row.idMax} {row.bps.toFixed(2)} + +
+ {row.issues.length ? ( + row.issues.map((issue) => ( + + {issue} + + )) + ) : ( + OK + )} +
+ ))} diff --git a/viewer/src/report_utils.ts b/viewer/src/report_utils.ts new file mode 100644 index 0000000..e95c254 --- /dev/null +++ b/viewer/src/report_utils.ts @@ -0,0 +1,72 @@ +import type { UnitMetrics } from "./report_types" + +export type StatusLevel = "ok" | "warning" | "critical" + +type Issue = { + label: string + level: StatusLevel +} + +const thresholds = { + csa: { + warning: 5, + critical: 10 + }, + idMax: { + warning: 3, + critical: 5 + }, + bps: { + warning: 0.7, + critical: 0.5 + } +} + +const levelScore: Record = { + ok: 0, + warning: 1, + critical: 2 +} + +export function evaluateMetrics(metrics: UnitMetrics): { + level: StatusLevel + score: number + issues: Issue[] +} { + const issues: Issue[] = [] + + if (metrics.csa_main >= thresholds.csa.critical) { + issues.push({ label: "CSA >= 10", level: "critical" }) + } else if (metrics.csa_main >= thresholds.csa.warning) { + issues.push({ label: "CSA >= 5", level: "warning" }) + } + + if (metrics.id_max >= thresholds.idMax.critical) { + issues.push({ label: "ID max >= 5", level: "critical" }) + } else if (metrics.id_max >= thresholds.idMax.warning) { + issues.push({ label: "ID max >= 3", level: "warning" }) + } + + if (metrics.bps <= thresholds.bps.critical) { + issues.push({ label: "BPS <= 0.5", level: "critical" }) + } else if (metrics.bps <= thresholds.bps.warning) { + issues.push({ label: "BPS <= 0.7", level: "warning" }) + } + + if (metrics.unresolved_calls.length > 0) { + issues.push({ label: "Chiamate non risolte", level: "warning" }) + } + + let level: StatusLevel = "ok" + for (const issue of issues) { + if (levelScore[issue.level] > levelScore[level]) { + level = issue.level + } + } + + return { + level, + score: levelScore[level], + issues + } +} diff --git a/viewer/src/styles.css b/viewer/src/styles.css index 7af25cd..f302822 100644 --- a/viewer/src/styles.css +++ b/viewer/src/styles.css @@ -42,13 +42,27 @@ header p { } .controls input[type="text"], -.controls input[type="file"] { +.controls input[type="file"], +.controls select { padding: 8px 10px; border-radius: 6px; border: 1px solid #cbd5f5; background: #ffffff; } +.checkbox-input { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + margin-bottom: 2px; +} + +.checkbox-input input { + width: 16px; + height: 16px; +} + .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); @@ -72,6 +86,58 @@ header p { color: #64748b; } +.summary-status span { + display: inline-flex; + align-items: center; + gap: 8px; +} + +.status { + display: inline-flex; + align-items: center; + gap: 8px; + text-transform: capitalize; + font-weight: 600; +} + +.status-dot { + width: 10px; + height: 10px; + border-radius: 50%; + background: #22c55e; + box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.15); +} + +.status-warning .status-dot { + background: #f59e0b; + box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.2); +} + +.status-critical .status-dot { + background: #ef4444; + box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.2); +} + +.issue-list { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.issue-pill { + padding: 2px 8px; + border-radius: 999px; + background: #fef3c7; + color: #92400e; + font-size: 12px; + font-weight: 600; +} + +.issue-pill.issue-ok { + background: #dcfce7; + color: #166534; +} + .report-table { width: 100%; border-collapse: collapse; From 32ae825a2e3cc79d3552388a379d1b2338552c1b Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 15:12:37 +0100 Subject: [PATCH 05/17] Refine unresolved call detection --- codra/indirection/analyzer.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/codra/indirection/analyzer.py b/codra/indirection/analyzer.py index b25a412..5f7adf8 100644 --- a/codra/indirection/analyzer.py +++ b/codra/indirection/analyzer.py @@ -1,11 +1,14 @@ from __future__ import annotations import ast +import builtins +import sys from dataclasses import dataclass from ..alias.collector import AliasCollector from ..call.collector import CallCollector from ..function.definition_collector import FunctionDefinitionCollector +from ..module.symbol_collector import ModuleSymbolCollector from ..unit.node_collector import UnitNodeCollector from .result import IndirectionResult @@ -18,6 +21,9 @@ def analyze_file(self, file_path: str) -> list[IndirectionResult]: tree = ast.parse(source, filename=file_path) function_names = FunctionDefinitionCollector().collect(tree) aliases = AliasCollector().collect(tree) + module_symbols = ModuleSymbolCollector().collect(tree) + builtin_names = set(dir(builtins)) + stdlib_modules = set(sys.stdlib_module_names) call_graph = self._build_call_graph(tree, function_names, aliases) depth_cache: dict[str, int] = {} results: list[IndirectionResult] = [] @@ -31,6 +37,12 @@ def analyze_file(self, file_path: str) -> list[IndirectionResult]: resolved = self._resolve_alias(name, aliases) if resolved in function_names: resolved_calls.append(resolved) + elif resolved in module_symbols: + continue + elif resolved in builtin_names: + continue + elif resolved in stdlib_modules: + continue else: unresolved_calls.add(name) call_depths = [ From beae8de2045e46e66c7bb2ef57ed4ff07a545ef7 Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 15:21:38 +0100 Subject: [PATCH 06/17] Reduce noise in CSA and indirection analysis --- codra/call/collector.py | 3 +++ codra/csa/analyzer.py | 9 +++++++ codra/indirection/analyzer.py | 48 ++++++++++++++++++++++++++++++++++- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/codra/call/collector.py b/codra/call/collector.py index 5ba57e4..5734541 100644 --- a/codra/call/collector.py +++ b/codra/call/collector.py @@ -25,4 +25,7 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None: def visit_Call(self, node: ast.Call) -> None: if isinstance(node.func, ast.Name): self.calls.append(node.func.id) + elif isinstance(node.func, ast.Attribute): + if isinstance(node.func.value, ast.Name) and node.func.value.id == "self": + self.calls.append(f"self.{node.func.attr}") self.generic_visit(node) diff --git a/codra/csa/analyzer.py b/codra/csa/analyzer.py index 595787f..893ca44 100644 --- a/codra/csa/analyzer.py +++ b/codra/csa/analyzer.py @@ -1,6 +1,8 @@ from __future__ import annotations import ast +import builtins +import sys from dataclasses import dataclass from ..function.symbol_collector import FunctionSymbolCollector @@ -16,6 +18,8 @@ def analyze_file(self, file_path: str) -> list[CsaResult]: source = handle.read() tree = ast.parse(source, filename=file_path) module_symbols = ModuleSymbolCollector().collect(tree) + builtin_names = set(dir(builtins)) + stdlib_modules = set(sys.stdlib_module_names) unit_collector = UnitNodeCollector(file_path=file_path) unit_collector.visit(tree) results: list[CsaResult] = [] @@ -26,6 +30,11 @@ def analyze_file(self, file_path: str) -> list[CsaResult]: global_symbols = used_names.intersection(module_symbols) - local_names free_symbols = used_names - local_names - module_symbols external_symbols = global_symbols.union(free_symbols) + external_symbols = { + symbol + for symbol in external_symbols + if symbol not in builtin_names and symbol not in stdlib_modules + } results.append( CsaResult( unit=unit_node.definition, diff --git a/codra/indirection/analyzer.py b/codra/indirection/analyzer.py index 5f7adf8..bb84d32 100644 --- a/codra/indirection/analyzer.py +++ b/codra/indirection/analyzer.py @@ -20,20 +20,31 @@ def analyze_file(self, file_path: str) -> list[IndirectionResult]: source = handle.read() tree = ast.parse(source, filename=file_path) function_names = FunctionDefinitionCollector().collect(tree) + class_methods = self._collect_class_methods(tree) aliases = AliasCollector().collect(tree) module_symbols = ModuleSymbolCollector().collect(tree) builtin_names = set(dir(builtins)) stdlib_modules = set(sys.stdlib_module_names) - call_graph = self._build_call_graph(tree, function_names, aliases) + call_graph = self._build_call_graph(tree, function_names, class_methods, aliases) depth_cache: dict[str, int] = {} results: list[IndirectionResult] = [] unit_collector = UnitNodeCollector(file_path=file_path) unit_collector.visit(tree) for unit_node in unit_collector.units: + class_name = ( + unit_node.definition.qualified_id.split(".", 1)[0] + if unit_node.definition.kind == "method" + else None + ) call_names = CallCollector().collect(unit_node.node) resolved_calls: list[str] = [] unresolved_calls: set[str] = set() for name in call_names: + if name.startswith("self.") and class_name: + method_name = name.split(".", 1)[1] + if method_name in class_methods.get(class_name, set()): + resolved_calls.append(f"{class_name}.{method_name}") + continue resolved = self._resolve_alias(name, aliases) if resolved in function_names: resolved_calls.append(resolved) @@ -65,13 +76,29 @@ def analyze_file(self, file_path: str) -> list[IndirectionResult]: ) return results + def _collect_class_methods(self, tree: ast.AST) -> dict[str, set[str]]: + class_methods: dict[str, set[str]] = {} + for node in tree.body: + if isinstance(node, ast.ClassDef): + methods = { + statement.name + for statement in node.body + if isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef)) + } + class_methods[node.name] = methods + return class_methods + def _build_call_graph( self, tree: ast.AST, function_names: set[str], + class_methods: dict[str, set[str]], aliases: dict[str, str], ) -> dict[str, list[str]]: call_graph: dict[str, list[str]] = {name: [] for name in function_names} + for class_name, methods in class_methods.items(): + for method_name in methods: + call_graph[f"{class_name}.{method_name}"] = [] for node in tree.body: if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): if node.name in function_names: @@ -80,6 +107,25 @@ def _build_call_graph( resolved = self._resolve_alias(name, aliases) if resolved in function_names: call_graph[node.name].append(resolved) + elif isinstance(node, ast.ClassDef): + methods = class_methods.get(node.name, set()) + for statement in node.body: + if isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef)): + qualified_name = f"{node.name}.{statement.name}" + if qualified_name not in call_graph: + continue + call_names = CallCollector().collect(statement) + for name in call_names: + if name.startswith("self."): + method_name = name.split(".", 1)[1] + if method_name in methods: + call_graph[qualified_name].append( + f"{node.name}.{method_name}" + ) + else: + resolved = self._resolve_alias(name, aliases) + if resolved in function_names: + call_graph[qualified_name].append(resolved) return call_graph def _resolve_alias(self, name: str, aliases: dict[str, str]) -> str: From 5da4edb6ca84a719ec2d71227ea3de685f2e8e69 Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 15:47:54 +0100 Subject: [PATCH 07/17] Treat self calls as resolved for indirection --- codra/indirection/analyzer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codra/indirection/analyzer.py b/codra/indirection/analyzer.py index bb84d32..c4074f8 100644 --- a/codra/indirection/analyzer.py +++ b/codra/indirection/analyzer.py @@ -44,7 +44,7 @@ def analyze_file(self, file_path: str) -> list[IndirectionResult]: method_name = name.split(".", 1)[1] if method_name in class_methods.get(class_name, set()): resolved_calls.append(f"{class_name}.{method_name}") - continue + continue resolved = self._resolve_alias(name, aliases) if resolved in function_names: resolved_calls.append(resolved) From ce2a75b2d05574e67cf6143a4ce940574200b07e Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 15:47:59 +0100 Subject: [PATCH 08/17] Ignore self calls in unresolved list --- codra/indirection/analyzer.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/codra/indirection/analyzer.py b/codra/indirection/analyzer.py index c4074f8..1b2ff16 100644 --- a/codra/indirection/analyzer.py +++ b/codra/indirection/analyzer.py @@ -40,10 +40,11 @@ def analyze_file(self, file_path: str) -> list[IndirectionResult]: resolved_calls: list[str] = [] unresolved_calls: set[str] = set() for name in call_names: - if name.startswith("self.") and class_name: - method_name = name.split(".", 1)[1] - if method_name in class_methods.get(class_name, set()): - resolved_calls.append(f"{class_name}.{method_name}") + if name.startswith("self."): + if class_name: + method_name = name.split(".", 1)[1] + if method_name in class_methods.get(class_name, set()): + resolved_calls.append(f"{class_name}.{method_name}") continue resolved = self._resolve_alias(name, aliases) if resolved in function_names: From 458d1fe3e4cff16869e052b2bbccffde4a4f686d Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 16:11:47 +0100 Subject: [PATCH 09/17] Refine collector visitors --- codra/alias/collector.py | 8 +- codra/call/collector.py | 8 +- codra/condition/collector.py | 12 +- codra/function/definition_collector.py | 12 +- codra/function/symbol_collector.py | 12 +- codra/if_/collector.py | 8 +- codra/module/symbol_collector.py | 22 ++-- codra/unit/node_collector.py | 8 +- tests/test_collectors.py | 176 +++++++++++++++++++++++++ 9 files changed, 229 insertions(+), 37 deletions(-) create mode 100644 tests/test_collectors.py diff --git a/codra/alias/collector.py b/codra/alias/collector.py index ef23a78..690dd5a 100644 --- a/codra/alias/collector.py +++ b/codra/alias/collector.py @@ -1,22 +1,24 @@ from __future__ import annotations import ast +from ast import NodeVisitor from dataclasses import dataclass, field @dataclass -class AliasCollector(ast.NodeVisitor): +class AliasCollector(NodeVisitor): + """AST visitor collecting top-level alias assignments via NodeVisitor.""" aliases: dict[str, str] = field(default_factory=dict) depth: int = 0 def collect(self, tree: ast.AST) -> dict[str, str]: - self.visit(tree) + super().visit(tree) return dict(self.aliases) def visit_Module(self, node: ast.Module) -> None: self.depth += 1 for statement in node.body: - self.visit(statement) + super().visit(statement) self.depth -= 1 def visit_FunctionDef(self, node: ast.FunctionDef) -> None: diff --git a/codra/call/collector.py b/codra/call/collector.py index 5734541..b311752 100644 --- a/codra/call/collector.py +++ b/codra/call/collector.py @@ -1,16 +1,18 @@ from __future__ import annotations import ast +from ast import NodeVisitor from dataclasses import dataclass, field @dataclass -class CallCollector(ast.NodeVisitor): +class CallCollector(NodeVisitor): + """AST visitor collecting call expressions via NodeVisitor.""" calls: list[str] = field(default_factory=list) def collect(self, node: ast.AST) -> list[str]: for statement in node.body: - self.visit(statement) + super().visit(statement) return list(self.calls) def visit_FunctionDef(self, node: ast.FunctionDef) -> None: @@ -28,4 +30,4 @@ def visit_Call(self, node: ast.Call) -> None: elif isinstance(node.func, ast.Attribute): if isinstance(node.func.value, ast.Name) and node.func.value.id == "self": self.calls.append(f"self.{node.func.attr}") - self.generic_visit(node) + super().generic_visit(node) diff --git a/codra/condition/collector.py b/codra/condition/collector.py index 0893384..fde44ce 100644 --- a/codra/condition/collector.py +++ b/codra/condition/collector.py @@ -1,13 +1,15 @@ from __future__ import annotations import ast +from ast import NodeVisitor from dataclasses import dataclass from .metrics import ConditionMetrics @dataclass -class ConditionMetricsCollector(ast.NodeVisitor): +class ConditionMetricsCollector(NodeVisitor): + """AST visitor collecting condition metrics via NodeVisitor.""" local_names: set[str] bool_ops: int = 0 compare_ops: int = 0 @@ -15,7 +17,7 @@ class ConditionMetricsCollector(ast.NodeVisitor): external_refs: int = 0 def collect(self, node: ast.AST) -> ConditionMetrics: - self.visit(node) + super().visit(node) return ConditionMetrics( bool_ops=self.bool_ops, compare_ops=self.compare_ops, @@ -25,15 +27,15 @@ def collect(self, node: ast.AST) -> ConditionMetrics: def visit_BoolOp(self, node: ast.BoolOp) -> None: self.bool_ops += max(0, len(node.values) - 1) - self.generic_visit(node) + super().generic_visit(node) def visit_Compare(self, node: ast.Compare) -> None: self.compare_ops += len(node.ops) - self.generic_visit(node) + super().generic_visit(node) def visit_Call(self, node: ast.Call) -> None: self.calls += 1 - self.generic_visit(node) + super().generic_visit(node) def visit_Name(self, node: ast.Name) -> None: if isinstance(node.ctx, ast.Load) and node.id not in self.local_names: diff --git a/codra/function/definition_collector.py b/codra/function/definition_collector.py index 875f066..e2c7958 100644 --- a/codra/function/definition_collector.py +++ b/codra/function/definition_collector.py @@ -1,32 +1,34 @@ from __future__ import annotations import ast +from ast import NodeVisitor from dataclasses import dataclass, field @dataclass -class FunctionDefinitionCollector(ast.NodeVisitor): +class FunctionDefinitionCollector(NodeVisitor): + """AST visitor collecting function definitions via NodeVisitor.""" names: set[str] = field(default_factory=set) class_stack: list[str] = field(default_factory=list) def collect(self, tree: ast.AST) -> set[str]: - self.visit(tree) + super().visit(tree) return set(self.names) def visit_ClassDef(self, node: ast.ClassDef) -> None: self.class_stack.append(node.name) for statement in node.body: - self.visit(statement) + super().visit(statement) self.class_stack.pop() def visit_FunctionDef(self, node: ast.FunctionDef) -> None: if not self.class_stack: self.names.add(node.name) for statement in node.body: - self.visit(statement) + super().visit(statement) def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None: if not self.class_stack: self.names.add(node.name) for statement in node.body: - self.visit(statement) + super().visit(statement) diff --git a/codra/function/symbol_collector.py b/codra/function/symbol_collector.py index db81046..8232c8a 100644 --- a/codra/function/symbol_collector.py +++ b/codra/function/symbol_collector.py @@ -1,19 +1,21 @@ from __future__ import annotations import ast +from ast import NodeVisitor from dataclasses import dataclass, field from .symbol_usage import FunctionSymbolUsage @dataclass -class FunctionSymbolCollector(ast.NodeVisitor): +class FunctionSymbolCollector(NodeVisitor): + """AST visitor collecting symbol usage via NodeVisitor.""" usage: FunctionSymbolUsage = field(default_factory=FunctionSymbolUsage) def collect(self, node: ast.AST) -> FunctionSymbolUsage: self._add_arguments(node) for statement in node.body: - self.visit(statement) + super().visit(statement) return self.usage def visit_FunctionDef(self, node: ast.FunctionDef) -> None: @@ -45,15 +47,15 @@ def visit_Attribute(self, node: ast.Attribute) -> None: if isinstance(node.ctx, ast.Load): if isinstance(node.value, ast.Name) and node.value.id == "self": self.usage.self_fields_read.add(node.attr) - self.generic_visit(node) + super().generic_visit(node) def visit_ExceptHandler(self, node: ast.ExceptHandler) -> None: if isinstance(node.name, str): self.usage.locals.add(node.name) elif node.name is not None: - self.visit(node.name) + super().visit(node.name) for statement in node.body: - self.visit(statement) + super().visit(statement) def _add_arguments(self, node: ast.AST) -> None: arguments = node.args diff --git a/codra/if_/collector.py b/codra/if_/collector.py index 629bc31..f32748c 100644 --- a/codra/if_/collector.py +++ b/codra/if_/collector.py @@ -1,16 +1,18 @@ from __future__ import annotations import ast +from ast import NodeVisitor from dataclasses import dataclass, field @dataclass -class IfCollector(ast.NodeVisitor): +class IfCollector(NodeVisitor): + """AST visitor collecting if statements via NodeVisitor.""" nodes: list[ast.If] = field(default_factory=list) def collect(self, node: ast.AST) -> list[ast.If]: for statement in node.body: - self.visit(statement) + super().visit(statement) return list(self.nodes) def visit_FunctionDef(self, node: ast.FunctionDef) -> None: @@ -24,4 +26,4 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None: def visit_If(self, node: ast.If) -> None: self.nodes.append(node) - self.generic_visit(node) + super().generic_visit(node) diff --git a/codra/module/symbol_collector.py b/codra/module/symbol_collector.py index 5133a9f..713896c 100644 --- a/codra/module/symbol_collector.py +++ b/codra/module/symbol_collector.py @@ -1,15 +1,17 @@ from __future__ import annotations import ast +from ast import NodeVisitor from dataclasses import dataclass, field @dataclass -class ModuleSymbolCollector(ast.NodeVisitor): +class ModuleSymbolCollector(NodeVisitor): + """AST visitor collecting module symbols via NodeVisitor.""" symbols: set[str] = field(default_factory=set) def collect(self, tree: ast.AST) -> set[str]: - self.visit(tree) + super().visit(tree) return set(self.symbols) def visit_FunctionDef(self, node: ast.FunctionDef) -> None: @@ -34,42 +36,42 @@ def visit_ImportFrom(self, node: ast.ImportFrom) -> None: def visit_Assign(self, node: ast.Assign) -> None: for target in node.targets: self.symbols.update(self._extract_target_names(target)) - self.generic_visit(node) + super().generic_visit(node) def visit_AnnAssign(self, node: ast.AnnAssign) -> None: self.symbols.update(self._extract_target_names(node.target)) - self.generic_visit(node) + super().generic_visit(node) def visit_AugAssign(self, node: ast.AugAssign) -> None: self.symbols.update(self._extract_target_names(node.target)) - self.generic_visit(node) + super().generic_visit(node) def visit_For(self, node: ast.For) -> None: self.symbols.update(self._extract_target_names(node.target)) - self.generic_visit(node) + super().generic_visit(node) def visit_AsyncFor(self, node: ast.AsyncFor) -> None: self.symbols.update(self._extract_target_names(node.target)) - self.generic_visit(node) + super().generic_visit(node) def visit_With(self, node: ast.With) -> None: for item in node.items: if item.optional_vars is not None: self.symbols.update(self._extract_target_names(item.optional_vars)) - self.generic_visit(node) + super().generic_visit(node) def visit_AsyncWith(self, node: ast.AsyncWith) -> None: for item in node.items: if item.optional_vars is not None: self.symbols.update(self._extract_target_names(item.optional_vars)) - self.generic_visit(node) + super().generic_visit(node) def visit_ExceptHandler(self, node: ast.ExceptHandler) -> None: if isinstance(node.name, str): self.symbols.add(node.name) elif node.name is not None: self.symbols.update(self._extract_target_names(node.name)) - self.generic_visit(node) + super().generic_visit(node) def _extract_target_names(self, node: ast.AST) -> set[str]: names: set[str] = set() diff --git a/codra/unit/node_collector.py b/codra/unit/node_collector.py index c279c8d..8aa1397 100644 --- a/codra/unit/node_collector.py +++ b/codra/unit/node_collector.py @@ -1,6 +1,7 @@ from __future__ import annotations import ast +from ast import NodeVisitor from dataclasses import dataclass, field from .definition import UnitDefinition @@ -8,7 +9,8 @@ @dataclass -class UnitNodeCollector(ast.NodeVisitor): +class UnitNodeCollector(NodeVisitor): + """AST visitor collecting unit nodes via NodeVisitor.""" file_path: str units: list[UnitNode] = field(default_factory=list) class_stack: list[str] = field(default_factory=list) @@ -16,7 +18,7 @@ class UnitNodeCollector(ast.NodeVisitor): def visit_ClassDef(self, node: ast.ClassDef) -> None: self.class_stack.append(node.name) - self.generic_visit(node) + super().generic_visit(node) self.class_stack.pop() def visit_FunctionDef(self, node: ast.FunctionDef) -> None: @@ -51,5 +53,5 @@ def _handle_function(self, node: ast.AST) -> None: ) self.units.append(UnitNode(definition=definition, node=node)) self.function_stack.append(name) - self.generic_visit(node) + super().generic_visit(node) self.function_stack.pop() diff --git a/tests/test_collectors.py b/tests/test_collectors.py new file mode 100644 index 0000000..e43e4bd --- /dev/null +++ b/tests/test_collectors.py @@ -0,0 +1,176 @@ +import ast +import unittest + +from codra.alias.collector import AliasCollector +from codra.call.collector import CallCollector +from codra.condition.collector import ConditionMetricsCollector +from codra.function.definition_collector import FunctionDefinitionCollector +from codra.function.symbol_collector import FunctionSymbolCollector +from codra.if_.collector import IfCollector +from codra.module.symbol_collector import ModuleSymbolCollector +from codra.unit.node_collector import UnitNodeCollector + + +class CollectorVisitTests(unittest.TestCase): + def test_alias_collector_tracks_top_level_aliases(self) -> None: + tree = ast.parse( + """ +alias = original +other: int = alias + +def inner(): + shadow = original +""" + ) + collector = AliasCollector() + self.assertEqual( + collector.collect(tree), + {"alias": "original", "other": "alias"}, + ) + + def test_call_collector_tracks_module_calls_only(self) -> None: + tree = ast.parse( + """ +foo() +self.bar() + +def inner(): + baz() + +class Thing: + def method(self): + qux() +""" + ) + collector = CallCollector() + self.assertEqual(collector.collect(tree), ["foo", "self.bar"]) + + def test_condition_metrics_collector_counts_operations(self) -> None: + tree = ast.parse( + """ +if a and b and c and foo(x) and y < z: + pass +""" + ) + if_node = tree.body[0] + collector = ConditionMetricsCollector( + local_names={"a", "b", "c", "x", "y", "z"} + ) + metrics = collector.collect(if_node.test) + self.assertEqual(metrics.bool_ops, 4) + self.assertEqual(metrics.compare_ops, 1) + self.assertEqual(metrics.calls, 1) + self.assertEqual(metrics.external_refs, 1) + + def test_if_collector_tracks_module_if_statements(self) -> None: + tree = ast.parse( + """ +if ready: + pass + +def inner(): + if nested: + pass +""" + ) + collector = IfCollector() + nodes = collector.collect(tree) + self.assertEqual(len(nodes), 1) + self.assertIsInstance(nodes[0], ast.If) + + def test_function_definition_collector_skips_methods(self) -> None: + tree = ast.parse( + """ +def top(): + def inner(): + return 1 + return inner() + +class Thing: + def method(self): + return 2 +""" + ) + collector = FunctionDefinitionCollector() + self.assertEqual(collector.collect(tree), {"top", "inner"}) + + def test_function_symbol_collector_tracks_usage(self) -> None: + tree = ast.parse( + """ +def foo(arg): + global g + x = arg + self.attr + return g + x +""" + ) + function_node = next( + node for node in tree.body if isinstance(node, ast.FunctionDef) + ) + collector = FunctionSymbolCollector() + usage = collector.collect(function_node) + self.assertIn("arg", usage.locals) + self.assertIn("x", usage.locals) + self.assertIn("g", usage.used_names) + self.assertIn("arg", usage.used_names) + self.assertIn("attr", usage.self_fields_read) + self.assertIn("g", usage.global_decls) + + def test_module_symbol_collector_gathers_symbols(self) -> None: + tree = ast.parse( + """ +import os as operating +from sys import path as sys_path + +class Foo: + pass + +def bar(): + pass + +baz = 1 +count: int = 2 + +for i in range(3): + pass + +with open("a") as f: + pass + +try: + pass +except Exception as exc: + pass +""" + ) + collector = ModuleSymbolCollector() + symbols = collector.collect(tree) + self.assertTrue( + {"operating", "sys_path", "Foo", "bar", "baz", "count", "i", "f", "exc"} + <= symbols + ) + + def test_unit_node_collector_builds_qualified_ids(self) -> None: + tree = ast.parse( + """ +def top(): + def inner(): + return 1 + return inner() + +class Thing: + def method(self): + return 2 +""" + ) + collector = UnitNodeCollector(file_path="module.py") + collector.visit(tree) + qualified_ids = {unit.definition.qualified_id for unit in collector.units} + kinds = {unit.definition.qualified_id: unit.definition.kind for unit in collector.units} + self.assertEqual(qualified_ids, {"top", "top.inner", "Thing.method"}) + self.assertEqual(kinds["Thing.method"], "method") + self.assertEqual(kinds["top"], "function") + + +if __name__ == "__main__": + unittest.main() From 3e7e6ec02961eb2acbb7e4909aa9f4f1de3bf876 Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 16:24:52 +0100 Subject: [PATCH 10/17] Refactor analyzers and report builder --- codra/bps/analyzer.py | 63 +++++++----- codra/csa/analyzer.py | 67 +++++++++--- codra/indirection/analyzer.py | 52 ++++++---- codra/report/builder.py | 64 ++++++++---- tests/test_analyzers.py | 185 ++++++++++++++++++++++++++++++++++ 5 files changed, 356 insertions(+), 75 deletions(-) create mode 100644 tests/test_analyzers.py diff --git a/codra/bps/analyzer.py b/codra/bps/analyzer.py index b2a4ca9..dba6ffc 100644 --- a/codra/bps/analyzer.py +++ b/codra/bps/analyzer.py @@ -6,6 +6,7 @@ from ..condition.collector import ConditionMetricsCollector from ..function.symbol_collector import FunctionSymbolCollector from ..if_.collector import IfCollector +from ..unit.node import UnitNode from ..unit.node_collector import UnitNodeCollector from .result import BpsResult @@ -13,31 +14,45 @@ @dataclass class BpsAnalyzer: def analyze_file(self, file_path: str) -> list[BpsResult]: + tree = self._parse_file(file_path) + unit_nodes = self._collect_units(tree, file_path) + scores = [self._collect_bps(unit_node) for unit_node in unit_nodes] + return self._build_results(unit_nodes, scores) + + def _parse_file(self, file_path: str) -> ast.AST: with open(file_path, "r", encoding="utf-8") as handle: source = handle.read() - tree = ast.parse(source, filename=file_path) + return ast.parse(source, filename=file_path) + + def _collect_units(self, tree: ast.AST, file_path: str) -> list[UnitNode]: unit_collector = UnitNodeCollector(file_path=file_path) unit_collector.visit(tree) - results: list[BpsResult] = [] - for unit_node in unit_collector.units: - usage = FunctionSymbolCollector().collect(unit_node.node) - local_names = usage.locals - if_nodes = IfCollector().collect(unit_node.node) - if not if_nodes: - bps = 1.0 - else: - scores: list[float] = [] - for if_node in if_nodes: - metrics = ConditionMetricsCollector(local_names=local_names).collect( - if_node.test - ) - penalty = ( - metrics.bool_ops - + metrics.compare_ops - + 2 * metrics.calls - + 2 * metrics.external_refs - ) - scores.append(1.0 / (1.0 + penalty)) - bps = sum(scores) / len(scores) - results.append(BpsResult(unit=unit_node.definition, bps=bps)) - return results + return unit_collector.units + + def _collect_bps(self, unit_node: UnitNode) -> float: + usage = FunctionSymbolCollector().collect(unit_node.node) + local_names = usage.locals + if_nodes = IfCollector().collect(unit_node.node) + if not if_nodes: + return 1.0 + scores: list[float] = [] + for if_node in if_nodes: + metrics = ConditionMetricsCollector(local_names=local_names).collect( + if_node.test + ) + penalty = ( + metrics.bool_ops + + metrics.compare_ops + + 2 * metrics.calls + + 2 * metrics.external_refs + ) + scores.append(1.0 / (1.0 + penalty)) + return sum(scores) / len(scores) + + def _build_results( + self, unit_nodes: list[UnitNode], scores: list[float] + ) -> list[BpsResult]: + return [ + BpsResult(unit=unit_node.definition, bps=bps) + for unit_node, bps in zip(unit_nodes, scores, strict=True) + ] diff --git a/codra/csa/analyzer.py b/codra/csa/analyzer.py index 893ca44..40bcd5c 100644 --- a/codra/csa/analyzer.py +++ b/codra/csa/analyzer.py @@ -6,7 +6,9 @@ from dataclasses import dataclass from ..function.symbol_collector import FunctionSymbolCollector +from ..function.symbol_usage import FunctionSymbolUsage from ..module.symbol_collector import ModuleSymbolCollector +from ..unit.node import UnitNode from ..unit.node_collector import UnitNodeCollector from .result import CsaResult @@ -14,27 +16,62 @@ @dataclass class CsaAnalyzer: def analyze_file(self, file_path: str) -> list[CsaResult]: - with open(file_path, "r", encoding="utf-8") as handle: - source = handle.read() - tree = ast.parse(source, filename=file_path) - module_symbols = ModuleSymbolCollector().collect(tree) + tree = self._parse_file(file_path) + module_symbols = self._collect_module_symbols(tree) builtin_names = set(dir(builtins)) stdlib_modules = set(sys.stdlib_module_names) + unit_nodes = self._collect_unit_nodes(tree, file_path) + return self._build_results( + unit_nodes, + module_symbols, + builtin_names, + stdlib_modules, + ) + + def _parse_file(self, file_path: str) -> ast.AST: + with open(file_path, "r", encoding="utf-8") as handle: + source = handle.read() + return ast.parse(source, filename=file_path) + + def _collect_module_symbols(self, tree: ast.AST) -> set[str]: + return ModuleSymbolCollector().collect(tree) + + def _collect_unit_nodes(self, tree: ast.AST, file_path: str) -> list[UnitNode]: unit_collector = UnitNodeCollector(file_path=file_path) unit_collector.visit(tree) + return unit_collector.units + + def _extract_external_symbols( + self, + usage: FunctionSymbolUsage, + module_symbols: set[str], + builtin_names: set[str], + stdlib_modules: set[str], + ) -> set[str]: + used_names = usage.used_names + local_names = usage.locals + global_symbols = used_names.intersection(module_symbols) - local_names + free_symbols = used_names - local_names - module_symbols + external_symbols = global_symbols.union(free_symbols) + return { + symbol + for symbol in external_symbols + if symbol not in builtin_names and symbol not in stdlib_modules + } + + def _build_results( + self, + unit_nodes: list[UnitNode], + module_symbols: set[str], + builtin_names: set[str], + stdlib_modules: set[str], + ) -> list[CsaResult]: results: list[CsaResult] = [] - for unit_node in unit_collector.units: + for unit_node in unit_nodes: usage = FunctionSymbolCollector().collect(unit_node.node) - used_names = usage.used_names - local_names = usage.locals - global_symbols = used_names.intersection(module_symbols) - local_names - free_symbols = used_names - local_names - module_symbols - external_symbols = global_symbols.union(free_symbols) - external_symbols = { - symbol - for symbol in external_symbols - if symbol not in builtin_names and symbol not in stdlib_modules - } + external_symbols = self._extract_external_symbols( + usage, module_symbols, builtin_names, stdlib_modules + ) results.append( CsaResult( unit=unit_node.definition, diff --git a/codra/indirection/analyzer.py b/codra/indirection/analyzer.py index 1b2ff16..f17016a 100644 --- a/codra/indirection/analyzer.py +++ b/codra/indirection/analyzer.py @@ -16,16 +16,16 @@ @dataclass class IndirectionAnalyzer: def analyze_file(self, file_path: str) -> list[IndirectionResult]: - with open(file_path, "r", encoding="utf-8") as handle: - source = handle.read() - tree = ast.parse(source, filename=file_path) - function_names = FunctionDefinitionCollector().collect(tree) + tree = self._parse_file(file_path) + function_names = self._collect_function_definitions(tree) class_methods = self._collect_class_methods(tree) - aliases = AliasCollector().collect(tree) + aliases = self._collect_aliases(tree) module_symbols = ModuleSymbolCollector().collect(tree) builtin_names = set(dir(builtins)) stdlib_modules = set(sys.stdlib_module_names) - call_graph = self._build_call_graph(tree, function_names, class_methods, aliases) + call_graph = self._collect_call_graph( + tree, function_names, class_methods, aliases + ) depth_cache: dict[str, int] = {} results: list[IndirectionResult] = [] unit_collector = UnitNodeCollector(file_path=file_path) @@ -57,16 +57,9 @@ def analyze_file(self, file_path: str) -> list[IndirectionResult]: continue else: unresolved_calls.add(name) - call_depths = [ - 1 + self._depth(call_name, call_graph, depth_cache) - for call_name in resolved_calls - ] - if call_depths: - id_max = max(call_depths) - id_avg = sum(call_depths) / len(call_depths) - else: - id_max = 0 - id_avg = 0.0 + id_max, id_avg = self._calculate_indirection_depths( + resolved_calls, call_graph, depth_cache + ) results.append( IndirectionResult( unit=unit_node.definition, @@ -77,6 +70,17 @@ def analyze_file(self, file_path: str) -> list[IndirectionResult]: ) return results + def _parse_file(self, file_path: str) -> ast.AST: + with open(file_path, "r", encoding="utf-8") as handle: + source = handle.read() + return ast.parse(source, filename=file_path) + + def _collect_function_definitions(self, tree: ast.AST) -> set[str]: + return FunctionDefinitionCollector().collect(tree) + + def _collect_aliases(self, tree: ast.AST) -> dict[str, str]: + return AliasCollector().collect(tree) + def _collect_class_methods(self, tree: ast.AST) -> dict[str, set[str]]: class_methods: dict[str, set[str]] = {} for node in tree.body: @@ -89,7 +93,7 @@ def _collect_class_methods(self, tree: ast.AST) -> dict[str, set[str]]: class_methods[node.name] = methods return class_methods - def _build_call_graph( + def _collect_call_graph( self, tree: ast.AST, function_names: set[str], @@ -159,3 +163,17 @@ def _depth( depth = max(depth, 1 + self._depth(callee, call_graph, depth_cache)) depth_cache[name] = depth return depth + + def _calculate_indirection_depths( + self, + resolved_calls: list[str], + call_graph: dict[str, list[str]], + depth_cache: dict[str, int], + ) -> tuple[int, float]: + call_depths = [ + 1 + self._depth(call_name, call_graph, depth_cache) + for call_name in resolved_calls + ] + if not call_depths: + return 0, 0.0 + return max(call_depths), sum(call_depths) / len(call_depths) diff --git a/codra/report/builder.py b/codra/report/builder.py index 6de5556..94d9483 100644 --- a/codra/report/builder.py +++ b/codra/report/builder.py @@ -4,9 +4,12 @@ from dataclasses import dataclass from ..bps.analyzer import BpsAnalyzer +from ..bps.result import BpsResult from ..csa.analyzer import CsaAnalyzer +from ..csa.result import CsaResult from ..file.path_collector import FilePathCollector from ..indirection.analyzer import IndirectionAnalyzer +from ..indirection.result import IndirectionResult from ..threshold.config import ThresholdConfig from ..unit.definition import UnitDefinition from ..unit.key import UnitKey @@ -34,26 +37,15 @@ def build(self, root_path: str) -> Report: total_units = 0 for file_path in file_paths: logger.info("Analyzing %s", file_path) - csa_results = self.csa_analyzer.analyze_file(file_path) - indirection_results = self.indirection_analyzer.analyze_file(file_path) - bps_results = self.bps_analyzer.analyze_file(file_path) - csa_map = {self._key(result.unit): result for result in csa_results} - indirection_map = { - self._key(result.unit): result for result in indirection_results - } - bps_map = {self._key(result.unit): result for result in bps_results} - keys = sorted( - {**csa_map, **indirection_map, **bps_map}.keys(), - key=self._sort_key, + csa_results, indirection_results, bps_results = self._collect_file_analysis( + file_path ) - units: list[UnitReport] = [] - for key in keys: - unit = self._resolve_unit(key, csa_map, indirection_map, bps_map) - metrics = self._resolve_metrics(key, csa_map, indirection_map, bps_map) - units.append(UnitReport(unit=unit, metrics=metrics)) - files.append(FileReport(file_path=file_path, units=units)) - total_units += len(units) - summary = ReportSummary(total_files=len(files), total_units=total_units) + file_report, unit_count = self._build_file_report( + file_path, csa_results, indirection_results, bps_results + ) + files.append(file_report) + total_units += unit_count + summary = self._build_summary(files, total_units) return Report( schema_version="1.0", language="python", @@ -88,6 +80,40 @@ def _key(self, unit: UnitDefinition) -> UnitKey: def _sort_key(self, key: UnitKey) -> tuple[str, int, int, str]: return (key.qualified_id, key.start_line, key.end_line, key.kind) + def _collect_file_analysis( + self, file_path: str + ) -> tuple[list[CsaResult], list[IndirectionResult], list[BpsResult]]: + csa_results = self.csa_analyzer.analyze_file(file_path) + indirection_results = self.indirection_analyzer.analyze_file(file_path) + bps_results = self.bps_analyzer.analyze_file(file_path) + return csa_results, indirection_results, bps_results + + def _build_file_report( + self, + file_path: str, + csa_results: list[CsaResult], + indirection_results: list[IndirectionResult], + bps_results: list[BpsResult], + ) -> tuple[FileReport, int]: + csa_map = {self._key(result.unit): result for result in csa_results} + indirection_map = { + self._key(result.unit): result for result in indirection_results + } + bps_map = {self._key(result.unit): result for result in bps_results} + keys = sorted( + {**csa_map, **indirection_map, **bps_map}.keys(), + key=self._sort_key, + ) + units: list[UnitReport] = [] + for key in keys: + unit = self._resolve_unit(key, csa_map, indirection_map, bps_map) + metrics = self._resolve_metrics(key, csa_map, indirection_map, bps_map) + units.append(UnitReport(unit=unit, metrics=metrics)) + return FileReport(file_path=file_path, units=units), len(units) + + def _build_summary(self, files: list[FileReport], total_units: int) -> ReportSummary: + return ReportSummary(total_files=len(files), total_units=total_units) + def _resolve_unit( self, key: UnitKey, diff --git a/tests/test_analyzers.py b/tests/test_analyzers.py new file mode 100644 index 0000000..501f2e5 --- /dev/null +++ b/tests/test_analyzers.py @@ -0,0 +1,185 @@ +import builtins +import sys +import tempfile +import textwrap +import unittest + +from codra.bps.analyzer import BpsAnalyzer +from codra.bps.result import BpsResult +from codra.csa.analyzer import CsaAnalyzer +from codra.csa.result import CsaResult +from codra.function.symbol_collector import FunctionSymbolCollector +from codra.indirection.analyzer import IndirectionAnalyzer +from codra.indirection.result import IndirectionResult +from codra.report.builder import ReportBuilder +from codra.unit.definition import UnitDefinition +from codra.unit.overview import UnitReport + + +class BpsAnalyzerHelperTests(unittest.TestCase): + def test_helpers_collect_bps_scores(self) -> None: + source = textwrap.dedent( + """ + def foo(x): + if x and y: + return 1 + return 2 + """ + ) + with tempfile.TemporaryDirectory() as tmpdir: + file_path = f"{tmpdir}/sample.py" + with open(file_path, "w", encoding="utf-8") as handle: + handle.write(source) + analyzer = BpsAnalyzer() + tree = analyzer._parse_file(file_path) + unit_nodes = analyzer._collect_units(tree, file_path) + self.assertEqual(len(unit_nodes), 1) + bps = analyzer._collect_bps(unit_nodes[0]) + self.assertAlmostEqual(bps, 0.25) + results = analyzer._build_results(unit_nodes, [bps]) + self.assertEqual(results, [BpsResult(unit=unit_nodes[0].definition, bps=bps)]) + + +class CsaAnalyzerHelperTests(unittest.TestCase): + def test_helpers_extract_external_symbols(self) -> None: + source = textwrap.dedent( + """ + import os + from math import sqrt + + CONST = 10 + + def foo(self, x): + return self.attr + sqrt(x) + CONST + y + """ + ) + with tempfile.TemporaryDirectory() as tmpdir: + file_path = f"{tmpdir}/sample.py" + with open(file_path, "w", encoding="utf-8") as handle: + handle.write(source) + analyzer = CsaAnalyzer() + tree = analyzer._parse_file(file_path) + module_symbols = analyzer._collect_module_symbols(tree) + unit_nodes = analyzer._collect_unit_nodes(tree, file_path) + usage = FunctionSymbolCollector().collect(unit_nodes[0].node) + external_symbols = analyzer._extract_external_symbols( + usage, + module_symbols, + set(dir(builtins)), + set(sys.stdlib_module_names), + ) + self.assertEqual(external_symbols, {"CONST", "sqrt", "y"}) + results = analyzer._build_results( + unit_nodes, + module_symbols, + set(dir(builtins)), + set(sys.stdlib_module_names), + ) + self.assertEqual( + results, + [ + CsaResult( + unit=unit_nodes[0].definition, + csa_main=3, + external_symbols=["CONST", "sqrt", "y"], + self_fields_read=["attr"], + ) + ], + ) + + +class IndirectionAnalyzerHelperTests(unittest.TestCase): + def test_helpers_collect_call_graph_and_depth(self) -> None: + source = textwrap.dedent( + """ + def bar(): + pass + + def foo(): + bar() + + alias = bar + + def baz(): + alias() + """ + ) + with tempfile.TemporaryDirectory() as tmpdir: + file_path = f"{tmpdir}/sample.py" + with open(file_path, "w", encoding="utf-8") as handle: + handle.write(source) + analyzer = IndirectionAnalyzer() + tree = analyzer._parse_file(file_path) + function_names = analyzer._collect_function_definitions(tree) + self.assertEqual(function_names, {"bar", "foo", "baz"}) + aliases = analyzer._collect_aliases(tree) + self.assertEqual(aliases, {"alias": "bar"}) + class_methods = analyzer._collect_class_methods(tree) + call_graph = analyzer._collect_call_graph( + tree, function_names, class_methods, aliases + ) + self.assertEqual(call_graph["foo"], ["bar"]) + self.assertEqual(call_graph["baz"], ["bar"]) + id_max, id_avg = analyzer._calculate_indirection_depths( + ["bar"], call_graph, {} + ) + self.assertEqual(id_max, 1) + self.assertEqual(id_avg, 1.0) + + +class ReportBuilderHelperTests(unittest.TestCase): + def test_helpers_build_file_report_and_summary(self) -> None: + builder = ReportBuilder( + csa_analyzer=CsaAnalyzer(), + indirection_analyzer=IndirectionAnalyzer(), + bps_analyzer=BpsAnalyzer(), + path_collector=None, + ) + file_path = "sample.py" + unit_a = UnitDefinition( + file_path=file_path, + qualified_id="a", + kind="function", + start_line=1, + end_line=2, + ) + unit_b = UnitDefinition( + file_path=file_path, + qualified_id="b", + kind="function", + start_line=3, + end_line=4, + ) + csa_results = [ + CsaResult( + unit=unit_b, + csa_main=1, + external_symbols=["z"], + self_fields_read=[], + ) + ] + indirection_results = [ + IndirectionResult( + unit=unit_a, id_max=2, id_avg=1.5, unresolved_calls=["x"] + ) + ] + bps_results = [BpsResult(unit=unit_a, bps=0.5), BpsResult(unit=unit_b, bps=1.0)] + file_report, unit_count = builder._build_file_report( + file_path, csa_results, indirection_results, bps_results + ) + self.assertEqual(unit_count, 2) + self.assertEqual(file_report.file_path, file_path) + self.assertEqual( + [unit.unit.qualified_id for unit in file_report.units], ["a", "b"] + ) + unit_a_report, unit_b_report = file_report.units + self.assertIsInstance(unit_a_report, UnitReport) + self.assertEqual(unit_a_report.metrics.id_max, 2) + self.assertEqual(unit_b_report.metrics.csa_main, 1) + summary = builder._build_summary([file_report], unit_count) + self.assertEqual(summary.total_files, 1) + self.assertEqual(summary.total_units, 2) + + +if __name__ == "__main__": + unittest.main() From e5ae002b22b10cb57895ec2bea85227e7671bab8 Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 16:25:30 +0100 Subject: [PATCH 11/17] Adds initial scaffolding and setup Adds a basic project scaffolding for the viewer application and includes a placeholder prompt, a sample report, and gitignore configuration. --- .gitignore | 2 + PROMT.md | 31 + report.json | 1 + viewer/package-lock.json | 1731 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 1765 insertions(+) create mode 100644 PROMT.md create mode 100644 report.json create mode 100644 viewer/package-lock.json diff --git a/.gitignore b/.gitignore index 4d61e89..088786b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ codra.egg-info/ result.json + +viewer/node_modules/ diff --git a/PROMT.md b/PROMT.md new file mode 100644 index 0000000..feda880 --- /dev/null +++ b/PROMT.md @@ -0,0 +1,31 @@ +You are a refactoring agent for a Python static-analysis tool. + +I will provide: +1) A report JSON (schema_version 1.0) produced by the tool. +2) The tool’s repository is available locally. + +The JSON must be treated as an observation of the tool’s current behavior, not something to edit or fix directly. + +Goal +Improve the tool so that future reports have lower noise and higher semantic accuracy, while keeping the exact same JSON schema (same keys, same types). + +What to do +- Use the JSON only to understand how the tool currently behaves. +- Identify weaknesses in the analysis logic (signal inflation, missing resolution, overly generic symbol tracking). +- Modify the tool code to address those weaknesses. +- Do not add new features unrelated to the reported metrics. +- Do not change the report schema or naming. + +Required analysis improvements +- `unresolved_calls` must not include Python builtins (derive the builtin set dynamically from the `builtins` module). +- `csa_main` must not be incremented by builtins; optionally also ignore standard-library symbols (e.g. ast, os, sys, json, argparse, logging). +- Improve call-graph resolution for indirection metrics at least for: + - calls to functions defined in the same module + - calls of the form `self.foo()` where `foo` is defined in the same class + +Constraints +- The analyzed project is unknown and must not be special-cased. +- The JSON is assumed to be structurally correct and semantically valid for the current implementation. +- Changes must affect how the tool computes metrics, not how results are post-processed. + +Wait for the JSON, then start. diff --git a/report.json b/report.json new file mode 100644 index 0000000..ea69da2 --- /dev/null +++ b/report.json @@ -0,0 +1 @@ +{"files": [{"file_path": "/Users/muge/workspace/codra/codra/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/alias/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/alias/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["aliases", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 14, "file_path": "/Users/muge/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.collect", "start_line": 12}}, {"metrics": {"bps": 0.2619047619047619, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["aliases", "depth"], "unresolved_calls": []}, "unit": {"end_line": 47, "file_path": "/Users/muge/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_AnnAssign", "start_line": 40}}, {"metrics": {"bps": 0.2619047619047619, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["aliases", "depth"], "unresolved_calls": []}, "unit": {"end_line": 38, "file_path": "/Users/muge/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_Assign", "start_line": 31}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 26, "file_path": "/Users/muge/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_AsyncFunctionDef", "start_line": 25}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 29, "file_path": "/Users/muge/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_ClassDef", "start_line": 28}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 23, "file_path": "/Users/muge/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_FunctionDef", "start_line": 22}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 20, "file_path": "/Users/muge/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_Module", "start_line": 16}}]}, {"file_path": "/Users/muge/workspace/codra/codra/bps/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/bps/analyzer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 5, "external_symbols": ["BpsResult", "ConditionMetricsCollector", "FunctionSymbolCollector", "IfCollector", "UnitNodeCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 43, "file_path": "/Users/muge/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer.analyze_file", "start_line": 15}}]}, {"file_path": "/Users/muge/workspace/codra/codra/bps/result.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/call/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/call/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["calls", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 14, "file_path": "/Users/muge/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.collect", "start_line": 11}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 20, "file_path": "/Users/muge/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_AsyncFunctionDef", "start_line": 19}}, {"metrics": {"bps": 0.13227513227513227, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["calls", "generic_visit"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 31, "file_path": "/Users/muge/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_Call", "start_line": 25}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 23, "file_path": "/Users/muge/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_ClassDef", "start_line": 22}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 17, "file_path": "/Users/muge/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_FunctionDef", "start_line": 16}}]}, {"file_path": "/Users/muge/workspace/codra/codra/cli/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/cli/args.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/cli/dependencies.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/cli/main.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 7, "external_symbols": ["BpsAnalyzer", "CliDependencies", "CsaAnalyzer", "FilePathCollector", "IndirectionAnalyzer", "ReportBuilder", "ReportSerializer"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 63, "file_path": "/Users/muge/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "build_dependencies", "start_line": 56}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ThresholdConfig"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 53, "file_path": "/Users/muge/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "build_thresholds", "start_line": 48}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 71, "file_path": "/Users/muge/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "configure_logging", "start_line": 66}}, {"metrics": {"bps": 1.0, "csa_main": 4, "external_symbols": ["build_dependencies", "configure_logging", "parse_args", "run_cli"], "id_avg": 1.25, "id_max": 2, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 93, "file_path": "/Users/muge/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "main", "start_line": 90}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["CliArgs"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 45, "file_path": "/Users/muge/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "parse_args", "start_line": 21}}, {"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["build_thresholds", "logger"], "id_avg": 1.0, "id_max": 1, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 87, "file_path": "/Users/muge/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "run_cli", "start_line": 74}}]}, {"file_path": "/Users/muge/workspace/codra/codra/condition/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/condition/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ConditionMetrics"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["bool_ops", "calls", "compare_ops", "external_refs", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 24, "file_path": "/Users/muge/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.collect", "start_line": 17}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["generic_visit"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 28, "file_path": "/Users/muge/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_BoolOp", "start_line": 26}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["generic_visit"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 36, "file_path": "/Users/muge/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_Call", "start_line": 34}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["generic_visit"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 32, "file_path": "/Users/muge/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_Compare", "start_line": 30}}, {"metrics": {"bps": 0.1111111111111111, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["local_names"], "unresolved_calls": []}, "unit": {"end_line": 40, "file_path": "/Users/muge/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_Name", "start_line": 38}}]}, {"file_path": "/Users/muge/workspace/codra/codra/condition/metrics.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/csa/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/csa/analyzer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 4, "external_symbols": ["CsaResult", "FunctionSymbolCollector", "ModuleSymbolCollector", "UnitNodeCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 46, "file_path": "/Users/muge/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer.analyze_file", "start_line": 16}}]}, {"file_path": "/Users/muge/workspace/codra/codra/csa/result.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/directory/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/directory/scanner.py", "units": [{"metrics": {"bps": 0.3333333333333333, "csa_main": 1, "external_symbols": ["UnitDefinition"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["file_scanner"], "unresolved_calls": []}, "unit": {"end_line": 24, "file_path": "/Users/muge/workspace/codra/codra/directory/scanner.py", "kind": "method", "qualified_id": "DirectoryScanner.scan", "start_line": 14}}]}, {"file_path": "/Users/muge/workspace/codra/codra/file/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/file/path_collector.py", "units": [{"metrics": {"bps": 0.3333333333333333, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 17, "file_path": "/Users/muge/workspace/codra/codra/file/path_collector.py", "kind": "method", "qualified_id": "FilePathCollector.collect", "start_line": 9}}]}, {"file_path": "/Users/muge/workspace/codra/codra/function/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/function/definition_collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["names", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 14, "file_path": "/Users/muge/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.collect", "start_line": 12}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "names", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 32, "file_path": "/Users/muge/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.visit_AsyncFunctionDef", "start_line": 28}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 20, "file_path": "/Users/muge/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.visit_ClassDef", "start_line": 16}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "names", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 26, "file_path": "/Users/muge/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.visit_FunctionDef", "start_line": 22}}]}, {"file_path": "/Users/muge/workspace/codra/codra/function/extractor.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitDefinition"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "file_path", "function_stack", "generic_visit", "units"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 55, "file_path": "/Users/muge/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor._handle_function", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/Users/muge/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor.visit_AsyncFunctionDef", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "generic_visit"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 19, "file_path": "/Users/muge/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor.visit_ClassDef", "start_line": 16}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/Users/muge/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor.visit_FunctionDef", "start_line": 21}}]}, {"file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "units": [{"metrics": {"bps": 0.5, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 69, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._add_arguments", "start_line": 58}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_add_arguments", "usage", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 17, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.collect", "start_line": 13}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 23, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_AsyncFunctionDef", "start_line": 22}}, {"metrics": {"bps": 0.12698412698412698, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["generic_visit", "usage"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 48, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Attribute", "start_line": 44}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 26, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_ClassDef", "start_line": 25}}, {"metrics": {"bps": 0.3214285714285714, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 56, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_ExceptHandler", "start_line": 50}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 20, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_FunctionDef", "start_line": 19}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 29, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Global", "start_line": 28}}, {"metrics": {"bps": 0.3134920634920635, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 42, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Name", "start_line": 34}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 32, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Nonlocal", "start_line": 31}}]}, {"file_path": "/Users/muge/workspace/codra/codra/function/symbol_usage.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/if_/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/if_/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["nodes", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 14, "file_path": "/Users/muge/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.collect", "start_line": 11}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 20, "file_path": "/Users/muge/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_AsyncFunctionDef", "start_line": 19}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 23, "file_path": "/Users/muge/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_ClassDef", "start_line": 22}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 17, "file_path": "/Users/muge/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_FunctionDef", "start_line": 16}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["generic_visit", "nodes"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 27, "file_path": "/Users/muge/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_If", "start_line": 25}}]}, {"file_path": "/Users/muge/workspace/codra/codra/indirection/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/indirection/analyzer.py", "units": [{"metrics": {"bps": 0.35537918871252205, "csa_main": 1, "external_symbols": ["CallCollector"], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_resolve_alias"], "unresolved_calls": []}, "unit": {"end_line": 129, "file_path": "/Users/muge/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._build_call_graph", "start_line": 91}}, {"metrics": {"bps": 0.14285714285714285, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 89, "file_path": "/Users/muge/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_class_methods", "start_line": 79}}, {"metrics": {"bps": 0.5625, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_depth"], "unresolved_calls": []}, "unit": {"end_line": 160, "file_path": "/Users/muge/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._depth", "start_line": 139}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 137, "file_path": "/Users/muge/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._resolve_alias", "start_line": 131}}, {"metrics": {"bps": 0.48214285714285715, "csa_main": 6, "external_symbols": ["AliasCollector", "CallCollector", "FunctionDefinitionCollector", "IndirectionResult", "ModuleSymbolCollector", "UnitNodeCollector"], "id_avg": 1.25, "id_max": 2, "self_fields_read": ["_build_call_graph", "_collect_class_methods", "_depth", "_resolve_alias"], "unresolved_calls": []}, "unit": {"end_line": 77, "file_path": "/Users/muge/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer.analyze_file", "start_line": 18}}]}, {"file_path": "/Users/muge/workspace/codra/codra/indirection/result.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/module/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "units": [{"metrics": {"bps": 0.14285714285714285, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 79, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector._extract_target_names", "start_line": 74}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 13, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.collect", "start_line": 11}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "generic_visit", "symbols"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 41, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AnnAssign", "start_line": 39}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "generic_visit", "symbols"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 37, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_Assign", "start_line": 34}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "generic_visit", "symbols"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 53, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AsyncFor", "start_line": 51}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 19, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AsyncFunctionDef", "start_line": 18}}, {"metrics": {"bps": 0.5, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "generic_visit", "symbols"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 65, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AsyncWith", "start_line": 61}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "generic_visit", "symbols"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 45, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AugAssign", "start_line": 43}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_ClassDef", "start_line": 21}}, {"metrics": {"bps": 0.3214285714285714, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "generic_visit", "symbols"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 72, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_ExceptHandler", "start_line": 67}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "generic_visit", "symbols"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 49, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_For", "start_line": 47}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 16, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_FunctionDef", "start_line": 15}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 27, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_Import", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 32, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_ImportFrom", "start_line": 29}}, {"metrics": {"bps": 0.5, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "generic_visit", "symbols"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 59, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_With", "start_line": 55}}]}, {"file_path": "/Users/muge/workspace/codra/codra/python/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/python/file_scanner.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["FunctionExtractor"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 18, "file_path": "/Users/muge/workspace/codra/codra/python/file_scanner.py", "kind": "method", "qualified_id": "PythonFileScanner.scan_file", "start_line": 12}}]}, {"file_path": "/Users/muge/workspace/codra/codra/report/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/report/builder.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitKey"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 86, "file_path": "/Users/muge/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._key", "start_line": 79}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitMetrics"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 129, "file_path": "/Users/muge/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._resolve_metrics", "start_line": 109}}, {"metrics": {"bps": 0.5, "csa_main": 1, "external_symbols": ["UnitDefinition"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 107, "file_path": "/Users/muge/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._resolve_unit", "start_line": 91}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 89, "file_path": "/Users/muge/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._sort_key", "start_line": 88}}, {"metrics": {"bps": 1.0, "csa_main": 5, "external_symbols": ["FileReport", "Report", "ReportSummary", "UnitReport", "logger"], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_key", "_resolve_metrics", "_resolve_unit", "_sort_key", "bps_analyzer", "csa_analyzer", "indirection_analyzer", "path_collector"], "unresolved_calls": []}, "unit": {"end_line": 62, "file_path": "/Users/muge/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder.build", "start_line": 30}}, {"metrics": {"bps": 0.25, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 77, "file_path": "/Users/muge/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder.check_thresholds", "start_line": 64}}]}, {"file_path": "/Users/muge/workspace/codra/codra/report/file.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/report/model.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/report/serializer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["asdict"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 13, "file_path": "/Users/muge/workspace/codra/codra/report/serializer.py", "kind": "method", "qualified_id": "ReportSerializer.to_json", "start_line": 12}}]}, {"file_path": "/Users/muge/workspace/codra/codra/report/summary.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/threshold/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/threshold/config.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/unit/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/unit/definition.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/unit/key.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/unit/metrics.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/unit/node.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/unit/node_collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["UnitDefinition", "UnitNode"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "file_path", "function_stack", "generic_visit", "units"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 55, "file_path": "/Users/muge/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector._handle_function", "start_line": 28}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 26, "file_path": "/Users/muge/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector.visit_AsyncFunctionDef", "start_line": 25}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "generic_visit"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 20, "file_path": "/Users/muge/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector.visit_ClassDef", "start_line": 17}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 23, "file_path": "/Users/muge/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector.visit_FunctionDef", "start_line": 22}}]}, {"file_path": "/Users/muge/workspace/codra/codra/unit/overview.py", "units": []}], "language": "python", "schema_version": "1.0", "summary": {"total_files": 51, "total_units": 82}} diff --git a/viewer/package-lock.json b/viewer/package-lock.json new file mode 100644 index 0000000..58a95f4 --- /dev/null +++ b/viewer/package-lock.json @@ -0,0 +1,1731 @@ +{ + "name": "codra-viewer", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "codra-viewer", + "version": "0.1.0", + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.21", + "@types/react-dom": "^18.2.7", + "@vitejs/plugin-react": "^4.2.1", + "typescript": "^5.3.3", + "vite": "^5.0.8" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.27", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", + "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", + "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001764", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", + "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} From 808edecef2023812bd44ac86f3c98d5685f2200a Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 16:28:58 +0100 Subject: [PATCH 12/17] Improve indirection alias resolution and tests --- codra/indirection/analyzer.py | 98 +++++++++++++++++++++-------------- tests/test_analyzers.py | 58 ++++++++++++++++++++- 2 files changed, 115 insertions(+), 41 deletions(-) diff --git a/codra/indirection/analyzer.py b/codra/indirection/analyzer.py index f17016a..09b71d7 100644 --- a/codra/indirection/analyzer.py +++ b/codra/indirection/analyzer.py @@ -4,15 +4,37 @@ import builtins import sys from dataclasses import dataclass +from typing import Iterable, Mapping from ..alias.collector import AliasCollector from ..call.collector import CallCollector from ..function.definition_collector import FunctionDefinitionCollector from ..module.symbol_collector import ModuleSymbolCollector +from ..unit.node import UnitNode from ..unit.node_collector import UnitNodeCollector from .result import IndirectionResult +def resolve_alias(name: str, aliases: Mapping[str, str]) -> str: + seen: set[str] = set() + current = name + while current in aliases and current not in seen: + seen.add(current) + current = aliases[current] + return current + + +def collect_calls(node: ast.AST) -> list[str]: + return CallCollector().collect(node) + + +def collect_calls_by_unit(unit_nodes: Iterable[UnitNode]) -> dict[str, list[str]]: + return { + unit.definition.qualified_id: collect_calls(unit.node) + for unit in unit_nodes + } + + @dataclass class IndirectionAnalyzer: def analyze_file(self, file_path: str) -> list[IndirectionResult]: @@ -23,20 +45,22 @@ def analyze_file(self, file_path: str) -> list[IndirectionResult]: module_symbols = ModuleSymbolCollector().collect(tree) builtin_names = set(dir(builtins)) stdlib_modules = set(sys.stdlib_module_names) + unit_collector = UnitNodeCollector(file_path=file_path) + unit_collector.visit(tree) + call_map = collect_calls_by_unit(unit_collector.units) + alias_cache: dict[str, str] = {} call_graph = self._collect_call_graph( - tree, function_names, class_methods, aliases + function_names, class_methods, aliases, call_map, alias_cache ) depth_cache: dict[str, int] = {} results: list[IndirectionResult] = [] - unit_collector = UnitNodeCollector(file_path=file_path) - unit_collector.visit(tree) for unit_node in unit_collector.units: class_name = ( unit_node.definition.qualified_id.split(".", 1)[0] if unit_node.definition.kind == "method" else None ) - call_names = CallCollector().collect(unit_node.node) + call_names = call_map.get(unit_node.definition.qualified_id, []) resolved_calls: list[str] = [] unresolved_calls: set[str] = set() for name in call_names: @@ -46,7 +70,7 @@ def analyze_file(self, file_path: str) -> list[IndirectionResult]: if method_name in class_methods.get(class_name, set()): resolved_calls.append(f"{class_name}.{method_name}") continue - resolved = self._resolve_alias(name, aliases) + resolved = self._resolve_alias(name, aliases, alias_cache) if resolved in function_names: resolved_calls.append(resolved) elif resolved in module_symbols: @@ -95,51 +119,47 @@ def _collect_class_methods(self, tree: ast.AST) -> dict[str, set[str]]: def _collect_call_graph( self, - tree: ast.AST, function_names: set[str], class_methods: dict[str, set[str]], aliases: dict[str, str], + call_map: dict[str, list[str]], + alias_cache: dict[str, str], ) -> dict[str, list[str]]: call_graph: dict[str, list[str]] = {name: [] for name in function_names} for class_name, methods in class_methods.items(): for method_name in methods: call_graph[f"{class_name}.{method_name}"] = [] - for node in tree.body: - if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): - if node.name in function_names: - call_names = CallCollector().collect(node) - for name in call_names: - resolved = self._resolve_alias(name, aliases) + for function_name in function_names: + call_names = call_map.get(function_name, []) + for name in call_names: + resolved = self._resolve_alias(name, aliases, alias_cache) + if resolved in function_names: + call_graph[function_name].append(resolved) + for class_name, methods in class_methods.items(): + for method_name in methods: + qualified_name = f"{class_name}.{method_name}" + call_names = call_map.get(qualified_name, []) + for name in call_names: + if name.startswith("self."): + callee_name = name.split(".", 1)[1] + if callee_name in methods: + call_graph[qualified_name].append( + f"{class_name}.{callee_name}" + ) + else: + resolved = self._resolve_alias(name, aliases, alias_cache) if resolved in function_names: - call_graph[node.name].append(resolved) - elif isinstance(node, ast.ClassDef): - methods = class_methods.get(node.name, set()) - for statement in node.body: - if isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef)): - qualified_name = f"{node.name}.{statement.name}" - if qualified_name not in call_graph: - continue - call_names = CallCollector().collect(statement) - for name in call_names: - if name.startswith("self."): - method_name = name.split(".", 1)[1] - if method_name in methods: - call_graph[qualified_name].append( - f"{node.name}.{method_name}" - ) - else: - resolved = self._resolve_alias(name, aliases) - if resolved in function_names: - call_graph[qualified_name].append(resolved) + call_graph[qualified_name].append(resolved) return call_graph - def _resolve_alias(self, name: str, aliases: dict[str, str]) -> str: - seen: set[str] = set() - current = name - while current in aliases and current not in seen: - seen.add(current) - current = aliases[current] - return current + def _resolve_alias( + self, name: str, aliases: dict[str, str], alias_cache: dict[str, str] + ) -> str: + if name in alias_cache: + return alias_cache[name] + resolved = resolve_alias(name, aliases) + alias_cache[name] = resolved + return resolved def _depth( self, diff --git a/tests/test_analyzers.py b/tests/test_analyzers.py index 501f2e5..1527a02 100644 --- a/tests/test_analyzers.py +++ b/tests/test_analyzers.py @@ -9,10 +9,15 @@ from codra.csa.analyzer import CsaAnalyzer from codra.csa.result import CsaResult from codra.function.symbol_collector import FunctionSymbolCollector -from codra.indirection.analyzer import IndirectionAnalyzer +from codra.indirection.analyzer import ( + IndirectionAnalyzer, + collect_calls_by_unit, + resolve_alias, +) from codra.indirection.result import IndirectionResult from codra.report.builder import ReportBuilder from codra.unit.definition import UnitDefinition +from codra.unit.node_collector import UnitNodeCollector from codra.unit.overview import UnitReport @@ -115,8 +120,11 @@ def baz(): aliases = analyzer._collect_aliases(tree) self.assertEqual(aliases, {"alias": "bar"}) class_methods = analyzer._collect_class_methods(tree) + unit_collector = UnitNodeCollector(file_path=file_path) + unit_collector.visit(tree) + call_map = collect_calls_by_unit(unit_collector.units) call_graph = analyzer._collect_call_graph( - tree, function_names, class_methods, aliases + function_names, class_methods, aliases, call_map, {} ) self.assertEqual(call_graph["foo"], ["bar"]) self.assertEqual(call_graph["baz"], ["bar"]) @@ -126,6 +134,52 @@ def baz(): self.assertEqual(id_max, 1) self.assertEqual(id_avg, 1.0) + def test_helpers_resolve_alias_chain_and_class_methods(self) -> None: + source = textwrap.dedent( + """ + def helper(): + pass + + alias = helper + alias2 = alias + + def uses_alias(): + alias2() + + class Foo: + def ping(self): + self.pong() + alias2() + + def pong(self): + alias() + """ + ) + with tempfile.TemporaryDirectory() as tmpdir: + file_path = f"{tmpdir}/sample.py" + with open(file_path, "w", encoding="utf-8") as handle: + handle.write(source) + analyzer = IndirectionAnalyzer() + tree = analyzer._parse_file(file_path) + aliases = analyzer._collect_aliases(tree) + self.assertEqual(resolve_alias("alias2", aliases), "helper") + function_names = analyzer._collect_function_definitions(tree) + class_methods = analyzer._collect_class_methods(tree) + unit_collector = UnitNodeCollector(file_path=file_path) + unit_collector.visit(tree) + call_map = collect_calls_by_unit(unit_collector.units) + call_graph = analyzer._collect_call_graph( + function_names, class_methods, aliases, call_map, {} + ) + self.assertEqual(call_graph["uses_alias"], ["helper"]) + self.assertEqual( + call_graph["Foo.ping"], ["Foo.pong", "helper"] + ) + results = analyzer.analyze_file(file_path) + results_by_unit = {result.unit.qualified_id: result for result in results} + self.assertEqual(results_by_unit["uses_alias"].id_max, 1) + self.assertEqual(results_by_unit["Foo.ping"].id_max, 2) + class ReportBuilderHelperTests(unittest.TestCase): def test_helpers_build_file_report_and_summary(self) -> None: From 20604a6eca661ac058c56deaf4334b29900464f3 Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 16:31:26 +0100 Subject: [PATCH 13/17] Refactor report builder aggregation --- codra/report/builder.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/codra/report/builder.py b/codra/report/builder.py index 94d9483..02216e1 100644 --- a/codra/report/builder.py +++ b/codra/report/builder.py @@ -33,19 +33,8 @@ class ReportBuilder: def build(self, root_path: str) -> Report: logger.info("Collecting files from %s", root_path) file_paths = self.path_collector.collect(root_path) - files: list[FileReport] = [] - total_units = 0 - for file_path in file_paths: - logger.info("Analyzing %s", file_path) - csa_results, indirection_results, bps_results = self._collect_file_analysis( - file_path - ) - file_report, unit_count = self._build_file_report( - file_path, csa_results, indirection_results, bps_results - ) - files.append(file_report) - total_units += unit_count - summary = self._build_summary(files, total_units) + files = self._build_file_reports(file_paths) + summary = self._build_summary(files) return Report( schema_version="1.0", language="python", @@ -54,6 +43,9 @@ def build(self, root_path: str) -> Report: ) def check_thresholds(self, report: Report, thresholds: ThresholdConfig) -> bool: + return self._apply_thresholds(report, thresholds) + + def _apply_thresholds(self, report: Report, thresholds: ThresholdConfig) -> bool: for file_report in report.files: for unit in file_report.units: metrics = unit.metrics @@ -88,6 +80,19 @@ def _collect_file_analysis( bps_results = self.bps_analyzer.analyze_file(file_path) return csa_results, indirection_results, bps_results + def _build_file_reports(self, file_paths: list[str]) -> list[FileReport]: + files: list[FileReport] = [] + for file_path in file_paths: + logger.info("Analyzing %s", file_path) + csa_results, indirection_results, bps_results = self._collect_file_analysis( + file_path + ) + file_report, _ = self._build_file_report( + file_path, csa_results, indirection_results, bps_results + ) + files.append(file_report) + return files + def _build_file_report( self, file_path: str, @@ -111,7 +116,8 @@ def _build_file_report( units.append(UnitReport(unit=unit, metrics=metrics)) return FileReport(file_path=file_path, units=units), len(units) - def _build_summary(self, files: list[FileReport], total_units: int) -> ReportSummary: + def _build_summary(self, files: list[FileReport]) -> ReportSummary: + total_units = sum(len(file_report.units) for file_report in files) return ReportSummary(total_files=len(files), total_units=total_units) def _resolve_unit( From c7ce9516ce0ec4f3b2604ee411d3a0e56a99b946 Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 16:53:45 +0100 Subject: [PATCH 14/17] Refactor condition helpers for BPS --- codra/condition/collector.py | 9 +++++- codra/function/symbol_collector.py | 49 +++++++++++++++++++++--------- report.json | 2 +- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/codra/condition/collector.py b/codra/condition/collector.py index fde44ce..a0ab219 100644 --- a/codra/condition/collector.py +++ b/codra/condition/collector.py @@ -38,5 +38,12 @@ def visit_Call(self, node: ast.Call) -> None: super().generic_visit(node) def visit_Name(self, node: ast.Name) -> None: - if isinstance(node.ctx, ast.Load) and node.id not in self.local_names: + if self._is_external_name(node): self.external_refs += 1 + + def _is_external_name(self, node: ast.Name) -> bool: + context = node.ctx + is_load = isinstance(context, ast.Load) + name = node.id + is_local = name in self.local_names + return is_load and not is_local diff --git a/codra/function/symbol_collector.py b/codra/function/symbol_collector.py index 8232c8a..fead68d 100644 --- a/codra/function/symbol_collector.py +++ b/codra/function/symbol_collector.py @@ -34,26 +34,26 @@ def visit_Nonlocal(self, node: ast.Nonlocal) -> None: self.usage.nonlocal_decls.update(node.names) def visit_Name(self, node: ast.Name) -> None: - if isinstance(node.ctx, ast.Load): - self.usage.used_names.add(node.id) - elif isinstance(node.ctx, (ast.Store, ast.Del)): - if node.id in self.usage.global_decls: + context = node.ctx + name = node.id + if self._is_load_context(context): + self.usage.used_names.add(name) + elif self._is_store_or_del_context(context): + if self._is_declared_nonlocal_or_global(name): return - if node.id in self.usage.nonlocal_decls: - return - self.usage.locals.add(node.id) + self.usage.locals.add(name) def visit_Attribute(self, node: ast.Attribute) -> None: - if isinstance(node.ctx, ast.Load): - if isinstance(node.value, ast.Name) and node.value.id == "self": - self.usage.self_fields_read.add(node.attr) + if self._is_self_attribute_read(node): + self.usage.self_fields_read.add(node.attr) super().generic_visit(node) def visit_ExceptHandler(self, node: ast.ExceptHandler) -> None: - if isinstance(node.name, str): - self.usage.locals.add(node.name) - elif node.name is not None: - super().visit(node.name) + handler_name = node.name + if self._is_exception_name(handler_name): + self.usage.locals.add(handler_name) + elif handler_name is not None: + super().visit(handler_name) for statement in node.body: super().visit(statement) @@ -69,3 +69,24 @@ def _add_arguments(self, node: ast.AST) -> None: self.usage.locals.add(arguments.vararg.arg) if arguments.kwarg is not None: self.usage.locals.add(arguments.kwarg.arg) + + def _is_load_context(self, context: ast.expr_context) -> bool: + return isinstance(context, ast.Load) + + def _is_store_or_del_context(self, context: ast.expr_context) -> bool: + return isinstance(context, (ast.Store, ast.Del)) + + def _is_declared_nonlocal_or_global(self, name: str) -> bool: + is_global = name in self.usage.global_decls + is_nonlocal = name in self.usage.nonlocal_decls + return is_global or is_nonlocal + + def _is_self_attribute_read(self, node: ast.Attribute) -> bool: + context = node.ctx + if not self._is_load_context(context): + return False + value = node.value + return isinstance(value, ast.Name) and value.id == "self" + + def _is_exception_name(self, name: object) -> bool: + return isinstance(name, str) diff --git a/report.json b/report.json index ea69da2..80b8601 100644 --- a/report.json +++ b/report.json @@ -1 +1 @@ -{"files": [{"file_path": "/Users/muge/workspace/codra/codra/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/alias/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/alias/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["aliases", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 14, "file_path": "/Users/muge/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.collect", "start_line": 12}}, {"metrics": {"bps": 0.2619047619047619, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["aliases", "depth"], "unresolved_calls": []}, "unit": {"end_line": 47, "file_path": "/Users/muge/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_AnnAssign", "start_line": 40}}, {"metrics": {"bps": 0.2619047619047619, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["aliases", "depth"], "unresolved_calls": []}, "unit": {"end_line": 38, "file_path": "/Users/muge/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_Assign", "start_line": 31}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 26, "file_path": "/Users/muge/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_AsyncFunctionDef", "start_line": 25}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 29, "file_path": "/Users/muge/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_ClassDef", "start_line": 28}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 23, "file_path": "/Users/muge/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_FunctionDef", "start_line": 22}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 20, "file_path": "/Users/muge/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_Module", "start_line": 16}}]}, {"file_path": "/Users/muge/workspace/codra/codra/bps/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/bps/analyzer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 5, "external_symbols": ["BpsResult", "ConditionMetricsCollector", "FunctionSymbolCollector", "IfCollector", "UnitNodeCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 43, "file_path": "/Users/muge/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer.analyze_file", "start_line": 15}}]}, {"file_path": "/Users/muge/workspace/codra/codra/bps/result.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/call/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/call/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["calls", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 14, "file_path": "/Users/muge/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.collect", "start_line": 11}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 20, "file_path": "/Users/muge/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_AsyncFunctionDef", "start_line": 19}}, {"metrics": {"bps": 0.13227513227513227, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["calls", "generic_visit"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 31, "file_path": "/Users/muge/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_Call", "start_line": 25}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 23, "file_path": "/Users/muge/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_ClassDef", "start_line": 22}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 17, "file_path": "/Users/muge/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_FunctionDef", "start_line": 16}}]}, {"file_path": "/Users/muge/workspace/codra/codra/cli/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/cli/args.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/cli/dependencies.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/cli/main.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 7, "external_symbols": ["BpsAnalyzer", "CliDependencies", "CsaAnalyzer", "FilePathCollector", "IndirectionAnalyzer", "ReportBuilder", "ReportSerializer"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 63, "file_path": "/Users/muge/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "build_dependencies", "start_line": 56}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ThresholdConfig"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 53, "file_path": "/Users/muge/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "build_thresholds", "start_line": 48}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 71, "file_path": "/Users/muge/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "configure_logging", "start_line": 66}}, {"metrics": {"bps": 1.0, "csa_main": 4, "external_symbols": ["build_dependencies", "configure_logging", "parse_args", "run_cli"], "id_avg": 1.25, "id_max": 2, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 93, "file_path": "/Users/muge/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "main", "start_line": 90}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["CliArgs"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 45, "file_path": "/Users/muge/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "parse_args", "start_line": 21}}, {"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["build_thresholds", "logger"], "id_avg": 1.0, "id_max": 1, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 87, "file_path": "/Users/muge/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "run_cli", "start_line": 74}}]}, {"file_path": "/Users/muge/workspace/codra/codra/condition/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/condition/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ConditionMetrics"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["bool_ops", "calls", "compare_ops", "external_refs", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 24, "file_path": "/Users/muge/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.collect", "start_line": 17}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["generic_visit"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 28, "file_path": "/Users/muge/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_BoolOp", "start_line": 26}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["generic_visit"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 36, "file_path": "/Users/muge/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_Call", "start_line": 34}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["generic_visit"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 32, "file_path": "/Users/muge/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_Compare", "start_line": 30}}, {"metrics": {"bps": 0.1111111111111111, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["local_names"], "unresolved_calls": []}, "unit": {"end_line": 40, "file_path": "/Users/muge/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_Name", "start_line": 38}}]}, {"file_path": "/Users/muge/workspace/codra/codra/condition/metrics.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/csa/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/csa/analyzer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 4, "external_symbols": ["CsaResult", "FunctionSymbolCollector", "ModuleSymbolCollector", "UnitNodeCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 46, "file_path": "/Users/muge/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer.analyze_file", "start_line": 16}}]}, {"file_path": "/Users/muge/workspace/codra/codra/csa/result.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/directory/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/directory/scanner.py", "units": [{"metrics": {"bps": 0.3333333333333333, "csa_main": 1, "external_symbols": ["UnitDefinition"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["file_scanner"], "unresolved_calls": []}, "unit": {"end_line": 24, "file_path": "/Users/muge/workspace/codra/codra/directory/scanner.py", "kind": "method", "qualified_id": "DirectoryScanner.scan", "start_line": 14}}]}, {"file_path": "/Users/muge/workspace/codra/codra/file/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/file/path_collector.py", "units": [{"metrics": {"bps": 0.3333333333333333, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 17, "file_path": "/Users/muge/workspace/codra/codra/file/path_collector.py", "kind": "method", "qualified_id": "FilePathCollector.collect", "start_line": 9}}]}, {"file_path": "/Users/muge/workspace/codra/codra/function/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/function/definition_collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["names", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 14, "file_path": "/Users/muge/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.collect", "start_line": 12}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "names", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 32, "file_path": "/Users/muge/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.visit_AsyncFunctionDef", "start_line": 28}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 20, "file_path": "/Users/muge/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.visit_ClassDef", "start_line": 16}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "names", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 26, "file_path": "/Users/muge/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.visit_FunctionDef", "start_line": 22}}]}, {"file_path": "/Users/muge/workspace/codra/codra/function/extractor.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitDefinition"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "file_path", "function_stack", "generic_visit", "units"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 55, "file_path": "/Users/muge/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor._handle_function", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/Users/muge/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor.visit_AsyncFunctionDef", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "generic_visit"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 19, "file_path": "/Users/muge/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor.visit_ClassDef", "start_line": 16}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/Users/muge/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor.visit_FunctionDef", "start_line": 21}}]}, {"file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "units": [{"metrics": {"bps": 0.5, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 69, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._add_arguments", "start_line": 58}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_add_arguments", "usage", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 17, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.collect", "start_line": 13}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 23, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_AsyncFunctionDef", "start_line": 22}}, {"metrics": {"bps": 0.12698412698412698, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["generic_visit", "usage"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 48, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Attribute", "start_line": 44}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 26, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_ClassDef", "start_line": 25}}, {"metrics": {"bps": 0.3214285714285714, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 56, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_ExceptHandler", "start_line": 50}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 20, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_FunctionDef", "start_line": 19}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 29, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Global", "start_line": 28}}, {"metrics": {"bps": 0.3134920634920635, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 42, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Name", "start_line": 34}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 32, "file_path": "/Users/muge/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Nonlocal", "start_line": 31}}]}, {"file_path": "/Users/muge/workspace/codra/codra/function/symbol_usage.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/if_/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/if_/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["nodes", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 14, "file_path": "/Users/muge/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.collect", "start_line": 11}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 20, "file_path": "/Users/muge/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_AsyncFunctionDef", "start_line": 19}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 23, "file_path": "/Users/muge/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_ClassDef", "start_line": 22}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 17, "file_path": "/Users/muge/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_FunctionDef", "start_line": 16}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["generic_visit", "nodes"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 27, "file_path": "/Users/muge/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_If", "start_line": 25}}]}, {"file_path": "/Users/muge/workspace/codra/codra/indirection/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/indirection/analyzer.py", "units": [{"metrics": {"bps": 0.35537918871252205, "csa_main": 1, "external_symbols": ["CallCollector"], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_resolve_alias"], "unresolved_calls": []}, "unit": {"end_line": 129, "file_path": "/Users/muge/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._build_call_graph", "start_line": 91}}, {"metrics": {"bps": 0.14285714285714285, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 89, "file_path": "/Users/muge/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_class_methods", "start_line": 79}}, {"metrics": {"bps": 0.5625, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_depth"], "unresolved_calls": []}, "unit": {"end_line": 160, "file_path": "/Users/muge/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._depth", "start_line": 139}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 137, "file_path": "/Users/muge/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._resolve_alias", "start_line": 131}}, {"metrics": {"bps": 0.48214285714285715, "csa_main": 6, "external_symbols": ["AliasCollector", "CallCollector", "FunctionDefinitionCollector", "IndirectionResult", "ModuleSymbolCollector", "UnitNodeCollector"], "id_avg": 1.25, "id_max": 2, "self_fields_read": ["_build_call_graph", "_collect_class_methods", "_depth", "_resolve_alias"], "unresolved_calls": []}, "unit": {"end_line": 77, "file_path": "/Users/muge/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer.analyze_file", "start_line": 18}}]}, {"file_path": "/Users/muge/workspace/codra/codra/indirection/result.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/module/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "units": [{"metrics": {"bps": 0.14285714285714285, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 79, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector._extract_target_names", "start_line": 74}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols", "visit"], "unresolved_calls": ["self.visit"]}, "unit": {"end_line": 13, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.collect", "start_line": 11}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "generic_visit", "symbols"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 41, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AnnAssign", "start_line": 39}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "generic_visit", "symbols"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 37, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_Assign", "start_line": 34}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "generic_visit", "symbols"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 53, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AsyncFor", "start_line": 51}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 19, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AsyncFunctionDef", "start_line": 18}}, {"metrics": {"bps": 0.5, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "generic_visit", "symbols"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 65, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AsyncWith", "start_line": 61}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "generic_visit", "symbols"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 45, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AugAssign", "start_line": 43}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_ClassDef", "start_line": 21}}, {"metrics": {"bps": 0.3214285714285714, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "generic_visit", "symbols"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 72, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_ExceptHandler", "start_line": 67}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "generic_visit", "symbols"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 49, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_For", "start_line": 47}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 16, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_FunctionDef", "start_line": 15}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 27, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_Import", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 32, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_ImportFrom", "start_line": 29}}, {"metrics": {"bps": 0.5, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "generic_visit", "symbols"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 59, "file_path": "/Users/muge/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_With", "start_line": 55}}]}, {"file_path": "/Users/muge/workspace/codra/codra/python/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/python/file_scanner.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["FunctionExtractor"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 18, "file_path": "/Users/muge/workspace/codra/codra/python/file_scanner.py", "kind": "method", "qualified_id": "PythonFileScanner.scan_file", "start_line": 12}}]}, {"file_path": "/Users/muge/workspace/codra/codra/report/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/report/builder.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitKey"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 86, "file_path": "/Users/muge/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._key", "start_line": 79}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitMetrics"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 129, "file_path": "/Users/muge/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._resolve_metrics", "start_line": 109}}, {"metrics": {"bps": 0.5, "csa_main": 1, "external_symbols": ["UnitDefinition"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 107, "file_path": "/Users/muge/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._resolve_unit", "start_line": 91}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 89, "file_path": "/Users/muge/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._sort_key", "start_line": 88}}, {"metrics": {"bps": 1.0, "csa_main": 5, "external_symbols": ["FileReport", "Report", "ReportSummary", "UnitReport", "logger"], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_key", "_resolve_metrics", "_resolve_unit", "_sort_key", "bps_analyzer", "csa_analyzer", "indirection_analyzer", "path_collector"], "unresolved_calls": []}, "unit": {"end_line": 62, "file_path": "/Users/muge/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder.build", "start_line": 30}}, {"metrics": {"bps": 0.25, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 77, "file_path": "/Users/muge/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder.check_thresholds", "start_line": 64}}]}, {"file_path": "/Users/muge/workspace/codra/codra/report/file.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/report/model.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/report/serializer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["asdict"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 13, "file_path": "/Users/muge/workspace/codra/codra/report/serializer.py", "kind": "method", "qualified_id": "ReportSerializer.to_json", "start_line": 12}}]}, {"file_path": "/Users/muge/workspace/codra/codra/report/summary.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/threshold/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/threshold/config.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/unit/__init__.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/unit/definition.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/unit/key.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/unit/metrics.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/unit/node.py", "units": []}, {"file_path": "/Users/muge/workspace/codra/codra/unit/node_collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["UnitDefinition", "UnitNode"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "file_path", "function_stack", "generic_visit", "units"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 55, "file_path": "/Users/muge/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector._handle_function", "start_line": 28}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 26, "file_path": "/Users/muge/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector.visit_AsyncFunctionDef", "start_line": 25}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "generic_visit"], "unresolved_calls": ["self.generic_visit"]}, "unit": {"end_line": 20, "file_path": "/Users/muge/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector.visit_ClassDef", "start_line": 17}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 23, "file_path": "/Users/muge/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector.visit_FunctionDef", "start_line": 22}}]}, {"file_path": "/Users/muge/workspace/codra/codra/unit/overview.py", "units": []}], "language": "python", "schema_version": "1.0", "summary": {"total_files": 51, "total_units": 82}} +{"files": [{"file_path": "/workspace/codra/setup.py", "units": []}, {"file_path": "/workspace/codra/codra/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/alias/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/alias/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["aliases"], "unresolved_calls": []}, "unit": {"end_line": 16, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.collect", "start_line": 14}}, {"metrics": {"bps": 0.26190476190476186, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["aliases", "depth"], "unresolved_calls": []}, "unit": {"end_line": 49, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_AnnAssign", "start_line": 42}}, {"metrics": {"bps": 0.26190476190476186, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["aliases", "depth"], "unresolved_calls": []}, "unit": {"end_line": 40, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_Assign", "start_line": 33}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 28, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_AsyncFunctionDef", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 31, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_ClassDef", "start_line": 30}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_FunctionDef", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_Module", "start_line": 18}}]}, {"file_path": "/workspace/codra/codra/bps/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/bps/analyzer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["BpsResult"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 58, "file_path": "/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer._build_results", "start_line": 52}}, {"metrics": {"bps": 1.0, "csa_main": 3, "external_symbols": ["ConditionMetricsCollector", "FunctionSymbolCollector", "IfCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 50, "file_path": "/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer._collect_bps", "start_line": 32}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitNodeCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 30, "file_path": "/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer._collect_units", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer._parse_file", "start_line": 22}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_build_results", "_collect_bps", "_collect_units", "_parse_file"], "unresolved_calls": []}, "unit": {"end_line": 20, "file_path": "/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer.analyze_file", "start_line": 16}}]}, {"file_path": "/workspace/codra/codra/bps/result.py", "units": []}, {"file_path": "/workspace/codra/codra/call/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/call/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["calls"], "unresolved_calls": []}, "unit": {"end_line": 16, "file_path": "/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.collect", "start_line": 13}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_AsyncFunctionDef", "start_line": 21}}, {"metrics": {"bps": 0.13227513227513227, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["calls"], "unresolved_calls": []}, "unit": {"end_line": 33, "file_path": "/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_Call", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_ClassDef", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 19, "file_path": "/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_FunctionDef", "start_line": 18}}]}, {"file_path": "/workspace/codra/codra/cli/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/cli/args.py", "units": []}, {"file_path": "/workspace/codra/codra/cli/dependencies.py", "units": []}, {"file_path": "/workspace/codra/codra/cli/main.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 7, "external_symbols": ["BpsAnalyzer", "CliDependencies", "CsaAnalyzer", "FilePathCollector", "IndirectionAnalyzer", "ReportBuilder", "ReportSerializer"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 63, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "build_dependencies", "start_line": 56}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ThresholdConfig"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 53, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "build_thresholds", "start_line": 48}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 71, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "configure_logging", "start_line": 66}}, {"metrics": {"bps": 1.0, "csa_main": 4, "external_symbols": ["build_dependencies", "configure_logging", "parse_args", "run_cli"], "id_avg": 1.25, "id_max": 2, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 93, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "main", "start_line": 90}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["CliArgs"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 45, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "parse_args", "start_line": 21}}, {"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["build_thresholds", "logger"], "id_avg": 1.0, "id_max": 1, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 87, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "run_cli", "start_line": 74}}]}, {"file_path": "/workspace/codra/codra/condition/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/condition/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["local_names"], "unresolved_calls": []}, "unit": {"end_line": 49, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector._is_external_name", "start_line": 44}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ConditionMetrics"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["bool_ops", "calls", "compare_ops", "external_refs"], "unresolved_calls": []}, "unit": {"end_line": 26, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.collect", "start_line": 19}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 30, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_BoolOp", "start_line": 28}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 38, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_Call", "start_line": 36}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 34, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_Compare", "start_line": 32}}, {"metrics": {"bps": 0.3333333333333333, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_is_external_name"], "unresolved_calls": []}, "unit": {"end_line": 42, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_Name", "start_line": 40}}]}, {"file_path": "/workspace/codra/codra/condition/metrics.py", "units": []}, {"file_path": "/workspace/codra/codra/csa/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/csa/analyzer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["CsaResult", "FunctionSymbolCollector"], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_external_symbols"], "unresolved_calls": []}, "unit": {"end_line": 83, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer._build_results", "start_line": 62}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ModuleSymbolCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 37, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer._collect_module_symbols", "start_line": 36}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitNodeCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 42, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer._collect_unit_nodes", "start_line": 39}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 60, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer._extract_external_symbols", "start_line": 44}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 34, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer._parse_file", "start_line": 31}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.25, "id_max": 2, "self_fields_read": ["_build_results", "_collect_module_symbols", "_collect_unit_nodes", "_parse_file"], "unresolved_calls": []}, "unit": {"end_line": 29, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer.analyze_file", "start_line": 18}}]}, {"file_path": "/workspace/codra/codra/csa/result.py", "units": []}, {"file_path": "/workspace/codra/codra/directory/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/directory/scanner.py", "units": [{"metrics": {"bps": 0.3333333333333333, "csa_main": 1, "external_symbols": ["UnitDefinition"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["file_scanner"], "unresolved_calls": []}, "unit": {"end_line": 24, "file_path": "/workspace/codra/codra/directory/scanner.py", "kind": "method", "qualified_id": "DirectoryScanner.scan", "start_line": 14}}]}, {"file_path": "/workspace/codra/codra/file/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/file/path_collector.py", "units": [{"metrics": {"bps": 0.3333333333333333, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 17, "file_path": "/workspace/codra/codra/file/path_collector.py", "kind": "method", "qualified_id": "FilePathCollector.collect", "start_line": 9}}]}, {"file_path": "/workspace/codra/codra/function/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/function/definition_collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["names"], "unresolved_calls": []}, "unit": {"end_line": 16, "file_path": "/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.collect", "start_line": 14}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "names"], "unresolved_calls": []}, "unit": {"end_line": 34, "file_path": "/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.visit_AsyncFunctionDef", "start_line": 30}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack"], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.visit_ClassDef", "start_line": 18}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "names"], "unresolved_calls": []}, "unit": {"end_line": 28, "file_path": "/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.visit_FunctionDef", "start_line": 24}}]}, {"file_path": "/workspace/codra/codra/function/extractor.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitDefinition"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "file_path", "function_stack", "generic_visit", "units"], "unresolved_calls": []}, "unit": {"end_line": 55, "file_path": "/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor._handle_function", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor.visit_AsyncFunctionDef", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "generic_visit"], "unresolved_calls": []}, "unit": {"end_line": 19, "file_path": "/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor.visit_ClassDef", "start_line": 16}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor.visit_FunctionDef", "start_line": 21}}]}, {"file_path": "/workspace/codra/codra/function/symbol_collector.py", "units": [{"metrics": {"bps": 0.5, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 71, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._add_arguments", "start_line": 60}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 82, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._is_declared_nonlocal_or_global", "start_line": 79}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 92, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._is_exception_name", "start_line": 91}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 74, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._is_load_context", "start_line": 73}}, {"metrics": {"bps": 0.3333333333333333, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_is_load_context"], "unresolved_calls": []}, "unit": {"end_line": 89, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._is_self_attribute_read", "start_line": 84}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 77, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._is_store_or_del_context", "start_line": 76}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_add_arguments", "usage"], "unresolved_calls": []}, "unit": {"end_line": 19, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.collect", "start_line": 15}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_AsyncFunctionDef", "start_line": 24}}, {"metrics": {"bps": 0.3333333333333333, "csa_main": 0, "external_symbols": [], "id_avg": 2.0, "id_max": 2, "self_fields_read": ["_is_self_attribute_read", "usage"], "unresolved_calls": []}, "unit": {"end_line": 49, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Attribute", "start_line": 46}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 28, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_ClassDef", "start_line": 27}}, {"metrics": {"bps": 0.41666666666666663, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_is_exception_name", "usage"], "unresolved_calls": []}, "unit": {"end_line": 58, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_ExceptHandler", "start_line": 51}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_FunctionDef", "start_line": 21}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 31, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Global", "start_line": 30}}, {"metrics": {"bps": 0.3333333333333333, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_is_declared_nonlocal_or_global", "_is_load_context", "_is_store_or_del_context", "usage"], "unresolved_calls": []}, "unit": {"end_line": 44, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Name", "start_line": 36}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 34, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Nonlocal", "start_line": 33}}]}, {"file_path": "/workspace/codra/codra/function/symbol_usage.py", "units": []}, {"file_path": "/workspace/codra/codra/if_/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/if_/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["nodes"], "unresolved_calls": []}, "unit": {"end_line": 16, "file_path": "/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.collect", "start_line": 13}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_AsyncFunctionDef", "start_line": 21}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_ClassDef", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 19, "file_path": "/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_FunctionDef", "start_line": 18}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["nodes"], "unresolved_calls": []}, "unit": {"end_line": 29, "file_path": "/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_If", "start_line": 27}}]}, {"file_path": "/workspace/codra/codra/indirection/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/indirection/analyzer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_depth"], "unresolved_calls": []}, "unit": {"end_line": 199, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._calculate_indirection_depths", "start_line": 187}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["AliasCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 106, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_aliases", "start_line": 105}}, {"metrics": {"bps": 0.4583333333333333, "csa_main": 0, "external_symbols": [], "id_avg": 2.0, "id_max": 2, "self_fields_read": ["_resolve_alias"], "unresolved_calls": []}, "unit": {"end_line": 153, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_call_graph", "start_line": 120}}, {"metrics": {"bps": 0.14285714285714285, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 118, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_class_methods", "start_line": 108}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["FunctionDefinitionCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 103, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_function_definitions", "start_line": 102}}, {"metrics": {"bps": 0.5625, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_depth"], "unresolved_calls": []}, "unit": {"end_line": 185, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._depth", "start_line": 164}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 100, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._parse_file", "start_line": 97}}, {"metrics": {"bps": 0.5, "csa_main": 1, "external_symbols": ["resolve_alias"], "id_avg": 1.0, "id_max": 1, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 162, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._resolve_alias", "start_line": 155}}, {"metrics": {"bps": 0.494047619047619, "csa_main": 4, "external_symbols": ["IndirectionResult", "ModuleSymbolCollector", "UnitNodeCollector", "collect_calls_by_unit"], "id_avg": 1.625, "id_max": 3, "self_fields_read": ["_calculate_indirection_depths", "_collect_aliases", "_collect_call_graph", "_collect_class_methods", "_collect_function_definitions", "_parse_file", "_resolve_alias"], "unresolved_calls": []}, "unit": {"end_line": 95, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer.analyze_file", "start_line": 40}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["CallCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 28, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "function", "qualified_id": "collect_calls", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["collect_calls"], "id_avg": 1.0, "id_max": 1, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 35, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "function", "qualified_id": "collect_calls_by_unit", "start_line": 31}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 24, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "function", "qualified_id": "resolve_alias", "start_line": 18}}]}, {"file_path": "/workspace/codra/codra/indirection/result.py", "units": []}, {"file_path": "/workspace/codra/codra/module/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/module/symbol_collector.py", "units": [{"metrics": {"bps": 0.14285714285714285, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 81, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector._extract_target_names", "start_line": 76}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 15, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.collect", "start_line": 13}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 43, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AnnAssign", "start_line": 41}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 39, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_Assign", "start_line": 36}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 55, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AsyncFor", "start_line": 53}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 21, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AsyncFunctionDef", "start_line": 20}}, {"metrics": {"bps": 0.5, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 67, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AsyncWith", "start_line": 63}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 47, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AugAssign", "start_line": 45}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 24, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_ClassDef", "start_line": 23}}, {"metrics": {"bps": 0.3214285714285714, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 74, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_ExceptHandler", "start_line": 69}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 51, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_For", "start_line": 49}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 18, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_FunctionDef", "start_line": 17}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 29, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_Import", "start_line": 26}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 34, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_ImportFrom", "start_line": 31}}, {"metrics": {"bps": 0.5, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 61, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_With", "start_line": 57}}]}, {"file_path": "/workspace/codra/codra/python/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/python/file_scanner.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["FunctionExtractor"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 18, "file_path": "/workspace/codra/codra/python/file_scanner.py", "kind": "method", "qualified_id": "PythonFileScanner.scan_file", "start_line": 12}}]}, {"file_path": "/workspace/codra/codra/report/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/report/builder.py", "units": [{"metrics": {"bps": 0.25, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 61, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._apply_thresholds", "start_line": 48}}, {"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["FileReport", "UnitReport"], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_key", "_resolve_metrics", "_resolve_unit", "_sort_key"], "unresolved_calls": []}, "unit": {"end_line": 117, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._build_file_report", "start_line": 96}}, {"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["FileReport", "logger"], "id_avg": 1.5, "id_max": 2, "self_fields_read": ["_build_file_report", "_collect_file_analysis"], "unresolved_calls": []}, "unit": {"end_line": 94, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._build_file_reports", "start_line": 83}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ReportSummary"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 121, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._build_summary", "start_line": 119}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["bps_analyzer", "csa_analyzer", "indirection_analyzer"], "unresolved_calls": []}, "unit": {"end_line": 81, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._collect_file_analysis", "start_line": 75}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitKey"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 70, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._key", "start_line": 63}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitMetrics"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 161, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._resolve_metrics", "start_line": 141}}, {"metrics": {"bps": 0.5, "csa_main": 1, "external_symbols": ["UnitDefinition"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 139, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._resolve_unit", "start_line": 123}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 73, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._sort_key", "start_line": 72}}, {"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["Report", "logger"], "id_avg": 2.0, "id_max": 3, "self_fields_read": ["_build_file_reports", "_build_summary", "path_collector"], "unresolved_calls": []}, "unit": {"end_line": 43, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder.build", "start_line": 33}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_apply_thresholds"], "unresolved_calls": []}, "unit": {"end_line": 46, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder.check_thresholds", "start_line": 45}}]}, {"file_path": "/workspace/codra/codra/report/file.py", "units": []}, {"file_path": "/workspace/codra/codra/report/model.py", "units": []}, {"file_path": "/workspace/codra/codra/report/serializer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["asdict"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 13, "file_path": "/workspace/codra/codra/report/serializer.py", "kind": "method", "qualified_id": "ReportSerializer.to_json", "start_line": 12}}]}, {"file_path": "/workspace/codra/codra/report/summary.py", "units": []}, {"file_path": "/workspace/codra/codra/threshold/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/threshold/config.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/definition.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/key.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/metrics.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/node.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/node_collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["UnitDefinition", "UnitNode"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "file_path", "function_stack", "units"], "unresolved_calls": []}, "unit": {"end_line": 57, "file_path": "/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector._handle_function", "start_line": 30}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 28, "file_path": "/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector.visit_AsyncFunctionDef", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack"], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector.visit_ClassDef", "start_line": 19}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector.visit_FunctionDef", "start_line": 24}}]}, {"file_path": "/workspace/codra/codra/unit/overview.py", "units": []}, {"file_path": "/workspace/codra/tests/test_analyzers.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["BpsAnalyzer", "BpsResult"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertAlmostEqual", "assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 45, "file_path": "/workspace/codra/tests/test_analyzers.py", "kind": "method", "qualified_id": "BpsAnalyzerHelperTests.test_helpers_collect_bps_scores", "start_line": 25}}, {"metrics": {"bps": 1.0, "csa_main": 3, "external_symbols": ["CsaAnalyzer", "CsaResult", "FunctionSymbolCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 93, "file_path": "/workspace/codra/tests/test_analyzers.py", "kind": "method", "qualified_id": "CsaAnalyzerHelperTests.test_helpers_extract_external_symbols", "start_line": 49}}, {"metrics": {"bps": 1.0, "csa_main": 3, "external_symbols": ["IndirectionAnalyzer", "UnitNodeCollector", "collect_calls_by_unit"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 135, "file_path": "/workspace/codra/tests/test_analyzers.py", "kind": "method", "qualified_id": "IndirectionAnalyzerHelperTests.test_helpers_collect_call_graph_and_depth", "start_line": 97}}, {"metrics": {"bps": 1.0, "csa_main": 4, "external_symbols": ["IndirectionAnalyzer", "UnitNodeCollector", "collect_calls_by_unit", "resolve_alias"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 181, "file_path": "/workspace/codra/tests/test_analyzers.py", "kind": "method", "qualified_id": "IndirectionAnalyzerHelperTests.test_helpers_resolve_alias_chain_and_class_methods", "start_line": 137}}, {"metrics": {"bps": 1.0, "csa_main": 9, "external_symbols": ["BpsAnalyzer", "BpsResult", "CsaAnalyzer", "CsaResult", "IndirectionAnalyzer", "IndirectionResult", "ReportBuilder", "UnitDefinition", "UnitReport"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual", "assertIsInstance"], "unresolved_calls": []}, "unit": {"end_line": 235, "file_path": "/workspace/codra/tests/test_analyzers.py", "kind": "method", "qualified_id": "ReportBuilderHelperTests.test_helpers_build_file_report_and_summary", "start_line": 185}}]}, {"file_path": "/workspace/codra/tests/test_collectors.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["AliasCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 29, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_alias_collector_tracks_top_level_aliases", "start_line": 15}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["CallCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 46, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_call_collector_tracks_module_calls_only", "start_line": 31}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ConditionMetricsCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 63, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_condition_metrics_collector_counts_operations", "start_line": 48}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["FunctionDefinitionCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 95, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_function_definition_collector_skips_methods", "start_line": 81}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["FunctionSymbolCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertIn"], "unresolved_calls": []}, "unit": {"end_line": 117, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_function_symbol_collector_tracks_usage", "start_line": 97}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["IfCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual", "assertIsInstance"], "unresolved_calls": []}, "unit": {"end_line": 79, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_if_collector_tracks_module_if_statements", "start_line": 65}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ModuleSymbolCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertTrue"], "unresolved_calls": []}, "unit": {"end_line": 151, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_module_symbol_collector_gathers_symbols", "start_line": 119}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitNodeCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 172, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_unit_node_collector_builds_qualified_ids", "start_line": 153}}]}], "language": "python", "schema_version": "1.0", "summary": {"total_files": 54, "total_units": 122}} From ee28ed090a2418421cb6becdce46d7d67cc68908 Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 16:57:46 +0100 Subject: [PATCH 15/17] Refactor condition checks in collectors --- codra/directory/scanner.py | 3 ++- codra/file/path_collector.py | 3 ++- codra/function/symbol_collector.py | 15 ++++++++++----- codra/indirection/analyzer.py | 8 ++++++-- codra/module/symbol_collector.py | 13 ++++++++----- 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/codra/directory/scanner.py b/codra/directory/scanner.py index 63b6500..7c934a8 100644 --- a/codra/directory/scanner.py +++ b/codra/directory/scanner.py @@ -17,7 +17,8 @@ def scan(self, root_path: str) -> list[UnitDefinition]: dirnames.sort() filenames.sort() for filename in filenames: - if not filename.endswith(".py"): + is_python_file = filename.endswith(".py") + if not is_python_file: continue file_path = os.path.join(current_root, filename) units.extend(self.file_scanner.scan_file(file_path)) diff --git a/codra/file/path_collector.py b/codra/file/path_collector.py index fae4211..007fe68 100644 --- a/codra/file/path_collector.py +++ b/codra/file/path_collector.py @@ -12,6 +12,7 @@ def collect(self, root_path: str) -> list[str]: dirnames.sort() filenames.sort() for filename in filenames: - if filename.endswith(".py"): + is_python_file = filename.endswith(".py") + if is_python_file: paths.append(os.path.join(current_root, filename)) return paths diff --git a/codra/function/symbol_collector.py b/codra/function/symbol_collector.py index fead68d..4669872 100644 --- a/codra/function/symbol_collector.py +++ b/codra/function/symbol_collector.py @@ -36,15 +36,19 @@ def visit_Nonlocal(self, node: ast.Nonlocal) -> None: def visit_Name(self, node: ast.Name) -> None: context = node.ctx name = node.id - if self._is_load_context(context): + is_load_context = self._is_load_context(context) + is_store_or_del_context = self._is_store_or_del_context(context) + if is_load_context: self.usage.used_names.add(name) - elif self._is_store_or_del_context(context): - if self._is_declared_nonlocal_or_global(name): + elif is_store_or_del_context: + is_declared_nonlocal_or_global = self._is_declared_nonlocal_or_global(name) + if is_declared_nonlocal_or_global: return self.usage.locals.add(name) def visit_Attribute(self, node: ast.Attribute) -> None: - if self._is_self_attribute_read(node): + is_self_attribute_read = self._is_self_attribute_read(node) + if is_self_attribute_read: self.usage.self_fields_read.add(node.attr) super().generic_visit(node) @@ -83,7 +87,8 @@ def _is_declared_nonlocal_or_global(self, name: str) -> bool: def _is_self_attribute_read(self, node: ast.Attribute) -> bool: context = node.ctx - if not self._is_load_context(context): + is_load_context = self._is_load_context(context) + if not is_load_context: return False value = node.value return isinstance(value, ast.Name) and value.id == "self" diff --git a/codra/indirection/analyzer.py b/codra/indirection/analyzer.py index 09b71d7..82e6491 100644 --- a/codra/indirection/analyzer.py +++ b/codra/indirection/analyzer.py @@ -60,14 +60,18 @@ def analyze_file(self, file_path: str) -> list[IndirectionResult]: if unit_node.definition.kind == "method" else None ) + class_method_names = ( + class_methods.get(class_name, set()) if class_name else set() + ) call_names = call_map.get(unit_node.definition.qualified_id, []) resolved_calls: list[str] = [] unresolved_calls: set[str] = set() for name in call_names: - if name.startswith("self."): + is_self_call = name.startswith("self.") + if is_self_call: if class_name: method_name = name.split(".", 1)[1] - if method_name in class_methods.get(class_name, set()): + if method_name in class_method_names: resolved_calls.append(f"{class_name}.{method_name}") continue resolved = self._resolve_alias(name, aliases, alias_cache) diff --git a/codra/module/symbol_collector.py b/codra/module/symbol_collector.py index 713896c..5f5336d 100644 --- a/codra/module/symbol_collector.py +++ b/codra/module/symbol_collector.py @@ -67,15 +67,18 @@ def visit_AsyncWith(self, node: ast.AsyncWith) -> None: super().generic_visit(node) def visit_ExceptHandler(self, node: ast.ExceptHandler) -> None: - if isinstance(node.name, str): - self.symbols.add(node.name) - elif node.name is not None: - self.symbols.update(self._extract_target_names(node.name)) + handler_name = node.name + is_handler_name = isinstance(handler_name, str) + if is_handler_name: + self.symbols.add(handler_name) + elif handler_name is not None: + self.symbols.update(self._extract_target_names(handler_name)) super().generic_visit(node) def _extract_target_names(self, node: ast.AST) -> set[str]: names: set[str] = set() for target in ast.walk(node): - if isinstance(target, ast.Name): + is_name = isinstance(target, ast.Name) + if is_name: names.add(target.id) return names From 43b9f8691abae3c30ce7ea8527ca75e87ad4adf3 Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 17:00:27 +0100 Subject: [PATCH 16/17] Refactor indirection analysis helpers --- codra/indirection/analyzer.py | 193 +++++++++++++++++++++++++--------- report.json | 2 +- 2 files changed, 144 insertions(+), 51 deletions(-) diff --git a/codra/indirection/analyzer.py b/codra/indirection/analyzer.py index 82e6491..9d3ab47 100644 --- a/codra/indirection/analyzer.py +++ b/codra/indirection/analyzer.py @@ -43,60 +43,25 @@ def analyze_file(self, file_path: str) -> list[IndirectionResult]: class_methods = self._collect_class_methods(tree) aliases = self._collect_aliases(tree) module_symbols = ModuleSymbolCollector().collect(tree) - builtin_names = set(dir(builtins)) - stdlib_modules = set(sys.stdlib_module_names) - unit_collector = UnitNodeCollector(file_path=file_path) - unit_collector.visit(tree) - call_map = collect_calls_by_unit(unit_collector.units) + builtin_names = self._collect_builtin_names() + stdlib_modules = self._collect_stdlib_modules() + unit_nodes, call_map = self._collect_unit_calls(file_path, tree) alias_cache: dict[str, str] = {} call_graph = self._collect_call_graph( function_names, class_methods, aliases, call_map, alias_cache ) - depth_cache: dict[str, int] = {} - results: list[IndirectionResult] = [] - for unit_node in unit_collector.units: - class_name = ( - unit_node.definition.qualified_id.split(".", 1)[0] - if unit_node.definition.kind == "method" - else None - ) - class_method_names = ( - class_methods.get(class_name, set()) if class_name else set() - ) - call_names = call_map.get(unit_node.definition.qualified_id, []) - resolved_calls: list[str] = [] - unresolved_calls: set[str] = set() - for name in call_names: - is_self_call = name.startswith("self.") - if is_self_call: - if class_name: - method_name = name.split(".", 1)[1] - if method_name in class_method_names: - resolved_calls.append(f"{class_name}.{method_name}") - continue - resolved = self._resolve_alias(name, aliases, alias_cache) - if resolved in function_names: - resolved_calls.append(resolved) - elif resolved in module_symbols: - continue - elif resolved in builtin_names: - continue - elif resolved in stdlib_modules: - continue - else: - unresolved_calls.add(name) - id_max, id_avg = self._calculate_indirection_depths( - resolved_calls, call_graph, depth_cache - ) - results.append( - IndirectionResult( - unit=unit_node.definition, - id_max=id_max, - id_avg=id_avg, - unresolved_calls=sorted(unresolved_calls), - ) - ) - return results + return self._build_results( + unit_nodes=unit_nodes, + call_map=call_map, + class_methods=class_methods, + aliases=aliases, + function_names=function_names, + module_symbols=module_symbols, + builtin_names=builtin_names, + stdlib_modules=stdlib_modules, + alias_cache=alias_cache, + call_graph=call_graph, + ) def _parse_file(self, file_path: str) -> ast.AST: with open(file_path, "r", encoding="utf-8") as handle: @@ -156,6 +121,134 @@ def _collect_call_graph( call_graph[qualified_name].append(resolved) return call_graph + def _collect_builtin_names(self) -> set[str]: + return set(dir(builtins)) + + def _collect_stdlib_modules(self) -> set[str]: + return set(sys.stdlib_module_names) + + def _collect_unit_calls( + self, file_path: str, tree: ast.AST + ) -> tuple[list[UnitNode], dict[str, list[str]]]: + unit_collector = UnitNodeCollector(file_path=file_path) + unit_collector.visit(tree) + call_map = collect_calls_by_unit(unit_collector.units) + return unit_collector.units, call_map + + def _build_results( + self, + unit_nodes: list[UnitNode], + call_map: dict[str, list[str]], + class_methods: dict[str, set[str]], + aliases: dict[str, str], + function_names: set[str], + module_symbols: set[str], + builtin_names: set[str], + stdlib_modules: set[str], + alias_cache: dict[str, str], + call_graph: dict[str, list[str]], + ) -> list[IndirectionResult]: + depth_cache: dict[str, int] = {} + results: list[IndirectionResult] = [] + for unit_node in unit_nodes: + class_name, class_method_names = self._class_context( + unit_node, class_methods + ) + call_names = call_map.get(unit_node.definition.qualified_id, []) + resolved_calls, unresolved_calls = self._resolve_unit_calls( + call_names=call_names, + class_name=class_name, + class_method_names=class_method_names, + aliases=aliases, + alias_cache=alias_cache, + function_names=function_names, + module_symbols=module_symbols, + builtin_names=builtin_names, + stdlib_modules=stdlib_modules, + ) + id_max, id_avg = self._calculate_indirection_depths( + resolved_calls, call_graph, depth_cache + ) + results.append( + IndirectionResult( + unit=unit_node.definition, + id_max=id_max, + id_avg=id_avg, + unresolved_calls=sorted(unresolved_calls), + ) + ) + return results + + def _class_context( + self, unit_node: UnitNode, class_methods: dict[str, set[str]] + ) -> tuple[str | None, set[str]]: + if unit_node.definition.kind != "method": + return None, set() + class_name = unit_node.definition.qualified_id.split(".", 1)[0] + return class_name, class_methods.get(class_name, set()) + + def _resolve_unit_calls( + self, + call_names: list[str], + class_name: str | None, + class_method_names: set[str], + aliases: dict[str, str], + alias_cache: dict[str, str], + function_names: set[str], + module_symbols: set[str], + builtin_names: set[str], + stdlib_modules: set[str], + ) -> tuple[list[str], set[str]]: + resolved_calls: list[str] = [] + unresolved_calls: set[str] = set() + for name in call_names: + resolved_self = self._resolve_self_call( + name, class_name, class_method_names + ) + if resolved_self: + resolved_calls.append(resolved_self) + continue + if name.startswith("self."): + continue + resolved = self._resolve_alias(name, aliases, alias_cache) + if resolved in function_names: + resolved_calls.append(resolved) + continue + if self._is_ignorable_call( + resolved, module_symbols, builtin_names, stdlib_modules + ): + continue + unresolved_calls.add(name) + return resolved_calls, unresolved_calls + + def _resolve_self_call( + self, + name: str, + class_name: str | None, + class_method_names: set[str], + ) -> str | None: + if not name.startswith("self."): + return None + if not class_name: + return None + method_name = name.split(".", 1)[1] + if method_name not in class_method_names: + return None + return f"{class_name}.{method_name}" + + def _is_ignorable_call( + self, + resolved: str, + module_symbols: set[str], + builtin_names: set[str], + stdlib_modules: set[str], + ) -> bool: + return ( + resolved in module_symbols + or resolved in builtin_names + or resolved in stdlib_modules + ) + def _resolve_alias( self, name: str, aliases: dict[str, str], alias_cache: dict[str, str] ) -> str: diff --git a/report.json b/report.json index 80b8601..97398c1 100644 --- a/report.json +++ b/report.json @@ -1 +1 @@ -{"files": [{"file_path": "/workspace/codra/setup.py", "units": []}, {"file_path": "/workspace/codra/codra/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/alias/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/alias/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["aliases"], "unresolved_calls": []}, "unit": {"end_line": 16, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.collect", "start_line": 14}}, {"metrics": {"bps": 0.26190476190476186, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["aliases", "depth"], "unresolved_calls": []}, "unit": {"end_line": 49, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_AnnAssign", "start_line": 42}}, {"metrics": {"bps": 0.26190476190476186, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["aliases", "depth"], "unresolved_calls": []}, "unit": {"end_line": 40, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_Assign", "start_line": 33}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 28, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_AsyncFunctionDef", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 31, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_ClassDef", "start_line": 30}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_FunctionDef", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_Module", "start_line": 18}}]}, {"file_path": "/workspace/codra/codra/bps/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/bps/analyzer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["BpsResult"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 58, "file_path": "/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer._build_results", "start_line": 52}}, {"metrics": {"bps": 1.0, "csa_main": 3, "external_symbols": ["ConditionMetricsCollector", "FunctionSymbolCollector", "IfCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 50, "file_path": "/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer._collect_bps", "start_line": 32}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitNodeCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 30, "file_path": "/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer._collect_units", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer._parse_file", "start_line": 22}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_build_results", "_collect_bps", "_collect_units", "_parse_file"], "unresolved_calls": []}, "unit": {"end_line": 20, "file_path": "/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer.analyze_file", "start_line": 16}}]}, {"file_path": "/workspace/codra/codra/bps/result.py", "units": []}, {"file_path": "/workspace/codra/codra/call/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/call/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["calls"], "unresolved_calls": []}, "unit": {"end_line": 16, "file_path": "/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.collect", "start_line": 13}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_AsyncFunctionDef", "start_line": 21}}, {"metrics": {"bps": 0.13227513227513227, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["calls"], "unresolved_calls": []}, "unit": {"end_line": 33, "file_path": "/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_Call", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_ClassDef", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 19, "file_path": "/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_FunctionDef", "start_line": 18}}]}, {"file_path": "/workspace/codra/codra/cli/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/cli/args.py", "units": []}, {"file_path": "/workspace/codra/codra/cli/dependencies.py", "units": []}, {"file_path": "/workspace/codra/codra/cli/main.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 7, "external_symbols": ["BpsAnalyzer", "CliDependencies", "CsaAnalyzer", "FilePathCollector", "IndirectionAnalyzer", "ReportBuilder", "ReportSerializer"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 63, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "build_dependencies", "start_line": 56}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ThresholdConfig"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 53, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "build_thresholds", "start_line": 48}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 71, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "configure_logging", "start_line": 66}}, {"metrics": {"bps": 1.0, "csa_main": 4, "external_symbols": ["build_dependencies", "configure_logging", "parse_args", "run_cli"], "id_avg": 1.25, "id_max": 2, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 93, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "main", "start_line": 90}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["CliArgs"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 45, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "parse_args", "start_line": 21}}, {"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["build_thresholds", "logger"], "id_avg": 1.0, "id_max": 1, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 87, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "run_cli", "start_line": 74}}]}, {"file_path": "/workspace/codra/codra/condition/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/condition/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["local_names"], "unresolved_calls": []}, "unit": {"end_line": 49, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector._is_external_name", "start_line": 44}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ConditionMetrics"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["bool_ops", "calls", "compare_ops", "external_refs"], "unresolved_calls": []}, "unit": {"end_line": 26, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.collect", "start_line": 19}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 30, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_BoolOp", "start_line": 28}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 38, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_Call", "start_line": 36}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 34, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_Compare", "start_line": 32}}, {"metrics": {"bps": 0.3333333333333333, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_is_external_name"], "unresolved_calls": []}, "unit": {"end_line": 42, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_Name", "start_line": 40}}]}, {"file_path": "/workspace/codra/codra/condition/metrics.py", "units": []}, {"file_path": "/workspace/codra/codra/csa/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/csa/analyzer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["CsaResult", "FunctionSymbolCollector"], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_external_symbols"], "unresolved_calls": []}, "unit": {"end_line": 83, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer._build_results", "start_line": 62}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ModuleSymbolCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 37, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer._collect_module_symbols", "start_line": 36}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitNodeCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 42, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer._collect_unit_nodes", "start_line": 39}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 60, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer._extract_external_symbols", "start_line": 44}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 34, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer._parse_file", "start_line": 31}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.25, "id_max": 2, "self_fields_read": ["_build_results", "_collect_module_symbols", "_collect_unit_nodes", "_parse_file"], "unresolved_calls": []}, "unit": {"end_line": 29, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer.analyze_file", "start_line": 18}}]}, {"file_path": "/workspace/codra/codra/csa/result.py", "units": []}, {"file_path": "/workspace/codra/codra/directory/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/directory/scanner.py", "units": [{"metrics": {"bps": 0.3333333333333333, "csa_main": 1, "external_symbols": ["UnitDefinition"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["file_scanner"], "unresolved_calls": []}, "unit": {"end_line": 24, "file_path": "/workspace/codra/codra/directory/scanner.py", "kind": "method", "qualified_id": "DirectoryScanner.scan", "start_line": 14}}]}, {"file_path": "/workspace/codra/codra/file/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/file/path_collector.py", "units": [{"metrics": {"bps": 0.3333333333333333, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 17, "file_path": "/workspace/codra/codra/file/path_collector.py", "kind": "method", "qualified_id": "FilePathCollector.collect", "start_line": 9}}]}, {"file_path": "/workspace/codra/codra/function/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/function/definition_collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["names"], "unresolved_calls": []}, "unit": {"end_line": 16, "file_path": "/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.collect", "start_line": 14}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "names"], "unresolved_calls": []}, "unit": {"end_line": 34, "file_path": "/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.visit_AsyncFunctionDef", "start_line": 30}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack"], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.visit_ClassDef", "start_line": 18}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "names"], "unresolved_calls": []}, "unit": {"end_line": 28, "file_path": "/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.visit_FunctionDef", "start_line": 24}}]}, {"file_path": "/workspace/codra/codra/function/extractor.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitDefinition"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "file_path", "function_stack", "generic_visit", "units"], "unresolved_calls": []}, "unit": {"end_line": 55, "file_path": "/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor._handle_function", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor.visit_AsyncFunctionDef", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "generic_visit"], "unresolved_calls": []}, "unit": {"end_line": 19, "file_path": "/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor.visit_ClassDef", "start_line": 16}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor.visit_FunctionDef", "start_line": 21}}]}, {"file_path": "/workspace/codra/codra/function/symbol_collector.py", "units": [{"metrics": {"bps": 0.5, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 71, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._add_arguments", "start_line": 60}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 82, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._is_declared_nonlocal_or_global", "start_line": 79}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 92, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._is_exception_name", "start_line": 91}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 74, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._is_load_context", "start_line": 73}}, {"metrics": {"bps": 0.3333333333333333, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_is_load_context"], "unresolved_calls": []}, "unit": {"end_line": 89, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._is_self_attribute_read", "start_line": 84}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 77, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._is_store_or_del_context", "start_line": 76}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_add_arguments", "usage"], "unresolved_calls": []}, "unit": {"end_line": 19, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.collect", "start_line": 15}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_AsyncFunctionDef", "start_line": 24}}, {"metrics": {"bps": 0.3333333333333333, "csa_main": 0, "external_symbols": [], "id_avg": 2.0, "id_max": 2, "self_fields_read": ["_is_self_attribute_read", "usage"], "unresolved_calls": []}, "unit": {"end_line": 49, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Attribute", "start_line": 46}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 28, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_ClassDef", "start_line": 27}}, {"metrics": {"bps": 0.41666666666666663, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_is_exception_name", "usage"], "unresolved_calls": []}, "unit": {"end_line": 58, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_ExceptHandler", "start_line": 51}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_FunctionDef", "start_line": 21}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 31, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Global", "start_line": 30}}, {"metrics": {"bps": 0.3333333333333333, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_is_declared_nonlocal_or_global", "_is_load_context", "_is_store_or_del_context", "usage"], "unresolved_calls": []}, "unit": {"end_line": 44, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Name", "start_line": 36}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 34, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Nonlocal", "start_line": 33}}]}, {"file_path": "/workspace/codra/codra/function/symbol_usage.py", "units": []}, {"file_path": "/workspace/codra/codra/if_/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/if_/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["nodes"], "unresolved_calls": []}, "unit": {"end_line": 16, "file_path": "/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.collect", "start_line": 13}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_AsyncFunctionDef", "start_line": 21}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_ClassDef", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 19, "file_path": "/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_FunctionDef", "start_line": 18}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["nodes"], "unresolved_calls": []}, "unit": {"end_line": 29, "file_path": "/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_If", "start_line": 27}}]}, {"file_path": "/workspace/codra/codra/indirection/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/indirection/analyzer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_depth"], "unresolved_calls": []}, "unit": {"end_line": 199, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._calculate_indirection_depths", "start_line": 187}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["AliasCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 106, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_aliases", "start_line": 105}}, {"metrics": {"bps": 0.4583333333333333, "csa_main": 0, "external_symbols": [], "id_avg": 2.0, "id_max": 2, "self_fields_read": ["_resolve_alias"], "unresolved_calls": []}, "unit": {"end_line": 153, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_call_graph", "start_line": 120}}, {"metrics": {"bps": 0.14285714285714285, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 118, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_class_methods", "start_line": 108}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["FunctionDefinitionCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 103, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_function_definitions", "start_line": 102}}, {"metrics": {"bps": 0.5625, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_depth"], "unresolved_calls": []}, "unit": {"end_line": 185, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._depth", "start_line": 164}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 100, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._parse_file", "start_line": 97}}, {"metrics": {"bps": 0.5, "csa_main": 1, "external_symbols": ["resolve_alias"], "id_avg": 1.0, "id_max": 1, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 162, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._resolve_alias", "start_line": 155}}, {"metrics": {"bps": 0.494047619047619, "csa_main": 4, "external_symbols": ["IndirectionResult", "ModuleSymbolCollector", "UnitNodeCollector", "collect_calls_by_unit"], "id_avg": 1.625, "id_max": 3, "self_fields_read": ["_calculate_indirection_depths", "_collect_aliases", "_collect_call_graph", "_collect_class_methods", "_collect_function_definitions", "_parse_file", "_resolve_alias"], "unresolved_calls": []}, "unit": {"end_line": 95, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer.analyze_file", "start_line": 40}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["CallCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 28, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "function", "qualified_id": "collect_calls", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["collect_calls"], "id_avg": 1.0, "id_max": 1, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 35, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "function", "qualified_id": "collect_calls_by_unit", "start_line": 31}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 24, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "function", "qualified_id": "resolve_alias", "start_line": 18}}]}, {"file_path": "/workspace/codra/codra/indirection/result.py", "units": []}, {"file_path": "/workspace/codra/codra/module/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/module/symbol_collector.py", "units": [{"metrics": {"bps": 0.14285714285714285, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 81, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector._extract_target_names", "start_line": 76}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 15, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.collect", "start_line": 13}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 43, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AnnAssign", "start_line": 41}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 39, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_Assign", "start_line": 36}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 55, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AsyncFor", "start_line": 53}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 21, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AsyncFunctionDef", "start_line": 20}}, {"metrics": {"bps": 0.5, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 67, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AsyncWith", "start_line": 63}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 47, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AugAssign", "start_line": 45}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 24, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_ClassDef", "start_line": 23}}, {"metrics": {"bps": 0.3214285714285714, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 74, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_ExceptHandler", "start_line": 69}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 51, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_For", "start_line": 49}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 18, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_FunctionDef", "start_line": 17}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 29, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_Import", "start_line": 26}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 34, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_ImportFrom", "start_line": 31}}, {"metrics": {"bps": 0.5, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 61, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_With", "start_line": 57}}]}, {"file_path": "/workspace/codra/codra/python/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/python/file_scanner.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["FunctionExtractor"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 18, "file_path": "/workspace/codra/codra/python/file_scanner.py", "kind": "method", "qualified_id": "PythonFileScanner.scan_file", "start_line": 12}}]}, {"file_path": "/workspace/codra/codra/report/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/report/builder.py", "units": [{"metrics": {"bps": 0.25, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 61, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._apply_thresholds", "start_line": 48}}, {"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["FileReport", "UnitReport"], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_key", "_resolve_metrics", "_resolve_unit", "_sort_key"], "unresolved_calls": []}, "unit": {"end_line": 117, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._build_file_report", "start_line": 96}}, {"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["FileReport", "logger"], "id_avg": 1.5, "id_max": 2, "self_fields_read": ["_build_file_report", "_collect_file_analysis"], "unresolved_calls": []}, "unit": {"end_line": 94, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._build_file_reports", "start_line": 83}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ReportSummary"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 121, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._build_summary", "start_line": 119}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["bps_analyzer", "csa_analyzer", "indirection_analyzer"], "unresolved_calls": []}, "unit": {"end_line": 81, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._collect_file_analysis", "start_line": 75}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitKey"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 70, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._key", "start_line": 63}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitMetrics"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 161, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._resolve_metrics", "start_line": 141}}, {"metrics": {"bps": 0.5, "csa_main": 1, "external_symbols": ["UnitDefinition"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 139, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._resolve_unit", "start_line": 123}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 73, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._sort_key", "start_line": 72}}, {"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["Report", "logger"], "id_avg": 2.0, "id_max": 3, "self_fields_read": ["_build_file_reports", "_build_summary", "path_collector"], "unresolved_calls": []}, "unit": {"end_line": 43, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder.build", "start_line": 33}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_apply_thresholds"], "unresolved_calls": []}, "unit": {"end_line": 46, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder.check_thresholds", "start_line": 45}}]}, {"file_path": "/workspace/codra/codra/report/file.py", "units": []}, {"file_path": "/workspace/codra/codra/report/model.py", "units": []}, {"file_path": "/workspace/codra/codra/report/serializer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["asdict"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 13, "file_path": "/workspace/codra/codra/report/serializer.py", "kind": "method", "qualified_id": "ReportSerializer.to_json", "start_line": 12}}]}, {"file_path": "/workspace/codra/codra/report/summary.py", "units": []}, {"file_path": "/workspace/codra/codra/threshold/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/threshold/config.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/definition.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/key.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/metrics.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/node.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/node_collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["UnitDefinition", "UnitNode"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "file_path", "function_stack", "units"], "unresolved_calls": []}, "unit": {"end_line": 57, "file_path": "/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector._handle_function", "start_line": 30}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 28, "file_path": "/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector.visit_AsyncFunctionDef", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack"], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector.visit_ClassDef", "start_line": 19}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector.visit_FunctionDef", "start_line": 24}}]}, {"file_path": "/workspace/codra/codra/unit/overview.py", "units": []}, {"file_path": "/workspace/codra/tests/test_analyzers.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["BpsAnalyzer", "BpsResult"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertAlmostEqual", "assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 45, "file_path": "/workspace/codra/tests/test_analyzers.py", "kind": "method", "qualified_id": "BpsAnalyzerHelperTests.test_helpers_collect_bps_scores", "start_line": 25}}, {"metrics": {"bps": 1.0, "csa_main": 3, "external_symbols": ["CsaAnalyzer", "CsaResult", "FunctionSymbolCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 93, "file_path": "/workspace/codra/tests/test_analyzers.py", "kind": "method", "qualified_id": "CsaAnalyzerHelperTests.test_helpers_extract_external_symbols", "start_line": 49}}, {"metrics": {"bps": 1.0, "csa_main": 3, "external_symbols": ["IndirectionAnalyzer", "UnitNodeCollector", "collect_calls_by_unit"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 135, "file_path": "/workspace/codra/tests/test_analyzers.py", "kind": "method", "qualified_id": "IndirectionAnalyzerHelperTests.test_helpers_collect_call_graph_and_depth", "start_line": 97}}, {"metrics": {"bps": 1.0, "csa_main": 4, "external_symbols": ["IndirectionAnalyzer", "UnitNodeCollector", "collect_calls_by_unit", "resolve_alias"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 181, "file_path": "/workspace/codra/tests/test_analyzers.py", "kind": "method", "qualified_id": "IndirectionAnalyzerHelperTests.test_helpers_resolve_alias_chain_and_class_methods", "start_line": 137}}, {"metrics": {"bps": 1.0, "csa_main": 9, "external_symbols": ["BpsAnalyzer", "BpsResult", "CsaAnalyzer", "CsaResult", "IndirectionAnalyzer", "IndirectionResult", "ReportBuilder", "UnitDefinition", "UnitReport"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual", "assertIsInstance"], "unresolved_calls": []}, "unit": {"end_line": 235, "file_path": "/workspace/codra/tests/test_analyzers.py", "kind": "method", "qualified_id": "ReportBuilderHelperTests.test_helpers_build_file_report_and_summary", "start_line": 185}}]}, {"file_path": "/workspace/codra/tests/test_collectors.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["AliasCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 29, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_alias_collector_tracks_top_level_aliases", "start_line": 15}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["CallCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 46, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_call_collector_tracks_module_calls_only", "start_line": 31}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ConditionMetricsCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 63, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_condition_metrics_collector_counts_operations", "start_line": 48}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["FunctionDefinitionCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 95, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_function_definition_collector_skips_methods", "start_line": 81}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["FunctionSymbolCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertIn"], "unresolved_calls": []}, "unit": {"end_line": 117, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_function_symbol_collector_tracks_usage", "start_line": 97}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["IfCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual", "assertIsInstance"], "unresolved_calls": []}, "unit": {"end_line": 79, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_if_collector_tracks_module_if_statements", "start_line": 65}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ModuleSymbolCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertTrue"], "unresolved_calls": []}, "unit": {"end_line": 151, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_module_symbol_collector_gathers_symbols", "start_line": 119}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitNodeCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 172, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_unit_node_collector_builds_qualified_ids", "start_line": 153}}]}], "language": "python", "schema_version": "1.0", "summary": {"total_files": 54, "total_units": 122}} +{"files": [{"file_path": "/workspace/codra/setup.py", "units": []}, {"file_path": "/workspace/codra/codra/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/alias/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/alias/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["aliases"], "unresolved_calls": []}, "unit": {"end_line": 16, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.collect", "start_line": 14}}, {"metrics": {"bps": 0.26190476190476186, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["aliases", "depth"], "unresolved_calls": []}, "unit": {"end_line": 49, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_AnnAssign", "start_line": 42}}, {"metrics": {"bps": 0.26190476190476186, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["aliases", "depth"], "unresolved_calls": []}, "unit": {"end_line": 40, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_Assign", "start_line": 33}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 28, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_AsyncFunctionDef", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 31, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_ClassDef", "start_line": 30}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_FunctionDef", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/alias/collector.py", "kind": "method", "qualified_id": "AliasCollector.visit_Module", "start_line": 18}}]}, {"file_path": "/workspace/codra/codra/bps/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/bps/analyzer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["BpsResult"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 58, "file_path": "/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer._build_results", "start_line": 52}}, {"metrics": {"bps": 1.0, "csa_main": 3, "external_symbols": ["ConditionMetricsCollector", "FunctionSymbolCollector", "IfCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 50, "file_path": "/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer._collect_bps", "start_line": 32}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitNodeCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 30, "file_path": "/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer._collect_units", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer._parse_file", "start_line": 22}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_build_results", "_collect_bps", "_collect_units", "_parse_file"], "unresolved_calls": []}, "unit": {"end_line": 20, "file_path": "/workspace/codra/codra/bps/analyzer.py", "kind": "method", "qualified_id": "BpsAnalyzer.analyze_file", "start_line": 16}}]}, {"file_path": "/workspace/codra/codra/bps/result.py", "units": []}, {"file_path": "/workspace/codra/codra/call/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/call/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["calls"], "unresolved_calls": []}, "unit": {"end_line": 16, "file_path": "/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.collect", "start_line": 13}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_AsyncFunctionDef", "start_line": 21}}, {"metrics": {"bps": 0.13227513227513227, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["calls"], "unresolved_calls": []}, "unit": {"end_line": 33, "file_path": "/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_Call", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_ClassDef", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 19, "file_path": "/workspace/codra/codra/call/collector.py", "kind": "method", "qualified_id": "CallCollector.visit_FunctionDef", "start_line": 18}}]}, {"file_path": "/workspace/codra/codra/cli/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/cli/args.py", "units": []}, {"file_path": "/workspace/codra/codra/cli/dependencies.py", "units": []}, {"file_path": "/workspace/codra/codra/cli/main.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 7, "external_symbols": ["BpsAnalyzer", "CliDependencies", "CsaAnalyzer", "FilePathCollector", "IndirectionAnalyzer", "ReportBuilder", "ReportSerializer"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 63, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "build_dependencies", "start_line": 56}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ThresholdConfig"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 53, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "build_thresholds", "start_line": 48}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 71, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "configure_logging", "start_line": 66}}, {"metrics": {"bps": 1.0, "csa_main": 4, "external_symbols": ["build_dependencies", "configure_logging", "parse_args", "run_cli"], "id_avg": 1.25, "id_max": 2, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 93, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "main", "start_line": 90}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["CliArgs"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 45, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "parse_args", "start_line": 21}}, {"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["build_thresholds", "logger"], "id_avg": 1.0, "id_max": 1, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 87, "file_path": "/workspace/codra/codra/cli/main.py", "kind": "function", "qualified_id": "run_cli", "start_line": 74}}]}, {"file_path": "/workspace/codra/codra/condition/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/condition/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["local_names"], "unresolved_calls": []}, "unit": {"end_line": 49, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector._is_external_name", "start_line": 44}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ConditionMetrics"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["bool_ops", "calls", "compare_ops", "external_refs"], "unresolved_calls": []}, "unit": {"end_line": 26, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.collect", "start_line": 19}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 30, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_BoolOp", "start_line": 28}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 38, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_Call", "start_line": 36}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 34, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_Compare", "start_line": 32}}, {"metrics": {"bps": 0.3333333333333333, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_is_external_name"], "unresolved_calls": []}, "unit": {"end_line": 42, "file_path": "/workspace/codra/codra/condition/collector.py", "kind": "method", "qualified_id": "ConditionMetricsCollector.visit_Name", "start_line": 40}}]}, {"file_path": "/workspace/codra/codra/condition/metrics.py", "units": []}, {"file_path": "/workspace/codra/codra/csa/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/csa/analyzer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["CsaResult", "FunctionSymbolCollector"], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_external_symbols"], "unresolved_calls": []}, "unit": {"end_line": 83, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer._build_results", "start_line": 62}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ModuleSymbolCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 37, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer._collect_module_symbols", "start_line": 36}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitNodeCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 42, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer._collect_unit_nodes", "start_line": 39}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 60, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer._extract_external_symbols", "start_line": 44}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 34, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer._parse_file", "start_line": 31}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.25, "id_max": 2, "self_fields_read": ["_build_results", "_collect_module_symbols", "_collect_unit_nodes", "_parse_file"], "unresolved_calls": []}, "unit": {"end_line": 29, "file_path": "/workspace/codra/codra/csa/analyzer.py", "kind": "method", "qualified_id": "CsaAnalyzer.analyze_file", "start_line": 18}}]}, {"file_path": "/workspace/codra/codra/csa/result.py", "units": []}, {"file_path": "/workspace/codra/codra/directory/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/directory/scanner.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitDefinition"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["file_scanner"], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/directory/scanner.py", "kind": "method", "qualified_id": "DirectoryScanner.scan", "start_line": 14}}]}, {"file_path": "/workspace/codra/codra/file/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/file/path_collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 18, "file_path": "/workspace/codra/codra/file/path_collector.py", "kind": "method", "qualified_id": "FilePathCollector.collect", "start_line": 9}}]}, {"file_path": "/workspace/codra/codra/function/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/function/definition_collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["names"], "unresolved_calls": []}, "unit": {"end_line": 16, "file_path": "/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.collect", "start_line": 14}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "names"], "unresolved_calls": []}, "unit": {"end_line": 34, "file_path": "/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.visit_AsyncFunctionDef", "start_line": 30}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack"], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.visit_ClassDef", "start_line": 18}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "names"], "unresolved_calls": []}, "unit": {"end_line": 28, "file_path": "/workspace/codra/codra/function/definition_collector.py", "kind": "method", "qualified_id": "FunctionDefinitionCollector.visit_FunctionDef", "start_line": 24}}]}, {"file_path": "/workspace/codra/codra/function/extractor.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitDefinition"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "file_path", "function_stack", "generic_visit", "units"], "unresolved_calls": []}, "unit": {"end_line": 55, "file_path": "/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor._handle_function", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor.visit_AsyncFunctionDef", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "generic_visit"], "unresolved_calls": []}, "unit": {"end_line": 19, "file_path": "/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor.visit_ClassDef", "start_line": 16}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/function/extractor.py", "kind": "method", "qualified_id": "FunctionExtractor.visit_FunctionDef", "start_line": 21}}]}, {"file_path": "/workspace/codra/codra/function/symbol_collector.py", "units": [{"metrics": {"bps": 0.5, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 75, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._add_arguments", "start_line": 64}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 86, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._is_declared_nonlocal_or_global", "start_line": 83}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 97, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._is_exception_name", "start_line": 96}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 78, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._is_load_context", "start_line": 77}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_is_load_context"], "unresolved_calls": []}, "unit": {"end_line": 94, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._is_self_attribute_read", "start_line": 88}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 81, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector._is_store_or_del_context", "start_line": 80}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_add_arguments", "usage"], "unresolved_calls": []}, "unit": {"end_line": 19, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.collect", "start_line": 15}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_AsyncFunctionDef", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 2.0, "id_max": 2, "self_fields_read": ["_is_self_attribute_read", "usage"], "unresolved_calls": []}, "unit": {"end_line": 53, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Attribute", "start_line": 49}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 28, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_ClassDef", "start_line": 27}}, {"metrics": {"bps": 0.41666666666666663, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_is_exception_name", "usage"], "unresolved_calls": []}, "unit": {"end_line": 62, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_ExceptHandler", "start_line": 55}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_FunctionDef", "start_line": 21}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 31, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Global", "start_line": 30}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_is_declared_nonlocal_or_global", "_is_load_context", "_is_store_or_del_context", "usage"], "unresolved_calls": []}, "unit": {"end_line": 47, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Name", "start_line": 36}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["usage"], "unresolved_calls": []}, "unit": {"end_line": 34, "file_path": "/workspace/codra/codra/function/symbol_collector.py", "kind": "method", "qualified_id": "FunctionSymbolCollector.visit_Nonlocal", "start_line": 33}}]}, {"file_path": "/workspace/codra/codra/function/symbol_usage.py", "units": []}, {"file_path": "/workspace/codra/codra/if_/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/if_/collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["nodes"], "unresolved_calls": []}, "unit": {"end_line": 16, "file_path": "/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.collect", "start_line": 13}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_AsyncFunctionDef", "start_line": 21}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_ClassDef", "start_line": 24}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 19, "file_path": "/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_FunctionDef", "start_line": 18}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["nodes"], "unresolved_calls": []}, "unit": {"end_line": 29, "file_path": "/workspace/codra/codra/if_/collector.py", "kind": "method", "qualified_id": "IfCollector.visit_If", "start_line": 27}}]}, {"file_path": "/workspace/codra/codra/indirection/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/indirection/analyzer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["IndirectionResult"], "id_avg": 2.0, "id_max": 3, "self_fields_read": ["_calculate_indirection_depths", "_class_context", "_resolve_unit_calls"], "unresolved_calls": []}, "unit": {"end_line": 180, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._build_results", "start_line": 138}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_depth"], "unresolved_calls": []}, "unit": {"end_line": 296, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._calculate_indirection_depths", "start_line": 284}}, {"metrics": {"bps": 0.5, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 188, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._class_context", "start_line": 182}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["AliasCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 75, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_aliases", "start_line": 74}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 125, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_builtin_names", "start_line": 124}}, {"metrics": {"bps": 0.4583333333333333, "csa_main": 0, "external_symbols": [], "id_avg": 2.0, "id_max": 2, "self_fields_read": ["_resolve_alias"], "unresolved_calls": []}, "unit": {"end_line": 122, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_call_graph", "start_line": 89}}, {"metrics": {"bps": 0.14285714285714285, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 87, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_class_methods", "start_line": 77}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["FunctionDefinitionCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 72, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_function_definitions", "start_line": 71}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 128, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_stdlib_modules", "start_line": 127}}, {"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["UnitNodeCollector", "collect_calls_by_unit"], "id_avg": 2.0, "id_max": 2, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 136, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._collect_unit_calls", "start_line": 130}}, {"metrics": {"bps": 0.5625, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_depth"], "unresolved_calls": []}, "unit": {"end_line": 282, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._depth", "start_line": 261}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 250, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._is_ignorable_call", "start_line": 239}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 69, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._parse_file", "start_line": 66}}, {"metrics": {"bps": 0.5, "csa_main": 1, "external_symbols": ["resolve_alias"], "id_avg": 1.0, "id_max": 1, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 259, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._resolve_alias", "start_line": 252}}, {"metrics": {"bps": 0.611111111111111, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 237, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._resolve_self_call", "start_line": 224}}, {"metrics": {"bps": 0.5416666666666666, "csa_main": 0, "external_symbols": [], "id_avg": 1.3333333333333333, "id_max": 2, "self_fields_read": ["_is_ignorable_call", "_resolve_alias", "_resolve_self_call"], "unresolved_calls": []}, "unit": {"end_line": 222, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer._resolve_unit_calls", "start_line": 190}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ModuleSymbolCollector"], "id_avg": 1.7777777777777777, "id_max": 4, "self_fields_read": ["_build_results", "_collect_aliases", "_collect_builtin_names", "_collect_call_graph", "_collect_class_methods", "_collect_function_definitions", "_collect_stdlib_modules", "_collect_unit_calls", "_parse_file"], "unresolved_calls": []}, "unit": {"end_line": 64, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "method", "qualified_id": "IndirectionAnalyzer.analyze_file", "start_line": 40}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["CallCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 28, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "function", "qualified_id": "collect_calls", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["collect_calls"], "id_avg": 1.0, "id_max": 1, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 35, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "function", "qualified_id": "collect_calls_by_unit", "start_line": 31}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 24, "file_path": "/workspace/codra/codra/indirection/analyzer.py", "kind": "function", "qualified_id": "resolve_alias", "start_line": 18}}]}, {"file_path": "/workspace/codra/codra/indirection/result.py", "units": []}, {"file_path": "/workspace/codra/codra/module/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/module/symbol_collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 84, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector._extract_target_names", "start_line": 78}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 15, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.collect", "start_line": 13}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 43, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AnnAssign", "start_line": 41}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 39, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_Assign", "start_line": 36}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 55, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AsyncFor", "start_line": 53}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 21, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AsyncFunctionDef", "start_line": 20}}, {"metrics": {"bps": 0.5, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 67, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AsyncWith", "start_line": 63}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 47, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_AugAssign", "start_line": 45}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 24, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_ClassDef", "start_line": 23}}, {"metrics": {"bps": 0.75, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 76, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_ExceptHandler", "start_line": 69}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 51, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_For", "start_line": 49}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 18, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_FunctionDef", "start_line": 17}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 29, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_Import", "start_line": 26}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["symbols"], "unresolved_calls": []}, "unit": {"end_line": 34, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_ImportFrom", "start_line": 31}}, {"metrics": {"bps": 0.5, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_extract_target_names", "symbols"], "unresolved_calls": []}, "unit": {"end_line": 61, "file_path": "/workspace/codra/codra/module/symbol_collector.py", "kind": "method", "qualified_id": "ModuleSymbolCollector.visit_With", "start_line": 57}}]}, {"file_path": "/workspace/codra/codra/python/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/python/file_scanner.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["FunctionExtractor"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 18, "file_path": "/workspace/codra/codra/python/file_scanner.py", "kind": "method", "qualified_id": "PythonFileScanner.scan_file", "start_line": 12}}]}, {"file_path": "/workspace/codra/codra/report/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/report/builder.py", "units": [{"metrics": {"bps": 0.25, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 61, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._apply_thresholds", "start_line": 48}}, {"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["FileReport", "UnitReport"], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_key", "_resolve_metrics", "_resolve_unit", "_sort_key"], "unresolved_calls": []}, "unit": {"end_line": 117, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._build_file_report", "start_line": 96}}, {"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["FileReport", "logger"], "id_avg": 1.5, "id_max": 2, "self_fields_read": ["_build_file_report", "_collect_file_analysis"], "unresolved_calls": []}, "unit": {"end_line": 94, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._build_file_reports", "start_line": 83}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ReportSummary"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 121, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._build_summary", "start_line": 119}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["bps_analyzer", "csa_analyzer", "indirection_analyzer"], "unresolved_calls": []}, "unit": {"end_line": 81, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._collect_file_analysis", "start_line": 75}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitKey"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 70, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._key", "start_line": 63}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitMetrics"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 161, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._resolve_metrics", "start_line": 141}}, {"metrics": {"bps": 0.5, "csa_main": 1, "external_symbols": ["UnitDefinition"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 139, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._resolve_unit", "start_line": 123}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 73, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder._sort_key", "start_line": 72}}, {"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["Report", "logger"], "id_avg": 2.0, "id_max": 3, "self_fields_read": ["_build_file_reports", "_build_summary", "path_collector"], "unresolved_calls": []}, "unit": {"end_line": 43, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder.build", "start_line": 33}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_apply_thresholds"], "unresolved_calls": []}, "unit": {"end_line": 46, "file_path": "/workspace/codra/codra/report/builder.py", "kind": "method", "qualified_id": "ReportBuilder.check_thresholds", "start_line": 45}}]}, {"file_path": "/workspace/codra/codra/report/file.py", "units": []}, {"file_path": "/workspace/codra/codra/report/model.py", "units": []}, {"file_path": "/workspace/codra/codra/report/serializer.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["asdict"], "id_avg": 0.0, "id_max": 0, "self_fields_read": [], "unresolved_calls": []}, "unit": {"end_line": 13, "file_path": "/workspace/codra/codra/report/serializer.py", "kind": "method", "qualified_id": "ReportSerializer.to_json", "start_line": 12}}]}, {"file_path": "/workspace/codra/codra/report/summary.py", "units": []}, {"file_path": "/workspace/codra/codra/threshold/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/threshold/config.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/__init__.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/definition.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/key.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/metrics.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/node.py", "units": []}, {"file_path": "/workspace/codra/codra/unit/node_collector.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["UnitDefinition", "UnitNode"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack", "file_path", "function_stack", "units"], "unresolved_calls": []}, "unit": {"end_line": 57, "file_path": "/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector._handle_function", "start_line": 30}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 28, "file_path": "/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector.visit_AsyncFunctionDef", "start_line": 27}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["class_stack"], "unresolved_calls": []}, "unit": {"end_line": 22, "file_path": "/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector.visit_ClassDef", "start_line": 19}}, {"metrics": {"bps": 1.0, "csa_main": 0, "external_symbols": [], "id_avg": 1.0, "id_max": 1, "self_fields_read": ["_handle_function"], "unresolved_calls": []}, "unit": {"end_line": 25, "file_path": "/workspace/codra/codra/unit/node_collector.py", "kind": "method", "qualified_id": "UnitNodeCollector.visit_FunctionDef", "start_line": 24}}]}, {"file_path": "/workspace/codra/codra/unit/overview.py", "units": []}, {"file_path": "/workspace/codra/tests/test_analyzers.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 2, "external_symbols": ["BpsAnalyzer", "BpsResult"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertAlmostEqual", "assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 45, "file_path": "/workspace/codra/tests/test_analyzers.py", "kind": "method", "qualified_id": "BpsAnalyzerHelperTests.test_helpers_collect_bps_scores", "start_line": 25}}, {"metrics": {"bps": 1.0, "csa_main": 3, "external_symbols": ["CsaAnalyzer", "CsaResult", "FunctionSymbolCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 93, "file_path": "/workspace/codra/tests/test_analyzers.py", "kind": "method", "qualified_id": "CsaAnalyzerHelperTests.test_helpers_extract_external_symbols", "start_line": 49}}, {"metrics": {"bps": 1.0, "csa_main": 3, "external_symbols": ["IndirectionAnalyzer", "UnitNodeCollector", "collect_calls_by_unit"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 135, "file_path": "/workspace/codra/tests/test_analyzers.py", "kind": "method", "qualified_id": "IndirectionAnalyzerHelperTests.test_helpers_collect_call_graph_and_depth", "start_line": 97}}, {"metrics": {"bps": 1.0, "csa_main": 4, "external_symbols": ["IndirectionAnalyzer", "UnitNodeCollector", "collect_calls_by_unit", "resolve_alias"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 181, "file_path": "/workspace/codra/tests/test_analyzers.py", "kind": "method", "qualified_id": "IndirectionAnalyzerHelperTests.test_helpers_resolve_alias_chain_and_class_methods", "start_line": 137}}, {"metrics": {"bps": 1.0, "csa_main": 9, "external_symbols": ["BpsAnalyzer", "BpsResult", "CsaAnalyzer", "CsaResult", "IndirectionAnalyzer", "IndirectionResult", "ReportBuilder", "UnitDefinition", "UnitReport"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual", "assertIsInstance"], "unresolved_calls": []}, "unit": {"end_line": 235, "file_path": "/workspace/codra/tests/test_analyzers.py", "kind": "method", "qualified_id": "ReportBuilderHelperTests.test_helpers_build_file_report_and_summary", "start_line": 185}}]}, {"file_path": "/workspace/codra/tests/test_collectors.py", "units": [{"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["AliasCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 29, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_alias_collector_tracks_top_level_aliases", "start_line": 15}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["CallCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 46, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_call_collector_tracks_module_calls_only", "start_line": 31}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ConditionMetricsCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 63, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_condition_metrics_collector_counts_operations", "start_line": 48}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["FunctionDefinitionCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 95, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_function_definition_collector_skips_methods", "start_line": 81}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["FunctionSymbolCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertIn"], "unresolved_calls": []}, "unit": {"end_line": 117, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_function_symbol_collector_tracks_usage", "start_line": 97}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["IfCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual", "assertIsInstance"], "unresolved_calls": []}, "unit": {"end_line": 79, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_if_collector_tracks_module_if_statements", "start_line": 65}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["ModuleSymbolCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertTrue"], "unresolved_calls": []}, "unit": {"end_line": 151, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_module_symbol_collector_gathers_symbols", "start_line": 119}}, {"metrics": {"bps": 1.0, "csa_main": 1, "external_symbols": ["UnitNodeCollector"], "id_avg": 0.0, "id_max": 0, "self_fields_read": ["assertEqual"], "unresolved_calls": []}, "unit": {"end_line": 172, "file_path": "/workspace/codra/tests/test_collectors.py", "kind": "method", "qualified_id": "CollectorVisitTests.test_unit_node_collector_builds_qualified_ids", "start_line": 153}}]}], "language": "python", "schema_version": "1.0", "summary": {"total_files": 54, "total_units": 130}} From 90998d3e3bea760f0280880c7a029f540f8bd540 Mon Sep 17 00:00:00 2001 From: Andrea Mugellini Date: Sun, 11 Jan 2026 17:02:42 +0100 Subject: [PATCH 17/17] Adds prompts for code writing and refactoring Introduces prompts to guide code generation and refactoring, focusing on optimizing complexity, indirection depth, and branching penalty metrics. These prompts aim to produce cleaner, more maintainable, and efficient code by emphasizing single-purpose functions, localized dependencies, and simplified conditional logic. --- PROMT.md | 67 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/PROMT.md b/PROMT.md index feda880..08ac8e5 100644 --- a/PROMT.md +++ b/PROMT.md @@ -1,31 +1,50 @@ -You are a refactoring agent for a Python static-analysis tool. +1) Prompt for Writing New Code (optimize CSA, ID, BPS) +Prompt: -I will provide: -1) A report JSON (schema_version 1.0) produced by the tool. -2) The tool’s repository is available locally. +You are writing Python code that will be analyzed using three metrics: -The JSON must be treated as an observation of the tool’s current behavior, not something to edit or fix directly. +CSA (complexity / symbol usage), -Goal -Improve the tool so that future reports have lower noise and higher semantic accuracy, while keeping the exact same JSON schema (same keys, same types). +ID (indirection depth / call graph complexity), -What to do -- Use the JSON only to understand how the tool currently behaves. -- Identify weaknesses in the analysis logic (signal inflation, missing resolution, overly generic symbol tracking). -- Modify the tool code to address those weaknesses. -- Do not add new features unrelated to the reported metrics. -- Do not change the report schema or naming. +BPS (branching/condition penalty; complex conditions reduce the score). -Required analysis improvements -- `unresolved_calls` must not include Python builtins (derive the builtin set dynamically from the `builtins` module). -- `csa_main` must not be incremented by builtins; optionally also ignore standard-library symbols (e.g. ast, os, sys, json, argparse, logging). -- Improve call-graph resolution for indirection metrics at least for: - - calls to functions defined in the same module - - calls of the form `self.foo()` where `foo` is defined in the same class +Write code that optimizes all three metrics: -Constraints -- The analyzed project is unknown and must not be special-cased. -- The JSON is assumed to be structurally correct and semantically valid for the current implementation. -- Changes must affect how the tool computes metrics, not how results are post-processed. +Keep functions small, single-purpose, and avoid deep call chains (low ID). -Wait for the JSON, then start. +Minimize external symbol usage and keep dependencies localized (low CSA). + +Keep if conditions simple; avoid chained boolean logic and function calls inside conditions (high BPS). + +Precompute values before conditionals, and use helper functions to isolate logic. + +Prefer clear, flat control flow (early returns) over nested branches. + +Produce clean, readable, and maintainable code that adheres to these constraints. + + + + +2) Prompt for Refactoring Existing Code (improve CSA, ID, BPS) +Prompt: + +You are refactoring existing Python code to improve CSA, ID, and BPS metrics: + +CSA improves by reducing external symbol usage and simplifying symbol dependencies. + +ID improves by shortening call chains and reducing indirection depth. + +BPS improves by simplifying conditional expressions and removing calls inside conditions. + +Refactor the code while preserving behavior: + +Extract complex condition logic into small helpers and precompute values outside if statements. + +Split large functions into smaller ones only if it does not create deeper call chains. + +Reduce dependency on external symbols by localizing logic and data. + +Flatten control flow and avoid unnecessary layers of abstraction. + +Keep the output behavior identical, add tests only when needed, and explain how the changes improve CSA, ID, and BPS. \ No newline at end of file