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

Improve call chain #4153

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
blocks, except immediately before a docstring (#4130)
- For stubs, fix logic to enforce empty line after nested classes with bodies (#4141)
- Fix crash when using a walrus in a dictionary (#4155)
- When a line contains more than one method call, it is treated as a call chain, and the
fluent style is prioritized for formatting this line (#4153)

### Configuration

Expand Down
5 changes: 3 additions & 2 deletions src/black/brackets.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,9 @@ def is_split_before_delimiter(leaf: Leaf, previous: Optional[Leaf] = None) -> Pr
if (
leaf.type == token.DOT
and leaf.parent
and leaf.parent.type not in {syms.import_from, syms.dotted_name}
and (previous is None or previous.type in CLOSING_BRACKETS)
and leaf.parent.type == syms.trailer
and previous
and previous.type in CLOSING_BRACKETS
):
return DOT_PRIORITY

Expand Down
12 changes: 9 additions & 3 deletions src/black/linegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,9 @@ def _maybe_split_omitting_optional_parens(
and not line.is_import
# and we can actually remove the parens
and can_omit_invisible_parens(rhs, mode.line_length)
and not (
Preview.improve_call_chain in mode and rhs.body.should_use_fluent_style
)
):
omit = {id(rhs.closing_bracket), *omit}
try:
Expand Down Expand Up @@ -1184,9 +1187,12 @@ def delimiter_split(
except ValueError:
raise CannotSplit("No delimiters found") from None

if delimiter_priority == DOT_PRIORITY:
if bt.delimiter_count_with_priority(delimiter_priority) == 1:
raise CannotSplit("Splitting a single attribute from its owner looks wrong")
if (
Copy link
Collaborator

Choose a reason for hiding this comment

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

We might want to keep this one as is, see the comment on the PR.

Copy link
Author

Choose a reason for hiding this comment

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

Sorry, I'm not quite sure which COMMENT you're referring to?

not (Preview.improve_call_chain in mode and line.should_use_fluent_style)
and delimiter_priority == DOT_PRIORITY
and bt.delimiter_count_with_priority(delimiter_priority) == 1
):
raise CannotSplit("Splitting a single attribute from its owner looks wrong")

current_line = Line(
mode=line.mode, depth=line.depth, inside_brackets=line.inside_brackets
Expand Down
25 changes: 25 additions & 0 deletions src/black/lines.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,31 @@ def is_chained_assignment(self) -> bool:
"""Is the line a chained assignment"""
return [leaf.type for leaf in self.leaves].count(token.EQUAL) > 1

@property
def should_use_fluent_style(self) -> bool:
"""Should the line use fluent style?"""
if self.comments:
return False
line_node = self.leaves[0].parent
if not line_node:
return False
is_dot_trailer_open = False
significant_method_call_count = 0
for child in line_node.children:
if isinstance(child, Node) and child.type == syms.trailer:
if child.children[0].type == token.DOT:
is_dot_trailer_open = True
elif (
child.children[0].type in OPENING_BRACKETS
and len(str(child)) > 10
and is_dot_trailer_open
):
is_dot_trailer_open = False
significant_method_call_count += 1
if significant_method_call_count > 1:
return True
return False

@property
def opens_block(self) -> bool:
"""Does this line open a new level of indentation."""
Expand Down
1 change: 1 addition & 0 deletions src/black/mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ class Preview(Enum):
allow_form_feeds = auto()
unify_docstring_detection = auto()
respect_east_asian_width = auto()
improve_call_chain = auto()


class Deprecated(UserWarning):
Expand Down
27 changes: 27 additions & 0 deletions tests/data/cases/preview_call_chain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# flags: --preview
a = str(my_very_very_very_long_url_name.with_user(and_very_long_username).with_password(and_very_long_password))
a = my_very_very_very_long_url_name.with_user(and_very_long_username).with_password(and_very_long_password)


def foo():
completion_time = (a.read_namespaced_job(job.metadata.name, namespace="default").status().completion_time())


# output

a = str(
my_very_very_very_long_url_name.with_user(and_very_long_username)
.with_password(and_very_long_password)
)
a = (
my_very_very_very_long_url_name.with_user(and_very_long_username)
.with_password(and_very_long_password)
)


def foo():
completion_time = (
a.read_namespaced_job(job.metadata.name, namespace="default")
.status()
.completion_time()
)