Compare commits
2 Commits
8e8f82dd94
...
a4e7d5f750
Author | SHA1 | Date |
---|---|---|
Miss Islington (bot) | a4e7d5f750 | |
Miss Islington (bot) | 3b14f18205 |
|
@ -837,7 +837,12 @@ def findsource(object):
|
|||
lnum = object.co_firstlineno - 1
|
||||
pat = re.compile(r'^(\s*def\s)|(\s*async\s+def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)')
|
||||
while lnum > 0:
|
||||
if pat.match(lines[lnum]): break
|
||||
try:
|
||||
line = lines[lnum]
|
||||
except IndexError:
|
||||
raise OSError('lineno is out of bounds')
|
||||
if pat.match(line):
|
||||
break
|
||||
lnum = lnum - 1
|
||||
return lines, lnum
|
||||
raise OSError('could not find code object')
|
||||
|
@ -899,6 +904,7 @@ class BlockFinder:
|
|||
self.indecorator = False
|
||||
self.decoratorhasargs = False
|
||||
self.last = 1
|
||||
self.body_col0 = None
|
||||
|
||||
def tokeneater(self, type, token, srowcol, erowcol, line):
|
||||
if not self.started and not self.indecorator:
|
||||
|
@ -930,6 +936,8 @@ class BlockFinder:
|
|||
elif self.passline:
|
||||
pass
|
||||
elif type == tokenize.INDENT:
|
||||
if self.body_col0 is None and self.started:
|
||||
self.body_col0 = erowcol[1]
|
||||
self.indent = self.indent + 1
|
||||
self.passline = True
|
||||
elif type == tokenize.DEDENT:
|
||||
|
@ -939,6 +947,10 @@ class BlockFinder:
|
|||
# not e.g. for "if: else:" or "try: finally:" blocks)
|
||||
if self.indent <= 0:
|
||||
raise EndOfBlock
|
||||
elif type == tokenize.COMMENT:
|
||||
if self.body_col0 is not None and srowcol[1] >= self.body_col0:
|
||||
# Include comments if indented at least as much as the block
|
||||
self.last = srowcol[0]
|
||||
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.
|
||||
|
|
|
@ -91,3 +91,25 @@ class Callable:
|
|||
|
||||
custom_method = Callable().as_method_of(42)
|
||||
del Callable
|
||||
|
||||
# line 95
|
||||
class WhichComments:
|
||||
# line 97
|
||||
# before f
|
||||
def f(self):
|
||||
# line 100
|
||||
# start f
|
||||
return 1
|
||||
# line 103
|
||||
# end f
|
||||
# line 105
|
||||
# after f
|
||||
|
||||
# before asyncf - line 108
|
||||
async def asyncf(self):
|
||||
# start asyncf
|
||||
return 2
|
||||
# end asyncf
|
||||
# after asyncf - line 113
|
||||
# end of WhichComments - line 114
|
||||
# after WhichComments - line 115
|
||||
|
|
|
@ -392,6 +392,7 @@ class TestRetrievingSourceCode(GetSourceBase):
|
|||
('ParrotDroppings', mod.ParrotDroppings),
|
||||
('StupidGit', mod.StupidGit),
|
||||
('Tit', mod.MalodorousPervert),
|
||||
('WhichComments', mod.WhichComments),
|
||||
])
|
||||
tree = inspect.getclasstree([cls[1] for cls in classes])
|
||||
self.assertEqual(tree,
|
||||
|
@ -405,7 +406,8 @@ class TestRetrievingSourceCode(GetSourceBase):
|
|||
[(mod.FesteringGob, (mod.MalodorousPervert,
|
||||
mod.ParrotDroppings))
|
||||
]
|
||||
]
|
||||
],
|
||||
(mod.WhichComments, (object,),)
|
||||
]
|
||||
])
|
||||
tree = inspect.getclasstree([cls[1] for cls in classes], True)
|
||||
|
@ -417,7 +419,8 @@ class TestRetrievingSourceCode(GetSourceBase):
|
|||
[(mod.FesteringGob, (mod.MalodorousPervert,
|
||||
mod.ParrotDroppings))
|
||||
]
|
||||
]
|
||||
],
|
||||
(mod.WhichComments, (object,),)
|
||||
]
|
||||
])
|
||||
|
||||
|
@ -647,6 +650,18 @@ class TestOneliners(GetSourceBase):
|
|||
# as argument to another function.
|
||||
self.assertSourceEqual(mod2.anonymous, 55, 55)
|
||||
|
||||
class TestBlockComments(GetSourceBase):
|
||||
fodderModule = mod
|
||||
|
||||
def test_toplevel_class(self):
|
||||
self.assertSourceEqual(mod.WhichComments, 96, 114)
|
||||
|
||||
def test_class_method(self):
|
||||
self.assertSourceEqual(mod.WhichComments.f, 99, 104)
|
||||
|
||||
def test_class_async_method(self):
|
||||
self.assertSourceEqual(mod.WhichComments.asyncf, 109, 112)
|
||||
|
||||
class TestBuggyCases(GetSourceBase):
|
||||
fodderModule = mod2
|
||||
|
||||
|
@ -698,6 +713,17 @@ class TestBuggyCases(GetSourceBase):
|
|||
self.assertRaises(IOError, inspect.findsource, co)
|
||||
self.assertRaises(IOError, inspect.getsource, co)
|
||||
|
||||
def test_findsource_with_out_of_bounds_lineno(self):
|
||||
mod_len = len(inspect.getsource(mod))
|
||||
src = '\n' * 2* mod_len + "def f(): pass"
|
||||
co = compile(src, mod.__file__, "exec")
|
||||
g, l = {}, {}
|
||||
eval(co, g, l)
|
||||
func = l['f']
|
||||
self.assertEqual(func.__code__.co_firstlineno, 1+2*mod_len)
|
||||
with self.assertRaisesRegex(IOError, "lineno is out of bounds"):
|
||||
inspect.findsource(func)
|
||||
|
||||
def test_getsource_on_method(self):
|
||||
self.assertSourceEqual(mod2.ClassWithMethod.method, 118, 119)
|
||||
|
||||
|
@ -3970,8 +3996,8 @@ def foo():
|
|||
|
||||
def test_main():
|
||||
run_unittest(
|
||||
TestDecorators, TestRetrievingSourceCode, TestOneliners, TestBuggyCases,
|
||||
TestInterpreterStack, TestClassesAndFunctions, TestPredicates,
|
||||
TestDecorators, TestRetrievingSourceCode, TestOneliners, TestBlockComments,
|
||||
TestBuggyCases, TestInterpreterStack, TestClassesAndFunctions, TestPredicates,
|
||||
TestGetcallargsFunctions, TestGetcallargsMethods,
|
||||
TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState,
|
||||
TestNoEOL, TestSignatureObject, TestSignatureBind, TestParameterObject,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Fix handling of trailing comments by :func:`inspect.getsource`.
|
|
@ -0,0 +1,4 @@
|
|||
:func:`inspect.findsource` now raises :exc:`OSError` instead of
|
||||
:exc:`IndexError` when :attr:`co_lineno` of a code object is greater than the
|
||||
file length. This can happen, for example, when a file is edited after it was
|
||||
imported. PR by Irit Katriel.
|
Loading…
Reference in New Issue