rfmt 1.2.3 → 1.2.4

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: b5c30419e6083cc4be5e665f6cd4f5d40a725a1790df70ef78cf68638681459c
4
- data.tar.gz: 066f373ebe30238a91b34a3d01acccdfbbd270256f8c0e7ba5c909e280e009b0
3
+ metadata.gz: 7c95517c6abaad1c4bf47425dd4853dcd78bf5a2e47ab2bdc0cae1a3d3698c54
4
+ data.tar.gz: a207798a5811f989a5ebb3405e48e0c86ac60d40b208f141e6d31a83de62dc0d
5
5
  SHA512:
6
- metadata.gz: c17af15dfa819de322a14bde2c318f85d7db27f8d2446f1793550dac256e3309bf4cc3b30269c86cf5bd8aef4269f4666040d7e163b078902c4f75197e72424e
7
- data.tar.gz: c3d5a19ed768dcbd8616321dcdfb4374645e933d1eb89e0530683ff28a4f6aec848870f263d94b69a123315c5f505587d183e8c8fca13c6cbc2e406278c8e3c7
6
+ metadata.gz: 86307164b77b1836c57718c62ac84bbc920fbfc36a7f0367656bc4fadd65cd846df9f45e5d8985a3eff2f49a548a576a8a73cb05e8e78679fe666589e904762c
7
+ data.tar.gz: 69a7f17c2e547616ebea1a109628df5d3695fb9d9359715d90707a51c4d1c2d72095bad48ffc1d370e7229a01f193392235c3c7c2a0f13fd211dc0885ef2c447
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.2.4] - 2026-01-04
4
+
5
+ ### Fixed
6
+ - Fix comment indent space handling
7
+
3
8
  ## [1.2.3] - 2026-01-04
4
9
 
5
10
  ### Fixed
data/Cargo.lock CHANGED
@@ -1219,7 +1219,7 @@ checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
1219
1219
 
1220
1220
  [[package]]
1221
1221
  name = "rfmt"
1222
- version = "1.2.3"
1222
+ version = "1.2.4"
1223
1223
  dependencies = [
1224
1224
  "anyhow",
1225
1225
  "clap",
data/ext/rfmt/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "rfmt"
3
- version = "1.2.3"
3
+ version = "1.2.4"
4
4
  edition = "2021"
5
5
  authors = ["fujitani sora <fujitanisora0414@gmail.com>"]
6
6
  license = "MIT"
@@ -50,8 +50,11 @@ impl Emitter {
50
50
 
51
51
  self.emit_node(ast, 0)?;
52
52
 
53
+ // Find the last emitted code line for proper blank line handling
54
+ let last_code_line = Self::find_last_code_line(ast);
55
+
53
56
  // Emit any remaining comments that weren't emitted
54
- self.emit_remaining_comments()?;
57
+ self.emit_remaining_comments(last_code_line)?;
55
58
 
56
59
  // Ensure file ends with a newline
57
60
  if !self.buffer.ends_with('\n') {
@@ -61,14 +64,30 @@ impl Emitter {
61
64
  Ok(self.buffer.clone())
62
65
  }
63
66
 
67
+ /// Find the last line of code in the AST (excluding comments)
68
+ fn find_last_code_line(ast: &Node) -> usize {
69
+ let mut max_line = ast.location.end_line;
70
+ for child in &ast.children {
71
+ let child_end = Self::find_last_code_line(child);
72
+ if child_end > max_line {
73
+ max_line = child_end;
74
+ }
75
+ }
76
+ max_line
77
+ }
78
+
64
79
  /// Emit all comments that haven't been emitted yet
65
- fn emit_remaining_comments(&mut self) -> Result<()> {
66
- let mut last_end_line: Option<usize> = None;
80
+ fn emit_remaining_comments(&mut self, last_code_line: usize) -> Result<()> {
81
+ let mut last_end_line: Option<usize> = Some(last_code_line);
67
82
  for (idx, comment) in self.all_comments.iter().enumerate() {
68
83
  if self.emitted_comment_indices.contains(&idx) {
69
84
  continue;
70
85
  }
71
- // Preserve blank lines between comments
86
+ // Ensure we start on a new line for remaining comments
87
+ if !self.buffer.ends_with('\n') {
88
+ self.buffer.push('\n');
89
+ }
90
+ // Preserve blank lines between code/comments
72
91
  if let Some(prev_line) = last_end_line {
73
92
  let gap = comment.location.start_line.saturating_sub(prev_line);
74
93
  for _ in 1..gap {
@@ -104,16 +123,37 @@ impl Emitter {
104
123
  }
105
124
 
106
125
  if comment.location.end_line < line {
107
- comments_to_emit.push((idx, comment.text.clone(), comment.location.end_line));
126
+ comments_to_emit.push((
127
+ idx,
128
+ comment.text.clone(),
129
+ comment.location.start_line,
130
+ comment.location.end_line,
131
+ ));
108
132
  }
109
133
  }
110
134
 
135
+ // Sort by start_line to emit in order
136
+ comments_to_emit.sort_by_key(|(_, _, start, _)| *start);
137
+
111
138
  let comments_count = comments_to_emit.len();
112
- for (i, (idx, text, comment_end_line)) in comments_to_emit.into_iter().enumerate() {
139
+ let mut last_comment_end_line: Option<usize> = None;
140
+
141
+ for (i, (idx, text, comment_start_line, comment_end_line)) in
142
+ comments_to_emit.into_iter().enumerate()
143
+ {
144
+ // Preserve blank lines between comments
145
+ if let Some(prev_end) = last_comment_end_line {
146
+ let gap = comment_start_line.saturating_sub(prev_end);
147
+ for _ in 1..gap {
148
+ self.buffer.push('\n');
149
+ }
150
+ }
151
+
113
152
  writeln!(self.buffer, "{}{}", indent_str, text)?;
114
153
  self.emitted_comment_indices.push(idx);
154
+ last_comment_end_line = Some(comment_end_line);
115
155
 
116
- // Only add blank line after the LAST comment if there was a gap in the original
156
+ // Add blank line after the LAST comment if there was a gap to the code
117
157
  if i == comments_count - 1 && line > comment_end_line + 1 {
118
158
  self.buffer.push('\n');
119
159
  }
@@ -122,6 +162,65 @@ impl Emitter {
122
162
  Ok(())
123
163
  }
124
164
 
165
+ /// Check if there are any unemitted comments in the given line range
166
+ fn has_comments_in_range(&self, start_line: usize, end_line: usize) -> bool {
167
+ self.all_comments.iter().enumerate().any(|(idx, comment)| {
168
+ !self.emitted_comment_indices.contains(&idx)
169
+ && comment.location.start_line >= start_line
170
+ && comment.location.end_line < end_line
171
+ })
172
+ }
173
+
174
+ /// Emit comments that are within a given line range (exclusive of end_line)
175
+ fn emit_comments_in_range(
176
+ &mut self,
177
+ start_line: usize,
178
+ end_line: usize,
179
+ indent_level: usize,
180
+ ) -> Result<()> {
181
+ let indent_str = match self.config.formatting.indent_style {
182
+ IndentStyle::Spaces => " ".repeat(self.config.formatting.indent_width * indent_level),
183
+ IndentStyle::Tabs => "\t".repeat(indent_level),
184
+ };
185
+
186
+ let mut comments_to_emit = Vec::new();
187
+ for (idx, comment) in self.all_comments.iter().enumerate() {
188
+ if self.emitted_comment_indices.contains(&idx) {
189
+ continue;
190
+ }
191
+
192
+ if comment.location.start_line >= start_line && comment.location.end_line < end_line {
193
+ comments_to_emit.push((
194
+ idx,
195
+ comment.text.clone(),
196
+ comment.location.start_line,
197
+ comment.location.end_line,
198
+ ));
199
+ }
200
+ }
201
+
202
+ // Sort by start_line to emit in order
203
+ comments_to_emit.sort_by_key(|(_, _, start, _)| *start);
204
+
205
+ let mut last_comment_end_line: Option<usize> = None;
206
+
207
+ for (idx, text, comment_start_line, comment_end_line) in comments_to_emit {
208
+ // Preserve blank lines between comments
209
+ if let Some(prev_end) = last_comment_end_line {
210
+ let gap = comment_start_line.saturating_sub(prev_end);
211
+ for _ in 1..gap {
212
+ self.buffer.push('\n');
213
+ }
214
+ }
215
+
216
+ writeln!(self.buffer, "{}{}", indent_str, text)?;
217
+ self.emitted_comment_indices.push(idx);
218
+ last_comment_end_line = Some(comment_end_line);
219
+ }
220
+
221
+ Ok(())
222
+ }
223
+
125
224
  /// Emit comments that appear on the same line (trailing comments)
126
225
  fn emit_trailing_comments(&mut self, line: usize) -> Result<()> {
127
226
  let mut indices_to_emit = Vec::new();
@@ -249,6 +348,9 @@ impl Emitter {
249
348
  // Emit body (children), but skip structural nodes (class name, superclass)
250
349
  // Use start_line check to properly handle CallNode superclasses like ActiveRecord::Migration[8.0]
251
350
  let class_start_line = node.location.start_line;
351
+ let class_end_line = node.location.end_line;
352
+ let mut has_body_content = false;
353
+
252
354
  for child in &node.children {
253
355
  // Skip nodes on the same line as class definition (name, superclass)
254
356
  if child.location.start_line == class_start_line {
@@ -257,13 +359,18 @@ impl Emitter {
257
359
  if self.is_structural_node(&child.node_type) {
258
360
  continue;
259
361
  }
362
+ has_body_content = true;
260
363
  self.emit_node(child, indent_level + 1)?;
261
364
  }
262
365
 
263
- // Add newline before end if there was body content
264
- if node.children.iter().any(|c| {
265
- c.location.start_line != class_start_line && !self.is_structural_node(&c.node_type)
266
- }) {
366
+ // Emit comments that are inside the class body but not attached to any node
367
+ // These are comments between class_start_line and class_end_line
368
+ self.emit_comments_in_range(class_start_line + 1, class_end_line, indent_level + 1)?;
369
+
370
+ // Add newline before end if there was body content or internal comments
371
+ if (has_body_content || self.has_comments_in_range(class_start_line + 1, class_end_line))
372
+ && !self.buffer.ends_with('\n')
373
+ {
267
374
  self.buffer.push('\n');
268
375
  }
269
376
 
@@ -289,19 +396,25 @@ impl Emitter {
289
396
  self.emit_trailing_comments(node.location.start_line)?;
290
397
  self.buffer.push('\n');
291
398
 
399
+ let module_start_line = node.location.start_line;
400
+ let module_end_line = node.location.end_line;
401
+ let mut has_body_content = false;
402
+
292
403
  // Emit body (children), but skip structural nodes
293
404
  for child in &node.children {
294
405
  if self.is_structural_node(&child.node_type) {
295
406
  continue;
296
407
  }
408
+ has_body_content = true;
297
409
  self.emit_node(child, indent_level + 1)?;
298
410
  }
299
411
 
300
- // Add newline before end if there was body content
301
- if node
302
- .children
303
- .iter()
304
- .any(|c| !self.is_structural_node(&c.node_type))
412
+ // Emit comments that are inside the module body but not attached to any node
413
+ self.emit_comments_in_range(module_start_line + 1, module_end_line, indent_level + 1)?;
414
+
415
+ // Add newline before end if there was body content or internal comments
416
+ if (has_body_content || self.has_comments_in_range(module_start_line + 1, module_end_line))
417
+ && !self.buffer.ends_with('\n')
305
418
  {
306
419
  self.buffer.push('\n');
307
420
  }
@@ -463,7 +576,8 @@ impl Emitter {
463
576
  }
464
577
  } else if expect_continuation {
465
578
  // Continuation line after trailing comma or backslash
466
- if !rescue_decl.ends_with(' ') && !rescue_decl.ends_with(',') {
579
+ // Add space after comma or if no trailing space
580
+ if !rescue_decl.ends_with(' ') {
467
581
  rescue_decl.push(' ');
468
582
  }
469
583
  let content = trimmed.trim_end_matches('\\').trim();
@@ -995,6 +1109,20 @@ impl Emitter {
995
1109
  if let Some(text) = text_owned {
996
1110
  self.emit_indent(indent_level)?;
997
1111
  write!(self.buffer, "{}", text)?;
1112
+
1113
+ // Mark comments that are strictly inside this node's line range as emitted
1114
+ // (they are included in the source extraction)
1115
+ // Don't mark trailing comments on the last line (they come after the node ends)
1116
+ for (idx, comment) in self.all_comments.iter().enumerate() {
1117
+ if !self.emitted_comment_indices.contains(&idx)
1118
+ && comment.location.start_line >= node.location.start_line
1119
+ && comment.location.end_line < node.location.end_line
1120
+ {
1121
+ self.emitted_comment_indices.push(idx);
1122
+ }
1123
+ }
1124
+
1125
+ // Emit trailing comments on the same line (after the node ends)
998
1126
  self.emit_trailing_comments(node.location.end_line)?;
999
1127
  }
1000
1128
  }
@@ -1015,12 +1143,13 @@ impl Emitter {
1015
1143
  self.emit_indent(indent_level)?;
1016
1144
  write!(self.buffer, "{}", text)?;
1017
1145
 
1018
- // Mark comments within this node's range as emitted
1146
+ // Mark comments that are strictly inside this node's line range as emitted
1019
1147
  // (they are included in the source extraction)
1148
+ // Don't mark trailing comments on the last line (they come after the node ends)
1020
1149
  for (idx, comment) in self.all_comments.iter().enumerate() {
1021
1150
  if !self.emitted_comment_indices.contains(&idx)
1022
1151
  && comment.location.start_line >= node.location.start_line
1023
- && comment.location.end_line <= node.location.end_line
1152
+ && comment.location.end_line < node.location.end_line
1024
1153
  {
1025
1154
  self.emitted_comment_indices.push(idx);
1026
1155
  }
data/lib/rfmt/rfmt.so CHANGED
Binary file
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.2.3'
4
+ VERSION = '1.2.4'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rfmt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.3
4
+ version: 1.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - fujitani sora