rfmt 1.4.1 → 1.5.1

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: a17203d9ab18d011d42790fc039889490f78bd9c1eb86701a15e15784bfafd4c
4
- data.tar.gz: ae34f8b79a4d7e64fec13ea9b1a24ae8d1b46f03aeb4c64f302e52a3895c62e6
3
+ metadata.gz: 75dbfbcc5bd176d4dc5394a718354c0d027c6ae2d855e2ee70422b3b17c91c8f
4
+ data.tar.gz: dafa925df2d12a4f055da3e03c777f96fb235eb5fde1b1d28c34ca8ba2ad110d
5
5
  SHA512:
6
- metadata.gz: 390bd25696b21224e5c182b6516a4a1733281a93bae01eb3ae58a787f3d53b430e308a760d1b47adc1389f2bd389b544aa6fe41e6990d0de654c5ead89d8e31f
7
- data.tar.gz: f638716804d25efc8d74cd18c6a2c1e014d459a63a91dea4968ff2e1f21fee4c51890781051e6c92e2c5cac6e924687b531d9ac412d1e9eae6bfef8155b1b166
6
+ metadata.gz: 6444ec42f910b398e3129dbb9028577a9eb893de5224fcf35bd1ccc961142b6a9f33bda5f26c369ca6b8c569a5d26c166a81526c323f1cf7483d639e6e8dbc14
7
+ data.tar.gz: 4c013c8e0f7263c472c38cdbbdb12990f517e66fa5be011b9d7535ccb2cba473ba9ed958e0b5ff7fc789c693bc39c47bafa3cdfdb7bf06f6d55ba6d4b5b2e6a3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.5.1] - 2026-02-21
4
+
5
+ ### Fixed
6
+ - Fix inline modifier `if`/`unless` formatting (#87)
7
+ - Fix heredoc command incorrectly removed (#90, #86)
8
+ - Fix method chain command dependency handling (#89, #85)
9
+
10
+ ### Added
11
+ - Heredoc comment deletion support
12
+ - Place block loop emission support
13
+
14
+ ### Changed
15
+ - Update README.md
16
+ - Code formatting improvements (RuboCop compliance)
17
+
18
+ ## [1.5.0] - 2026-01-25
19
+
20
+ ### Added
21
+ - Docker Compose test environment setup (#84)
22
+ - Support for `then` expression emission (#80)
23
+ - CI support for Ruby 3.4 and Ruby 4 (#82)
24
+
25
+ ### Changed
26
+ - Upgrade Magnus (Rust-Ruby FFI library) (#83)
27
+ - Optimize Docker build with multi-stage and caching (#84)
28
+ - Upgrade unicode-emoji dependency
29
+
30
+ ### Fixed
31
+ - Preserve heredoc content and closing identifier (#81)
32
+ - Fix `rescue`/`ensure` clauses being deleted inside `do...end` blocks (#78)
33
+ - Fix inline comment node handling (#77)
34
+ - Fix BTreeMap range error (Issue #71)
35
+
3
36
  ## [1.4.1] - 2026-01-17
4
37
 
5
38
  ### Fixed
data/Cargo.lock CHANGED
@@ -838,9 +838,9 @@ dependencies = [
838
838
 
839
839
  [[package]]
840
840
  name = "magnus"
841
- version = "0.6.4"
841
+ version = "0.8.2"
842
842
  source = "registry+https://github.com/rust-lang/crates.io-index"
843
- checksum = "b1597ef40aa8c36be098249e82c9a20cf7199278ac1c1a1a995eeead6a184479"
843
+ checksum = "3b36a5b126bbe97eb0d02d07acfeb327036c6319fd816139a49824a83b7f9012"
844
844
  dependencies = [
845
845
  "magnus-macros",
846
846
  "rb-sys",
@@ -850,9 +850,9 @@ dependencies = [
850
850
 
851
851
  [[package]]
852
852
  name = "magnus-macros"
853
- version = "0.6.0"
853
+ version = "0.8.0"
854
854
  source = "registry+https://github.com/rust-lang/crates.io-index"
855
- checksum = "5968c820e2960565f647819f5928a42d6e874551cab9d88d75e3e0660d7f71e3"
855
+ checksum = "47607461fd8e1513cb4f2076c197d8092d921a1ea75bd08af97398f593751892"
856
856
  dependencies = [
857
857
  "proc-macro2",
858
858
  "quote",
@@ -1168,9 +1168,9 @@ dependencies = [
1168
1168
 
1169
1169
  [[package]]
1170
1170
  name = "rb-sys-env"
1171
- version = "0.1.2"
1171
+ version = "0.2.3"
1172
1172
  source = "registry+https://github.com/rust-lang/crates.io-index"
1173
- checksum = "a35802679f07360454b418a5d1735c89716bde01d35b1560fc953c1415a0b3bb"
1173
+ checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
1174
1174
 
1175
1175
  [[package]]
1176
1176
  name = "redox_users"
@@ -1214,7 +1214,7 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
1214
1214
 
1215
1215
  [[package]]
1216
1216
  name = "rfmt"
1217
- version = "1.4.1"
1217
+ version = "1.5.1"
1218
1218
  dependencies = [
1219
1219
  "anyhow",
1220
1220
  "clap",
data/README.md CHANGED
@@ -23,6 +23,7 @@ A Ruby code formatter written in Rust
23
23
  ## What is rfmt?
24
24
 
25
25
  [RubyGems reference](https://rubygems.org/gems/rfmt)
26
+ [DeepWiki rfmt](https://deepwiki.com/fs0414/rfmt)
26
27
 
27
28
  **rfmt** is a Ruby code formatter that enforces consistent style across your codebase. Key characteristics:
28
29
 
data/ext/rfmt/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "rfmt"
3
- version = "1.4.1"
3
+ version = "1.5.1"
4
4
  edition = "2021"
5
5
  authors = ["fujitani sora <fujitanisora0414@gmail.com>"]
6
6
  license = "MIT"
@@ -12,8 +12,8 @@ crate-type = ["cdylib"]
12
12
 
13
13
  [dependencies]
14
14
  # Ruby FFI
15
- magnus = { version = "0.6.2" }
16
- rb-sys = "0.9.117"
15
+ magnus = { version = "0.8.2" }
16
+ rb-sys = "0.9.124"
17
17
 
18
18
  # Serialization
19
19
  serde = { version = "1.0", features = ["derive"] }
@@ -144,6 +144,10 @@ impl Emitter {
144
144
  /// Get comment indices in the given line range [start_line, end_line)
145
145
  /// Uses BTreeMap range for O(log n) lookup instead of O(n) iteration
146
146
  fn get_comment_indices_in_range(&self, start_line: usize, end_line: usize) -> Vec<usize> {
147
+ // Guard against invalid range (e.g., endless methods where start_line >= end_line)
148
+ if start_line >= end_line {
149
+ return Vec::new();
150
+ }
147
151
  self.comments_by_line
148
152
  .range(start_line..end_line)
149
153
  .flat_map(|(_, indices)| indices.iter().copied())
@@ -228,6 +232,10 @@ impl Emitter {
228
232
  /// Check if there are any unemitted comments in the given line range
229
233
  /// Uses BTreeMap index for O(log n) lookup instead of O(n) iteration
230
234
  fn has_comments_in_range(&self, start_line: usize, end_line: usize) -> bool {
235
+ // Guard against invalid range (e.g., endless methods where start_line >= end_line)
236
+ if start_line >= end_line {
237
+ return false;
238
+ }
231
239
  self.comments_by_line
232
240
  .range(start_line..end_line)
233
241
  .flat_map(|(_, indices)| indices.iter())
@@ -344,6 +352,31 @@ impl Emitter {
344
352
  false
345
353
  }
346
354
 
355
+ /// Check if the node spans only a single line
356
+ fn is_single_line(&self, node: &Node) -> bool {
357
+ node.location.start_line == node.location.end_line
358
+ }
359
+
360
+ /// Extract and write source text for a node
361
+ fn write_source_text(&mut self, node: &Node) -> Result<()> {
362
+ let start = node.location.start_offset;
363
+ let end = node.location.end_offset;
364
+ if let Some(text) = self.source.get(start..end) {
365
+ write!(self.buffer, "{}", text)?;
366
+ }
367
+ Ok(())
368
+ }
369
+
370
+ /// Extract and write trimmed source text for a node
371
+ fn write_source_text_trimmed(&mut self, node: &Node) -> Result<()> {
372
+ let start = node.location.start_offset;
373
+ let end = node.location.end_offset;
374
+ if let Some(text) = self.source.get(start..end) {
375
+ write!(self.buffer, "{}", text.trim())?;
376
+ }
377
+ Ok(())
378
+ }
379
+
347
380
  /// Emit comments that are within a given line range, preserving blank lines from prev_line
348
381
  /// Uses BTreeMap index for O(log n) lookup instead of O(n) iteration
349
382
  fn emit_comments_in_range_with_prev_line(
@@ -899,25 +932,30 @@ impl Emitter {
899
932
 
900
933
  // Emit conditions with comma separator
901
934
  for (i, cond) in conditions.iter().enumerate() {
902
- let start = cond.location.start_offset;
903
- let end = cond.location.end_offset;
904
- if let Some(text) = self.source.get(start..end) {
905
- write!(self.buffer, "{}", text)?;
906
- }
935
+ self.write_source_text(cond)?;
907
936
  if i < conditions.len() - 1 {
908
937
  write!(self.buffer, ", ")?;
909
938
  }
910
939
  }
911
940
 
912
- self.buffer.push('\n');
913
-
914
- // Emit statements body
915
- if let Some(statements) = node
941
+ let statements = node
916
942
  .children
917
943
  .iter()
918
- .find(|c| matches!(c.node_type, NodeType::StatementsNode))
919
- {
920
- self.emit_statements(statements, indent_level + 1)?;
944
+ .find(|c| matches!(c.node_type, NodeType::StatementsNode));
945
+
946
+ if self.is_single_line(node) {
947
+ // Inline style: when X then Y
948
+ if let Some(statements) = statements {
949
+ write!(self.buffer, " then ")?;
950
+ self.write_source_text(statements)?;
951
+ }
952
+ } else {
953
+ // Multi-line style: when X\n Y
954
+ self.buffer.push('\n');
955
+
956
+ if let Some(statements) = statements {
957
+ self.emit_statements(statements, indent_level + 1)?;
958
+ }
921
959
  }
922
960
 
923
961
  Ok(())
@@ -951,14 +989,7 @@ impl Emitter {
951
989
  // Emit statement
952
990
  if let Some(statements) = node.children.get(1) {
953
991
  if matches!(statements.node_type, NodeType::StatementsNode) {
954
- // Extract the statement text (without extra indentation)
955
- if !self.source.is_empty() {
956
- let start = statements.location.start_offset;
957
- let end = statements.location.end_offset;
958
- if let Some(text) = self.source.get(start..end) {
959
- write!(self.buffer, "{}", text.trim())?;
960
- }
961
- }
992
+ self.write_source_text_trimmed(statements)?;
962
993
  }
963
994
  }
964
995
 
@@ -966,15 +997,10 @@ impl Emitter {
966
997
 
967
998
  // Emit condition
968
999
  if let Some(predicate) = node.children.first() {
969
- if !self.source.is_empty() {
970
- let start = predicate.location.start_offset;
971
- let end = predicate.location.end_offset;
972
- if let Some(text) = self.source.get(start..end) {
973
- write!(self.buffer, "{}", text)?;
974
- }
975
- }
1000
+ self.write_source_text(predicate)?;
976
1001
  }
977
1002
 
1003
+ self.emit_trailing_comments(node.location.end_line)?;
978
1004
  return Ok(());
979
1005
  }
980
1006
 
@@ -991,26 +1017,14 @@ impl Emitter {
991
1017
 
992
1018
  // Emit condition
993
1019
  if let Some(predicate) = node.children.first() {
994
- if !self.source.is_empty() {
995
- let start = predicate.location.start_offset;
996
- let end = predicate.location.end_offset;
997
- if let Some(text) = self.source.get(start..end) {
998
- write!(self.buffer, "{}", text)?;
999
- }
1000
- }
1020
+ self.write_source_text(predicate)?;
1001
1021
  }
1002
1022
 
1003
1023
  write!(self.buffer, " ? ")?;
1004
1024
 
1005
1025
  // Emit then expression
1006
1026
  if let Some(statements) = node.children.get(1) {
1007
- if !self.source.is_empty() {
1008
- let start = statements.location.start_offset;
1009
- let end = statements.location.end_offset;
1010
- if let Some(text) = self.source.get(start..end) {
1011
- write!(self.buffer, "{}", text.trim())?;
1012
- }
1013
- }
1027
+ self.write_source_text_trimmed(statements)?;
1014
1028
  }
1015
1029
 
1016
1030
  write!(self.buffer, " : ")?;
@@ -1018,16 +1032,38 @@ impl Emitter {
1018
1032
  // Emit else expression
1019
1033
  if let Some(else_node) = node.children.get(2) {
1020
1034
  if let Some(else_statements) = else_node.children.first() {
1021
- if !self.source.is_empty() {
1022
- let start = else_statements.location.start_offset;
1023
- let end = else_statements.location.end_offset;
1024
- if let Some(text) = self.source.get(start..end) {
1025
- write!(self.buffer, "{}", text.trim())?;
1026
- }
1027
- }
1035
+ self.write_source_text_trimmed(else_statements)?;
1028
1036
  }
1029
1037
  }
1030
1038
 
1039
+ self.emit_trailing_comments(node.location.end_line)?;
1040
+ return Ok(());
1041
+ }
1042
+
1043
+ // Check for inline then style: "if true then 1 end"
1044
+ // Single line, not postfix, not ternary, no else clause
1045
+ let is_inline_then =
1046
+ !is_elsif && self.is_single_line(node) && node.children.get(2).is_none();
1047
+
1048
+ if is_inline_then {
1049
+ self.emit_comments_before(node.location.start_line, indent_level)?;
1050
+ self.emit_indent(indent_level)?;
1051
+ write!(self.buffer, "{} ", keyword)?;
1052
+
1053
+ // Emit condition
1054
+ if let Some(predicate) = node.children.first() {
1055
+ self.write_source_text(predicate)?;
1056
+ }
1057
+
1058
+ write!(self.buffer, " then ")?;
1059
+
1060
+ // Emit statement
1061
+ if let Some(statements) = node.children.get(1) {
1062
+ self.write_source_text_trimmed(statements)?;
1063
+ }
1064
+
1065
+ write!(self.buffer, " end")?;
1066
+ self.emit_trailing_comments(node.location.end_line)?;
1031
1067
  return Ok(());
1032
1068
  }
1033
1069
 
@@ -1046,14 +1082,7 @@ impl Emitter {
1046
1082
 
1047
1083
  // Emit predicate (condition) - first child
1048
1084
  if let Some(predicate) = node.children.first() {
1049
- // Extract predicate from source
1050
- if !self.source.is_empty() {
1051
- let start = predicate.location.start_offset;
1052
- let end = predicate.location.end_offset;
1053
- if let Some(text) = self.source.get(start..end) {
1054
- write!(self.buffer, "{}", text)?;
1055
- }
1056
- }
1085
+ self.write_source_text(predicate)?;
1057
1086
  }
1058
1087
 
1059
1088
  // Emit trailing comment on same line as if/unless/elsif
@@ -1169,6 +1198,16 @@ impl Emitter {
1169
1198
  if let Some(text) = self.source.get(start..end) {
1170
1199
  // Trim trailing whitespace but preserve the content
1171
1200
  write!(self.buffer, "{}", text.trim_end())?;
1201
+
1202
+ // Mark comments within the extracted range as emitted
1203
+ for (idx, comment) in self.all_comments.iter().enumerate() {
1204
+ if !self.emitted_comment_indices.contains(&idx)
1205
+ && comment.location.start_offset >= start
1206
+ && comment.location.end_offset <= end
1207
+ {
1208
+ self.emitted_comment_indices.insert(idx);
1209
+ }
1210
+ }
1172
1211
  }
1173
1212
  }
1174
1213
 
@@ -1193,14 +1232,27 @@ impl Emitter {
1193
1232
  let mut last_stmt_end_line = block_start_line;
1194
1233
 
1195
1234
  for child in &block_node.children {
1196
- if matches!(child.node_type, NodeType::StatementsNode) {
1197
- self.emit_statements(child, indent_level + 1)?;
1198
- // Track the last statement's end line for blank line preservation
1199
- if let Some(last_child) = child.children.last() {
1200
- last_stmt_end_line = last_child.location.end_line;
1235
+ match &child.node_type {
1236
+ NodeType::StatementsNode => {
1237
+ self.emit_statements(child, indent_level + 1)?;
1238
+ // Track the last statement's end line for blank line preservation
1239
+ if let Some(last_child) = child.children.last() {
1240
+ last_stmt_end_line = last_child.location.end_line;
1241
+ }
1242
+ self.buffer.push('\n');
1243
+ break;
1244
+ }
1245
+ NodeType::BeginNode => {
1246
+ // Block with rescue/ensure/else - delegate to emit_begin
1247
+ // which handles implicit begin (no "begin" keyword)
1248
+ self.emit_begin(child, indent_level + 1)?;
1249
+ self.buffer.push('\n');
1250
+ last_stmt_end_line = child.location.end_line;
1251
+ break;
1252
+ }
1253
+ _ => {
1254
+ // Skip parameter nodes
1201
1255
  }
1202
- self.buffer.push('\n');
1203
- break;
1204
1256
  }
1205
1257
  }
1206
1258
 
@@ -1236,6 +1288,7 @@ impl Emitter {
1236
1288
  fn emit_brace_block(&mut self, block_node: &Node, indent_level: usize) -> Result<()> {
1237
1289
  // Determine if block should be inline or multiline
1238
1290
  let is_multiline = block_node.location.start_line != block_node.location.end_line;
1291
+ let block_end_line = block_node.location.end_line;
1239
1292
 
1240
1293
  if is_multiline {
1241
1294
  // Multiline brace block
@@ -1254,6 +1307,7 @@ impl Emitter {
1254
1307
 
1255
1308
  self.emit_indent(indent_level)?;
1256
1309
  write!(self.buffer, "}}")?;
1310
+ self.emit_trailing_comments(block_end_line)?;
1257
1311
  } else {
1258
1312
  // Inline brace block - extract from source to preserve spacing
1259
1313
  write!(self.buffer, " ")?;
@@ -1263,6 +1317,7 @@ impl Emitter {
1263
1317
  {
1264
1318
  write!(self.buffer, "{}", text)?;
1265
1319
  }
1320
+ self.emit_trailing_comments(block_end_line)?;
1266
1321
  }
1267
1322
 
1268
1323
  Ok(())
@@ -1571,7 +1626,6 @@ impl Emitter {
1571
1626
  match &child.node_type {
1572
1627
  NodeType::InNode => {
1573
1628
  self.emit_in(child, indent_level)?;
1574
- self.buffer.push('\n');
1575
1629
  }
1576
1630
  NodeType::ElseNode => {
1577
1631
  self.emit_indent(indent_level)?;
@@ -1607,19 +1661,25 @@ impl Emitter {
1607
1661
 
1608
1662
  // First child is the pattern
1609
1663
  if let Some(pattern) = node.children.first() {
1610
- let start = pattern.location.start_offset;
1611
- let end = pattern.location.end_offset;
1612
- if let Some(text) = self.source.get(start..end) {
1613
- write!(self.buffer, "{}", text)?;
1614
- }
1664
+ self.write_source_text(pattern)?;
1615
1665
  }
1616
1666
 
1617
- self.buffer.push('\n');
1667
+ if self.is_single_line(node) {
1668
+ // Inline style: in X then Y
1669
+ if let Some(statements) = node.children.get(1) {
1670
+ write!(self.buffer, " then ")?;
1671
+ self.write_source_text(statements)?;
1672
+ }
1673
+ self.buffer.push('\n');
1674
+ } else {
1675
+ // Multi-line style: in X\n Y
1676
+ self.buffer.push('\n');
1618
1677
 
1619
- // Second child is the statements body
1620
- if let Some(statements) = node.children.get(1) {
1621
- if matches!(statements.node_type, NodeType::StatementsNode) {
1622
- self.emit_statements(statements, indent_level + 1)?;
1678
+ // Second child is the statements body
1679
+ if let Some(statements) = node.children.get(1) {
1680
+ if matches!(statements.node_type, NodeType::StatementsNode) {
1681
+ self.emit_statements(statements, indent_level + 1)?;
1682
+ }
1623
1683
  }
1624
1684
  }
1625
1685
 
data/ext/rfmt/src/lib.rs CHANGED
@@ -10,7 +10,7 @@ use policy::SecurityPolicy;
10
10
 
11
11
  use config::Config;
12
12
  use emitter::Emitter;
13
- use magnus::{define_module, function, prelude::*, Error, Ruby};
13
+ use magnus::{function, prelude::*, Error, Ruby};
14
14
  use parser::{PrismAdapter, RubyParser};
15
15
 
16
16
  fn format_ruby_code(ruby: &Ruby, source: String, json: String) -> Result<String, Error> {
@@ -45,10 +45,10 @@ fn rust_version() -> String {
45
45
  }
46
46
 
47
47
  #[magnus::init]
48
- fn init(_ruby: &Ruby) -> Result<(), Error> {
48
+ fn init(ruby: &Ruby) -> Result<(), Error> {
49
49
  logging::RfmtLogger::init();
50
50
 
51
- let module = define_module("Rfmt")?;
51
+ let module = ruby.define_module("Rfmt")?;
52
52
 
53
53
  module.define_singleton_method("format_code", function!(format_ruby_code, 2))?;
54
54
  module.define_singleton_method("parse_to_json", function!(parse_to_json, 1))?;
@@ -108,16 +108,69 @@ module Rfmt
108
108
  # Extract location information from node
109
109
  def self.extract_location(node)
110
110
  loc = node.location
111
+
112
+ # For heredoc nodes, the location only covers the opening tag (<<~CSV)
113
+ # We need to find the maximum end_offset including closing_loc
114
+ end_offset = loc.end_offset
115
+ end_line = loc.end_line
116
+ end_column = loc.end_column
117
+
118
+ # Check this node's closing_loc
119
+ if node.respond_to?(:closing_loc) && node.closing_loc
120
+ closing = node.closing_loc
121
+ if closing.end_offset > end_offset
122
+ end_offset = closing.end_offset
123
+ end_line = closing.end_line
124
+ end_column = closing.end_column
125
+ end
126
+ end
127
+
128
+ # Recursively check all descendant nodes for heredoc closing_loc
129
+ # Issue #74: handled direct children (e.g., LocalVariableWriteNode -> StringNode)
130
+ # Issue #86: handles deeper nesting (e.g., CallNode -> ArgumentsNode -> StringNode)
131
+ max_closing = find_max_closing_loc_recursive(node)
132
+ if max_closing && max_closing[:end_offset] > end_offset
133
+ end_offset = max_closing[:end_offset]
134
+ end_line = max_closing[:end_line]
135
+ end_column = max_closing[:end_column]
136
+ end
137
+
111
138
  {
112
139
  start_line: loc.start_line,
113
140
  start_column: loc.start_column,
114
- end_line: loc.end_line,
115
- end_column: loc.end_column,
141
+ end_line: end_line,
142
+ end_column: end_column,
116
143
  start_offset: loc.start_offset,
117
- end_offset: loc.end_offset
144
+ end_offset: end_offset
118
145
  }
119
146
  end
120
147
 
148
+ # Recursively find the maximum closing_loc among all descendant nodes
149
+ # Returns nil if no closing_loc found, otherwise { end_offset:, end_line:, end_column: }
150
+ def self.find_max_closing_loc_recursive(node, depth: 0)
151
+ return nil if depth > 10
152
+
153
+ max_closing = nil
154
+
155
+ node.child_nodes.compact.each do |child|
156
+ if child.respond_to?(:closing_loc) && child.closing_loc
157
+ closing = child.closing_loc
158
+ if max_closing.nil? || closing.end_offset > max_closing[:end_offset]
159
+ max_closing = {
160
+ end_offset: closing.end_offset,
161
+ end_line: closing.end_line,
162
+ end_column: closing.end_column
163
+ }
164
+ end
165
+ end
166
+
167
+ child_max = find_max_closing_loc_recursive(child, depth: depth + 1)
168
+ max_closing = child_max if child_max && (max_closing.nil? || child_max[:end_offset] > max_closing[:end_offset])
169
+ end
170
+
171
+ max_closing
172
+ end
173
+
121
174
  # Extract child nodes
122
175
  def self.extract_children(node)
123
176
  children = []
@@ -176,6 +229,7 @@ module Rfmt
176
229
  [
177
230
  node.statements,
178
231
  node.rescue_clause,
232
+ node.else_clause,
179
233
  node.ensure_clause
180
234
  ].compact
181
235
  when Prism::EnsureNode
data/lib/rfmt/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rfmt
4
- VERSION = '1.4.1'
4
+ VERSION = '1.5.1'
5
5
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rfmt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.1
4
+ version: 1.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - fujitani sora
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2026-01-17 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: diff-lcs
@@ -113,7 +112,6 @@ metadata:
113
112
  source_code_uri: https://github.com/fs0414/rfmt
114
113
  changelog_uri: https://github.com/fs0414/rfmt/releases
115
114
  ruby_lsp_addon: 'true'
116
- post_install_message:
117
115
  rdoc_options: []
118
116
  require_paths:
119
117
  - lib
@@ -128,8 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
126
  - !ruby/object:Gem::Version
129
127
  version: 3.0.0
130
128
  requirements: []
131
- rubygems_version: 3.5.22
132
- signing_key:
129
+ rubygems_version: 3.6.9
133
130
  specification_version: 4
134
131
  summary: Ruby Formatter impl Rust lang.
135
132
  test_files: []