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 +4 -4
- data/CHANGELOG.md +25 -0
- data/Cargo.lock +1 -1
- data/README.md +17 -19
- data/ext/rfmt/Cargo.toml +1 -1
- data/ext/rfmt/src/emitter/mod.rs +86 -18
- data/lib/rfmt/cli.rb +6 -2
- data/lib/rfmt/version.rb +1 -1
- metadata +43 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a17203d9ab18d011d42790fc039889490f78bd9c1eb86701a15e15784bfafd4c
|
|
4
|
+
data.tar.gz: ae34f8b79a4d7e64fec13ea9b1a24ae8d1b46f03aeb4c64f302e52a3895c62e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
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
|
|
49
|
+
## Performance
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
rfmt delivers consistent, fast formatting across projects of any size:
|
|
52
52
|
|
|
53
|
-
|
|
|
54
|
-
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
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
|
-
**
|
|
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
|
-
**
|
|
67
|
-
-
|
|
68
|
-
-
|
|
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.
|
|
73
|
-
-
|
|
68
|
+
- Ruby: 3.4.8
|
|
69
|
+
- Average of 5 runs per test
|
|
74
70
|
|
|
75
|
-
|
|
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
data/ext/rfmt/src/emitter/mod.rs
CHANGED
|
@@ -237,34 +237,58 @@ impl Emitter {
|
|
|
237
237
|
})
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
-
/// Emit comments that
|
|
241
|
-
///
|
|
242
|
-
fn
|
|
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
|
-
|
|
245
|
-
|
|
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
|
-
|
|
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|
|
|
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
|
-
|
|
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
|
-
|
|
267
|
-
|
|
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
|
-
|
|
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
|
|
468
|
-
|
|
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
|
|
516
|
-
self.
|
|
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
|
|
1461
|
-
self.
|
|
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?('
|
|
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,
|
|
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
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
|
+
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
|