summaryrefslogtreecommitdiff
path: root/coverage/parser.py
diff options
context:
space:
mode:
Diffstat (limited to 'coverage/parser.py')
-rw-r--r--coverage/parser.py75
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