Merge pull request #4069 from asottile/deindent_4066

Fix source reindenting by using `textwrap.dedent` directly.
This commit is contained in:
Anthony Sottile 2018-10-03 16:11:51 -07:00 committed by GitHub
commit b098292352
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 16 additions and 80 deletions

View File

@ -0,0 +1 @@
Fix source reindenting by using ``textwrap.dedent`` directly.

View File

@ -7,6 +7,7 @@ import linecache
import sys import sys
import six import six
import inspect import inspect
import textwrap
import tokenize import tokenize
import py import py
@ -23,7 +24,6 @@ class Source(object):
def __init__(self, *parts, **kwargs): def __init__(self, *parts, **kwargs):
self.lines = lines = [] self.lines = lines = []
de = kwargs.get("deindent", True) de = kwargs.get("deindent", True)
rstrip = kwargs.get("rstrip", True)
for part in parts: for part in parts:
if not part: if not part:
partlines = [] partlines = []
@ -33,11 +33,6 @@ class Source(object):
partlines = [x.rstrip("\n") for x in part] partlines = [x.rstrip("\n") for x in part]
elif isinstance(part, six.string_types): elif isinstance(part, six.string_types):
partlines = part.split("\n") partlines = part.split("\n")
if rstrip:
while partlines:
if partlines[-1].strip():
break
partlines.pop()
else: else:
partlines = getsource(part, deindent=de).lines partlines = getsource(part, deindent=de).lines
if de: if de:
@ -115,17 +110,10 @@ class Source(object):
ast, start, end = getstatementrange_ast(lineno, self) ast, start, end = getstatementrange_ast(lineno, self)
return start, end return start, end
def deindent(self, offset=None): def deindent(self):
""" return a new source object deindented by offset. """return a new source object deindented."""
If offset is None then guess an indentation offset from
the first non-blank line. Subsequent lines which have a
lower indentation offset will be copied verbatim as
they are assumed to be part of multilines.
"""
# XXX maybe use the tokenizer to properly handle multiline
# strings etc.pp?
newsource = Source() newsource = Source()
newsource.lines[:] = deindent(self.lines, offset) newsource.lines[:] = deindent(self.lines)
return newsource return newsource
def isparseable(self, deindent=True): def isparseable(self, deindent=True):
@ -268,47 +256,8 @@ def getsource(obj, **kwargs):
return Source(strsrc, **kwargs) return Source(strsrc, **kwargs)
def deindent(lines, offset=None): def deindent(lines):
if offset is None: return textwrap.dedent("\n".join(lines)).splitlines()
for line in lines:
line = line.expandtabs()
s = line.lstrip()
if s:
offset = len(line) - len(s)
break
else:
offset = 0
if offset == 0:
return list(lines)
newlines = []
def readline_generator(lines):
for line in lines:
yield line + "\n"
it = readline_generator(lines)
try:
for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(
lambda: next(it)
):
if sline > len(lines):
break # End of input reached
if sline > len(newlines):
line = lines[sline - 1].expandtabs()
if line.lstrip() and line[:offset].isspace():
line = line[offset:] # Deindent
newlines.append(line)
for i in range(sline, eline):
# Don't deindent continuing lines of
# multiline tokens (i.e. multiline strings)
newlines.append(lines[i])
except (IndentationError, tokenize.TokenError):
pass
# Add any lines we didn't see. E.g. if an exception was raised.
newlines.extend(lines[len(newlines) :])
return newlines
def get_statement_startend2(lineno, node): def get_statement_startend2(lineno, node):

View File

@ -27,16 +27,7 @@ def test_source_str_function():
x = Source( x = Source(
""" """
3 3
""",
rstrip=False,
)
assert str(x) == "\n3\n "
x = Source(
""" """
3
""",
rstrip=True,
) )
assert str(x) == "\n3" assert str(x) == "\n3"
@ -400,10 +391,13 @@ def test_getfuncsource_with_multine_string():
pass pass
""" """
assert ( expected = '''\
str(_pytest._code.Source(f)).strip() def f():
== 'def f():\n c = """while True:\n pass\n"""' c = """while True:
) pass
"""
'''
assert str(_pytest._code.Source(f)) == expected.rstrip()
def test_deindent(): def test_deindent():
@ -411,21 +405,13 @@ def test_deindent():
assert deindent(["\tfoo", "\tbar"]) == ["foo", "bar"] assert deindent(["\tfoo", "\tbar"]) == ["foo", "bar"]
def f(): source = """\
c = """while True:
pass
"""
lines = deindent(inspect.getsource(f).splitlines())
assert lines == ["def f():", ' c = """while True:', " pass", '"""']
source = """
def f(): def f():
def g(): def g():
pass pass
""" """
lines = deindent(source.splitlines()) lines = deindent(source.splitlines())
assert lines == ["", "def f():", " def g():", " pass", " "] assert lines == ["def f():", " def g():", " pass"]
def test_source_of_class_at_eof_without_newline(tmpdir): def test_source_of_class_at_eof_without_newline(tmpdir):