rfmt 1.5.0 → 1.5.2

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: 2df39db6eb525d4ca6bc42d55e01057e5a12772f0b61a7a8e3101e3a8fff76ec
4
- data.tar.gz: b34bbf251457c8eb0d9174497c124f71d6a7b098ad506712acaec54812bcc674
3
+ metadata.gz: b17e8b19308af7368b77ac8b7a89bab1b7c321e16de03f1aae0b8d3df27f678c
4
+ data.tar.gz: 9929e63f33a0e116ce70441e86d6c525157517b37e2f83f4e9cb931d53eab8af
5
5
  SHA512:
6
- metadata.gz: 1d341d108c8875f683a2ef3654e6e540b1fe9840a4f908abc75725da544c0b8be2b27cf5534cdf634eb2e9eaaa36bed0df9500f978a6fffc95ec7379e2485cf3
7
- data.tar.gz: 9cbce53637c758998c79c77af60d10130dd293dac67c4a7918d882e816d848ef525400bd6fcebc030523a2407e08140dc1bc9227cbd9c1cfc3f579f0e5b477be
6
+ metadata.gz: f2c375826007f03f24163fa83b1b51dffa3ef241ad4e38c800145b8387b108368807cc221b4d453860861b2dea8fcfbea33ad7391b61c24f2d973da004078aaa
7
+ data.tar.gz: 7305b6778b6dcecb116a7e9df522d5085eee28f763fa88283ecc70a1a0d71e70eace61e7be736eb010a8cf8aacf556b34a9226d05cf83f9216c3e385d43ce720
data/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.5.2] - 2026-02-21
4
+
5
+ ### Added
6
+ - Instance variable write node emission support (#92)
7
+
8
+ ### Changed
9
+ - Nix dev environment optimization (build caching, direnv support, devShell splitting)
10
+ - Code formatting improvements (clippy, rustfmt)
11
+
12
+ ### Fixed
13
+ - Fix Nix bundler version conflict (remove pkgs.bundler, use Ruby built-in)
14
+
15
+ ## [1.5.1] - 2026-02-21
16
+
17
+ ### Fixed
18
+ - Fix inline modifier `if`/`unless` formatting (#87)
19
+ - Fix heredoc command incorrectly removed (#90, #86)
20
+ - Fix method chain command dependency handling (#89, #85)
21
+
22
+ ### Added
23
+ - Heredoc comment deletion support
24
+ - Place block loop emission support
25
+
26
+ ### Changed
27
+ - Update README.md
28
+ - Code formatting improvements (RuboCop compliance)
29
+
3
30
  ## [1.5.0] - 2026-01-25
4
31
 
5
32
  ### Added
data/Cargo.lock CHANGED
@@ -1214,7 +1214,7 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
1214
1214
 
1215
1215
  [[package]]
1216
1216
  name = "rfmt"
1217
- version = "1.5.0"
1217
+ version = "1.5.2"
1218
1218
  dependencies = [
1219
1219
  "anyhow",
1220
1220
  "clap",
data/README.md CHANGED
@@ -23,6 +23,7 @@ A Ruby code formatter written in Rust
23
23
  ## What is rfmt?
24
24
 
25
25
  [RubyGems reference](https://rubygems.org/gems/rfmt)
26
+ [DeepWiki rfmt](https://deepwiki.com/fs0414/rfmt)
26
27
 
27
28
  **rfmt** is a Ruby code formatter that enforces consistent style across your codebase. Key characteristics:
28
29
 
data/ext/rfmt/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "rfmt"
3
- version = "1.5.0"
3
+ version = "1.5.2"
4
4
  edition = "2021"
5
5
  authors = ["fujitani sora <fujitanisora0414@gmail.com>"]
6
6
  license = "MIT"
@@ -463,6 +463,9 @@ impl Emitter {
463
463
  NodeType::SingletonClassNode => self.emit_singleton_class(node, indent_level)?,
464
464
  NodeType::CaseMatchNode => self.emit_case_match(node, indent_level)?,
465
465
  NodeType::InNode => self.emit_in(node, indent_level)?,
466
+ NodeType::LocalVariableWriteNode | NodeType::InstanceVariableWriteNode => {
467
+ self.emit_variable_write(node, indent_level)?
468
+ }
466
469
  _ => self.emit_generic(node, indent_level)?,
467
470
  }
468
471
  Ok(())
@@ -1000,6 +1003,7 @@ impl Emitter {
1000
1003
  self.write_source_text(predicate)?;
1001
1004
  }
1002
1005
 
1006
+ self.emit_trailing_comments(node.location.end_line)?;
1003
1007
  return Ok(());
1004
1008
  }
1005
1009
 
@@ -1035,6 +1039,7 @@ impl Emitter {
1035
1039
  }
1036
1040
  }
1037
1041
 
1042
+ self.emit_trailing_comments(node.location.end_line)?;
1038
1043
  return Ok(());
1039
1044
  }
1040
1045
 
@@ -1196,6 +1201,16 @@ impl Emitter {
1196
1201
  if let Some(text) = self.source.get(start..end) {
1197
1202
  // Trim trailing whitespace but preserve the content
1198
1203
  write!(self.buffer, "{}", text.trim_end())?;
1204
+
1205
+ // Mark comments within the extracted range as emitted
1206
+ for (idx, comment) in self.all_comments.iter().enumerate() {
1207
+ if !self.emitted_comment_indices.contains(&idx)
1208
+ && comment.location.start_offset >= start
1209
+ && comment.location.end_offset <= end
1210
+ {
1211
+ self.emitted_comment_indices.insert(idx);
1212
+ }
1213
+ }
1199
1214
  }
1200
1215
  }
1201
1216
 
@@ -1368,6 +1383,48 @@ impl Emitter {
1368
1383
  Ok(())
1369
1384
  }
1370
1385
 
1386
+ /// Emit variable write node (LocalVariableWriteNode, InstanceVariableWriteNode)
1387
+ /// Handles `x = value` and `@x = value` patterns
1388
+ fn emit_variable_write(&mut self, node: &Node, indent_level: usize) -> Result<()> {
1389
+ self.emit_comments_before(node.location.start_line, indent_level)?;
1390
+
1391
+ let name = node.metadata.get("name").map(|s| s.as_str()).unwrap_or("_");
1392
+
1393
+ // Get value node (first child)
1394
+ let value = match node.children.first() {
1395
+ Some(v) => v,
1396
+ None => {
1397
+ // No value: fallback to source extraction
1398
+ return self.emit_generic(node, indent_level);
1399
+ }
1400
+ };
1401
+
1402
+ let is_block_value = matches!(
1403
+ value.node_type,
1404
+ NodeType::IfNode
1405
+ | NodeType::UnlessNode
1406
+ | NodeType::CaseNode
1407
+ | NodeType::CaseMatchNode
1408
+ | NodeType::BeginNode
1409
+ | NodeType::WhileNode
1410
+ | NodeType::UntilNode
1411
+ | NodeType::ForNode
1412
+ );
1413
+
1414
+ self.emit_indent(indent_level)?;
1415
+ if is_block_value {
1416
+ writeln!(self.buffer, "{} =", name)?;
1417
+ self.emit_node(value, indent_level + 1)?;
1418
+ } else {
1419
+ write!(self.buffer, "{} = ", name)?;
1420
+ self.write_source_text_trimmed(value)?;
1421
+ }
1422
+
1423
+ self.emit_trailing_comments(node.location.end_line)?;
1424
+
1425
+ Ok(())
1426
+ }
1427
+
1371
1428
  /// Emit generic node by extracting from source
1372
1429
  fn emit_generic(&mut self, node: &Node, indent_level: usize) -> Result<()> {
1373
1430
  self.emit_comments_before(node.location.start_line, indent_level)?;
@@ -368,7 +368,7 @@ mod tests {
368
368
 
369
369
  let node = result.unwrap();
370
370
  assert_eq!(node.node_type, NodeType::ClassNode);
371
- assert_eq!(node.formatting.multiline, true);
371
+ assert!(node.formatting.multiline);
372
372
  assert!(node.is_multiline());
373
373
  assert_eq!(node.line_count(), 3);
374
374
  }
@@ -125,16 +125,14 @@ module Rfmt
125
125
  end
126
126
  end
127
127
 
128
- # Check child nodes for heredoc (e.g., LocalVariableWriteNode containing StringNode)
129
- node.child_nodes.compact.each do |child|
130
- next unless child.respond_to?(:closing_loc) && child.closing_loc
131
-
132
- closing = child.closing_loc
133
- next unless closing.end_offset > end_offset
134
-
135
- end_offset = closing.end_offset
136
- end_line = closing.end_line
137
- end_column = closing.end_column
128
+ # Recursively check all descendant nodes for heredoc closing_loc
129
+ # Issue #74: handled direct children (e.g., LocalVariableWriteNode -> StringNode)
130
+ # Issue #86: handles deeper nesting (e.g., CallNode -> ArgumentsNode -> StringNode)
131
+ max_closing = find_max_closing_loc_recursive(node)
132
+ if max_closing && max_closing[:end_offset] > end_offset
133
+ end_offset = max_closing[:end_offset]
134
+ end_line = max_closing[:end_line]
135
+ end_column = max_closing[:end_column]
138
136
  end
139
137
 
140
138
  {
@@ -147,6 +145,32 @@ module Rfmt
147
145
  }
148
146
  end
149
147
 
148
+ # Recursively find the maximum closing_loc among all descendant nodes
149
+ # Returns nil if no closing_loc found, otherwise { end_offset:, end_line:, end_column: }
150
+ def self.find_max_closing_loc_recursive(node, depth: 0)
151
+ return nil if depth > 10
152
+
153
+ max_closing = nil
154
+
155
+ node.child_nodes.compact.each do |child|
156
+ if child.respond_to?(:closing_loc) && child.closing_loc
157
+ closing = child.closing_loc
158
+ if max_closing.nil? || closing.end_offset > max_closing[:end_offset]
159
+ max_closing = {
160
+ end_offset: closing.end_offset,
161
+ end_line: closing.end_line,
162
+ end_column: closing.end_column
163
+ }
164
+ end
165
+ end
166
+
167
+ child_max = find_max_closing_loc_recursive(child, depth: depth + 1)
168
+ max_closing = child_max if child_max && (max_closing.nil? || child_max[:end_offset] > max_closing[:end_offset])
169
+ end
170
+
171
+ max_closing
172
+ end
173
+
150
174
  # Extract child nodes
151
175
  def self.extract_children(node)
152
176
  children = []
@@ -441,6 +465,8 @@ module Rfmt
441
465
  if (value = extract_literal_value(node))
442
466
  metadata['value'] = value
443
467
  end
468
+ when Prism::LocalVariableWriteNode, Prism::InstanceVariableWriteNode
469
+ metadata['name'] = node.name.to_s
444
470
  when Prism::IfNode, Prism::UnlessNode
445
471
  # Detect ternary operator: if_keyword_loc is nil for ternary
446
472
  metadata['is_ternary'] = node.if_keyword_loc.nil?.to_s if node.respond_to?(:if_keyword_loc)
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.5.0'
4
+ VERSION = '1.5.2'
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.5.0
4
+ version: 1.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - fujitani sora