11 import tokenize |
11 import tokenize |
12 |
12 |
13 from coverage import env |
13 from coverage import env |
14 from coverage.bytecode import code_objects |
14 from coverage.bytecode import code_objects |
15 from coverage.debug import short_stack |
15 from coverage.debug import short_stack |
16 from coverage.exceptions import NoSource, NotPython, StopEverything |
16 from coverage.exceptions import NoSource, NotPython, _StopEverything |
17 from coverage.misc import contract, join_regex, new_contract, nice_pair, one_of |
17 from coverage.misc import contract, join_regex, new_contract, nice_pair, one_of |
18 from coverage.phystokens import compile_unicode, generate_tokens, neuter_encoding_declaration |
18 from coverage.phystokens import compile_unicode, generate_tokens, neuter_encoding_declaration |
19 |
19 |
20 |
20 |
21 class PythonParser: |
21 class PythonParser: |
64 # number table in the bytecode. |
64 # number table in the bytecode. |
65 self.raw_statements = set() |
65 self.raw_statements = set() |
66 |
66 |
67 # The raw line numbers of excluded lines of code, as marked by pragmas. |
67 # The raw line numbers of excluded lines of code, as marked by pragmas. |
68 self.raw_excluded = set() |
68 self.raw_excluded = set() |
69 |
|
70 # The line numbers of class and function definitions. |
|
71 self.raw_classdefs = set() |
|
72 |
69 |
73 # The line numbers of docstring lines. |
70 # The line numbers of docstring lines. |
74 self.raw_docstrings = set() |
71 self.raw_docstrings = set() |
75 |
72 |
76 # Internal detail, used by lab/parser.py. |
73 # Internal detail, used by lab/parser.py. |
118 excluding_decorators = False |
115 excluding_decorators = False |
119 prev_toktype = token.INDENT |
116 prev_toktype = token.INDENT |
120 first_line = None |
117 first_line = None |
121 empty = True |
118 empty = True |
122 first_on_line = True |
119 first_on_line = True |
|
120 nesting = 0 |
123 |
121 |
124 tokgen = generate_tokens(self.text) |
122 tokgen = generate_tokens(self.text) |
125 for toktype, ttext, (slineno, _), (elineno, _), ltext in tokgen: |
123 for toktype, ttext, (slineno, _), (elineno, _), ltext in tokgen: |
126 if self.show_tokens: # pragma: debugging |
124 if self.show_tokens: # pragma: debugging |
127 print("%10s %5s %-20r %r" % ( |
125 print("%10s %5s %-20r %r" % ( |
130 )) |
128 )) |
131 if toktype == token.INDENT: |
129 if toktype == token.INDENT: |
132 indent += 1 |
130 indent += 1 |
133 elif toktype == token.DEDENT: |
131 elif toktype == token.DEDENT: |
134 indent -= 1 |
132 indent -= 1 |
135 elif toktype == token.NAME: |
|
136 if ttext == 'class': |
|
137 # Class definitions look like branches in the bytecode, so |
|
138 # we need to exclude them. The simplest way is to note the |
|
139 # lines with the 'class' keyword. |
|
140 self.raw_classdefs.add(slineno) |
|
141 elif toktype == token.OP: |
133 elif toktype == token.OP: |
142 if ttext == ':': |
134 if ttext == ':' and nesting == 0: |
143 should_exclude = (elineno in self.raw_excluded) or excluding_decorators |
135 should_exclude = (elineno in self.raw_excluded) or excluding_decorators |
144 if not excluding and should_exclude: |
136 if not excluding and should_exclude: |
145 # Start excluding a suite. We trigger off of the colon |
137 # Start excluding a suite. We trigger off of the colon |
146 # token so that the #pragma comment will be recognized on |
138 # token so that the #pragma comment will be recognized on |
147 # the same line as the colon. |
139 # the same line as the colon. |
153 # A decorator. |
145 # A decorator. |
154 if elineno in self.raw_excluded: |
146 if elineno in self.raw_excluded: |
155 excluding_decorators = True |
147 excluding_decorators = True |
156 if excluding_decorators: |
148 if excluding_decorators: |
157 self.raw_excluded.add(elineno) |
149 self.raw_excluded.add(elineno) |
|
150 elif ttext in "([{": |
|
151 nesting += 1 |
|
152 elif ttext in ")]}": |
|
153 nesting -= 1 |
158 elif toktype == token.STRING and prev_toktype == token.INDENT: |
154 elif toktype == token.STRING and prev_toktype == token.INDENT: |
159 # Strings that are first on an indented line are docstrings. |
155 # Strings that are first on an indented line are docstrings. |
160 # (a trick from trace.py in the stdlib.) This works for |
156 # (a trick from trace.py in the stdlib.) This works for |
161 # 99.9999% of cases. For the rest (!) see: |
157 # 99.9999% of cases. For the rest (!) see: |
162 # http://stackoverflow.com/questions/1769332/x/1769794#1769794 |
158 # http://stackoverflow.com/questions/1769332/x/1769794#1769794 |
294 if l2 in self.excluded: |
290 if l2 in self.excluded: |
295 # Arcs to excluded lines shouldn't count. |
291 # Arcs to excluded lines shouldn't count. |
296 continue |
292 continue |
297 exit_counts[l1] += 1 |
293 exit_counts[l1] += 1 |
298 |
294 |
299 # Class definitions have one extra exit, so remove one for each: |
|
300 for l in self.raw_classdefs: |
|
301 # Ensure key is there: class definitions can include excluded lines. |
|
302 if l in exit_counts: |
|
303 exit_counts[l] -= 1 |
|
304 |
|
305 return exit_counts |
295 return exit_counts |
306 |
296 |
307 def missing_arc_description(self, start, end, executed_arcs=None): |
297 def missing_arc_description(self, start, end, executed_arcs=None): |
308 """Provide an English sentence describing a missing arc.""" |
298 """Provide an English sentence describing a missing arc.""" |
309 if self._missing_arc_fragments is None: |
299 if self._missing_arc_fragments is None: |
364 |
354 |
365 # Alternative Python implementations don't always provide all the |
355 # Alternative Python implementations don't always provide all the |
366 # attributes on code objects that we need to do the analysis. |
356 # attributes on code objects that we need to do the analysis. |
367 for attr in ['co_lnotab', 'co_firstlineno']: |
357 for attr in ['co_lnotab', 'co_firstlineno']: |
368 if not hasattr(self.code, attr): |
358 if not hasattr(self.code, attr): |
369 raise StopEverything( # pragma: only jython |
359 raise _StopEverything( # pragma: only jython |
370 "This implementation of Python doesn't support code analysis.\n" + |
360 "This implementation of Python doesn't support code analysis.\n" + |
371 "Run coverage.py under another Python for this command." |
361 "Run coverage.py under another Python for this command." |
372 ) |
362 ) |
373 |
363 |
374 def child_parsers(self): |
364 def child_parsers(self): |