rfmt 0.5.0 → 1.0.0
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 +14 -0
- data/Cargo.lock +1 -1
- data/ext/rfmt/Cargo.toml +1 -1
- data/ext/rfmt/src/emitter/mod.rs +185 -0
- data/ext/rfmt/src/lib.rs +1 -10
- data/ext/rfmt/src/logging/logger.rs +4 -2
- data/lib/rfmt/rfmt.so +0 -0
- data/lib/rfmt/version.rb +1 -1
- data/lib/ruby_lsp/rfmt/addon.rb +20 -0
- data/lib/ruby_lsp/rfmt/formatter_runner.rb +26 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 75dece5f16cef2420a328e75f674416858eef7871c99a6aec1136c73f4b3b44c
|
|
4
|
+
data.tar.gz: f514b13a111e7113ed68beb1a1443b7815f8b11d47deee454ef352acf3e5ec7d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3a3575ed8e1e35770b6ac016521ee131fc1cf92b065c16ae91418a1835e7ab6695f30b9f1bb09c2b45c24801ae9427490ae835fffca228137c18ca1299416bf2
|
|
7
|
+
data.tar.gz: a5459089d49f7b905e565a3289dae2076df6a3d472a1332c0cca8f15ebab8d712a571e202b7053fcbbaeb85431d2f14f586e272e70966a0eeec1b72002e3fe4a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.0.0] - 2025-12-11
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
- First stable release (v1.0.0)
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- Set JSON as default output format
|
|
10
|
+
- Updated Japanese documentation
|
|
11
|
+
- Code formatting improvements
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- TOML configuration parsing fix
|
|
15
|
+
- Logger initialization fix
|
|
16
|
+
|
|
3
17
|
## [0.5.0] - 2025-12-07
|
|
4
18
|
|
|
5
19
|
### Changed
|
data/Cargo.lock
CHANGED
data/ext/rfmt/Cargo.toml
CHANGED
data/ext/rfmt/src/emitter/mod.rs
CHANGED
|
@@ -3,6 +3,13 @@ use crate::config::{Config, IndentStyle};
|
|
|
3
3
|
use crate::error::Result;
|
|
4
4
|
use std::fmt::Write;
|
|
5
5
|
|
|
6
|
+
/// Block style for Ruby blocks
|
|
7
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
8
|
+
enum BlockStyle {
|
|
9
|
+
DoEnd, // do ... end
|
|
10
|
+
Braces, // { ... }
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
/// Code emitter that converts AST back to Ruby source code
|
|
7
14
|
pub struct Emitter {
|
|
8
15
|
config: Config,
|
|
@@ -115,6 +122,7 @@ impl Emitter {
|
|
|
115
122
|
NodeType::DefNode => self.emit_method(node, indent_level)?,
|
|
116
123
|
NodeType::IfNode => self.emit_if_unless(node, indent_level, false, "if")?,
|
|
117
124
|
NodeType::UnlessNode => self.emit_if_unless(node, indent_level, false, "unless")?,
|
|
125
|
+
NodeType::CallNode => self.emit_call(node, indent_level)?,
|
|
118
126
|
_ => self.emit_generic(node, indent_level)?,
|
|
119
127
|
}
|
|
120
128
|
Ok(())
|
|
@@ -429,6 +437,183 @@ impl Emitter {
|
|
|
429
437
|
Ok(())
|
|
430
438
|
}
|
|
431
439
|
|
|
440
|
+
/// Emit method call, handling blocks specially for proper indentation
|
|
441
|
+
fn emit_call(&mut self, node: &Node, indent_level: usize) -> Result<()> {
|
|
442
|
+
// Emit any comments before this call
|
|
443
|
+
self.emit_comments_before(node.location.start_line, indent_level)?;
|
|
444
|
+
|
|
445
|
+
// Check if this call has a block (last child is BlockNode)
|
|
446
|
+
let has_block = node
|
|
447
|
+
.children
|
|
448
|
+
.last()
|
|
449
|
+
.map(|c| matches!(c.node_type, NodeType::BlockNode))
|
|
450
|
+
.unwrap_or(false);
|
|
451
|
+
|
|
452
|
+
if !has_block {
|
|
453
|
+
// No block - use generic emission (extracts from source)
|
|
454
|
+
return self.emit_generic_without_comments(node, indent_level);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Has block - need to handle specially
|
|
458
|
+
let block_node = node.children.last().unwrap();
|
|
459
|
+
|
|
460
|
+
// Determine block style from source (do...end vs { })
|
|
461
|
+
let block_style = self.detect_block_style(block_node);
|
|
462
|
+
|
|
463
|
+
// Emit the call part (receiver.method(args)) from source
|
|
464
|
+
self.emit_call_without_block(node, block_node, indent_level)?;
|
|
465
|
+
|
|
466
|
+
match block_style {
|
|
467
|
+
BlockStyle::DoEnd => self.emit_do_end_block(block_node, indent_level)?,
|
|
468
|
+
BlockStyle::Braces => self.emit_brace_block(block_node, indent_level)?,
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
Ok(())
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/// Detect whether block uses do...end or { } style
|
|
475
|
+
fn detect_block_style(&self, block_node: &Node) -> BlockStyle {
|
|
476
|
+
if self.source.is_empty() {
|
|
477
|
+
return BlockStyle::DoEnd; // Default fallback
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
let start = block_node.location.start_offset;
|
|
481
|
+
if let Some(first_char) = self.source.get(start..start + 1) {
|
|
482
|
+
if first_char == "{" {
|
|
483
|
+
return BlockStyle::Braces;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
BlockStyle::DoEnd // Default (includes 'do' keyword)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/// Emit the method call part without the block
|
|
491
|
+
fn emit_call_without_block(
|
|
492
|
+
&mut self,
|
|
493
|
+
call_node: &Node,
|
|
494
|
+
block_node: &Node,
|
|
495
|
+
indent_level: usize,
|
|
496
|
+
) -> Result<()> {
|
|
497
|
+
self.emit_indent(indent_level)?;
|
|
498
|
+
|
|
499
|
+
if !self.source.is_empty() {
|
|
500
|
+
let start = call_node.location.start_offset;
|
|
501
|
+
let end = block_node.location.start_offset;
|
|
502
|
+
|
|
503
|
+
if let Some(text) = self.source.get(start..end) {
|
|
504
|
+
// Trim trailing whitespace but preserve the content
|
|
505
|
+
write!(self.buffer, "{}", text.trim_end())?;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
Ok(())
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/// Emit a do...end style block with proper indentation
|
|
513
|
+
fn emit_do_end_block(&mut self, block_node: &Node, indent_level: usize) -> Result<()> {
|
|
514
|
+
// Add space before 'do' and emit 'do'
|
|
515
|
+
write!(self.buffer, " do")?;
|
|
516
|
+
|
|
517
|
+
// Emit block parameters if present (|x, y|)
|
|
518
|
+
self.emit_block_parameters(block_node)?;
|
|
519
|
+
|
|
520
|
+
self.buffer.push('\n');
|
|
521
|
+
|
|
522
|
+
// Find and emit the body (StatementsNode among children)
|
|
523
|
+
for child in &block_node.children {
|
|
524
|
+
if matches!(child.node_type, NodeType::StatementsNode) {
|
|
525
|
+
self.emit_statements(child, indent_level + 1)?;
|
|
526
|
+
self.buffer.push('\n');
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Emit 'end'
|
|
532
|
+
self.emit_indent(indent_level)?;
|
|
533
|
+
write!(self.buffer, "end")?;
|
|
534
|
+
|
|
535
|
+
Ok(())
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/// Emit a { } style block
|
|
539
|
+
fn emit_brace_block(&mut self, block_node: &Node, indent_level: usize) -> Result<()> {
|
|
540
|
+
// Determine if block should be inline or multiline
|
|
541
|
+
let is_multiline = block_node.location.start_line != block_node.location.end_line;
|
|
542
|
+
|
|
543
|
+
if is_multiline {
|
|
544
|
+
// Multiline brace block
|
|
545
|
+
write!(self.buffer, " {{")?;
|
|
546
|
+
self.emit_block_parameters(block_node)?;
|
|
547
|
+
self.buffer.push('\n');
|
|
548
|
+
|
|
549
|
+
// Emit body
|
|
550
|
+
for child in &block_node.children {
|
|
551
|
+
if matches!(child.node_type, NodeType::StatementsNode) {
|
|
552
|
+
self.emit_statements(child, indent_level + 1)?;
|
|
553
|
+
self.buffer.push('\n');
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
self.emit_indent(indent_level)?;
|
|
559
|
+
write!(self.buffer, "}}")?;
|
|
560
|
+
} else {
|
|
561
|
+
// Inline brace block - extract from source to preserve spacing
|
|
562
|
+
write!(self.buffer, " ")?;
|
|
563
|
+
if let Some(text) = self
|
|
564
|
+
.source
|
|
565
|
+
.get(block_node.location.start_offset..block_node.location.end_offset)
|
|
566
|
+
{
|
|
567
|
+
write!(self.buffer, "{}", text)?;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
Ok(())
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/// Emit block parameters (|x, y|)
|
|
575
|
+
fn emit_block_parameters(&mut self, block_node: &Node) -> Result<()> {
|
|
576
|
+
if self.source.is_empty() {
|
|
577
|
+
return Ok(());
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
let start = block_node.location.start_offset;
|
|
581
|
+
let end = block_node.location.end_offset;
|
|
582
|
+
|
|
583
|
+
if let Some(block_source) = self.source.get(start..end) {
|
|
584
|
+
// Only look at the first line of the block for parameters
|
|
585
|
+
let first_line = block_source.lines().next().unwrap_or("");
|
|
586
|
+
|
|
587
|
+
// Find |...| pattern in the first line only
|
|
588
|
+
if let Some(pipe_start) = first_line.find('|') {
|
|
589
|
+
// Find matching pipe after first one
|
|
590
|
+
if let Some(pipe_end) = first_line[pipe_start + 1..].find('|') {
|
|
591
|
+
let params = &first_line[pipe_start..=pipe_start + 1 + pipe_end];
|
|
592
|
+
write!(self.buffer, " {}", params)?;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
Ok(())
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/// Emit generic node without re-emitting comments (for use when comments already handled)
|
|
601
|
+
fn emit_generic_without_comments(&mut self, node: &Node, indent_level: usize) -> Result<()> {
|
|
602
|
+
if !self.source.is_empty() {
|
|
603
|
+
let start = node.location.start_offset;
|
|
604
|
+
let end = node.location.end_offset;
|
|
605
|
+
|
|
606
|
+
let text_owned = self.source.get(start..end).map(|s| s.to_string());
|
|
607
|
+
|
|
608
|
+
if let Some(text) = text_owned {
|
|
609
|
+
self.emit_indent(indent_level)?;
|
|
610
|
+
write!(self.buffer, "{}", text)?;
|
|
611
|
+
self.emit_trailing_comments(node.location.end_line)?;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
Ok(())
|
|
615
|
+
}
|
|
616
|
+
|
|
432
617
|
/// Emit generic node by extracting from source
|
|
433
618
|
fn emit_generic(&mut self, node: &Node, indent_level: usize) -> Result<()> {
|
|
434
619
|
// Emit any comments before this node
|
data/ext/rfmt/src/lib.rs
CHANGED
|
@@ -54,7 +54,7 @@ fn rust_version() -> String {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
#[magnus::init]
|
|
57
|
-
fn init(
|
|
57
|
+
fn init(_ruby: &Ruby) -> Result<(), Error> {
|
|
58
58
|
logging::RfmtLogger::init();
|
|
59
59
|
log::info!("Initializing rfmt Rust extension");
|
|
60
60
|
|
|
@@ -64,15 +64,6 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
|
64
64
|
module.define_singleton_method("parse_to_json", function!(parse_to_json, 1))?;
|
|
65
65
|
module.define_singleton_method("rust_version", function!(rust_version, 0))?;
|
|
66
66
|
|
|
67
|
-
let rfmt_error = ruby.define_error("RfmtError", ruby.exception_standard_error())?;
|
|
68
|
-
ruby.define_error("ParseError", rfmt_error)?;
|
|
69
|
-
ruby.define_error("ConfigError", rfmt_error)?;
|
|
70
|
-
ruby.define_error("PrismError", rfmt_error)?;
|
|
71
|
-
ruby.define_error("RuleError", rfmt_error)?;
|
|
72
|
-
ruby.define_error("InternalError", rfmt_error)?;
|
|
73
|
-
ruby.define_error("FormattingError", rfmt_error)?;
|
|
74
|
-
ruby.define_error("UnsupportedFeature", rfmt_error)?;
|
|
75
|
-
|
|
76
67
|
log::info!("rfmt Rust extension initialized successfully");
|
|
77
68
|
Ok(())
|
|
78
69
|
}
|
|
@@ -37,8 +37,10 @@ impl RfmtLogger {
|
|
|
37
37
|
LevelFilter::Warn
|
|
38
38
|
});
|
|
39
39
|
let logger = Self::new(level);
|
|
40
|
-
|
|
41
|
-
log::
|
|
40
|
+
// Ignore if logger is already set (e.g., in ruby_lsp environment)
|
|
41
|
+
if log::set_boxed_logger(Box::new(logger)).is_ok() {
|
|
42
|
+
log::set_max_level(LevelFilter::Trace);
|
|
43
|
+
}
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
|
data/lib/rfmt/rfmt.so
CHANGED
|
Binary file
|
data/lib/rfmt/version.rb
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ruby_lsp/addon'
|
|
4
|
+
require_relative 'formatter_runner'
|
|
5
|
+
|
|
6
|
+
module RubyLsp
|
|
7
|
+
module Rfmt
|
|
8
|
+
class Addon < ::RubyLsp::Addon
|
|
9
|
+
def name
|
|
10
|
+
'rfmt'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def activate(global_state, _message_queue)
|
|
14
|
+
global_state.register_formatter('rfmt', FormatterRunner.new)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def deactivate; end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rfmt'
|
|
4
|
+
|
|
5
|
+
module RubyLsp
|
|
6
|
+
module Rfmt
|
|
7
|
+
class FormatterRunner
|
|
8
|
+
# @param uri [URI::Generic] Document URI
|
|
9
|
+
# @param document [RubyLsp::RubyDocument] Target document
|
|
10
|
+
# @return [String, nil] Formatted text or nil on error
|
|
11
|
+
def run_formatting(_uri, document)
|
|
12
|
+
source = document.source
|
|
13
|
+
::Rfmt.format(source)
|
|
14
|
+
rescue ::Rfmt::Error
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @param uri [URI::Generic] Document URI
|
|
19
|
+
# @param document [RubyLsp::RubyDocument] Target document
|
|
20
|
+
# @return [Array<RubyLsp::Interface::Diagnostic>]
|
|
21
|
+
def run_diagnostic(_uri, _document)
|
|
22
|
+
[]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rfmt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- fujitani sora
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-12-
|
|
11
|
+
date: 2025-12-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|
|
@@ -62,6 +62,8 @@ files:
|
|
|
62
62
|
- lib/rfmt/prism_node_extractor.rb
|
|
63
63
|
- lib/rfmt/rfmt.so
|
|
64
64
|
- lib/rfmt/version.rb
|
|
65
|
+
- lib/ruby_lsp/rfmt/addon.rb
|
|
66
|
+
- lib/ruby_lsp/rfmt/formatter_runner.rb
|
|
65
67
|
homepage: https://github.com/fs0414/rfmt
|
|
66
68
|
licenses:
|
|
67
69
|
- MIT
|
|
@@ -70,6 +72,7 @@ metadata:
|
|
|
70
72
|
homepage_uri: https://github.com/fs0414/rfmt
|
|
71
73
|
source_code_uri: https://github.com/fs0414/rfmt
|
|
72
74
|
changelog_uri: https://github.com/fs0414/rfmt/releases
|
|
75
|
+
ruby_lsp_addon: 'true'
|
|
73
76
|
post_install_message:
|
|
74
77
|
rdoc_options: []
|
|
75
78
|
require_paths:
|