diff --git a/src/coretrace_python/engine.py b/src/coretrace_python/engine.py index e29a52c..63830e7 100644 --- a/src/coretrace_python/engine.py +++ b/src/coretrace_python/engine.py @@ -176,13 +176,14 @@ ) -def rule_descriptions(registry: PluginRegistry) -> Mapping[str, RuleDescription]: +def rule_descriptions(plugins: Iterable[Plugin]) -> Mapping[str, RuleDescription]: """What every rule of a check detects, for its report: the rules the engine reports - itself, and those each loaded plugin describes, the first declaration of an id kept.""" + itself, and those each of its plugins describes, loaded or given as an instance, the + first declaration of an id kept.""" described: dict[str, RuleDescription] = dict(ENGINE_RULES) - for loaded in registry: - for rule_id, description in loaded.plugin.rules.items(): + for plugin in plugins: + for rule_id, description in plugin.rules.items(): described.setdefault(rule_id, description) return MappingProxyType(described) @@ -395,7 +396,7 @@ def analyze_file(source: SourceFile, plugin_roots: Sequence[Path], root: Path | functions = len(analyzable_functions(manager.module)) coverage = Coverage((FileCoverage(str(source.source_id), "analysed", functions, len(supported)),)) kept, suppressed = partition(findings, lambda sid: source.text if sid == source.source_id else None) - return FileAnalysis(kept, coverage, suppressed, rule_descriptions(registry)) + return FileAnalysis(kept, coverage, suppressed, rule_descriptions(loaded.plugin for loaded in registry)) def resolve_dependencies(root: Path, sources: SourceManager) -> DependencyGraph: @@ -692,7 +693,7 @@ def analyze_project( suppressed, accepted, components, - rule_descriptions(registry), + rule_descriptions(all_plugins), ) diff --git a/tests/test_rule_descriptions_of_given_plugins.py b/tests/test_rule_descriptions_of_given_plugins.py new file mode 100644 index 0000000..f39d5f9 --- /dev/null +++ b/tests/test_rule_descriptions_of_given_plugins.py @@ -0,0 +1,40 @@ +"""Acceptance tests for the review of #274: a plugin given to ``analyze_project`` as an +instance, rather than loaded from a plugin root, describes its rules in the report as a +loaded plugin does, the first declaration of an id kept. +""" + +from __future__ import annotations + +from collections.abc import Mapping, Sequence +from pathlib import Path +from typing import ClassVar + +from coretrace_python import engine +from coretrace_python.findings import Finding, RuleDescription +from coretrace_python.plugins import Plugin, PluginContext + +QUIET = RuleDescription("Quiet rule", "A rule of a plugin given as an instance.") + + +class Quiet(Plugin): + name: ClassVar[str] = "quiet" + rules: ClassVar[Mapping[str, RuleDescription]] = { + "quiet-rule": QUIET, + "command-injection": RuleDescription("Another title", "Another description."), + } + + def analyze(self, ctx: PluginContext) -> Sequence[Finding]: + return () + + +def analysed(tmp_path: Path) -> engine.ProjectAnalysis: + (tmp_path / "app.py").write_text("x = 1\n", encoding="utf-8") + return engine.analyze_project(tmp_path, [engine.BUNDLED_PLUGINS], plugins=[Quiet()]) + + +def test_a_plugin_given_as_an_instance_describes_its_rules(tmp_path: Path) -> None: + assert analysed(tmp_path).rules["quiet-rule"] == QUIET + + +def test_a_loaded_plugin_keeps_the_description_of_a_rule_an_instance_describes_too(tmp_path: Path) -> None: + assert analysed(tmp_path).rules["command-injection"].title == "Command injection"