summaryrefslogtreecommitdiff
path: root/coverage
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2022-12-29 10:19:14 -0500
committerNed Batchelder <ned@nedbatchelder.com>2022-12-29 11:28:00 -0500
commitd4c2b18bdd0102ff873514e53ec560c3083c3413 (patch)
tree4e91bacd91f5a0fbcf4976376bbc87e88d46b551 /coverage
parent0accb68cd9ac353bd5464750987e02012bdb8e0c (diff)
downloadpython-coveragepy-git-d4c2b18bdd0102ff873514e53ec560c3083c3413.tar.gz
mypy: mypy checks plugin.py
Diffstat (limited to 'coverage')
-rw-r--r--coverage/config.py10
-rw-r--r--coverage/parser.py11
-rw-r--r--coverage/plugin.py84
-rw-r--r--coverage/types.py47
4 files changed, 105 insertions, 47 deletions
diff --git a/coverage/config.py b/coverage/config.py
index 3e4a8dd0..1846aee4 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -17,18 +17,12 @@ from typing import (
from coverage.exceptions import ConfigError
from coverage.misc import isolate_module, human_sorted_items, substitute_variables
-
from coverage.tomlconfig import TomlConfigParser, TomlDecodeError
+from coverage.types import TConfigurable, TConfigSection, TConfigValue
os = isolate_module(os)
-# One value read from a config file.
-TConfigValue = Union[str, List[str]]
-# An entire config section, mapping option names to values.
-TConfigSection = Dict[str, TConfigValue]
-
-
class HandyConfigParser(configparser.ConfigParser):
"""Our specialization of ConfigParser."""
@@ -169,7 +163,7 @@ DEFAULT_PARTIAL_ALWAYS = [
]
-class CoverageConfig:
+class CoverageConfig(TConfigurable):
"""Coverage.py configuration.
The attributes of this class are the various settings that control the
diff --git a/coverage/parser.py b/coverage/parser.py
index 1e2011e2..09b2f094 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -15,8 +15,7 @@ import tokenize
from types import CodeType
from typing import (
- cast, TYPE_CHECKING,
- Any, Callable, Dict, Iterable, List, Optional, Sequence, Set, Tuple,
+ cast, Any, Callable, Dict, Iterable, List, Optional, Sequence, Set, Tuple,
)
from coverage import env
@@ -25,13 +24,8 @@ from coverage.debug import short_stack
from coverage.exceptions import NoSource, NotPython, _StopEverything
from coverage.misc import join_regex, nice_pair
from coverage.phystokens import generate_tokens
+from coverage.types import Protocol, TArc
-if TYPE_CHECKING:
- # Protocol is new in 3.8. PYVERSIONS
- from typing import Protocol
-else:
- class Protocol: # pylint: disable=missing-class-docstring
- pass
class PythonParser:
"""Parse code to find executable lines, excluded lines, etc.
@@ -489,7 +483,6 @@ class TAddArcFn(Protocol):
) -> None:
...
-TArc = Tuple[int, int]
TArcFragments = Dict[TArc, List[Tuple[Optional[str], Optional[str]]]]
class Block:
diff --git a/coverage/plugin.py b/coverage/plugin.py
index bf30b1b7..b6df72e4 100644
--- a/coverage/plugin.py
+++ b/coverage/plugin.py
@@ -112,16 +112,22 @@ register your dynamic context switcher.
"""
+from __future__ import annotations
+
import functools
+from types import FrameType
+from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union
+
from coverage import files
-from coverage.misc import contract, _needs_to_implement
+from coverage.misc import _needs_to_implement
+from coverage.types import TArc, TConfigurable
class CoveragePlugin:
"""Base class for coverage.py plug-ins."""
- def file_tracer(self, filename): # pylint: disable=unused-argument
+ def file_tracer(self, filename: str) -> Optional[FileTracer]: # pylint: disable=unused-argument
"""Get a :class:`FileTracer` object for a file.
Plug-in type: file tracer.
@@ -161,7 +167,10 @@ class CoveragePlugin:
"""
return None
- def file_reporter(self, filename): # pylint: disable=unused-argument
+ def file_reporter( # type: ignore[return]
+ self,
+ filename: str, # pylint: disable=unused-argument
+ ) -> Union[FileReporter, str]: # str should be Literal["python"]
"""Get the :class:`FileReporter` class to use for a file.
Plug-in type: file tracer.
@@ -175,7 +184,10 @@ class CoveragePlugin:
"""
_needs_to_implement(self, "file_reporter")
- def dynamic_context(self, frame): # pylint: disable=unused-argument
+ def dynamic_context(
+ self,
+ frame: FrameType, # pylint: disable=unused-argument
+ ) -> Optional[str]:
"""Get the dynamically computed context label for `frame`.
Plug-in type: dynamic context.
@@ -191,7 +203,10 @@ class CoveragePlugin:
"""
return None
- def find_executable_files(self, src_dir): # pylint: disable=unused-argument
+ def find_executable_files(
+ self,
+ src_dir: str, # pylint: disable=unused-argument
+ ) -> Iterable[str]:
"""Yield all of the executable files in `src_dir`, recursively.
Plug-in type: file tracer.
@@ -206,7 +221,7 @@ class CoveragePlugin:
"""
return []
- def configure(self, config):
+ def configure(self, config: TConfigurable) -> None:
"""Modify the configuration of coverage.py.
Plug-in type: configurer.
@@ -220,7 +235,7 @@ class CoveragePlugin:
"""
pass
- def sys_info(self):
+ def sys_info(self) -> List[Tuple[str, str]]:
"""Get a list of information useful for debugging.
Plug-in type: any.
@@ -251,7 +266,7 @@ class FileTracer:
"""
- def source_filename(self):
+ def source_filename(self) -> str: # type: ignore[return]
"""The source file name for this file.
This may be any file name you like. A key responsibility of a plug-in
@@ -266,7 +281,7 @@ class FileTracer:
"""
_needs_to_implement(self, "source_filename")
- def has_dynamic_source_filename(self):
+ def has_dynamic_source_filename(self) -> bool:
"""Does this FileTracer have dynamic source file names?
FileTracers can provide dynamically determined file names by
@@ -284,7 +299,11 @@ class FileTracer:
"""
return False
- def dynamic_source_filename(self, filename, frame): # pylint: disable=unused-argument
+ def dynamic_source_filename(
+ self,
+ filename: str, # pylint: disable=unused-argument
+ frame: FrameType, # pylint: disable=unused-argument
+ ) -> Optional[str]:
"""Get a dynamically computed source file name.
Some plug-ins need to compute the source file name dynamically for each
@@ -299,7 +318,7 @@ class FileTracer:
"""
return None
- def line_number_range(self, frame):
+ def line_number_range(self, frame: FrameType) -> Tuple[int, int]:
"""Get the range of source line numbers for a given a call frame.
The call frame is examined, and the source line number in the original
@@ -331,7 +350,7 @@ class FileReporter:
"""
- def __init__(self, filename):
+ def __init__(self, filename: str) -> None:
"""Simple initialization of a `FileReporter`.
The `filename` argument is the path to the file being reported. This
@@ -341,10 +360,10 @@ class FileReporter:
"""
self.filename = filename
- def __repr__(self):
+ def __repr__(self) -> str:
return "<{0.__class__.__name__} filename={0.filename!r}>".format(self)
- def relative_filename(self):
+ def relative_filename(self) -> str:
"""Get the relative file name for this file.
This file path will be displayed in reports. The default
@@ -355,8 +374,7 @@ class FileReporter:
"""
return files.relative_filename(self.filename)
- @contract(returns='unicode')
- def source(self):
+ def source(self) -> str:
"""Get the source for the file.
Returns a Unicode string.
@@ -366,10 +384,10 @@ class FileReporter:
as a text file, or if you need other encoding support.
"""
- with open(self.filename, "rb") as f:
- return f.read().decode("utf-8")
+ with open(self.filename, encoding="utf-8") as f:
+ return f.read()
- def lines(self):
+ def lines(self) -> Set[int]: # type: ignore[return]
"""Get the executable lines in this file.
Your plug-in must determine which lines in the file were possibly
@@ -380,7 +398,7 @@ class FileReporter:
"""
_needs_to_implement(self, "lines")
- def excluded_lines(self):
+ def excluded_lines(self) -> Set[int]:
"""Get the excluded executable lines in this file.
Your plug-in can use any method it likes to allow the user to exclude
@@ -393,7 +411,7 @@ class FileReporter:
"""
return set()
- def translate_lines(self, lines):
+ def translate_lines(self, lines: Iterable[int]) -> Set[int]:
"""Translate recorded lines into reported lines.
Some file formats will want to report lines slightly differently than
@@ -413,7 +431,7 @@ class FileReporter:
"""
return set(lines)
- def arcs(self):
+ def arcs(self) -> Set[TArc]:
"""Get the executable arcs in this file.
To support branch coverage, your plug-in needs to be able to indicate
@@ -427,7 +445,7 @@ class FileReporter:
"""
return set()
- def no_branch_lines(self):
+ def no_branch_lines(self) -> Set[int]:
"""Get the lines excused from branch coverage in this file.
Your plug-in can use any method it likes to allow the user to exclude
@@ -440,7 +458,7 @@ class FileReporter:
"""
return set()
- def translate_arcs(self, arcs):
+ def translate_arcs(self, arcs: Set[TArc]) -> Set[TArc]:
"""Translate recorded arcs into reported arcs.
Similar to :meth:`translate_lines`, but for arcs. `arcs` is a set of
@@ -453,7 +471,7 @@ class FileReporter:
"""
return arcs
- def exit_counts(self):
+ def exit_counts(self) -> Dict[int, int]:
"""Get a count of exits from that each line.
To determine which lines are branches, coverage.py looks for lines that
@@ -466,7 +484,12 @@ class FileReporter:
"""
return {}
- def missing_arc_description(self, start, end, executed_arcs=None): # pylint: disable=unused-argument
+ def missing_arc_description(
+ self,
+ start: int,
+ end: int,
+ executed_arcs: Optional[Set[TArc]]=None, # pylint: disable=unused-argument
+ ) -> str:
"""Provide an English sentence describing a missing arc.
The `start` and `end` arguments are the line numbers of the missing
@@ -481,7 +504,7 @@ class FileReporter:
"""
return f"Line {start} didn't jump to line {end}"
- def source_token_lines(self):
+ def source_token_lines(self) -> Iterable[List[Tuple[str, str]]]:
"""Generate a series of tokenized lines, one for each line in `source`.
These tokens are used for syntax-colored reports.
@@ -512,10 +535,11 @@ class FileReporter:
for line in self.source().splitlines():
yield [('txt', line)]
- def __eq__(self, other):
+ def __eq__(self, other: Any) -> bool:
return isinstance(other, FileReporter) and self.filename == other.filename
- def __lt__(self, other):
+ def __lt__(self, other: Any) -> bool:
return isinstance(other, FileReporter) and self.filename < other.filename
- __hash__ = None # This object doesn't need to be hashed.
+ # This object doesn't need to be hashed.
+ __hash__ = None # type: ignore[assignment]
diff --git a/coverage/types.py b/coverage/types.py
new file mode 100644
index 00000000..23c7ef8b
--- /dev/null
+++ b/coverage/types.py
@@ -0,0 +1,47 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
+
+"""
+Types for use throughout coverage.py.
+"""
+
+from typing import Dict, List, Optional, Tuple, Union, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ # Protocol is new in 3.8. PYVERSIONS
+ from typing import Protocol
+else:
+ class Protocol: # pylint: disable=missing-class-docstring
+ pass
+
+# One value read from a config file.
+TConfigValue = Union[str, List[str]]
+# An entire config section, mapping option names to values.
+TConfigSection = Dict[str, TConfigValue]
+
+class TConfigurable(Protocol):
+ """Something that can proxy to the coverage configuration settings."""
+
+ def get_option(self, option_name: str) -> Optional[TConfigValue]:
+ """Get an option from the configuration.
+
+ `option_name` is a colon-separated string indicating the section and
+ option name. For example, the ``branch`` option in the ``[run]``
+ section of the config file would be indicated with `"run:branch"`.
+
+ Returns the value of the option.
+
+ """
+
+ def set_option(self, option_name: str, value: Union[TConfigValue, TConfigSection]) -> None:
+ """Set an option in the configuration.
+
+ `option_name` is a colon-separated string indicating the section and
+ option name. For example, the ``branch`` option in the ``[run]``
+ section of the config file would be indicated with `"run:branch"`.
+
+ `value` is the new value for the option.
+
+ """
+
+TArc = Tuple[int, int]