coverage 7.11.2__cp314-cp314t-musllinux_1_2_riscv64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. coverage/__init__.py +40 -0
  2. coverage/__main__.py +12 -0
  3. coverage/annotate.py +114 -0
  4. coverage/bytecode.py +196 -0
  5. coverage/cmdline.py +1184 -0
  6. coverage/collector.py +486 -0
  7. coverage/config.py +731 -0
  8. coverage/context.py +74 -0
  9. coverage/control.py +1481 -0
  10. coverage/core.py +142 -0
  11. coverage/data.py +227 -0
  12. coverage/debug.py +669 -0
  13. coverage/disposition.py +59 -0
  14. coverage/env.py +135 -0
  15. coverage/exceptions.py +91 -0
  16. coverage/execfile.py +329 -0
  17. coverage/files.py +553 -0
  18. coverage/html.py +856 -0
  19. coverage/htmlfiles/coverage_html.js +733 -0
  20. coverage/htmlfiles/favicon_32.png +0 -0
  21. coverage/htmlfiles/index.html +164 -0
  22. coverage/htmlfiles/keybd_closed.png +0 -0
  23. coverage/htmlfiles/pyfile.html +149 -0
  24. coverage/htmlfiles/style.css +377 -0
  25. coverage/htmlfiles/style.scss +824 -0
  26. coverage/inorout.py +614 -0
  27. coverage/jsonreport.py +188 -0
  28. coverage/lcovreport.py +219 -0
  29. coverage/misc.py +373 -0
  30. coverage/multiproc.py +120 -0
  31. coverage/numbits.py +146 -0
  32. coverage/parser.py +1213 -0
  33. coverage/patch.py +166 -0
  34. coverage/phystokens.py +197 -0
  35. coverage/plugin.py +617 -0
  36. coverage/plugin_support.py +299 -0
  37. coverage/py.typed +1 -0
  38. coverage/python.py +269 -0
  39. coverage/pytracer.py +369 -0
  40. coverage/regions.py +127 -0
  41. coverage/report.py +298 -0
  42. coverage/report_core.py +117 -0
  43. coverage/results.py +471 -0
  44. coverage/sqldata.py +1153 -0
  45. coverage/sqlitedb.py +239 -0
  46. coverage/sysmon.py +482 -0
  47. coverage/templite.py +306 -0
  48. coverage/tomlconfig.py +210 -0
  49. coverage/tracer.cpython-314t-riscv64-linux-musl.so +0 -0
  50. coverage/tracer.pyi +43 -0
  51. coverage/types.py +206 -0
  52. coverage/version.py +35 -0
  53. coverage/xmlreport.py +264 -0
  54. coverage-7.11.2.dist-info/METADATA +221 -0
  55. coverage-7.11.2.dist-info/RECORD +59 -0
  56. coverage-7.11.2.dist-info/WHEEL +5 -0
  57. coverage-7.11.2.dist-info/entry_points.txt +4 -0
  58. coverage-7.11.2.dist-info/licenses/LICENSE.txt +177 -0
  59. coverage-7.11.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,299 @@
1
+ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2
+ # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
3
+
4
+ """Support for plugins."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ import os.path
10
+ import sys
11
+ from collections.abc import Iterable, Iterator
12
+ from types import FrameType
13
+ from typing import Any, Callable
14
+
15
+ from coverage.exceptions import PluginError
16
+ from coverage.misc import isolate_module
17
+ from coverage.plugin import CoveragePlugin, FileReporter, FileTracer
18
+ from coverage.types import TArc, TConfigurable, TDebugCtl, TLineNo, TPluginConfig, TSourceTokenLines
19
+
20
+ os = isolate_module(os)
21
+
22
+
23
+ class Plugins:
24
+ """The currently loaded collection of coverage.py plugins."""
25
+
26
+ def __init__(self, debug: TDebugCtl | None = None) -> None:
27
+ self.order: list[CoveragePlugin] = []
28
+ self.names: dict[str, CoveragePlugin] = {}
29
+ self.file_tracers: list[CoveragePlugin] = []
30
+ self.configurers: list[CoveragePlugin] = []
31
+ self.context_switchers: list[CoveragePlugin] = []
32
+
33
+ self.current_module: str | None = None
34
+ self.debug = debug
35
+
36
+ def load_from_config(
37
+ self,
38
+ modules: Iterable[str],
39
+ config: TPluginConfig,
40
+ ) -> None:
41
+ """Load plugin modules, and read their settings from configuration."""
42
+
43
+ for module in modules:
44
+ self.current_module = module
45
+ __import__(module)
46
+ mod = sys.modules[module]
47
+
48
+ coverage_init = getattr(mod, "coverage_init", None)
49
+ if not coverage_init:
50
+ raise PluginError(
51
+ f"Plugin module {module!r} didn't define a coverage_init function",
52
+ )
53
+
54
+ options = config.get_plugin_options(module)
55
+ coverage_init(self, options)
56
+
57
+ self.current_module = None
58
+
59
+ def load_from_callables(
60
+ self,
61
+ plugin_inits: Iterable[TCoverageInit],
62
+ ) -> None:
63
+ """Load plugins from callables provided."""
64
+ for fn in plugin_inits:
65
+ fn(self)
66
+
67
+ def add_file_tracer(self, plugin: CoveragePlugin) -> None:
68
+ """Add a file tracer plugin.
69
+
70
+ `plugin` is an instance of a third-party plugin class. It must
71
+ implement the :meth:`CoveragePlugin.file_tracer` method.
72
+
73
+ """
74
+ self._add_plugin(plugin, self.file_tracers)
75
+
76
+ def add_configurer(self, plugin: CoveragePlugin) -> None:
77
+ """Add a configuring plugin.
78
+
79
+ `plugin` is an instance of a third-party plugin class. It must
80
+ implement the :meth:`CoveragePlugin.configure` method.
81
+
82
+ """
83
+ self._add_plugin(plugin, self.configurers)
84
+
85
+ def add_dynamic_context(self, plugin: CoveragePlugin) -> None:
86
+ """Add a dynamic context plugin.
87
+
88
+ `plugin` is an instance of a third-party plugin class. It must
89
+ implement the :meth:`CoveragePlugin.dynamic_context` method.
90
+
91
+ """
92
+ self._add_plugin(plugin, self.context_switchers)
93
+
94
+ def add_noop(self, plugin: CoveragePlugin) -> None:
95
+ """Add a plugin that does nothing.
96
+
97
+ This is only useful for testing the plugin support.
98
+
99
+ """
100
+ self._add_plugin(plugin, None)
101
+
102
+ def _add_plugin(
103
+ self,
104
+ plugin: CoveragePlugin,
105
+ specialized: list[CoveragePlugin] | None,
106
+ ) -> None:
107
+ """Add a plugin object.
108
+
109
+ `plugin` is a :class:`CoveragePlugin` instance to add. `specialized`
110
+ is a list to append the plugin to.
111
+
112
+ """
113
+ plugin_name = f"{self.current_module}.{plugin.__class__.__name__}"
114
+ if self.debug and self.debug.should("plugin"):
115
+ self.debug.write(f"Loaded plugin {self.current_module!r}: {plugin!r}")
116
+ labelled = LabelledDebug(f"plugin {self.current_module!r}", self.debug)
117
+ plugin = DebugPluginWrapper(plugin, labelled)
118
+
119
+ plugin._coverage_plugin_name = plugin_name
120
+ plugin._coverage_enabled = True
121
+ self.order.append(plugin)
122
+ self.names[plugin_name] = plugin
123
+ if specialized is not None:
124
+ specialized.append(plugin)
125
+
126
+ def __bool__(self) -> bool:
127
+ return bool(self.order)
128
+
129
+ def __iter__(self) -> Iterator[CoveragePlugin]:
130
+ return iter(self.order)
131
+
132
+ def get(self, plugin_name: str) -> CoveragePlugin:
133
+ """Return a plugin by name."""
134
+ return self.names[plugin_name]
135
+
136
+
137
+ TCoverageInit = Callable[[Plugins], None]
138
+
139
+
140
+ class LabelledDebug:
141
+ """A Debug writer, but with labels for prepending to the messages."""
142
+
143
+ def __init__(self, label: str, debug: TDebugCtl, prev_labels: Iterable[str] = ()):
144
+ self.labels = list(prev_labels) + [label]
145
+ self.debug = debug
146
+
147
+ def add_label(self, label: str) -> LabelledDebug:
148
+ """Add a label to the writer, and return a new `LabelledDebug`."""
149
+ return LabelledDebug(label, self.debug, self.labels)
150
+
151
+ def message_prefix(self) -> str:
152
+ """The prefix to use on messages, combining the labels."""
153
+ prefixes = self.labels + [""]
154
+ return ":\n".join(" " * i + label for i, label in enumerate(prefixes))
155
+
156
+ def write(self, message: str) -> None:
157
+ """Write `message`, but with the labels prepended."""
158
+ self.debug.write(f"{self.message_prefix()}{message}")
159
+
160
+
161
+ class DebugPluginWrapper(CoveragePlugin):
162
+ """Wrap a plugin, and use debug to report on what it's doing."""
163
+
164
+ def __init__(self, plugin: CoveragePlugin, debug: LabelledDebug) -> None:
165
+ super().__init__()
166
+ self.plugin = plugin
167
+ self.debug = debug
168
+
169
+ def file_tracer(self, filename: str) -> FileTracer | None:
170
+ tracer = self.plugin.file_tracer(filename)
171
+ self.debug.write(f"file_tracer({filename!r}) --> {tracer!r}")
172
+ if tracer:
173
+ debug = self.debug.add_label(f"file {filename!r}")
174
+ tracer = DebugFileTracerWrapper(tracer, debug)
175
+ return tracer
176
+
177
+ def file_reporter(self, filename: str) -> FileReporter | str:
178
+ reporter = self.plugin.file_reporter(filename)
179
+ assert isinstance(reporter, FileReporter)
180
+ self.debug.write(f"file_reporter({filename!r}) --> {reporter!r}")
181
+ if reporter:
182
+ debug = self.debug.add_label(f"file {filename!r}")
183
+ reporter = DebugFileReporterWrapper(filename, reporter, debug)
184
+ return reporter
185
+
186
+ def dynamic_context(self, frame: FrameType) -> str | None:
187
+ context = self.plugin.dynamic_context(frame)
188
+ self.debug.write(f"dynamic_context({frame!r}) --> {context!r}")
189
+ return context
190
+
191
+ def find_executable_files(self, src_dir: str) -> Iterable[str]:
192
+ executable_files = self.plugin.find_executable_files(src_dir)
193
+ self.debug.write(f"find_executable_files({src_dir!r}) --> {executable_files!r}")
194
+ return executable_files
195
+
196
+ def configure(self, config: TConfigurable) -> None:
197
+ self.debug.write(f"configure({config!r})")
198
+ self.plugin.configure(config)
199
+
200
+ def sys_info(self) -> Iterable[tuple[str, Any]]:
201
+ return self.plugin.sys_info()
202
+
203
+
204
+ class DebugFileTracerWrapper(FileTracer):
205
+ """A debugging `FileTracer`."""
206
+
207
+ def __init__(self, tracer: FileTracer, debug: LabelledDebug) -> None:
208
+ self.tracer = tracer
209
+ self.debug = debug
210
+
211
+ def _show_frame(self, frame: FrameType) -> str:
212
+ """A short string identifying a frame, for debug messages."""
213
+ filename = os.path.basename(frame.f_code.co_filename)
214
+ return f"{filename}@{frame.f_lineno}"
215
+
216
+ def source_filename(self) -> str:
217
+ sfilename = self.tracer.source_filename()
218
+ self.debug.write(f"source_filename() --> {sfilename!r}")
219
+ return sfilename
220
+
221
+ def has_dynamic_source_filename(self) -> bool:
222
+ has = self.tracer.has_dynamic_source_filename()
223
+ self.debug.write(f"has_dynamic_source_filename() --> {has!r}")
224
+ return has
225
+
226
+ def dynamic_source_filename(self, filename: str, frame: FrameType) -> str | None:
227
+ dyn = self.tracer.dynamic_source_filename(filename, frame)
228
+ self.debug.write(
229
+ "dynamic_source_filename({!r}, {}) --> {!r}".format(
230
+ filename,
231
+ self._show_frame(frame),
232
+ dyn,
233
+ )
234
+ )
235
+ return dyn
236
+
237
+ def line_number_range(self, frame: FrameType) -> tuple[TLineNo, TLineNo]:
238
+ pair = self.tracer.line_number_range(frame)
239
+ self.debug.write(f"line_number_range({self._show_frame(frame)}) --> {pair!r}")
240
+ return pair
241
+
242
+
243
+ class DebugFileReporterWrapper(FileReporter):
244
+ """A debugging `FileReporter`."""
245
+
246
+ def __init__(self, filename: str, reporter: FileReporter, debug: LabelledDebug) -> None:
247
+ super().__init__(filename)
248
+ self.reporter = reporter
249
+ self.debug = debug
250
+
251
+ def relative_filename(self) -> str:
252
+ ret = self.reporter.relative_filename()
253
+ self.debug.write(f"relative_filename() --> {ret!r}")
254
+ return ret
255
+
256
+ def lines(self) -> set[TLineNo]:
257
+ ret = self.reporter.lines()
258
+ self.debug.write(f"lines() --> {ret!r}")
259
+ return ret
260
+
261
+ def excluded_lines(self) -> set[TLineNo]:
262
+ ret = self.reporter.excluded_lines()
263
+ self.debug.write(f"excluded_lines() --> {ret!r}")
264
+ return ret
265
+
266
+ def translate_lines(self, lines: Iterable[TLineNo]) -> set[TLineNo]:
267
+ ret = self.reporter.translate_lines(lines)
268
+ self.debug.write(f"translate_lines({lines!r}) --> {ret!r}")
269
+ return ret
270
+
271
+ def translate_arcs(self, arcs: Iterable[TArc]) -> set[TArc]:
272
+ ret = self.reporter.translate_arcs(arcs)
273
+ self.debug.write(f"translate_arcs({arcs!r}) --> {ret!r}")
274
+ return ret
275
+
276
+ def no_branch_lines(self) -> set[TLineNo]:
277
+ ret = self.reporter.no_branch_lines()
278
+ self.debug.write(f"no_branch_lines() --> {ret!r}")
279
+ return ret
280
+
281
+ def exit_counts(self) -> dict[TLineNo, int]:
282
+ ret = self.reporter.exit_counts()
283
+ self.debug.write(f"exit_counts() --> {ret!r}")
284
+ return ret
285
+
286
+ def arcs(self) -> set[TArc]:
287
+ ret = self.reporter.arcs()
288
+ self.debug.write(f"arcs() --> {ret!r}")
289
+ return ret
290
+
291
+ def source(self) -> str:
292
+ ret = self.reporter.source()
293
+ self.debug.write(f"source() --> {len(ret)} chars")
294
+ return ret
295
+
296
+ def source_token_lines(self) -> TSourceTokenLines:
297
+ ret = list(self.reporter.source_token_lines())
298
+ self.debug.write(f"source_token_lines() --> {len(ret)} tokens")
299
+ return ret
coverage/py.typed ADDED
@@ -0,0 +1 @@
1
+ # Marker file for PEP 561 to indicate that this package has type hints.
coverage/python.py ADDED
@@ -0,0 +1,269 @@
1
+ # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2
+ # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
3
+
4
+ """Python source expertise for coverage.py"""
5
+
6
+ from __future__ import annotations
7
+
8
+ import os.path
9
+ import types
10
+ import zipimport
11
+ from collections.abc import Iterable
12
+ from typing import TYPE_CHECKING
13
+
14
+ from coverage import env
15
+ from coverage.exceptions import CoverageException, NoSource
16
+ from coverage.files import canonical_filename, relative_filename, zip_location
17
+ from coverage.misc import isolate_module, join_regex
18
+ from coverage.parser import PythonParser
19
+ from coverage.phystokens import source_encoding, source_token_lines
20
+ from coverage.plugin import CodeRegion, FileReporter
21
+ from coverage.regions import code_regions
22
+ from coverage.types import TArc, TLineNo, TMorf, TSourceTokenLines
23
+
24
+ if TYPE_CHECKING:
25
+ from coverage import Coverage
26
+
27
+ os = isolate_module(os)
28
+
29
+
30
+ def read_python_source(filename: str) -> bytes:
31
+ """Read the Python source text from `filename`.
32
+
33
+ Returns bytes.
34
+
35
+ """
36
+ with open(filename, "rb") as f:
37
+ source = f.read()
38
+
39
+ return source.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
40
+
41
+
42
+ def get_python_source(filename: str) -> str:
43
+ """Return the source code, as unicode."""
44
+ base, ext = os.path.splitext(filename)
45
+ if ext == ".py" and env.WINDOWS:
46
+ exts = [".py", ".pyw"]
47
+ else:
48
+ exts = [ext]
49
+
50
+ source_bytes: bytes | None
51
+ for ext in exts:
52
+ try_filename = base + ext
53
+ if os.path.exists(try_filename):
54
+ # A regular text file: open it.
55
+ source_bytes = read_python_source(try_filename)
56
+ break
57
+
58
+ # Maybe it's in a zip file?
59
+ source_bytes = get_zip_bytes(try_filename)
60
+ if source_bytes is not None:
61
+ break
62
+ else:
63
+ # Couldn't find source.
64
+ raise NoSource(f"No source for code: '{filename}'.", slug="no-source")
65
+
66
+ # Replace \f because of http://bugs.python.org/issue19035
67
+ source_bytes = source_bytes.replace(b"\f", b" ")
68
+ source = source_bytes.decode(source_encoding(source_bytes), "replace")
69
+
70
+ # Python code should always end with a line with a newline.
71
+ if source and source[-1] != "\n":
72
+ source += "\n"
73
+
74
+ return source
75
+
76
+
77
+ def get_zip_bytes(filename: str) -> bytes | None:
78
+ """Get data from `filename` if it is a zip file path.
79
+
80
+ Returns the bytestring data read from the zip file, or None if no zip file
81
+ could be found or `filename` isn't in it. The data returned will be
82
+ an empty string if the file is empty.
83
+
84
+ """
85
+ zipfile_inner = zip_location(filename)
86
+ if zipfile_inner is not None:
87
+ zipfile, inner = zipfile_inner
88
+ try:
89
+ zi = zipimport.zipimporter(zipfile)
90
+ except zipimport.ZipImportError:
91
+ return None
92
+ try:
93
+ data = zi.get_data(inner)
94
+ except OSError:
95
+ return None
96
+ return data
97
+ return None
98
+
99
+
100
+ def source_for_file(filename: str) -> str:
101
+ """Return the source filename for `filename`.
102
+
103
+ Given a file name being traced, return the best guess as to the source
104
+ file to attribute it to.
105
+
106
+ """
107
+ if filename.endswith(".py"):
108
+ # .py files are themselves source files.
109
+ return filename
110
+
111
+ elif filename.endswith((".pyc", ".pyo")):
112
+ # Bytecode files probably have source files near them.
113
+ py_filename = filename[:-1]
114
+ if os.path.exists(py_filename):
115
+ # Found a .py file, use that.
116
+ return py_filename
117
+ if env.WINDOWS:
118
+ # On Windows, it could be a .pyw file.
119
+ pyw_filename = py_filename + "w"
120
+ if os.path.exists(pyw_filename):
121
+ return pyw_filename
122
+ # Didn't find source, but it's probably the .py file we want.
123
+ return py_filename
124
+
125
+ # No idea, just use the file name as-is.
126
+ return filename
127
+
128
+
129
+ def source_for_morf(morf: TMorf) -> str:
130
+ """Get the source filename for the module-or-file `morf`."""
131
+ if hasattr(morf, "__file__") and morf.__file__:
132
+ filename = morf.__file__
133
+ elif isinstance(morf, types.ModuleType):
134
+ # A module should have had .__file__, otherwise we can't use it.
135
+ # This could be a PEP-420 namespace package.
136
+ raise CoverageException(f"Module {morf} has no file")
137
+ else:
138
+ filename = morf
139
+
140
+ filename = source_for_file(filename)
141
+ return filename
142
+
143
+
144
+ class PythonFileReporter(FileReporter):
145
+ """Report support for a Python file."""
146
+
147
+ def __init__(self, morf: TMorf, coverage: Coverage | None = None) -> None:
148
+ self.coverage = coverage
149
+
150
+ filename = source_for_morf(morf)
151
+
152
+ fname = filename
153
+ canonicalize = True
154
+ if self.coverage is not None:
155
+ if self.coverage.config.relative_files:
156
+ canonicalize = False
157
+ if canonicalize:
158
+ fname = canonical_filename(filename)
159
+ super().__init__(fname)
160
+
161
+ if hasattr(morf, "__name__"):
162
+ name = morf.__name__.replace(".", os.sep)
163
+ if os.path.basename(filename).startswith("__init__."):
164
+ name += os.sep + "__init__"
165
+ name += ".py"
166
+ else:
167
+ name = relative_filename(filename)
168
+ self.relname = name
169
+
170
+ self._source: str | None = None
171
+ self._parser: PythonParser | None = None
172
+ self._excluded = None
173
+
174
+ def __repr__(self) -> str:
175
+ return f"<PythonFileReporter {self.filename!r}>"
176
+
177
+ def relative_filename(self) -> str:
178
+ return self.relname
179
+
180
+ @property
181
+ def parser(self) -> PythonParser:
182
+ """Lazily create a :class:`PythonParser`."""
183
+ assert self.coverage is not None
184
+ if self._parser is None:
185
+ self._parser = PythonParser(
186
+ filename=self.filename,
187
+ exclude=self.coverage._exclude_regex("exclude"),
188
+ )
189
+ self._parser.parse_source()
190
+ return self._parser
191
+
192
+ def lines(self) -> set[TLineNo]:
193
+ """Return the line numbers of statements in the file."""
194
+ return self.parser.statements
195
+
196
+ def multiline_map(self) -> dict[TLineNo, TLineNo]:
197
+ """A map of line numbers to first-line in a multi-line statement."""
198
+ return self.parser.multiline_map
199
+
200
+ def excluded_lines(self) -> set[TLineNo]:
201
+ """Return the line numbers of statements in the file."""
202
+ return self.parser.excluded
203
+
204
+ def translate_lines(self, lines: Iterable[TLineNo]) -> set[TLineNo]:
205
+ return self.parser.translate_lines(lines)
206
+
207
+ def translate_arcs(self, arcs: Iterable[TArc]) -> set[TArc]:
208
+ return self.parser.translate_arcs(arcs)
209
+
210
+ def no_branch_lines(self) -> set[TLineNo]:
211
+ assert self.coverage is not None
212
+ no_branch = self.parser.lines_matching(
213
+ join_regex(self.coverage.config.partial_list + self.coverage.config.partial_always_list)
214
+ )
215
+ return no_branch
216
+
217
+ def arcs(self) -> set[TArc]:
218
+ return self.parser.arcs()
219
+
220
+ def exit_counts(self) -> dict[TLineNo, int]:
221
+ return self.parser.exit_counts()
222
+
223
+ def missing_arc_description(
224
+ self,
225
+ start: TLineNo,
226
+ end: TLineNo,
227
+ executed_arcs: Iterable[TArc] | None = None,
228
+ ) -> str:
229
+ return self.parser.missing_arc_description(start, end)
230
+
231
+ def arc_description(self, start: TLineNo, end: TLineNo) -> str:
232
+ return self.parser.arc_description(start, end)
233
+
234
+ def source(self) -> str:
235
+ if self._source is None:
236
+ self._source = get_python_source(self.filename)
237
+ return self._source
238
+
239
+ def should_be_python(self) -> bool:
240
+ """Does it seem like this file should contain Python?
241
+
242
+ This is used to decide if a file reported as part of the execution of
243
+ a program was really likely to have contained Python in the first
244
+ place.
245
+
246
+ """
247
+ # Get the file extension.
248
+ _, ext = os.path.splitext(self.filename)
249
+
250
+ # Anything named *.py* should be Python.
251
+ if ext.startswith(".py"):
252
+ return True
253
+ # A file with no extension should be Python.
254
+ if not ext:
255
+ return True
256
+ # Everything else is probably not Python.
257
+ return False
258
+
259
+ def source_token_lines(self) -> TSourceTokenLines:
260
+ return source_token_lines(self.source())
261
+
262
+ def code_regions(self) -> Iterable[CodeRegion]:
263
+ return code_regions(self.source())
264
+
265
+ def code_region_kinds(self) -> Iterable[tuple[str, str]]:
266
+ return [
267
+ ("function", "functions"),
268
+ ("class", "classes"),
269
+ ]