1 # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
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 |
2 # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt |
3 |
3 |
4 """Source file annotation for coverage.py.""" |
4 """Source file annotation for coverage.py.""" |
5 |
5 |
6 import io |
|
7 import os |
6 import os |
8 import re |
7 import re |
9 |
8 |
10 from coverage.files import flat_rootname |
9 from coverage.files import flat_rootname |
11 from coverage.misc import ensure_dir, isolate_module |
10 from coverage.misc import ensure_dir, isolate_module |
12 from coverage.report import get_analysis_to_report |
11 from coverage.report import get_analysis_to_report |
13 |
12 |
14 os = isolate_module(os) |
13 os = isolate_module(os) |
15 |
14 |
16 |
15 |
17 class AnnotateReporter(object): |
16 class AnnotateReporter: |
18 """Generate annotated source files showing line coverage. |
17 """Generate annotated source files showing line coverage. |
19 |
18 |
20 This reporter creates annotated copies of the measured source files. Each |
19 This reporter creates annotated copies of the measured source files. Each |
21 .py file is copied as a .py,cover file, with a left-hand margin annotating |
20 .py file is copied as a .py,cover file, with a left-hand margin annotating |
22 each line:: |
21 each line:: |
72 dest_file = dest_file[:-3] + ".py" |
71 dest_file = dest_file[:-3] + ".py" |
73 dest_file += ",cover" |
72 dest_file += ",cover" |
74 else: |
73 else: |
75 dest_file = fr.filename + ",cover" |
74 dest_file = fr.filename + ",cover" |
76 |
75 |
77 with io.open(dest_file, 'w', encoding='utf8') as dest: |
76 with open(dest_file, 'w', encoding='utf-8') as dest: |
78 i = 0 |
77 i = j = 0 |
79 j = 0 |
|
80 covered = True |
78 covered = True |
81 source = fr.source() |
79 source = fr.source() |
82 for lineno, line in enumerate(source.splitlines(True), start=1): |
80 for lineno, line in enumerate(source.splitlines(True), start=1): |
83 while i < len(statements) and statements[i] < lineno: |
81 while i < len(statements) and statements[i] < lineno: |
84 i += 1 |
82 i += 1 |
85 while j < len(missing) and missing[j] < lineno: |
83 while j < len(missing) and missing[j] < lineno: |
86 j += 1 |
84 j += 1 |
87 if i < len(statements) and statements[i] == lineno: |
85 if i < len(statements) and statements[i] == lineno: |
88 covered = j >= len(missing) or missing[j] > lineno |
86 covered = j >= len(missing) or missing[j] > lineno |
89 if self.blank_re.match(line): |
87 if self.blank_re.match(line): |
90 dest.write(u' ') |
88 dest.write(' ') |
91 elif self.else_re.match(line): |
89 elif self.else_re.match(line): |
92 # Special logic for lines containing only 'else:'. |
90 # Special logic for lines containing only 'else:'. |
93 if i >= len(statements) and j >= len(missing): |
91 if j >= len(missing): |
94 dest.write(u'! ') |
92 dest.write('> ') |
95 elif i >= len(statements) or j >= len(missing): |
|
96 dest.write(u'> ') |
|
97 elif statements[i] == missing[j]: |
93 elif statements[i] == missing[j]: |
98 dest.write(u'! ') |
94 dest.write('! ') |
99 else: |
95 else: |
100 dest.write(u'> ') |
96 dest.write('> ') |
101 elif lineno in excluded: |
97 elif lineno in excluded: |
102 dest.write(u'- ') |
98 dest.write('- ') |
103 elif covered: |
99 elif covered: |
104 dest.write(u'> ') |
100 dest.write('> ') |
105 else: |
101 else: |
106 dest.write(u'! ') |
102 dest.write('! ') |
107 |
103 |
108 dest.write(line) |
104 dest.write(line) |