1 """Source file annotation for Coverage."""
5 from coverage
.backward
import sorted # pylint: disable=W0622
6 from coverage
.report
import Reporter
8 class AnnotateReporter(Reporter
):
9 """Generate annotated source files showing line coverage.
11 This reporter creates annotated copies of the measured source files. Each
12 .py file is copied as a .py,cover file, with a left-hand margin annotating
16 - if 0: #pragma: no cover
25 Executed lines use '>', lines not executed use '!', lines excluded from
26 consideration use '-'.
30 def __init__(self
, coverage
, config
):
31 super(AnnotateReporter
, self
).__init
__(coverage
, config
)
34 blank_re
= re
.compile(r
"\s*(#|$)")
35 else_re
= re
.compile(r
"\s*else\s*:\s*(#|$)")
37 def report(self
, morfs
, directory
=None):
40 See `coverage.report()` for arguments.
43 self
.report_files(self
.annotate_file
, morfs
, directory
)
45 def annotate_file(self
, cu
, analysis
):
46 """Annotate a single file.
48 `cu` is the CodeUnit for the file to annotate.
54 filename
= cu
.filename
55 source
= cu
.source_file()
57 dest_file
= os
.path
.join(self
.directory
, cu
.flat_rootname())
58 dest_file
+= ".py,cover"
60 dest_file
= filename
+ ",cover"
61 dest
= open(dest_file
, 'w')
63 statements
= sorted(analysis
.statements
)
64 missing
= sorted(analysis
.missing
)
65 excluded
= sorted(analysis
.excluded
)
72 line
= source
.readline()
76 while i
< len(statements
) and statements
[i
] < lineno
:
78 while j
< len(missing
) and missing
[j
] < lineno
:
80 if i
< len(statements
) and statements
[i
] == lineno
:
81 covered
= j
>= len(missing
) or missing
[j
] > lineno
82 if self
.blank_re
.match(line
):
84 elif self
.else_re
.match(line
):
85 # Special logic for lines containing only 'else:'.
86 if i
>= len(statements
) and j
>= len(missing
):
88 elif i
>= len(statements
) or j
>= len(missing
):
90 elif statements
[i
] == missing
[j
]:
94 elif lineno
in excluded
: