rfmt 1.4.0 → 1.4.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: cb4f41a4ba34f1bc106746712bf144fe6f5ee8cf5a80cf26efaecc8450645d89
4
- data.tar.gz: 94f896c28015d9592d959ef3a42b4db80e309ad664ef97d5f28b0e8479605892
3
+ metadata.gz: a17203d9ab18d011d42790fc039889490f78bd9c1eb86701a15e15784bfafd4c
4
+ data.tar.gz: ae34f8b79a4d7e64fec13ea9b1a24ae8d1b46f03aeb4c64f302e52a3895c62e6
5
5
  SHA512:
6
- metadata.gz: 0aef4d71101fd621bcecf87b0160e437ec9f7836e12759dbb28cd56416d1428bb3b54aabae888566548dc11557d1179e519aa5c971e613c3c37c775590eef981
7
- data.tar.gz: 3ee34d6c250fdd9263822a8b361dc3fdde5005c36570a5a6a0be346f717090c581822c90cd08bee43674495c97a15fb22677558bd5be5b85f9ccd4c1d4fd9e51
6
+ metadata.gz: 390bd25696b21224e5c182b6516a4a1733281a93bae01eb3ae58a787f3d53b430e308a760d1b47adc1389f2bd389b544aa6fe41e6990d0de654c5ead89d8e31f
7
+ data.tar.gz: f638716804d25efc8d74cd18c6a2c1e014d459a63a91dea4968ff2e1f21fee4c51890781051e6c92e2c5cac6e924687b531d9ac412d1e9eae6bfef8155b1b166
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.4.1] - 2026-01-17
4
+
5
+ ### Fixed
6
+ - Fixed comment positioning issue where standalone comments before `end` statements were incorrectly attached to previous code lines
7
+ - Improved comment semantic preservation to maintain developer's original placement intent
8
+ - Enhanced standalone comment detection logic to distinguish between inline and independent comments
9
+
3
10
  ## [1.4.0] - 2026-01-17
4
11
 
5
12
  ### Added
data/Cargo.lock CHANGED
@@ -1214,7 +1214,7 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
1214
1214
 
1215
1215
  [[package]]
1216
1216
  name = "rfmt"
1217
- version = "1.4.0"
1217
+ version = "1.4.1"
1218
1218
  dependencies = [
1219
1219
  "anyhow",
1220
1220
  "clap",
data/ext/rfmt/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "rfmt"
3
- version = "1.4.0"
3
+ version = "1.4.1"
4
4
  edition = "2021"
5
5
  authors = ["fujitani sora <fujitanisora0414@gmail.com>"]
6
6
  license = "MIT"
@@ -237,34 +237,58 @@ impl Emitter {
237
237
  })
238
238
  }
239
239
 
240
- /// Emit comments that are within a given line range (exclusive of end_line)
241
- /// Uses BTreeMap index for O(log n) lookup instead of O(n) iteration
242
- fn emit_comments_in_range(
240
+ /// Emit comments that appear immediately before the end statement while preserving their position
241
+ /// This is crucial for maintaining semantic relationships between comments and the code they precede
242
+ fn emit_comments_before_end(
243
243
  &mut self,
244
- start_line: usize,
245
- end_line: usize,
244
+ construct_start_line: usize,
245
+ construct_end_line: usize,
246
246
  indent_level: usize,
247
247
  ) -> Result<()> {
248
248
  self.ensure_indent_cache(indent_level);
249
249
 
250
- let indices = self.get_comment_indices_in_range(start_line, end_line);
250
+ // Implement proper comment positioning logic
251
+ // Only emit standalone comments that appear on their own lines
252
+ // This prevents comments from being incorrectly attached to code statements
253
+
254
+ // Find comments that are between the construct and the end line
255
+ // Only emit comments that haven't been emitted yet AND are on their own lines
256
+ let indices =
257
+ self.get_comment_indices_in_range(construct_start_line + 1, construct_end_line);
251
258
 
252
259
  let mut comments_to_emit: Vec<_> = indices
253
260
  .into_iter()
254
- .filter(|&idx| self.all_comments[idx].location.end_line < end_line)
261
+ .filter(|&idx| {
262
+ let comment = &self.all_comments[idx];
263
+ // Only emit if: not already emitted, before end line, and is standalone
264
+ !self.emitted_comment_indices.contains(&idx)
265
+ && comment.location.end_line < construct_end_line
266
+ && self.is_standalone_comment(comment)
267
+ })
255
268
  .map(|idx| {
256
269
  let comment = &self.all_comments[idx];
257
270
  (idx, comment.location.start_line, comment.location.end_line)
258
271
  })
259
272
  .collect();
260
273
 
274
+ if comments_to_emit.is_empty() {
275
+ return Ok(());
276
+ }
277
+
261
278
  comments_to_emit.sort_by_key(|(_, start, _)| *start);
262
279
 
263
- let mut last_comment_end_line: Option<usize> = None;
280
+ // Ensure newline before first comment if buffer doesn't end with one
281
+ if !self.buffer.ends_with('\n') {
282
+ self.buffer.push('\n');
283
+ }
264
284
 
285
+ let mut last_emitted_line: Option<usize> = None;
286
+
287
+ // Emit comments while preserving their exact line positioning
265
288
  for (idx, comment_start_line, comment_end_line) in comments_to_emit {
266
- if let Some(prev_end) = last_comment_end_line {
267
- let gap = comment_start_line.saturating_sub(prev_end);
289
+ // Preserve blank lines between comments
290
+ if let Some(prev_line) = last_emitted_line {
291
+ let gap = comment_start_line.saturating_sub(prev_line);
268
292
  for _ in 1..gap {
269
293
  self.buffer.push('\n');
270
294
  }
@@ -276,12 +300,50 @@ impl Emitter {
276
300
  &self.indent_cache[indent_level], &self.all_comments[idx].text
277
301
  )?;
278
302
  self.emitted_comment_indices.insert(idx);
279
- last_comment_end_line = Some(comment_end_line);
303
+ last_emitted_line = Some(comment_end_line);
280
304
  }
281
305
 
282
306
  Ok(())
283
307
  }
284
308
 
309
+ /// Check if a comment should be treated as standalone
310
+ /// A standalone comment is one that should appear on its own line,
311
+ /// not attached to the end of a code statement
312
+ fn is_standalone_comment(&self, comment: &Comment) -> bool {
313
+ let comment_line = comment.location.start_line;
314
+ let _comment_start_offset = comment.location.start_offset;
315
+
316
+ // Get the source lines to analyze the comment's position
317
+ let lines: Vec<&str> = self.source.lines().collect();
318
+
319
+ // Check if we have a valid line number (1-indexed to 0-indexed)
320
+ if comment_line == 0 || comment_line > lines.len() {
321
+ return false;
322
+ }
323
+
324
+ let line = lines[comment_line - 1]; // Convert to 0-indexed
325
+
326
+ // Find where the comment starts within the line
327
+ let comment_text = &comment.text;
328
+
329
+ // Look for the comment marker (#) in the line
330
+ if let Some(hash_pos) = line.find('#') {
331
+ // Check if there's only whitespace before the comment
332
+ let before_comment = &line[..hash_pos];
333
+ let is_only_whitespace = before_comment.trim().is_empty();
334
+
335
+ // Also verify this is actually our comment by checking the text matches
336
+ let line_comment_text = &line[hash_pos..];
337
+ let is_same_comment = line_comment_text.trim_end() == comment_text.trim_end();
338
+
339
+ return is_only_whitespace && is_same_comment;
340
+ }
341
+
342
+ // If we can't find the comment marker, assume it's standalone
343
+ // This is a fallback for edge cases
344
+ false
345
+ }
346
+
285
347
  /// Emit comments that are within a given line range, preserving blank lines from prev_line
286
348
  /// Uses BTreeMap index for O(log n) lookup instead of O(n) iteration
287
349
  fn emit_comments_in_range_with_prev_line(
@@ -464,9 +526,8 @@ impl Emitter {
464
526
  self.emit_node(child, indent_level + 1)?;
465
527
  }
466
528
 
467
- // Emit comments that are inside the class body but not attached to any node
468
- // These are comments between class_start_line and class_end_line
469
- self.emit_comments_in_range(class_start_line + 1, class_end_line, indent_level + 1)?;
529
+ // Emit comments that appear before the end statement while preserving their position
530
+ self.emit_comments_before_end(class_start_line, class_end_line, indent_level + 1)?;
470
531
 
471
532
  // Add newline before end if there was body content or internal comments
472
533
  if (has_body_content || self.has_comments_in_range(class_start_line + 1, class_end_line))
@@ -512,8 +573,8 @@ impl Emitter {
512
573
  self.emit_node(child, indent_level + 1)?;
513
574
  }
514
575
 
515
- // Emit comments that are inside the module body but not attached to any node
516
- self.emit_comments_in_range(module_start_line + 1, module_end_line, indent_level + 1)?;
576
+ // Emit comments that appear before the end statement while preserving their position
577
+ self.emit_comments_before_end(module_start_line, module_end_line, indent_level + 1)?;
517
578
 
518
579
  // Add newline before end if there was body content or internal comments
519
580
  if (has_body_content || self.has_comments_in_range(module_start_line + 1, module_end_line))
@@ -573,6 +634,13 @@ impl Emitter {
573
634
  self.emit_node(child, indent_level + 1)?;
574
635
  }
575
636
 
637
+ // Emit comments that appear before the end statement while preserving their position
638
+ self.emit_comments_before_end(
639
+ node.location.start_line,
640
+ node.location.end_line,
641
+ indent_level + 1,
642
+ )?;
643
+
576
644
  // Add newline before end if there was body content
577
645
  if node
578
646
  .children
@@ -1457,8 +1525,8 @@ impl Emitter {
1457
1525
  }
1458
1526
  }
1459
1527
 
1460
- // Emit comments inside the singleton class body
1461
- self.emit_comments_in_range(class_start_line + 1, class_end_line, indent_level + 1)?;
1528
+ // Emit comments that appear before the end statement while preserving their position
1529
+ self.emit_comments_before_end(class_start_line, class_end_line, indent_level + 1)?;
1462
1530
 
1463
1531
  // Add newline before end if there was body content
1464
1532
  if (has_body_content || self.has_comments_in_range(class_start_line + 1, class_end_line))
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.0'
4
+ VERSION = '1.4.1'
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.4.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - fujitani sora