@@ -4018,7 +4018,15 @@ def deconstruct_keys(keys)
4018
4018
4019
4019
def format ( q )
4020
4020
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
4022
4030
end
4023
4031
end
4024
4032
@@ -4657,6 +4665,52 @@ def self.call(parent)
4657
4665
end
4658
4666
end
4659
4667
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
+
4660
4714
# Formats an If or Unless node.
4661
4715
class ConditionalFormatter
4662
4716
# [String] the keyword associated with this conditional
@@ -4673,7 +4727,7 @@ def initialize(keyword, node)
4673
4727
def format ( q )
4674
4728
# If we can transform this node into a ternary, then we're going to print
4675
4729
# a special version that uses the ternary operator if it fits on one line.
4676
- if can_ternary?
4730
+ if Ternaryable . call ( node )
4677
4731
format_ternary ( q )
4678
4732
return
4679
4733
end
@@ -4739,7 +4793,13 @@ def format_ternary(q)
4739
4793
q . group do
4740
4794
q . format ( node . consequent . keyword )
4741
4795
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 )
4743
4803
q . format ( node . consequent . statements )
4744
4804
end
4745
4805
end
@@ -4763,53 +4823,13 @@ def format_ternary(q)
4763
4823
end
4764
4824
4765
4825
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?
4776
4826
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
4782
4829
else
4783
4830
false
4784
4831
end
4785
4832
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
4813
4833
end
4814
4834
4815
4835
# If represents the first clause in an +if+ chain.
@@ -8348,9 +8368,28 @@ def deconstruct_keys(keys)
8348
8368
end
8349
8369
8350
8370
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
+
8352
8384
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
8354
8393
end
8355
8394
end
8356
8395
0 commit comments