Skip to content

Commit

Permalink
Merge pull request #4069 from asottile/deindent_4066
Browse files Browse the repository at this point in the history
Fix source reindenting by using `textwrap.dedent` directly.
  • Loading branch information
asottile authored Oct 3, 2018
2 parents e712adc + e5ab62b commit b098292
Show file tree
Hide file tree
Showing 3 changed files with 16 additions and 80 deletions.
1 change: 1 addition & 0 deletions changelog/4066.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix source reindenting by using ``textwrap.dedent`` directly.
63 changes: 6 additions & 57 deletions src/_pytest/_code/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import sys
import six
import inspect
import textwrap
import tokenize
import py

Expand All @@ -23,7 +24,6 @@ class Source(object):
def __init__(self, *parts, **kwargs):
self.lines = lines = []
de = kwargs.get("deindent", True)
rstrip = kwargs.get("rstrip", True)
for part in parts:
if not part:
partlines = []
Expand All @@ -33,11 +33,6 @@ def __init__(self, *parts, **kwargs):
partlines = [x.rstrip("\n") for x in part]
elif isinstance(part, six.string_types):
partlines = part.split("\n")
if rstrip:
while partlines:
if partlines[-1].strip():
break
partlines.pop()
else:
partlines = getsource(part, deindent=de).lines
if de:
Expand Down Expand Up @@ -115,17 +110,10 @@ def getstatementrange(self, lineno):
ast, start, end = getstatementrange_ast(lineno, self)
return start, end

def deindent(self, offset=None):
""" return a new source object deindented by offset.
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?
def deindent(self):
"""return a new source object deindented."""
newsource = Source()
newsource.lines[:] = deindent(self.lines, offset)
newsource.lines[:] = deindent(self.lines)
return newsource

def isparseable(self, deindent=True):
Expand Down Expand Up @@ -268,47 +256,8 @@ def getsource(obj, **kwargs):
return Source(strsrc, **kwargs)


def deindent(lines, offset=None):
if offset is None:
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 deindent(lines):
return textwrap.dedent("\n".join(lines)).splitlines()


def get_statement_startend2(lineno, node):
Expand Down
32 changes: 9 additions & 23 deletions testing/code/test_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,7 @@ def test_source_str_function():
x = Source(
"""
3
""",
rstrip=False,
)
assert str(x) == "\n3\n "

x = Source(
"""
3
""",
rstrip=True,
)
assert str(x) == "\n3"

Expand Down Expand Up @@ -400,32 +391,27 @@ def f():
pass
"""

assert (
str(_pytest._code.Source(f)).strip()
== 'def f():\n c = """while True:\n pass\n"""'
)
expected = '''\
def f():
c = """while True:
pass
"""
'''
assert str(_pytest._code.Source(f)) == expected.rstrip()


def test_deindent():
from _pytest._code.source import deindent as deindent

assert deindent(["\tfoo", "\tbar"]) == ["foo", "bar"]

def f():
c = """while True:
pass
"""

lines = deindent(inspect.getsource(f).splitlines())
assert lines == ["def f():", ' c = """while True:', " pass", '"""']

source = """
source = """\
def f():
def g():
pass
"""
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):
Expand Down

0 comments on commit b098292

Please sign in to comment.