Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 65d643d

Browse files
committed
Don't format FCall parentheses unless you need them, add parentheses to not operator in ternaries if you need them
1 parent 846058a commit 65d643d

File tree

3 files changed

+102
-47
lines changed

3 files changed

+102
-47
lines changed

lib/syntax_tree/node.rb

Lines changed: 86 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4018,7 +4018,15 @@ def deconstruct_keys(keys)
40184018

40194019
def format(q)
40204020
q.format(value)
4021-
q.format(arguments)
4021+
4022+
if arguments.is_a?(ArgParen) && arguments.arguments.nil? && !value.is_a?(Const)
4023+
# If you're using an explicit set of parentheses on something that looks
4024+
# like a constant, then we need to match that in order to maintain valid
4025+
# Ruby. For example, you could do something like Foo(), on which we
4026+
# would need to keep the parentheses to make it look like a method call.
4027+
else
4028+
q.format(arguments)
4029+
end
40224030
end
40234031
end
40244032

@@ -4657,6 +4665,52 @@ def self.call(parent)
46574665
end
46584666
end
46594667

4668+
# In order for an `if` or `unless` expression to be shortened to a ternary,
4669+
# there has to be one and only one consequent clause which is an Else. Both
4670+
# the body of the main node and the body of the Else node must have only one
4671+
# statement, and that statement must not be on the denied list of potential
4672+
# statements.
4673+
module Ternaryable
4674+
class << self
4675+
def call(node)
4676+
case node
4677+
in { predicate: Assign | Command | CommandCall | MAssign | OpAssign }
4678+
false
4679+
in { statements: { body: [truthy] }, consequent: Else[statements: { body: [falsy] }] }
4680+
ternaryable?(truthy) && ternaryable?(falsy)
4681+
else
4682+
false
4683+
end
4684+
end
4685+
4686+
private
4687+
4688+
# Certain expressions cannot be reduced to a ternary without adding
4689+
# parentheses around them. In this case we say they cannot be ternaried and
4690+
# default instead to breaking them into multiple lines.
4691+
def ternaryable?(statement)
4692+
# This is a list of nodes that should not be allowed to be a part of a
4693+
# ternary clause.
4694+
no_ternary = [
4695+
Alias, Assign, Break, Command, CommandCall, Heredoc, If, IfMod, IfOp,
4696+
Lambda, MAssign, Next, OpAssign, RescueMod, Return, Return0, Super,
4697+
Undef, Unless, UnlessMod, Until, UntilMod, VarAlias, VoidStmt, While,
4698+
WhileMod, Yield, Yield0, ZSuper
4699+
]
4700+
4701+
# Here we're going to check that the only statement inside the
4702+
# statements node is no a part of our denied list of nodes that can be
4703+
# ternaries.
4704+
#
4705+
# If the user is using one of the lower precedence "and" or "or"
4706+
# operators, then we can't use a ternary expression as it would break
4707+
# the flow control.
4708+
!no_ternary.include?(statement.class) &&
4709+
!(statement.is_a?(Binary) && %i[and or].include?(statement.operator))
4710+
end
4711+
end
4712+
end
4713+
46604714
# Formats an If or Unless node.
46614715
class ConditionalFormatter
46624716
# [String] the keyword associated with this conditional
@@ -4673,7 +4727,7 @@ def initialize(keyword, node)
46734727
def format(q)
46744728
# If we can transform this node into a ternary, then we're going to print
46754729
# a special version that uses the ternary operator if it fits on one line.
4676-
if can_ternary?
4730+
if Ternaryable.call(node)
46774731
format_ternary(q)
46784732
return
46794733
end
@@ -4739,7 +4793,13 @@ def format_ternary(q)
47394793
q.group do
47404794
q.format(node.consequent.keyword)
47414795
q.indent do
4742-
q.breakable
4796+
# This is a very special case of breakable where we want to force
4797+
# it into the output but we _don't_ want to explicitly break the
4798+
# parent. If a break-parent shows up in the tree, then it's going
4799+
# to force it all the way up to the tree, which is going to negate
4800+
# the ternary. Maybe this should be an option in prettyprint? As
4801+
# in force: :no_break_parent or something.
4802+
q.target << PrettyPrint::Breakable.new(" ", 1, force: true)
47434803
q.format(node.consequent.statements)
47444804
end
47454805
end
@@ -4763,53 +4823,13 @@ def format_ternary(q)
47634823
end
47644824

47654825
def contains_conditional?
4766-
node.statements.body.length == 1 &&
4767-
[If, IfMod, IfOp, Unless, UnlessMod].include?(node.statements.body.first.class)
4768-
end
4769-
4770-
# In order for an `if` or `unless` expression to be shortened to a ternary,
4771-
# there has to be one and only one consequent clause which is an Else. Both
4772-
# the body of the main node and the body of the Else node must have only one
4773-
# statement, and that statement must pass the `can_ternary_statements?`
4774-
# check.
4775-
def can_ternary?
47764826
case node
4777-
in { predicate: Assign | Command | CommandCall | MAssign | OpAssign }
4778-
false
4779-
in { consequent: Else[statements:] }
4780-
can_ternary_statements?(statements) &&
4781-
can_ternary_statements?(node.statements)
4827+
in { statements: { body: [If | IfMod | IfOp | Unless | UnlessMod] } }
4828+
true
47824829
else
47834830
false
47844831
end
47854832
end
4786-
4787-
# Certain expressions cannot be reduced to a ternary without adding
4788-
# parentheses around them. In this case we say they cannot be ternaried and
4789-
# default instead to breaking them into multiple lines.
4790-
def can_ternary_statements?(statements)
4791-
return false if statements.body.length != 1
4792-
statement = statements.body.first
4793-
4794-
# This is a list of nodes that should not be allowed to be a part of a
4795-
# ternary clause.
4796-
no_ternary = [
4797-
Alias, Assign, Break, Command, CommandCall, Heredoc, If, IfMod, IfOp,
4798-
Lambda, MAssign, Next, OpAssign, RescueMod, Return, Return0, Super,
4799-
Undef, Unless, UnlessMod, Until, UntilMod, VarAlias, VoidStmt, While,
4800-
WhileMod, Yield, Yield0, ZSuper
4801-
]
4802-
4803-
# Here we're going to check that the only statement inside the statements
4804-
# node is no a part of our denied list of nodes that can be ternaries.
4805-
#
4806-
# If the user is using one of the lower precedence "and" or "or"
4807-
# operators, then we can't use a ternary expression as it would break the
4808-
# flow control.
4809-
#
4810-
!no_ternary.include?(statement.class) &&
4811-
!(statement.is_a?(Binary) && %i[and or].include?(statement.operator))
4812-
end
48134833
end
48144834

48154835
# If represents the first clause in an +if+ chain.
@@ -8348,9 +8368,28 @@ def deconstruct_keys(keys)
83488368
end
83498369

83508370
def format(q)
8351-
q.text(parentheses ? "not(" : "not ")
8371+
parent = q.parents.take(2)[1]
8372+
ternary = (parent.is_a?(If) || parent.is_a?(Unless)) && Ternaryable.call(parent)
8373+
8374+
q.text("not")
8375+
8376+
if parentheses
8377+
q.text("(")
8378+
elsif ternary
8379+
q.if_break { q.text(" ") }.if_flat { q.text("(") }
8380+
else
8381+
q.text(" ")
8382+
end
8383+
83528384
q.format(statement) if statement
8353-
q.text(")") if parentheses
8385+
8386+
if parentheses
8387+
q.text(")")
8388+
elsif ternary
8389+
q.if_break {}.if_flat { q.text(")") }
8390+
else
8391+
q.text(" ")
8392+
end
83548393
end
83558394
end
83568395

test/fixtures/arg_paren.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
foo(bar)
33
%
44
foo()
5+
-
6+
foo
57
%
68
foo(barrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr)
79
-

test/fixtures/not.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,17 @@
88
not(foo)
99
%
1010
not (foo)
11+
%
12+
if foo
13+
not bar
14+
else
15+
baz
16+
end
17+
-
18+
foo ? not(bar) : baz
19+
%
20+
if foooooooooooooooooooooooooooooooooooooooooo
21+
not bar
22+
else
23+
bazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
24+
end

0 commit comments

Comments
 (0)