Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correctly handle inline tabs in docstrings #1810

Merged
merged 2 commits into from
Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions src/black/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6744,13 +6744,33 @@ def is_docstring(leaf: Leaf) -> bool:
return False


def lines_with_leading_tabs_expanded(s: str) -> List[str]:
"""
Splits string into lines and expands only leading tabs (following the normal
Python rules)
"""
lines = []
for line in s.splitlines():
# Find the index of the first non-whitespace character after a string of
# whitespace that includes at least one tab
match = re.match(r"\s*\t+\s*(\S)", line)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if there's something like space + tab + space + tab? It's probably easiest to just grab all leading whitespace and expandtabs() it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup this covers that case and I added tests that cover it. It effectively does what you described, but only for lines where there is at least one tab. Removing the requirement that there be at least one tab isn't a big deal since the string is likely to be short anyway, so I'm open to doing it that way if you prefer!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh you're right, I forgot that \s also matches tabs.

if match:
first_non_whitespace_idx = match.start(1)

lines.append(
line[:first_non_whitespace_idx].expandtabs()
+ line[first_non_whitespace_idx:]
)
else:
lines.append(line)
return lines


def fix_docstring(docstring: str, prefix: str) -> str:
# https://www.python.org/dev/peps/pep-0257/#handling-docstring-indentation
if not docstring:
return ""
# Convert tabs to spaces (following the normal Python rules)
# and split into a list of lines:
lines = docstring.expandtabs().splitlines()
lines = lines_with_leading_tabs_expanded(docstring)
# Determine minimum indentation (first line doesn't count):
indent = sys.maxsize
for line in lines[1:]:
Expand Down
53 changes: 52 additions & 1 deletion tests/data/docstring.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,32 @@ def ignored_docstring():
"""a => \
b"""


def docstring_with_inline_tabs_and_space_indentation():
"""hey

tab separated value
tab at start of line and then a tab separated value
multiple tabs at the beginning and inline
mixed tabs and spaces at beginning. next line has mixed tabs and spaces only.

line ends with some tabs
"""


def docstring_with_inline_tabs_and_tab_indentation():
"""hey

tab separated value
tab at start of line and then a tab separated value
multiple tabs at the beginning and inline
mixed tabs and spaces at beginning. next line has mixed tabs and spaces only.

line ends with some tabs
"""
pass


# output

class MyClass:
Expand Down Expand Up @@ -222,4 +248,29 @@ def believe_it_or_not_this_is_in_the_py_stdlib():

def ignored_docstring():
"""a => \
b"""
b"""


def docstring_with_inline_tabs_and_space_indentation():
"""hey

tab separated value
tab at start of line and then a tab separated value
multiple tabs at the beginning and inline
mixed tabs and spaces at beginning. next line has mixed tabs and spaces only.

line ends with some tabs
"""


def docstring_with_inline_tabs_and_tab_indentation():
"""hey

tab separated value
tab at start of line and then a tab separated value
multiple tabs at the beginning and inline
mixed tabs and spaces at beginning. next line has mixed tabs and spaces only.

line ends with some tabs
"""
pass