Coverage for coverage / plugin.py: 86.517%
85 statements
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
1# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
4"""
5.. versionadded:: 4.0
7Plug-in interfaces for coverage.py.
9Coverage.py supports a few different kinds of plug-ins that change its
10behavior:
12* File tracers implement tracing of non-Python file types.
14* Configurers add custom configuration, using Python code to change the
15 configuration.
17* Dynamic context switchers decide when the dynamic context has changed, for
18 example, to record what test function produced the coverage.
20To write a coverage.py plug-in, create a module with a subclass of
21:class:`~coverage.CoveragePlugin`. You will override methods in your class to
22participate in various aspects of coverage.py's processing.
23Different types of plug-ins have to override different methods.
25Any plug-in can optionally implement :meth:`~coverage.CoveragePlugin.sys_info`
26to provide debugging information about their operation.
28Your module must also contain a ``coverage_init`` function that registers an
29instance of your plug-in class::
31 import coverage
33 class MyPlugin(coverage.CoveragePlugin):
34 ...
36 def coverage_init(reg, options):
37 reg.add_file_tracer(MyPlugin())
39You use the `reg` parameter passed to your ``coverage_init`` function to
40register your plug-in object. The registration method you call depends on
41what kind of plug-in it is.
43If your plug-in takes options, the `options` parameter is a dictionary of your
44plug-in's options from the coverage.py configuration file. Use them however
45you want to configure your object before registering it.
47Coverage.py will store its own information on your plug-in object, using
48attributes whose names start with ``_coverage_``. Don't be startled.
50.. warning::
51 Plug-ins are imported by coverage.py before it begins measuring code.
52 If you write a plugin in your own project, it might import your product
53 code before coverage.py can start measuring. This can result in your
54 own code being reported as missing.
56 One solution is to put your plugins in your project tree, but not in
57 your importable Python package.
60.. _file_tracer_plugins:
62File Tracers
63============
65File tracers implement measurement support for non-Python files. File tracers
66implement the :meth:`~coverage.CoveragePlugin.file_tracer` method to claim
67files and the :meth:`~coverage.CoveragePlugin.file_reporter` method to report
68on those files.
70In your ``coverage_init`` function, use the ``add_file_tracer`` method to
71register your file tracer.
74.. _configurer_plugins:
76Configurers
77===========
79.. versionadded:: 4.5
81Configurers modify the configuration of coverage.py during start-up.
82Configurers implement the :meth:`~coverage.CoveragePlugin.configure` method to
83change the configuration.
85In your ``coverage_init`` function, use the ``add_configurer`` method to
86register your configurer.
89.. _dynamic_context_plugins:
91Dynamic Context Switchers
92=========================
94.. versionadded:: 5.0
96Dynamic context switcher plugins implement the
97:meth:`~coverage.CoveragePlugin.dynamic_context` method to dynamically compute
98the context label for each measured frame.
100Computed context labels are useful when you want to group measured data without
101modifying the source code.
103For example, you could write a plugin that checks `frame.f_code` to inspect
104the currently executed method, and set the context label to a fully qualified
105method name if it's an instance method of `unittest.TestCase` and the method
106name starts with 'test'. Such a plugin would provide basic coverage grouping
107by test and could be used with test runners that have no built-in coveragepy
108support.
110In your ``coverage_init`` function, use the ``add_dynamic_context`` method to
111register your dynamic context switcher.
113"""
115from __future__ import annotations 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
117import dataclasses 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
118import functools 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
119from collections.abc import Iterable 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
120from types import FrameType 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
121from typing import Any 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
123from coverage import files 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
124from coverage.misc import _needs_to_implement 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
125from coverage.types import TArc, TConfigurable, TLineNo, TSourceTokenLines 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
128class CoveragePlugin: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
129 """Base class for coverage.py plug-ins."""
131 _coverage_plugin_name: str 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
132 _coverage_enabled: bool 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
134 def file_tracer(self, filename: str) -> FileTracer | None: # pylint: disable=unused-argument 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
135 """Get a :class:`FileTracer` object for a file.
137 Plug-in type: file tracer.
139 Every Python source file is offered to your plug-in to give it a chance
140 to take responsibility for tracing the file. If your plug-in can
141 handle the file, it should return a :class:`FileTracer` object.
142 Otherwise return None.
144 There is no way to register your plug-in for particular files.
145 Instead, this method is invoked for all files as they are executed,
146 and the plug-in decides whether it can trace the file or not.
147 Be prepared for `filename` to refer to all kinds of files that have
148 nothing to do with your plug-in.
150 The file name will be a Python file being executed. There are two
151 broad categories of behavior for a plug-in, depending on the kind of
152 files your plug-in supports:
154 * Static file names: each of your original source files has been
155 converted into a distinct Python file. Your plug-in is invoked with
156 the Python file name, and it maps it back to its original source
157 file.
159 * Dynamic file names: all of your source files are executed by the same
160 Python file. In this case, your plug-in implements
161 :meth:`FileTracer.dynamic_source_filename` to provide the actual
162 source file for each execution frame.
164 `filename` is a string, the path to the file being considered. This is
165 the absolute real path to the file. If you are comparing to other
166 paths, be sure to take this into account.
168 Returns a :class:`FileTracer` object to use to trace `filename`, or
169 None if this plug-in cannot trace this file.
171 """
172 return None
174 def file_reporter( 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
175 self,
176 filename: str, # pylint: disable=unused-argument
177 ) -> FileReporter | str: # str should be Literal["python"]
178 """Get the :class:`FileReporter` class to use for a file.
180 Plug-in type: file tracer.
182 This will only be invoked if `filename` returns non-None from
183 :meth:`file_tracer`. It's an error to return None from this method.
185 Returns a :class:`FileReporter` object to use to report on `filename`,
186 or the string `"python"` to have coverage.py treat the file as Python.
188 """
189 _needs_to_implement(self, "file_reporter") 1abcdefghijklmnopqrstuvwxyzA
191 def dynamic_context( 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
192 self,
193 frame: FrameType, # pylint: disable=unused-argument
194 ) -> str | None:
195 """Get the dynamically computed context label for `frame`.
197 Plug-in type: dynamic context.
199 This method is invoked for each frame when outside of a dynamic
200 context, to see if a new dynamic context should be started. If it
201 returns a string, a new context label is set for this and deeper
202 frames. The dynamic context ends when this frame returns.
204 Returns a string to start a new dynamic context, or None if no new
205 context should be started.
207 """
208 return None
210 def find_executable_files( 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
211 self,
212 src_dir: str, # pylint: disable=unused-argument
213 ) -> Iterable[str]:
214 """Yield all of the executable files in `src_dir`, recursively.
216 Plug-in type: file tracer.
218 Executability is a plug-in-specific property, but generally means files
219 which would have been considered for coverage analysis, had they been
220 included automatically.
222 Returns or yields a sequence of strings, the paths to files that could
223 have been executed, including files that had been executed.
225 """
226 return [] 1abcdefghijklmnopqrstuvwxyzA
228 def configure(self, config: TConfigurable) -> None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
229 """Modify the configuration of coverage.py.
231 Plug-in type: configurer.
233 This method is called during coverage.py start-up, to give your plug-in
234 a chance to change the configuration. The `config` parameter is an
235 object with :meth:`~coverage.Coverage.get_option` and
236 :meth:`~coverage.Coverage.set_option` methods. Do not call any other
237 methods on the `config` object.
239 """
240 pass 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
242 def sys_info(self) -> Iterable[tuple[str, Any]]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
243 """Get a list of information useful for debugging.
245 Plug-in type: any.
247 This method will be invoked for ``--debug=sys``. Your
248 plug-in can return any information it wants to be displayed.
250 Returns a list of pairs: `[(name, value), ...]`.
252 """
253 return [] 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
256class CoveragePluginBase: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
257 """Plugins produce specialized objects, which point back to the original plugin."""
259 _coverage_plugin: CoveragePlugin 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
262class FileTracer(CoveragePluginBase): 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
263 """Support needed for files during the execution phase.
265 File tracer plug-ins implement subclasses of FileTracer to return from
266 their :meth:`~CoveragePlugin.file_tracer` method.
268 You may construct this object from :meth:`CoveragePlugin.file_tracer` any
269 way you like. A natural choice would be to pass the file name given to
270 `file_tracer`.
272 `FileTracer` objects should only be created in the
273 :meth:`CoveragePlugin.file_tracer` method.
275 See :ref:`howitworks` for details of the different coverage.py phases.
277 """
279 def source_filename(self) -> str: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
280 """The source file name for this file.
282 This may be any file name you like. A key responsibility of a plug-in
283 is to own the mapping from Python execution back to whatever source
284 file name was originally the source of the code.
286 See :meth:`CoveragePlugin.file_tracer` for details about static and
287 dynamic file names.
289 Returns the file name to credit with this execution.
291 """
292 _needs_to_implement(self, "source_filename")
294 def has_dynamic_source_filename(self) -> bool: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
295 """Does this FileTracer have dynamic source file names?
297 FileTracers can provide dynamically determined file names by
298 implementing :meth:`dynamic_source_filename`. Invoking that function
299 is expensive. To determine whether to invoke it, coverage.py uses the
300 result of this function to know if it needs to bother invoking
301 :meth:`dynamic_source_filename`.
303 See :meth:`CoveragePlugin.file_tracer` for details about static and
304 dynamic file names.
306 Returns True if :meth:`dynamic_source_filename` should be called to get
307 dynamic source file names.
309 """
310 return False
312 def dynamic_source_filename( 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
313 self,
314 filename: str, # pylint: disable=unused-argument
315 frame: FrameType, # pylint: disable=unused-argument
316 ) -> str | None:
317 """Get a dynamically computed source file name.
319 Some plug-ins need to compute the source file name dynamically for each
320 frame.
322 This function will not be invoked if
323 :meth:`has_dynamic_source_filename` returns False.
325 Returns the source file name for this frame, or None if this frame
326 shouldn't be measured.
328 """
329 return None
331 def line_number_range(self, frame: FrameType) -> tuple[TLineNo, TLineNo]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
332 """Get the range of source line numbers for a given a call frame.
334 The call frame is examined, and the source line number in the original
335 file is returned. The return value is a pair of numbers, the starting
336 line number and the ending line number, both inclusive. For example,
337 returning (5, 7) means that lines 5, 6, and 7 should be considered
338 executed.
340 This function might decide that the frame doesn't indicate any lines
341 from the source file were executed. Return (-1, -1) in this case to
342 tell coverage.py that no lines should be recorded for this frame.
344 """
345 lineno = frame.f_lineno
346 return lineno, lineno
349@dataclasses.dataclass 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
350class CodeRegion: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
351 """Data for a region of code found by :meth:`FileReporter.code_regions`."""
353 #: The kind of region, like `"function"` or `"class"`. Must be one of the
354 #: singular values returned by :meth:`FileReporter.code_region_kinds`.
355 kind: str 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
357 #: The name of the region. For example, a function or class name.
358 name: str 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
360 #: The line in the source file to link to when navigating to the region.
361 #: Can be a line not mentioned in `lines`.
362 start: int 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
364 #: The lines in the region. Should be lines that could be executed in the
365 #: region. For example, a class region includes all of the lines in the
366 #: methods of the class, but not the lines defining class attributes, since
367 #: they are executed on import, not as part of exercising the class. The
368 #: set can include non-executable lines like blanks and comments.
369 lines: set[int] 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
371 def __lt__(self, other: CodeRegion) -> bool: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
372 """To support sorting to make test-writing easier."""
373 if self.name == other.name: 373 ↛ 374line 373 didn't jump to line 374 because the condition on line 373 was never true1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
374 return min(self.lines) < min(other.lines)
375 return self.name < other.name 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
378@functools.total_ordering 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
379class FileReporter(CoveragePluginBase): 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
380 """Support needed for files during the analysis and reporting phases.
382 File tracer plug-ins implement a subclass of `FileReporter`, and return
383 instances from their :meth:`CoveragePlugin.file_reporter` method.
385 There are many methods here, but only :meth:`lines` is required, to provide
386 the set of executable lines in the file.
388 See :ref:`howitworks` for details of the different coverage.py phases.
390 """
392 def __init__(self, filename: str) -> None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
393 """Simple initialization of a `FileReporter`.
395 The `filename` argument is the path to the file being reported. This
396 will be available as the `.filename` attribute on the object. Other
397 method implementations on this base class rely on this attribute.
399 """
400 self.filename = filename 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
402 def __repr__(self) -> str: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
403 return f"<{self.__class__.__name__} filename={self.filename!r}>"
405 def relative_filename(self) -> str: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
406 """Get the relative file name for this file.
408 This file path will be displayed in reports. The default
409 implementation will supply the actual project-relative file path. You
410 only need to supply this method if you have an unusual syntax for file
411 paths.
413 """
414 return files.relative_filename(self.filename) 1abcdefghijklmnopqrstuvwxyzA
416 def source(self) -> str: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
417 """Get the source for the file.
419 Returns a Unicode string.
421 The base implementation simply reads the `self.filename` file and
422 decodes it as UTF-8. Override this method if your file isn't readable
423 as a text file, or if you need other encoding support.
425 """
426 with open(self.filename, encoding="utf-8") as f: 1abcdefghijklmnopqrstuvwxyzA
427 return f.read() 1abcdefghijklmnopqrstuvwxyzA
429 def lines(self) -> set[TLineNo]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
430 """Get the executable lines in this file.
432 Your plug-in must determine which lines in the file were possibly
433 executable. This method returns a set of those line numbers.
435 Returns a set of line numbers.
437 """
438 _needs_to_implement(self, "lines")
440 def excluded_lines(self) -> set[TLineNo]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
441 """Get the excluded executable lines in this file.
443 Your plug-in can use any method it likes to allow the user to exclude
444 executable lines from consideration.
446 Returns a set of line numbers.
448 The base implementation returns the empty set.
450 """
451 return set() 1abcdefghijklmnopqrstuvwxyzA
453 def translate_lines(self, lines: Iterable[TLineNo]) -> set[TLineNo]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
454 """Translate recorded lines into reported lines.
456 Some file formats will want to report lines slightly differently than
457 they are recorded. For example, Python records the last line of a
458 multi-line statement, but reports are nicer if they mention the first
459 line.
461 Your plug-in can optionally define this method to perform these kinds
462 of adjustment.
464 `lines` is a sequence of integers, the recorded line numbers.
466 Returns a set of integers, the adjusted line numbers.
468 The base implementation returns the numbers unchanged.
470 """
471 return set(lines) 1abcdefghijklmnopqrstuvwxyzA
473 def arcs(self) -> set[TArc]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
474 """Get the executable arcs in this file.
476 To support branch coverage, your plug-in needs to be able to indicate
477 possible execution paths, as a set of line number pairs. Each pair is
478 a `(prev, next)` pair indicating that execution can transition from the
479 `prev` line number to the `next` line number.
481 Returns a set of pairs of line numbers. The default implementation
482 returns an empty set.
484 """
485 return set() 1abcdefghijklmnopqrstuvwxyzA
487 def no_branch_lines(self) -> set[TLineNo]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
488 """Get the lines excused from branch coverage in this file.
490 Your plug-in can use any method it likes to allow the user to exclude
491 lines from consideration of branch coverage.
493 Returns a set of line numbers.
495 The base implementation returns the empty set.
497 """
498 return set() 1abcdefghijklmnopqrstuvwxyzA
500 def translate_arcs(self, arcs: Iterable[TArc]) -> set[TArc]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
501 """Translate recorded arcs into reported arcs.
503 Similar to :meth:`translate_lines`, but for arcs. `arcs` is a set of
504 line number pairs.
506 Returns a set of line number pairs.
508 The default implementation returns `arcs` unchanged.
510 """
511 return set(arcs) 1abcdefghijklmnopqrstuvwxyzA
513 def exit_counts(self) -> dict[TLineNo, int]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
514 """Get a count of exits from that each line.
516 To determine which lines are branches, coverage.py looks for lines that
517 have more than one exit. This function creates a dict mapping each
518 executable line number to a count of how many exits it has.
520 To be honest, this feels wrong, and should be refactored. Let me know
521 if you attempt to implement this method in your plug-in...
523 """
524 return {} 1abcdefghijklmnopqrstuvwxyzA
526 def missing_arc_description( 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1
527 self,
528 start: TLineNo,
529 end: TLineNo,
530 executed_arcs: Iterable[TArc] | None = None, # pylint: disable=unused-argument
531 ) -> str:
532 """Provide an English sentence describing a missing arc.
534 The `start` and `end` arguments are the line numbers of the missing
535 arc. Negative numbers indicate entering or exiting code objects.
537 The `executed_arcs` argument is a set of line number pairs, the arcs
538 that were executed in this file.
540 By default, this simply returns the string "Line {start} didn't jump
541 to {end}".
543 """
544 return f"Line {start} didn't jump to line {end}"
546 def arc_description( 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
547 self,
548 start: TLineNo, # pylint: disable=unused-argument
549 end: TLineNo,
550 ) -> str:
551 """Provide an English description of an arc's effect."""
552 return f"jump to line {end}"
554 def source_token_lines(self) -> TSourceTokenLines: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
555 """Generate a series of tokenized lines, one for each line in `source`.
557 These tokens are used for syntax-colored reports.
559 Each line is a list of pairs, each pair is a token::
561 [("key", "def"), ("ws", " "), ("nam", "hello"), ("op", "("), ... ]
563 Each pair has a token class, and the token text. The token classes
564 are:
566 * ``"com"``: a comment
567 * ``"key"``: a keyword
568 * ``"nam"``: a name, or identifier
569 * ``"num"``: a number
570 * ``"op"``: an operator
571 * ``"str"``: a string literal
572 * ``"ws"``: some white space
573 * ``"txt"``: some other kind of text
575 If you concatenate all the token texts, and then join them with
576 newlines, you should have your original source back.
578 The default implementation simply returns each line tagged as
579 ``"txt"``.
581 """
582 for line in self.source().splitlines(): 1abcdefghijklmnopqrstuvwxyzA
583 yield [("txt", line)] 1abcdefghijklmnopqrstuvwxyzA
585 def code_regions(self) -> Iterable[CodeRegion]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
586 """Identify regions in the source file for finer reporting than by file.
588 Returns an iterable of :class:`CodeRegion` objects. The kinds reported
589 should be in the possibilities returned by :meth:`code_region_kinds`.
591 """
592 return [] 1abcdefghijklmnopqrstuvwxyzA
594 def code_region_kinds(self) -> Iterable[tuple[str, str]]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
595 """Return the kinds of code regions this plugin can find.
597 The returned pairs are the singular and plural forms of the kinds::
599 [
600 ("function", "functions"),
601 ("class", "classes"),
602 ]
604 This will usually be hard-coded, but could also differ by the specific
605 source file involved.
607 """
608 return [] 1abcdefghijklmnopqrstuvwxyzA
610 def __eq__(self, other: Any) -> bool: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
611 return isinstance(other, FileReporter) and self.filename == other.filename 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
613 def __lt__(self, other: Any) -> bool: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234
614 return isinstance(other, FileReporter) and self.filename < other.filename 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234
616 # This object doesn't need to be hashed.
617 __hash__ = None # type: ignore[assignment] 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234