some more fixes and tests for inspect.getsource(), triggered by crashes
from the PyPy project as well as the SF bug #1295909.
This commit is contained in:
parent
e9f8ec98d4
commit
dd5c023af5
|
@ -485,19 +485,6 @@ def getcomments(object):
|
|||
comments[-1:] = []
|
||||
return string.join(comments, '')
|
||||
|
||||
class ListReader:
|
||||
"""Provide a readline() method to return lines from a list of strings."""
|
||||
def __init__(self, lines):
|
||||
self.lines = lines
|
||||
self.index = 0
|
||||
|
||||
def readline(self):
|
||||
i = self.index
|
||||
if i < len(self.lines):
|
||||
self.index = i + 1
|
||||
return self.lines[i]
|
||||
else: return ''
|
||||
|
||||
class EndOfBlock(Exception): pass
|
||||
|
||||
class BlockFinder:
|
||||
|
@ -507,40 +494,46 @@ class BlockFinder:
|
|||
self.islambda = False
|
||||
self.started = False
|
||||
self.passline = False
|
||||
self.last = 0
|
||||
self.last = 1
|
||||
|
||||
def tokeneater(self, type, token, (srow, scol), (erow, ecol), line):
|
||||
if not self.started:
|
||||
# look for the first "def", "class" or "lambda"
|
||||
if token in ("def", "class", "lambda"):
|
||||
if token == "lambda":
|
||||
self.islambda = True
|
||||
self.started = True
|
||||
self.passline = True
|
||||
self.passline = True # skip to the end of the line
|
||||
elif type == tokenize.NEWLINE:
|
||||
self.passline = False
|
||||
self.passline = False # stop skipping when a NEWLINE is seen
|
||||
self.last = srow
|
||||
if self.islambda: # lambdas always end at the first NEWLINE
|
||||
raise EndOfBlock
|
||||
elif self.passline:
|
||||
pass
|
||||
elif self.islambda:
|
||||
raise EndOfBlock, self.last
|
||||
elif type == tokenize.INDENT:
|
||||
self.indent = self.indent + 1
|
||||
self.passline = True
|
||||
elif type == tokenize.DEDENT:
|
||||
self.indent = self.indent - 1
|
||||
if self.indent == 0:
|
||||
raise EndOfBlock, self.last
|
||||
elif type == tokenize.NAME and scol == 0:
|
||||
raise EndOfBlock, self.last
|
||||
# the end of matching indent/dedent pairs end a block
|
||||
# (note that this only works for "def"/"class" blocks,
|
||||
# not e.g. for "if: else:" or "try: finally:" blocks)
|
||||
if self.indent <= 0:
|
||||
raise EndOfBlock
|
||||
elif self.indent == 0 and type not in (tokenize.COMMENT, tokenize.NL):
|
||||
# any other token on the same indentation level end the previous
|
||||
# block as well, except the pseudo-tokens COMMENT and NL.
|
||||
raise EndOfBlock
|
||||
|
||||
def getblock(lines):
|
||||
"""Extract the block of code at the top of the given list of lines."""
|
||||
blockfinder = BlockFinder()
|
||||
try:
|
||||
tokenize.tokenize(ListReader(lines).readline, BlockFinder().tokeneater)
|
||||
except EndOfBlock, eob:
|
||||
return lines[:eob.args[0]]
|
||||
# Fooling the indent/dedent logic implies a one-line definition
|
||||
return lines[:1]
|
||||
tokenize.tokenize(iter(lines).next, blockfinder.tokeneater)
|
||||
except (EndOfBlock, IndentationError):
|
||||
pass
|
||||
return lines[:blockfinder.last]
|
||||
|
||||
def getsourcelines(object):
|
||||
"""Return a list of source lines and starting line number for an object.
|
||||
|
|
|
@ -64,3 +64,27 @@ multiline_sig = [
|
|||
y): x+y,
|
||||
None,
|
||||
]
|
||||
|
||||
# line 68
|
||||
def func69():
|
||||
class cls70:
|
||||
def func71():
|
||||
pass
|
||||
return cls70
|
||||
extra74 = 74
|
||||
|
||||
# line 76
|
||||
def func77(): pass
|
||||
(extra78, stuff78) = 'xy'
|
||||
extra79 = 'stop'
|
||||
|
||||
# line 81
|
||||
class cls82:
|
||||
def func83(): pass
|
||||
(extra84, stuff84) = 'xy'
|
||||
extra85 = 'stop'
|
||||
|
||||
# line 87
|
||||
def func88():
|
||||
# comment
|
||||
return 90
|
||||
|
|
|
@ -238,6 +238,18 @@ class TestBuggyCases(GetSourceBase):
|
|||
def test_multiline_sig(self):
|
||||
self.assertSourceEqual(mod2.multiline_sig[0], 63, 64)
|
||||
|
||||
def test_nested_class(self):
|
||||
self.assertSourceEqual(mod2.func69().func71, 71, 72)
|
||||
|
||||
def test_one_liner_followed_by_non_name(self):
|
||||
self.assertSourceEqual(mod2.func77, 77, 77)
|
||||
|
||||
def test_one_liner_dedent_non_name(self):
|
||||
self.assertSourceEqual(mod2.cls82.func83, 83, 83)
|
||||
|
||||
def test_with_comment_instead_of_docstring(self):
|
||||
self.assertSourceEqual(mod2.func88, 88, 90)
|
||||
|
||||
# Helper for testing classify_class_attrs.
|
||||
def attrs_wo_objs(cls):
|
||||
return [t[:3] for t in inspect.classify_class_attrs(cls)]
|
||||
|
|
Loading…
Reference in New Issue