diff options
Diffstat (limited to 'coverage/parser.py')
-rw-r--r-- | coverage/parser.py | 75 |
1 files changed, 44 insertions, 31 deletions
diff --git a/coverage/parser.py b/coverage/parser.py index de6590aa..c5e95baa 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -3,14 +3,31 @@ import collections, dis, re, token, tokenize from coverage.backward import StringIO -from coverage.backward import open_source, range # pylint: disable=W0622 -from coverage.backward import bytes_to_ints +from coverage.backward import range # pylint: disable=W0622 +from coverage.backward import bytes_to_ints, open_python_source from coverage.bytecode import ByteCodes, CodeObjects from coverage.misc import nice_pair, expensive, join_regex from coverage.misc import CoverageException, NoSource, NotPython class CodeParser(object): + """ + Base class for any code parser. + """ + def translate_lines(self, lines): + return set(lines) + + def translate_arcs(self, arcs): + return arcs + + def exit_counts(self): + return {} + + def arcs(self): + return [] + + +class PythonParser(CodeParser): """Parse code to find executable lines, excluded lines, etc.""" def __init__(self, text=None, filename=None, exclude=None): @@ -20,12 +37,12 @@ class CodeParser(object): `exclude`, a regex. """ - assert text or filename, "CodeParser needs either text or filename" + assert text or filename, "PythonParser needs either text or filename" self.filename = filename or "<code>" self.text = text if not self.text: try: - with open_source(self.filename) as sourcef: + with open_python_source(self.filename) as sourcef: self.text = sourcef.read() except IOError as err: raise NoSource( @@ -137,9 +154,8 @@ class CodeParser(object): # We're at the end of a line, and we've ended on a # different line than the first line of the statement, # so record a multi-line range. - rng = (first_line, elineno) for l in range(first_line, elineno+1): - self.multiline[l] = rng + self.multiline[l] = first_line first_line = None if ttext.strip() and toktype != tokenize.COMMENT: @@ -163,33 +179,29 @@ class CodeParser(object): def first_line(self, line): """Return the first line number of the statement including `line`.""" - rng = self.multiline.get(line) - if rng: - first_line = rng[0] + first_line = self.multiline.get(line) + if first_line: + return first_line else: - first_line = line - return first_line + return line - def first_lines(self, lines, *ignores): + def first_lines(self, lines): """Map the line numbers in `lines` to the correct first line of the statement. - Skip any line mentioned in any of the sequences in `ignores`. - Returns a set of the first lines. """ - ignore = set() - for ign in ignores: - ignore.update(ign) - lset = set() - for l in lines: - if l in ignore: - continue - new_l = self.first_line(l) - if new_l not in ignore: - lset.add(new_l) - return lset + return set(self.first_line(l) for l in lines) + + def translate_lines(self, lines): + return self.first_lines(lines) + + def translate_arcs(self, arcs): + return [ + (self.first_line(a), self.first_line(b)) + for (a, b) in arcs + ] def parse_source(self): """Parse source text to find executable lines, excluded lines, etc. @@ -211,11 +223,12 @@ class CodeParser(object): ) excluded_lines = self.first_lines(self.excluded) - lines = self.first_lines( - self.statement_starts, - excluded_lines, - self.docstrings - ) + ignore = set() + ignore.update(excluded_lines) + ignore.update(self.docstrings) + starts = self.statement_starts - ignore + lines = self.first_lines(starts) + lines -= ignore return lines, excluded_lines @@ -328,7 +341,7 @@ class ByteParser(object): else: if not text: assert filename, "If no code or text, need a filename" - with open_source(filename) as sourcef: + with open_python_source(filename) as sourcef: text = sourcef.read() self.text = text |