Skip to content

Commit

Permalink
docs: document CodeRegion and its plugin methods
Browse files Browse the repository at this point in the history
  • Loading branch information
nedbat committed Apr 23, 2024
1 parent 2ff9933 commit 40a052e
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 44 deletions.
1 change: 1 addition & 0 deletions coverage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from coverage.data import CoverageData as CoverageData
from coverage.exceptions import CoverageException as CoverageException
from coverage.plugin import (
CodeRegion as CodeRegion,
CoveragePlugin as CoveragePlugin,
FileReporter as FileReporter,
FileTracer as FileTracer,
Expand Down
55 changes: 50 additions & 5 deletions coverage/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,16 +114,15 @@ def coverage_init(reg, options):

from __future__ import annotations

import dataclasses
import functools

from types import FrameType
from typing import Any, Iterable

from coverage import files
from coverage.misc import _needs_to_implement
from coverage.types import (
CodeRegion, TArc, TConfigurable, TLineNo, TSourceTokenLines,
)
from coverage.types import TArc, TConfigurable, TLineNo, TSourceTokenLines


class CoveragePlugin:
Expand Down Expand Up @@ -346,6 +345,35 @@ def line_number_range(self, frame: FrameType) -> tuple[TLineNo, TLineNo]:
return lineno, lineno


@dataclasses.dataclass
class CodeRegion:
"""Data for a region of code found by :meth:`FileReporter.code_regions`."""

#: The kind of region, like `"function"` or `"class"`. Must be one of the
#: singular values returned by :meth:`FileReporter.code_region_kinds`.
kind: str

#: The name of the region. For example, a function or class name.
name: str

#: The line in the source file to link to when navigating to the region.
#: Can be a line not mentioned in `lines`.
start: int

#: The lines in the region. Should be lines that could be executed in the
#: region. For example, a class region includes all of the lines in the
#: methods of the class, but not the lines defining class attributes, since
#: they are executed on import, not as part of exercising the class. The
#: set can include non-executable lines like blanks and comments.
lines: set[int]

def __lt__(self, other: CodeRegion) -> bool:
"""To support sorting to make test-writing easier."""
if self.name == other.name:
return min(self.lines) < min(other.lines)
return self.name < other.name


@functools.total_ordering
class FileReporter(CoveragePluginBase):
"""Support needed for files during the analysis and reporting phases.
Expand Down Expand Up @@ -546,11 +574,28 @@ def source_token_lines(self) -> TSourceTokenLines:
yield [("txt", line)]

def code_regions(self) -> Iterable[CodeRegion]:
"""TODO XXX"""
"""Identify regions in the source file for finer reporting than by file.
Returns an iterable of :class:`CodeRegion` objects. The kinds reported
should be in the possibilities returned by :meth:`code_region_kinds`.
"""
return []

def code_region_kinds(self) -> Iterable[tuple[str, str]]:
"""TODO XXX"""
"""Return the kinds of code regions this plugin can find.
The returned pairs are the singular and plural forms of the kinds::
[
("function", "functions"),
("class", "classes"),
]
This will usually be hard-coded, but could also differ by the specific
source file involved.
"""
return []

def __eq__(self, other: Any) -> bool:
Expand Down
26 changes: 9 additions & 17 deletions coverage/regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from typing import cast

from coverage.types import CodeRegion
from coverage.plugin import CodeRegion


@dataclasses.dataclass
Expand Down Expand Up @@ -91,22 +91,14 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None:
def code_regions(source: str) -> list[CodeRegion]:
"""Find function and class regions in source code.
TODO: Fix this description.
Takes the program `source`, and returns a dict: the keys are "function" and
"class". Each has a value which is a dict: the keys are fully qualified
names, the values are sets of line numbers included in that region::
{
"function": {
"func1": {10, 11, 12},
"func2": {20, 21, 22},
"MyClass.method: {34, 35, 36},
},
"class": {
"MyClass": {34, 35, 36},
},
}
Analyzes the code in `source`, and returns a list of :class:`CodeRegion`
objects describing functions and classes as regions of the code::
[
CodeRegion(kind="function", name="func1", start=8, lines={10, 11, 12}),
CodeRegion(kind="function", name="MyClass.method", start=30, lines={34, 35, 36}),
CodeRegion(kind="class", name="MyClass", start=25, lines={34, 35, 36}),
]
The line numbers will include comments and blank lines. Later processing
will need to ignore those lines as needed.
Expand Down
22 changes: 0 additions & 22 deletions coverage/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

from __future__ import annotations

import dataclasses
import os
import pathlib

Expand Down Expand Up @@ -163,27 +162,6 @@ def get_plugin_options(self, plugin: str) -> TConfigSectionOut:
TSourceTokenLines = Iterable[List[Tuple[str, str]]]


@dataclasses.dataclass
class CodeRegion:
"""Data for each region found by FileReporter.code_regions.
`kind`: the kind of region.
`name`: the description of the region.
`start`: the first line of the named region.
`lines`: a super-set of the executable source lines in the region.
"""
kind: str
name: str
start: int
lines: set[int]

def __lt__(self, other: CodeRegion) -> bool:
"""To support sorting to make test-writing easier."""
if self.name == other.name:
return min(self.lines) < min(other.lines)
return self.name < other.name


## Plugins

class TPlugin(Protocol):
Expand Down
7 changes: 7 additions & 0 deletions doc/api_plugin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,10 @@ The FileReporter class
.. autoclass:: FileReporter
:members:
:member-order: bysource

The CodeRegion class
--------------------

.. autoclass:: CodeRegion
:members:
:member-order: bysource

0 comments on commit 40a052e

Please sign in to comment.