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

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 

3 

4""" 

5.. versionadded:: 4.0 

6 

7Plug-in interfaces for coverage.py. 

8 

9Coverage.py supports a few different kinds of plug-ins that change its 

10behavior: 

11 

12* File tracers implement tracing of non-Python file types. 

13 

14* Configurers add custom configuration, using Python code to change the 

15 configuration. 

16 

17* Dynamic context switchers decide when the dynamic context has changed, for 

18 example, to record what test function produced the coverage. 

19 

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. 

24 

25Any plug-in can optionally implement :meth:`~coverage.CoveragePlugin.sys_info` 

26to provide debugging information about their operation. 

27 

28Your module must also contain a ``coverage_init`` function that registers an 

29instance of your plug-in class:: 

30 

31 import coverage 

32 

33 class MyPlugin(coverage.CoveragePlugin): 

34 ... 

35 

36 def coverage_init(reg, options): 

37 reg.add_file_tracer(MyPlugin()) 

38 

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. 

42 

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. 

46 

47Coverage.py will store its own information on your plug-in object, using 

48attributes whose names start with ``_coverage_``. Don't be startled. 

49 

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. 

55 

56 One solution is to put your plugins in your project tree, but not in 

57 your importable Python package. 

58 

59 

60.. _file_tracer_plugins: 

61 

62File Tracers 

63============ 

64 

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. 

69 

70In your ``coverage_init`` function, use the ``add_file_tracer`` method to 

71register your file tracer. 

72 

73 

74.. _configurer_plugins: 

75 

76Configurers 

77=========== 

78 

79.. versionadded:: 4.5 

80 

81Configurers modify the configuration of coverage.py during start-up. 

82Configurers implement the :meth:`~coverage.CoveragePlugin.configure` method to 

83change the configuration. 

84 

85In your ``coverage_init`` function, use the ``add_configurer`` method to 

86register your configurer. 

87 

88 

89.. _dynamic_context_plugins: 

90 

91Dynamic Context Switchers 

92========================= 

93 

94.. versionadded:: 5.0 

95 

96Dynamic context switcher plugins implement the 

97:meth:`~coverage.CoveragePlugin.dynamic_context` method to dynamically compute 

98the context label for each measured frame. 

99 

100Computed context labels are useful when you want to group measured data without 

101modifying the source code. 

102 

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. 

109 

110In your ``coverage_init`` function, use the ``add_dynamic_context`` method to 

111register your dynamic context switcher. 

112 

113""" 

114 

115from __future__ import annotations 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

116 

117import dataclasses 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

118import functools 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

119from collections.abc import Iterable 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

120from types import FrameType 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

121from typing import Any 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

122 

123from coverage import files 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

124from coverage.misc import _needs_to_implement 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

125from coverage.types import TArc, TConfigurable, TLineNo, TSourceTokenLines 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

126 

127 

128class CoveragePlugin: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

129 """Base class for coverage.py plug-ins.""" 

130 

131 _coverage_plugin_name: str 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

132 _coverage_enabled: bool 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

133 

134 def file_tracer(self, filename: str) -> FileTracer | None: # pylint: disable=unused-argument 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

135 """Get a :class:`FileTracer` object for a file. 

136 

137 Plug-in type: file tracer. 

138 

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. 

143 

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. 

149 

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: 

153 

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. 

158 

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. 

163 

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. 

167 

168 Returns a :class:`FileTracer` object to use to trace `filename`, or 

169 None if this plug-in cannot trace this file. 

170 

171 """ 

172 return None 

173 

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. 

179 

180 Plug-in type: file tracer. 

181 

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. 

184 

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. 

187 

188 """ 

189 _needs_to_implement(self, "file_reporter") 1abcdefghijklmnopqrstuvwxyzA

190 

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`. 

196 

197 Plug-in type: dynamic context. 

198 

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. 

203 

204 Returns a string to start a new dynamic context, or None if no new 

205 context should be started. 

206 

207 """ 

208 return None 

209 

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. 

215 

216 Plug-in type: file tracer. 

217 

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. 

221 

222 Returns or yields a sequence of strings, the paths to files that could 

223 have been executed, including files that had been executed. 

224 

225 """ 

226 return [] 1abcdefghijklmnopqrstuvwxyzA

227 

228 def configure(self, config: TConfigurable) -> None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

229 """Modify the configuration of coverage.py. 

230 

231 Plug-in type: configurer. 

232 

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. 

238 

239 """ 

240 pass 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234

241 

242 def sys_info(self) -> Iterable[tuple[str, Any]]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

243 """Get a list of information useful for debugging. 

244 

245 Plug-in type: any. 

246 

247 This method will be invoked for ``--debug=sys``. Your 

248 plug-in can return any information it wants to be displayed. 

249 

250 Returns a list of pairs: `[(name, value), ...]`. 

251 

252 """ 

253 return [] 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234

254 

255 

256class CoveragePluginBase: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

257 """Plugins produce specialized objects, which point back to the original plugin.""" 

258 

259 _coverage_plugin: CoveragePlugin 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

260 

261 

262class FileTracer(CoveragePluginBase): 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

263 """Support needed for files during the execution phase. 

264 

265 File tracer plug-ins implement subclasses of FileTracer to return from 

266 their :meth:`~CoveragePlugin.file_tracer` method. 

267 

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`. 

271 

272 `FileTracer` objects should only be created in the 

273 :meth:`CoveragePlugin.file_tracer` method. 

274 

275 See :ref:`howitworks` for details of the different coverage.py phases. 

276 

277 """ 

278 

279 def source_filename(self) -> str: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

280 """The source file name for this file. 

281 

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. 

285 

286 See :meth:`CoveragePlugin.file_tracer` for details about static and 

287 dynamic file names. 

288 

289 Returns the file name to credit with this execution. 

290 

291 """ 

292 _needs_to_implement(self, "source_filename") 

293 

294 def has_dynamic_source_filename(self) -> bool: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

295 """Does this FileTracer have dynamic source file names? 

296 

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`. 

302 

303 See :meth:`CoveragePlugin.file_tracer` for details about static and 

304 dynamic file names. 

305 

306 Returns True if :meth:`dynamic_source_filename` should be called to get 

307 dynamic source file names. 

308 

309 """ 

310 return False 

311 

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. 

318 

319 Some plug-ins need to compute the source file name dynamically for each 

320 frame. 

321 

322 This function will not be invoked if 

323 :meth:`has_dynamic_source_filename` returns False. 

324 

325 Returns the source file name for this frame, or None if this frame 

326 shouldn't be measured. 

327 

328 """ 

329 return None 

330 

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. 

333 

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. 

339 

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. 

343 

344 """ 

345 lineno = frame.f_lineno 

346 return lineno, lineno 

347 

348 

349@dataclasses.dataclass 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

350class CodeRegion: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

351 """Data for a region of code found by :meth:`FileReporter.code_regions`.""" 

352 

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

356 

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

358 name: str 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

359 

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

363 

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

370 

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

376 

377 

378@functools.total_ordering 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

379class FileReporter(CoveragePluginBase): 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

380 """Support needed for files during the analysis and reporting phases. 

381 

382 File tracer plug-ins implement a subclass of `FileReporter`, and return 

383 instances from their :meth:`CoveragePlugin.file_reporter` method. 

384 

385 There are many methods here, but only :meth:`lines` is required, to provide 

386 the set of executable lines in the file. 

387 

388 See :ref:`howitworks` for details of the different coverage.py phases. 

389 

390 """ 

391 

392 def __init__(self, filename: str) -> None: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

393 """Simple initialization of a `FileReporter`. 

394 

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. 

398 

399 """ 

400 self.filename = filename 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQ5qR6rS7sT8tU9uV!vW#wX$xY%yZ'z0(A1)234

401 

402 def __repr__(self) -> str: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

403 return f"<{self.__class__.__name__} filename={self.filename!r}>" 

404 

405 def relative_filename(self) -> str: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

406 """Get the relative file name for this file. 

407 

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. 

412 

413 """ 

414 return files.relative_filename(self.filename) 1abcdefghijklmnopqrstuvwxyzA

415 

416 def source(self) -> str: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

417 """Get the source for the file. 

418 

419 Returns a Unicode string. 

420 

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. 

424 

425 """ 

426 with open(self.filename, encoding="utf-8") as f: 1abcdefghijklmnopqrstuvwxyzA

427 return f.read() 1abcdefghijklmnopqrstuvwxyzA

428 

429 def lines(self) -> set[TLineNo]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

430 """Get the executable lines in this file. 

431 

432 Your plug-in must determine which lines in the file were possibly 

433 executable. This method returns a set of those line numbers. 

434 

435 Returns a set of line numbers. 

436 

437 """ 

438 _needs_to_implement(self, "lines") 

439 

440 def excluded_lines(self) -> set[TLineNo]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

441 """Get the excluded executable lines in this file. 

442 

443 Your plug-in can use any method it likes to allow the user to exclude 

444 executable lines from consideration. 

445 

446 Returns a set of line numbers. 

447 

448 The base implementation returns the empty set. 

449 

450 """ 

451 return set() 1abcdefghijklmnopqrstuvwxyzA

452 

453 def translate_lines(self, lines: Iterable[TLineNo]) -> set[TLineNo]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

454 """Translate recorded lines into reported lines. 

455 

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. 

460 

461 Your plug-in can optionally define this method to perform these kinds 

462 of adjustment. 

463 

464 `lines` is a sequence of integers, the recorded line numbers. 

465 

466 Returns a set of integers, the adjusted line numbers. 

467 

468 The base implementation returns the numbers unchanged. 

469 

470 """ 

471 return set(lines) 1abcdefghijklmnopqrstuvwxyzA

472 

473 def arcs(self) -> set[TArc]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

474 """Get the executable arcs in this file. 

475 

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. 

480 

481 Returns a set of pairs of line numbers. The default implementation 

482 returns an empty set. 

483 

484 """ 

485 return set() 1abcdefghijklmnopqrstuvwxyzA

486 

487 def no_branch_lines(self) -> set[TLineNo]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

488 """Get the lines excused from branch coverage in this file. 

489 

490 Your plug-in can use any method it likes to allow the user to exclude 

491 lines from consideration of branch coverage. 

492 

493 Returns a set of line numbers. 

494 

495 The base implementation returns the empty set. 

496 

497 """ 

498 return set() 1abcdefghijklmnopqrstuvwxyzA

499 

500 def translate_arcs(self, arcs: Iterable[TArc]) -> set[TArc]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

501 """Translate recorded arcs into reported arcs. 

502 

503 Similar to :meth:`translate_lines`, but for arcs. `arcs` is a set of 

504 line number pairs. 

505 

506 Returns a set of line number pairs. 

507 

508 The default implementation returns `arcs` unchanged. 

509 

510 """ 

511 return set(arcs) 1abcdefghijklmnopqrstuvwxyzA

512 

513 def exit_counts(self) -> dict[TLineNo, int]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

514 """Get a count of exits from that each line. 

515 

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. 

519 

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... 

522 

523 """ 

524 return {} 1abcdefghijklmnopqrstuvwxyzA

525 

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. 

533 

534 The `start` and `end` arguments are the line numbers of the missing 

535 arc. Negative numbers indicate entering or exiting code objects. 

536 

537 The `executed_arcs` argument is a set of line number pairs, the arcs 

538 that were executed in this file. 

539 

540 By default, this simply returns the string "Line {start} didn't jump 

541 to {end}". 

542 

543 """ 

544 return f"Line {start} didn't jump to line {end}" 

545 

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}" 

553 

554 def source_token_lines(self) -> TSourceTokenLines: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

555 """Generate a series of tokenized lines, one for each line in `source`. 

556 

557 These tokens are used for syntax-colored reports. 

558 

559 Each line is a list of pairs, each pair is a token:: 

560 

561 [("key", "def"), ("ws", " "), ("nam", "hello"), ("op", "("), ... ] 

562 

563 Each pair has a token class, and the token text. The token classes 

564 are: 

565 

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 

574 

575 If you concatenate all the token texts, and then join them with 

576 newlines, you should have your original source back. 

577 

578 The default implementation simply returns each line tagged as 

579 ``"txt"``. 

580 

581 """ 

582 for line in self.source().splitlines(): 1abcdefghijklmnopqrstuvwxyzA

583 yield [("txt", line)] 1abcdefghijklmnopqrstuvwxyzA

584 

585 def code_regions(self) -> Iterable[CodeRegion]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

586 """Identify regions in the source file for finer reporting than by file. 

587 

588 Returns an iterable of :class:`CodeRegion` objects. The kinds reported 

589 should be in the possibilities returned by :meth:`code_region_kinds`. 

590 

591 """ 

592 return [] 1abcdefghijklmnopqrstuvwxyzA

593 

594 def code_region_kinds(self) -> Iterable[tuple[str, str]]: 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234

595 """Return the kinds of code regions this plugin can find. 

596 

597 The returned pairs are the singular and plural forms of the kinds:: 

598 

599 [ 

600 ("function", "functions"), 

601 ("class", "classes"), 

602 ] 

603 

604 This will usually be hard-coded, but could also differ by the specific 

605 source file involved. 

606 

607 """ 

608 return [] 1abcdefghijklmnopqrstuvwxyzA

609 

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

612 

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

615 

616 # This object doesn't need to be hashed. 

617 __hash__ = None # type: ignore[assignment] 1aBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0A1234