code-ruby 3.0.11 → 3.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb5a51861ed784ebc8e0fbd2e6e6232ee05a3f3bafbc4f150e5cc92236590254
4
- data.tar.gz: b2a82b40b6740387fde8490e5054cc1a0ab5d39c34e2ce7a694421cf5e0852ed
3
+ metadata.gz: eef80c2ebc4ef6d318ee0af151704caca0f0a370c698dc727814836d6e5146db
4
+ data.tar.gz: 6ac8ae69e473aa937eb4794466e710ec40444c53f5d1a12149368e03de0bc4c0
5
5
  SHA512:
6
- metadata.gz: ca94cf8211ff117ca68db85a48d876a28f857398996b327a88a6488931d29bbdda34bc6d1b893af9196ecc25735a83875533f099cecbc15b5c9ffd248e34d0df
7
- data.tar.gz: 9ecf7d03fffb87b1d7ade9242bc0cc4e75a8ff04b31c57b3c05cb2caba5811c1cb567de362e0c95c5852b2bebc3794df6291adb773b8b4a13da0c7320df03b3f
6
+ metadata.gz: 6b99011ea5c71b86b7709949e2d4096f4708b2a4c985e1f30e704bac060c58c710922797c4c4ade47f91b31ad37a47153f66fd8e204783786d55da38234e0d79
7
+ data.tar.gz: 367d671e1fbb4aaacd278bd3e186a03a7304f7041f5381e502a54e44afe754048ed603eb83e6e21f2c8415916078b633fc352984ae6c5b4ab27f96c330394b19
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- code-ruby (3.0.11)
4
+ code-ruby (3.0.12)
5
5
  activesupport
6
6
  base64
7
7
  bigdecimal
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.0.11
1
+ 3.0.12
data/lib/code/format.rb CHANGED
@@ -57,13 +57,20 @@ class Code
57
57
  "begin\n#{body}\n#{INDENT * indent}end"
58
58
  end
59
59
 
60
+ def format_group(group, indent:)
61
+ formatted = format_code_inline(group, indent: indent)
62
+ return "(#{formatted})" unless formatted.include?("\n")
63
+
64
+ "(\n#{indent_lines(normalize_group_indentation(formatted, indent: indent), indent + 1)}\n#{INDENT * indent})"
65
+ end
66
+
60
67
  def format_statement(statement, indent:)
61
68
  if statement.is_a?(Hash) && statement.key?(:nothing)
62
69
  statement[:nothing].presence || "nothing"
63
70
  elsif statement.is_a?(Hash) && statement.key?(:boolean)
64
71
  statement[:boolean]
65
72
  elsif statement.is_a?(Hash) && statement.key?(:group)
66
- "(#{format_code_inline(statement[:group], indent: indent)})"
73
+ format_group(statement[:group], indent: indent)
67
74
  elsif statement.is_a?(Hash) && statement.key?(:call)
68
75
  format_call(statement[:call], indent: indent)
69
76
  elsif statement.is_a?(Hash) && statement.key?(:number)
@@ -431,7 +438,7 @@ class Code
431
438
 
432
439
  expression =
433
440
  if compact_operator?(operator)
434
- "#{expression}#{operator}#{right}"
441
+ "#{format_compact_receiver(expression, indent: indent)}#{operator}#{right}"
435
442
  else
436
443
  candidate = "#{expression} #{operator} #{right}"
437
444
  if right.include?("\n")
@@ -449,8 +456,10 @@ class Code
449
456
  right_lines =
450
457
  if right.include?("\n")
451
458
  right.lines(chomp: true).map(&:lstrip)
452
- else
459
+ elsif %w[and or].include?(operator)
453
460
  right.split(" #{operator} ")
461
+ else
462
+ [right]
454
463
  end
455
464
  continuation_lines =
456
465
  right_lines.map do |line|
@@ -469,6 +478,34 @@ class Code
469
478
  expression
470
479
  end
471
480
 
481
+ def format_compact_receiver(expression, indent:)
482
+ return expression unless compact_receiver_needs_parentheses?(expression)
483
+
484
+ "(\n#{indent_lines(expression, indent + 1)}\n#{INDENT * indent})"
485
+ end
486
+
487
+ def compact_receiver_needs_parentheses?(expression)
488
+ return false unless expression.include?("\n")
489
+ return false if expression.lstrip.start_with?("(")
490
+
491
+ continuation_lines =
492
+ expression
493
+ .lines(chomp: true)[1..]
494
+ .to_a
495
+ .reject { |line| line.strip.empty? || line.strip.match?(/\A[\)\]\}]+\z/) }
496
+
497
+ return false if continuation_lines.empty?
498
+
499
+ base_indent =
500
+ continuation_lines.map { |line| line[/\A */].to_s.length }.min
501
+
502
+ continuation_lines.any? do |line|
503
+ next false unless line[/\A */].to_s.length == base_indent
504
+
505
+ line.lstrip.match?(/\A(\+|-|\*|\/|%|<<|>>|\||\^|&|and\b|or\b)/)
506
+ end
507
+ end
508
+
472
509
  def extract_string_concatenation_parts(statement)
473
510
  return nil unless statement.is_a?(Hash)
474
511
 
@@ -496,8 +533,14 @@ class Code
496
533
 
497
534
  def format_right_operation(operation, indent:)
498
535
  operator = operation[:operator].to_s
499
- left = format_nested_statement(operation[:left], indent: indent)
536
+ left =
537
+ if %w[if unless].include?(operator)
538
+ format_modifier_left(operation[:left], indent: indent)
539
+ else
540
+ format_nested_statement(operation[:left], indent: indent)
541
+ end
500
542
  right = format_nested_statement(operation[:right], indent: indent)
543
+
501
544
  if right.include?("\n")
502
545
  first_line, *rest = right.lines(chomp: true)
503
546
  first_line = first_line.lstrip
@@ -512,6 +555,42 @@ class Code
512
555
  "#{left} #{operator} #{right}"
513
556
  end
514
557
 
558
+ def format_modifier_left(statement, indent:)
559
+ if statement.is_a?(Hash) && statement.key?(:right_operation)
560
+ nested = statement[:right_operation]
561
+ nested_operator = nested[:operator].to_s
562
+ if nested_operator == "="
563
+ left = format_nested_statement(nested[:left], indent: indent)
564
+ right = format_nested_statement(nested[:right], indent: indent)
565
+ return "#{left} #{nested_operator} #{group_multiline_expression(right, indent: indent)}"
566
+ end
567
+ end
568
+
569
+ format_nested_statement(statement, indent: indent)
570
+ end
571
+
572
+ def group_multiline_expression(expression, indent:)
573
+ return expression unless expression.include?("\n")
574
+ return expression if expression.lstrip.start_with?("(")
575
+
576
+ normalized = normalize_group_indentation(expression, indent: indent)
577
+ "(\n#{indent_lines(normalized, indent + 1)}\n#{INDENT * indent})"
578
+ end
579
+
580
+ def normalize_group_indentation(expression, indent:)
581
+ prefix = INDENT * indent
582
+
583
+ expression
584
+ .lines(chomp: true)
585
+ .map
586
+ .with_index do |line, index|
587
+ next line if index.zero? || line.empty? || prefix.empty?
588
+
589
+ line.delete_prefix(prefix)
590
+ end
591
+ .join("\n")
592
+ end
593
+
515
594
  def compact_operator?(operator)
516
595
  %w[. :: &. .. ...].include?(operator)
517
596
  end
@@ -519,9 +598,11 @@ class Code
519
598
  def format_ternary(ternary, indent:)
520
599
  left = format_nested_statement(ternary[:left], indent: indent)
521
600
  middle = format_nested_statement(ternary[:middle], indent: indent)
601
+ middle = group_multiline_expression(middle, indent: indent)
522
602
  return "#{left} ? #{middle}" unless ternary.key?(:right)
523
603
 
524
604
  right = format_nested_statement(ternary[:right], indent: indent)
605
+ right = group_multiline_expression(right, indent: indent)
525
606
  "#{left} ? #{middle} : #{right}"
526
607
  end
527
608
 
@@ -654,7 +735,7 @@ class Code
654
735
 
655
736
  def indent_lines(value, indent)
656
737
  prefix = INDENT * indent
657
- value.split("\n").map { |line| "#{prefix}#{line}" }.join("\n")
738
+ value.split("\n").map { |line| line.empty? ? "" : "#{prefix}#{line}" }.join("\n")
658
739
  end
659
740
 
660
741
  def statement_separator(inline:, indent: nil)
@@ -58,16 +58,34 @@ RSpec.describe Code::Format do
58
58
  ],
59
59
  [
60
60
  "safe = post.present? and !post[:over_18] and post[:post_hint] == :image and post[:url].to_string.strip.presence and (post[:url].to_string.strip.ends_with?(\".jpg\") or post[:url].to_string.strip.ends_with?(\".jpeg\") or post[:url].to_string.strip.ends_with?(\".png\") or post[:url].to_string.strip.include?(\"i.redd.it\"))",
61
- "safe = post.present?\n and !post[:over_18]\n and post[:post_hint] == :image\n and post[:url].to_string.strip.presence\n and (post[:url].to_string.strip.ends_with?(\".jpg\")\n or post[:url].to_string.strip.ends_with?(\".jpeg\")\n or post[:url].to_string.strip.ends_with?(\".png\")\n or post[:url].to_string.strip.include?(\"i.redd.it\"))"
61
+ "safe = post.present?\n and !post[:over_18]\n and post[:post_hint] == :image\n and post[:url].to_string.strip.presence\n and (\n post[:url].to_string.strip.ends_with?(\".jpg\")\n or post[:url].to_string.strip.ends_with?(\".jpeg\")\n or post[:url].to_string.strip.ends_with?(\".png\")\n or post[:url].to_string.strip.include?(\"i.redd.it\")\n)"
62
62
  ],
63
63
  [
64
64
  "items.each { |item, index| proxied_image_url = if image_url proxy_url(image_url) else nothing end }",
65
65
  "items.each { |item, index|\n proxied_image_url = if image_url\n proxy_url(image_url)\n else\n nothing\n end\n}"
66
+ ],
67
+ [
68
+ "lines << \"vacances scolaires france {zone} (prochains {months_ahead} mois) :\".downcase",
69
+ "lines << (\n \"vacances scolaires france {zone} (prochains \"\n + \"{months_ahead} mois) :\"\n).downcase"
70
+ ],
71
+ [
72
+ "src = \"https://proxy.dorianmarie.com?\" + \"{{ url: src, disposition: :inline }.to_query}\" if src",
73
+ "src = (\n \"https://proxy.dorianmarie.com?\"\n + \"{{ url: src, disposition: :inline }.to_query}\"\n) if src"
74
+ ],
75
+ [
76
+ "inline_image = image ? \"https://proxy.dorianmarie.com?\" + \"{inline_params.to_query}\" : nothing",
77
+ "inline_image = image ? (\n \"https://proxy.dorianmarie.com?\"\n + \"{inline_params.to_query}\"\n) : nothing"
66
78
  ]
67
79
  ].each do |input, expected|
68
80
  it "formats #{input.inspect}" do
69
81
  expect(described_class.format(Code.parse(input))).to eq(expected)
70
82
  end
83
+
84
+ it "formats #{input.inspect} idempotently" do
85
+ formatted = described_class.format(Code.parse(input))
86
+
87
+ expect(described_class.format(Code.parse(formatted))).to eq(formatted)
88
+ end
71
89
  end
72
90
 
73
91
  it "round-trips parse and evaluation semantics for formatted code" do
@@ -77,5 +95,50 @@ RSpec.describe Code::Format do
77
95
  expect(Code.parse(formatted)).to be_present
78
96
  expect(Code.evaluate(formatted)).to eq(Code.evaluate(input))
79
97
  end
98
+
99
+ it "keeps grouped multiline receivers stable" do
100
+ input = <<~CODE.chomp
101
+ lines << (
102
+ "vacances scolaires france {zone} (prochains "
103
+ + "{months_ahead} mois) :"
104
+ ).downcase
105
+ CODE
106
+
107
+ expect(described_class.format(Code.parse(input))).to eq(input)
108
+ end
109
+
110
+ it "does not split operators inside string interpolations when wrapping" do
111
+ input =
112
+ %q(body_text = "{title}\n\n{description}\n\ningrédients :\n" + ingredients.map { |ingredient| "- {ingredient}" }.join("\n") + "\n\ninstructions :\n" + instructions.map { |instruction, index| "{index + 1}. {instruction}" }.join("\n"))
113
+
114
+ formatted = described_class.format(Code.parse(input))
115
+
116
+ expect(formatted).to include(
117
+ 'instructions.map { |instruction, index| "{index + 1}. {instruction}" }'
118
+ )
119
+ expect(formatted).not_to include(
120
+ "\"{index\n + 1}. {instruction}\""
121
+ )
122
+ end
123
+
124
+ it "does not emit whitespace-only blank lines" do
125
+ input = <<~CODE.chomp
126
+ body = Html.join(elements.map { |element| title = element.at_css(".x") value = element.at_css(".y") title }, Html.br)
127
+ CODE
128
+
129
+ formatted = described_class.format(Code.parse(input))
130
+
131
+ expect(formatted.lines).not_to include(match(/\A[ \t]+\n\z/))
132
+ end
133
+
134
+ it "keeps lines within 80 characters" do
135
+ input = <<~CODE.chomp
136
+ src = "https://proxy.dorianmarie.com?" + "{{ url: src, disposition: :inline }.to_query}" if src
137
+ CODE
138
+
139
+ formatted = described_class.format(Code.parse(input))
140
+
141
+ expect(formatted.lines.map(&:chomp).map(&:length).max).to be <= 80
142
+ end
80
143
  end
81
144
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: code-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.11
4
+ version: 3.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dorian Marié