@@ -4671,6 +4671,13 @@ def initialize(keyword, node)
4671
4671
end
4672
4672
4673
4673
def format ( q )
4674
+ # If we can transform this node into a ternary, then we're going to print
4675
+ # a special version that uses the ternary operator if it fits on one line.
4676
+ if can_ternary?
4677
+ format_ternary ( q )
4678
+ return
4679
+ end
4680
+
4674
4681
# If the predicate of the conditional contains an assignment (in which
4675
4682
# case we can't know for certain that that assignment doesn't impact the
4676
4683
# statements inside the conditional) then we can't use the modifier form
@@ -4680,12 +4687,7 @@ def format(q)
4680
4687
return
4681
4688
end
4682
4689
4683
- if contains_conditional?
4684
- format_break ( q , force : true )
4685
- return
4686
- end
4687
-
4688
- if node . consequent || node . statements . empty?
4690
+ if node . consequent || node . statements . empty? || contains_conditional?
4689
4691
q . group { format_break ( q , force : true ) }
4690
4692
else
4691
4693
q . group do
@@ -4722,10 +4724,92 @@ def format_break(q, force:)
4722
4724
q . text ( "end" )
4723
4725
end
4724
4726
4727
+ def format_ternary ( q )
4728
+ q . group do
4729
+ q . if_break do
4730
+ q . text ( "#{ keyword } " )
4731
+ q . nest ( keyword . length + 1 ) { q . format ( node . predicate ) }
4732
+
4733
+ q . indent do
4734
+ q . breakable
4735
+ q . format ( node . statements )
4736
+ end
4737
+
4738
+ q . breakable
4739
+ q . group do
4740
+ q . format ( node . consequent . keyword )
4741
+ q . indent do
4742
+ q . breakable
4743
+ q . format ( node . consequent . statements )
4744
+ end
4745
+ end
4746
+
4747
+ q . breakable
4748
+ q . text ( "end" )
4749
+ end . if_flat do
4750
+ Parentheses . flat ( q ) do
4751
+ q . format ( node . predicate )
4752
+ q . text ( " ? " )
4753
+
4754
+ statements = [ node . statements , node . consequent . statements ]
4755
+ statements . reverse! if keyword == "unless"
4756
+
4757
+ q . format ( statements [ 0 ] )
4758
+ q . text ( " : " )
4759
+ q . format ( statements [ 1 ] )
4760
+ end
4761
+ end
4762
+ end
4763
+ end
4764
+
4725
4765
def contains_conditional?
4726
4766
node . statements . body . length == 1 &&
4727
4767
[ If , IfMod , IfOp , Unless , UnlessMod ] . include? ( node . statements . body . first . class )
4728
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
+ 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 )
4782
+ else
4783
+ false
4784
+ end
4785
+ 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
4729
4813
end
4730
4814
4731
4815
# If represents the first clause in an +if+ chain.
0 commit comments