rfmt 1.3.4 → 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: 9ac6a3e001e85e27839f04ba78564e5bc16675818b2603a179f62d7fa35c9062
4
- data.tar.gz: 4957bbc0de84f4e5c72dacf514ea0e87f211979667a50b53f365c005f1ee710a
3
+ metadata.gz: a17203d9ab18d011d42790fc039889490f78bd9c1eb86701a15e15784bfafd4c
4
+ data.tar.gz: ae34f8b79a4d7e64fec13ea9b1a24ae8d1b46f03aeb4c64f302e52a3895c62e6
5
5
  SHA512:
6
- metadata.gz: 470807974090b7b41d5c9bf5dd437cd34d80cdbb45ed1c9af2d97f93c0a01c88e5e709dcb47b6e077909be7bbb586f7df4eb798e2403dd2186a4d08b6770463e
7
- data.tar.gz: 6620293ef2c43673eecbf478e2c4d3ba1b79a0ad71e67ec984547c0aeda36e03f7c10d848c646a74bc392e4057aeb2f4c4dc56d2602a96e295bf77f0fbedbf3e
6
+ metadata.gz: 390bd25696b21224e5c182b6516a4a1733281a93bae01eb3ae58a787f3d53b430e308a760d1b47adc1389f2bd389b544aa6fe41e6990d0de654c5ead89d8e31f
7
+ data.tar.gz: f638716804d25efc8d74cd18c6a2c1e014d459a63a91dea4968ff2e1f21fee4c51890781051e6c92e2c5cac6e924687b531d9ac412d1e9eae6bfef8155b1b166
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
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
+
10
+ ## [1.4.0] - 2026-01-17
11
+
12
+ ### Added
13
+ - New `rfmt_fast` executable for optimized performance
14
+ - Automatic parallel processing detection logic
15
+ - Enhanced logging and summary display functionality
16
+ - CLI option mapping for `-v/--version` commands
17
+
18
+ ### Fixed
19
+ - Fixed `-v` flag incorrectly triggering format instead of showing version
20
+ - Fixed `--diff` option dependency issues (added `diffy` and `diff-lcs` to gemspec)
21
+ - CLI option conflicts between verbose and version flags
22
+
23
+ ### Changed
24
+ - Updated performance benchmarks documentation
25
+ - Code formatting improvements with Rubocop compliance
26
+ - Dependencies alphabetically sorted in gemspec
27
+
3
28
  ## [1.3.4] - 2026-01-17
4
29
 
5
30
  ### Added
data/Cargo.lock CHANGED
@@ -1214,7 +1214,7 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
1214
1214
 
1215
1215
  [[package]]
1216
1216
  name = "rfmt"
1217
- version = "1.3.4"
1217
+ version = "1.4.1"
1218
1218
  dependencies = [
1219
1219
  "anyhow",
1220
1220
  "clap",
data/README.md CHANGED
@@ -46,33 +46,31 @@ Enforces code style rules:
46
46
  - Quote style standardization
47
47
  - Method definition formatting
48
48
 
49
- ## Performance Benchmarks
49
+ ## Performance
50
50
 
51
- Execution time comparison on a Rails project (111 files, 3,241 lines):
51
+ rfmt delivers consistent, fast formatting across projects of any size:
52
52
 
53
- | Test Type | Files | rfmt | RuboCop | Ratio |
54
- |-----------|-------|------|---------|-------|
55
- | Single File | 1 | 191ms | 1.38s | 7.2x |
56
- | Directory | 14 | 176ms | 1.68s | 9.6x |
57
- | Full Project (check) | 111 | 172ms | 4.36s | 25.4x |
53
+ | Project Size | Files | Execution Time | Throughput |
54
+ |-------------|-------|----------------|------------|
55
+ | Small | 9 files | ~105ms | 85 files/sec |
56
+ | Medium | 35 files | ~110ms | 315 files/sec |
57
+ | Large | 151 files | ~100ms | 1,560 files/sec |
58
58
 
59
- **About this comparison:**
60
- - RuboCop times include startup overhead and loading all cops (linting rules)
61
- - RuboCop was run with default configuration (all cops enabled)
62
- - rfmt is a formatting-only tool with minimal overhead
63
- - Both tools were measured in check mode (no file modifications)
64
- - Results are averages from 10 runs per test
59
+ **Key Performance Characteristics:**
65
60
 
66
- **Observations:**
67
- - rfmt execution time remains constant (172-191ms) regardless of file count
68
- - Low variance across runs (standard deviation: 8-23ms)
61
+ - **Constant Time**: Execution time stays around 100ms regardless of project size
62
+ - **Parallel Processing**: Automatic scaling with available CPU cores
63
+ - **High Throughput**: Up to 1,500+ files per second on large projects
64
+ - **Low Overhead**: Minimal startup time and memory usage
69
65
 
70
66
  **Test Environment:**
71
67
  - CPU: Apple Silicon (arm64)
72
- - Ruby: 3.4.5
73
- - rfmt: 0.3.0, RuboCop: 1.81.7
68
+ - Ruby: 3.4.8
69
+ - Average of 5 runs per test
74
70
 
75
- See [detailed benchmark report](docs/benchmark.md) for complete data.
71
+ *Built with Rust for optimal performance and memory efficiency.*
72
+
73
+ For detailed performance comparisons and benchmarks, see [Performance Benchmarks](docs/benchmark.md).
76
74
 
77
75
  ## Installation
78
76
 
data/ext/rfmt/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "rfmt"
3
- version = "1.3.4"
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/cli.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require 'thor'
4
4
 
5
5
  # Check for verbose flag before loading rfmt to set debug mode early
6
- ENV['RFMT_DEBUG'] = '1' if ARGV.include?('-v') || ARGV.include?('--verbose')
6
+ ENV['RFMT_DEBUG'] = '1' if ARGV.include?('--verbose')
7
7
 
8
8
  require 'rfmt'
9
9
  require 'rfmt/configuration'
@@ -49,10 +49,14 @@ module Rfmt
49
49
  PROGRESS_INTERVAL = 10 # Update progress every N files
50
50
 
51
51
  class_option :config, type: :string, desc: 'Path to configuration file'
52
- class_option :verbose, type: :boolean, aliases: '-v', desc: 'Verbose output'
52
+ class_option :verbose, type: :boolean, desc: 'Verbose output'
53
53
 
54
54
  default_command :format
55
55
 
56
+ # Map -v and --version to version command
57
+ map '-v' => 'version'
58
+ map '--version' => 'version'
59
+
56
60
  desc 'format [FILES]', 'Format Ruby files (default command)'
57
61
  option :write, type: :boolean, default: true, desc: 'Write formatted output'
58
62
  option :check, type: :boolean, desc: "Check if files are formatted (don't write)"
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.3.4'
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.3.4
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - fujitani sora
@@ -10,6 +10,34 @@ bindir: exe
10
10
  cert_chain: []
11
11
  date: 2026-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: diff-lcs
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: diffy
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.4'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: rb_sys
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -24,6 +52,20 @@ dependencies:
24
52
  - - "~>"
25
53
  - !ruby/object:Gem::Version
26
54
  version: 0.9.120
55
+ - !ruby/object:Gem::Dependency
56
+ name: thor
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.3'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.3'
27
69
  description: Write a longer description or delete this line.
28
70
  email:
29
71
  - fujitanisora0414@gmail.com