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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e72bec96df5da8aafa6e1cd49ab3c14af9aaaaf79a880cbf2a6b05715a61aa7c
4
- data.tar.gz: 381c235d258b0db3f7c770dd360cc3816ccaa1fdbe1af4fc857e0f871618d29b
3
+ metadata.gz: 75dece5f16cef2420a328e75f674416858eef7871c99a6aec1136c73f4b3b44c
4
+ data.tar.gz: f514b13a111e7113ed68beb1a1443b7815f8b11d47deee454ef352acf3e5ec7d
5
5
  SHA512:
6
- metadata.gz: 97261a648e5aa560d4b3a2499ed82e208d445f422b50dc0d17be0b0aa2065b7d54cc8547e09a8632019a8538cafae750468e2f95ad73bf881955bc982e527d5c
7
- data.tar.gz: 8ee6c5ec43066f7d70d07198dc72032c87fe4be1759a74196f4c17c74bfbf91579116a0744ea2b39cb8a222748a61b1e65e723e43e22d19b4589c4a28ce4baed
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
@@ -1219,7 +1219,7 @@ checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
1219
1219
 
1220
1220
  [[package]]
1221
1221
  name = "rfmt"
1222
- version = "0.5.0"
1222
+ version = "1.0.0"
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 = "0.5.0"
3
+ version = "1.0.0"
4
4
  edition = "2021"
5
5
  authors = ["fujitani sora <fujitanisora0414@gmail.com>"]
6
6
  license = "MIT"
@@ -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(ruby: &Ruby) -> Result<(), Error> {
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
- log::set_boxed_logger(Box::new(logger)).expect("Failed to initialize logger");
41
- log::set_max_level(LevelFilter::Trace);
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rfmt
4
- VERSION = '0.5.0'
4
+ VERSION = '1.0.0'
5
5
  end
@@ -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.5.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-07 00:00:00.000000000 Z
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: