rfmt 1.4.1 → 1.5.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 +33 -0
- data/Cargo.lock +7 -7
- data/README.md +1 -0
- data/ext/rfmt/Cargo.toml +3 -3
- data/ext/rfmt/src/emitter/mod.rs +134 -74
- data/ext/rfmt/src/lib.rs +3 -3
- data/lib/rfmt/prism_bridge.rb +57 -3
- data/lib/rfmt/version.rb +1 -1
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 75dbfbcc5bd176d4dc5394a718354c0d027c6ae2d855e2ee70422b3b17c91c8f
|
|
4
|
+
data.tar.gz: dafa925df2d12a4f055da3e03c777f96fb235eb5fde1b1d28c34ca8ba2ad110d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6444ec42f910b398e3129dbb9028577a9eb893de5224fcf35bd1ccc961142b6a9f33bda5f26c369ca6b8c569a5d26c166a81526c323f1cf7483d639e6e8dbc14
|
|
7
|
+
data.tar.gz: 4c013c8e0f7263c472c38cdbbdb12990f517e66fa5be011b9d7535ccb2cba473ba9ed958e0b5ff7fc789c693bc39c47bafa3cdfdb7bf06f6d55ba6d4b5b2e6a3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.5.1] - 2026-02-21
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Fix inline modifier `if`/`unless` formatting (#87)
|
|
7
|
+
- Fix heredoc command incorrectly removed (#90, #86)
|
|
8
|
+
- Fix method chain command dependency handling (#89, #85)
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Heredoc comment deletion support
|
|
12
|
+
- Place block loop emission support
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Update README.md
|
|
16
|
+
- Code formatting improvements (RuboCop compliance)
|
|
17
|
+
|
|
18
|
+
## [1.5.0] - 2026-01-25
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- Docker Compose test environment setup (#84)
|
|
22
|
+
- Support for `then` expression emission (#80)
|
|
23
|
+
- CI support for Ruby 3.4 and Ruby 4 (#82)
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Upgrade Magnus (Rust-Ruby FFI library) (#83)
|
|
27
|
+
- Optimize Docker build with multi-stage and caching (#84)
|
|
28
|
+
- Upgrade unicode-emoji dependency
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- Preserve heredoc content and closing identifier (#81)
|
|
32
|
+
- Fix `rescue`/`ensure` clauses being deleted inside `do...end` blocks (#78)
|
|
33
|
+
- Fix inline comment node handling (#77)
|
|
34
|
+
- Fix BTreeMap range error (Issue #71)
|
|
35
|
+
|
|
3
36
|
## [1.4.1] - 2026-01-17
|
|
4
37
|
|
|
5
38
|
### Fixed
|
data/Cargo.lock
CHANGED
|
@@ -838,9 +838,9 @@ dependencies = [
|
|
|
838
838
|
|
|
839
839
|
[[package]]
|
|
840
840
|
name = "magnus"
|
|
841
|
-
version = "0.
|
|
841
|
+
version = "0.8.2"
|
|
842
842
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
843
|
-
checksum = "
|
|
843
|
+
checksum = "3b36a5b126bbe97eb0d02d07acfeb327036c6319fd816139a49824a83b7f9012"
|
|
844
844
|
dependencies = [
|
|
845
845
|
"magnus-macros",
|
|
846
846
|
"rb-sys",
|
|
@@ -850,9 +850,9 @@ dependencies = [
|
|
|
850
850
|
|
|
851
851
|
[[package]]
|
|
852
852
|
name = "magnus-macros"
|
|
853
|
-
version = "0.
|
|
853
|
+
version = "0.8.0"
|
|
854
854
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
855
|
-
checksum = "
|
|
855
|
+
checksum = "47607461fd8e1513cb4f2076c197d8092d921a1ea75bd08af97398f593751892"
|
|
856
856
|
dependencies = [
|
|
857
857
|
"proc-macro2",
|
|
858
858
|
"quote",
|
|
@@ -1168,9 +1168,9 @@ dependencies = [
|
|
|
1168
1168
|
|
|
1169
1169
|
[[package]]
|
|
1170
1170
|
name = "rb-sys-env"
|
|
1171
|
-
version = "0.
|
|
1171
|
+
version = "0.2.3"
|
|
1172
1172
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1173
|
-
checksum = "
|
|
1173
|
+
checksum = "cca7ad6a7e21e72151d56fe2495a259b5670e204c3adac41ee7ef676ea08117a"
|
|
1174
1174
|
|
|
1175
1175
|
[[package]]
|
|
1176
1176
|
name = "redox_users"
|
|
@@ -1214,7 +1214,7 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
|
|
1214
1214
|
|
|
1215
1215
|
[[package]]
|
|
1216
1216
|
name = "rfmt"
|
|
1217
|
-
version = "1.
|
|
1217
|
+
version = "1.5.1"
|
|
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.
|
|
3
|
+
version = "1.5.1"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
authors = ["fujitani sora <fujitanisora0414@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -12,8 +12,8 @@ crate-type = ["cdylib"]
|
|
|
12
12
|
|
|
13
13
|
[dependencies]
|
|
14
14
|
# Ruby FFI
|
|
15
|
-
magnus = { version = "0.
|
|
16
|
-
rb-sys = "0.9.
|
|
15
|
+
magnus = { version = "0.8.2" }
|
|
16
|
+
rb-sys = "0.9.124"
|
|
17
17
|
|
|
18
18
|
# Serialization
|
|
19
19
|
serde = { version = "1.0", features = ["derive"] }
|
data/ext/rfmt/src/emitter/mod.rs
CHANGED
|
@@ -144,6 +144,10 @@ impl Emitter {
|
|
|
144
144
|
/// Get comment indices in the given line range [start_line, end_line)
|
|
145
145
|
/// Uses BTreeMap range for O(log n) lookup instead of O(n) iteration
|
|
146
146
|
fn get_comment_indices_in_range(&self, start_line: usize, end_line: usize) -> Vec<usize> {
|
|
147
|
+
// Guard against invalid range (e.g., endless methods where start_line >= end_line)
|
|
148
|
+
if start_line >= end_line {
|
|
149
|
+
return Vec::new();
|
|
150
|
+
}
|
|
147
151
|
self.comments_by_line
|
|
148
152
|
.range(start_line..end_line)
|
|
149
153
|
.flat_map(|(_, indices)| indices.iter().copied())
|
|
@@ -228,6 +232,10 @@ impl Emitter {
|
|
|
228
232
|
/// Check if there are any unemitted comments in the given line range
|
|
229
233
|
/// Uses BTreeMap index for O(log n) lookup instead of O(n) iteration
|
|
230
234
|
fn has_comments_in_range(&self, start_line: usize, end_line: usize) -> bool {
|
|
235
|
+
// Guard against invalid range (e.g., endless methods where start_line >= end_line)
|
|
236
|
+
if start_line >= end_line {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
231
239
|
self.comments_by_line
|
|
232
240
|
.range(start_line..end_line)
|
|
233
241
|
.flat_map(|(_, indices)| indices.iter())
|
|
@@ -344,6 +352,31 @@ impl Emitter {
|
|
|
344
352
|
false
|
|
345
353
|
}
|
|
346
354
|
|
|
355
|
+
/// Check if the node spans only a single line
|
|
356
|
+
fn is_single_line(&self, node: &Node) -> bool {
|
|
357
|
+
node.location.start_line == node.location.end_line
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/// Extract and write source text for a node
|
|
361
|
+
fn write_source_text(&mut self, node: &Node) -> Result<()> {
|
|
362
|
+
let start = node.location.start_offset;
|
|
363
|
+
let end = node.location.end_offset;
|
|
364
|
+
if let Some(text) = self.source.get(start..end) {
|
|
365
|
+
write!(self.buffer, "{}", text)?;
|
|
366
|
+
}
|
|
367
|
+
Ok(())
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/// Extract and write trimmed source text for a node
|
|
371
|
+
fn write_source_text_trimmed(&mut self, node: &Node) -> Result<()> {
|
|
372
|
+
let start = node.location.start_offset;
|
|
373
|
+
let end = node.location.end_offset;
|
|
374
|
+
if let Some(text) = self.source.get(start..end) {
|
|
375
|
+
write!(self.buffer, "{}", text.trim())?;
|
|
376
|
+
}
|
|
377
|
+
Ok(())
|
|
378
|
+
}
|
|
379
|
+
|
|
347
380
|
/// Emit comments that are within a given line range, preserving blank lines from prev_line
|
|
348
381
|
/// Uses BTreeMap index for O(log n) lookup instead of O(n) iteration
|
|
349
382
|
fn emit_comments_in_range_with_prev_line(
|
|
@@ -899,25 +932,30 @@ impl Emitter {
|
|
|
899
932
|
|
|
900
933
|
// Emit conditions with comma separator
|
|
901
934
|
for (i, cond) in conditions.iter().enumerate() {
|
|
902
|
-
|
|
903
|
-
let end = cond.location.end_offset;
|
|
904
|
-
if let Some(text) = self.source.get(start..end) {
|
|
905
|
-
write!(self.buffer, "{}", text)?;
|
|
906
|
-
}
|
|
935
|
+
self.write_source_text(cond)?;
|
|
907
936
|
if i < conditions.len() - 1 {
|
|
908
937
|
write!(self.buffer, ", ")?;
|
|
909
938
|
}
|
|
910
939
|
}
|
|
911
940
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
// Emit statements body
|
|
915
|
-
if let Some(statements) = node
|
|
941
|
+
let statements = node
|
|
916
942
|
.children
|
|
917
943
|
.iter()
|
|
918
|
-
.find(|c| matches!(c.node_type, NodeType::StatementsNode))
|
|
919
|
-
|
|
920
|
-
|
|
944
|
+
.find(|c| matches!(c.node_type, NodeType::StatementsNode));
|
|
945
|
+
|
|
946
|
+
if self.is_single_line(node) {
|
|
947
|
+
// Inline style: when X then Y
|
|
948
|
+
if let Some(statements) = statements {
|
|
949
|
+
write!(self.buffer, " then ")?;
|
|
950
|
+
self.write_source_text(statements)?;
|
|
951
|
+
}
|
|
952
|
+
} else {
|
|
953
|
+
// Multi-line style: when X\n Y
|
|
954
|
+
self.buffer.push('\n');
|
|
955
|
+
|
|
956
|
+
if let Some(statements) = statements {
|
|
957
|
+
self.emit_statements(statements, indent_level + 1)?;
|
|
958
|
+
}
|
|
921
959
|
}
|
|
922
960
|
|
|
923
961
|
Ok(())
|
|
@@ -951,14 +989,7 @@ impl Emitter {
|
|
|
951
989
|
// Emit statement
|
|
952
990
|
if let Some(statements) = node.children.get(1) {
|
|
953
991
|
if matches!(statements.node_type, NodeType::StatementsNode) {
|
|
954
|
-
|
|
955
|
-
if !self.source.is_empty() {
|
|
956
|
-
let start = statements.location.start_offset;
|
|
957
|
-
let end = statements.location.end_offset;
|
|
958
|
-
if let Some(text) = self.source.get(start..end) {
|
|
959
|
-
write!(self.buffer, "{}", text.trim())?;
|
|
960
|
-
}
|
|
961
|
-
}
|
|
992
|
+
self.write_source_text_trimmed(statements)?;
|
|
962
993
|
}
|
|
963
994
|
}
|
|
964
995
|
|
|
@@ -966,15 +997,10 @@ impl Emitter {
|
|
|
966
997
|
|
|
967
998
|
// Emit condition
|
|
968
999
|
if let Some(predicate) = node.children.first() {
|
|
969
|
-
|
|
970
|
-
let start = predicate.location.start_offset;
|
|
971
|
-
let end = predicate.location.end_offset;
|
|
972
|
-
if let Some(text) = self.source.get(start..end) {
|
|
973
|
-
write!(self.buffer, "{}", text)?;
|
|
974
|
-
}
|
|
975
|
-
}
|
|
1000
|
+
self.write_source_text(predicate)?;
|
|
976
1001
|
}
|
|
977
1002
|
|
|
1003
|
+
self.emit_trailing_comments(node.location.end_line)?;
|
|
978
1004
|
return Ok(());
|
|
979
1005
|
}
|
|
980
1006
|
|
|
@@ -991,26 +1017,14 @@ impl Emitter {
|
|
|
991
1017
|
|
|
992
1018
|
// Emit condition
|
|
993
1019
|
if let Some(predicate) = node.children.first() {
|
|
994
|
-
|
|
995
|
-
let start = predicate.location.start_offset;
|
|
996
|
-
let end = predicate.location.end_offset;
|
|
997
|
-
if let Some(text) = self.source.get(start..end) {
|
|
998
|
-
write!(self.buffer, "{}", text)?;
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1020
|
+
self.write_source_text(predicate)?;
|
|
1001
1021
|
}
|
|
1002
1022
|
|
|
1003
1023
|
write!(self.buffer, " ? ")?;
|
|
1004
1024
|
|
|
1005
1025
|
// Emit then expression
|
|
1006
1026
|
if let Some(statements) = node.children.get(1) {
|
|
1007
|
-
|
|
1008
|
-
let start = statements.location.start_offset;
|
|
1009
|
-
let end = statements.location.end_offset;
|
|
1010
|
-
if let Some(text) = self.source.get(start..end) {
|
|
1011
|
-
write!(self.buffer, "{}", text.trim())?;
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1027
|
+
self.write_source_text_trimmed(statements)?;
|
|
1014
1028
|
}
|
|
1015
1029
|
|
|
1016
1030
|
write!(self.buffer, " : ")?;
|
|
@@ -1018,16 +1032,38 @@ impl Emitter {
|
|
|
1018
1032
|
// Emit else expression
|
|
1019
1033
|
if let Some(else_node) = node.children.get(2) {
|
|
1020
1034
|
if let Some(else_statements) = else_node.children.first() {
|
|
1021
|
-
|
|
1022
|
-
let start = else_statements.location.start_offset;
|
|
1023
|
-
let end = else_statements.location.end_offset;
|
|
1024
|
-
if let Some(text) = self.source.get(start..end) {
|
|
1025
|
-
write!(self.buffer, "{}", text.trim())?;
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1035
|
+
self.write_source_text_trimmed(else_statements)?;
|
|
1028
1036
|
}
|
|
1029
1037
|
}
|
|
1030
1038
|
|
|
1039
|
+
self.emit_trailing_comments(node.location.end_line)?;
|
|
1040
|
+
return Ok(());
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// Check for inline then style: "if true then 1 end"
|
|
1044
|
+
// Single line, not postfix, not ternary, no else clause
|
|
1045
|
+
let is_inline_then =
|
|
1046
|
+
!is_elsif && self.is_single_line(node) && node.children.get(2).is_none();
|
|
1047
|
+
|
|
1048
|
+
if is_inline_then {
|
|
1049
|
+
self.emit_comments_before(node.location.start_line, indent_level)?;
|
|
1050
|
+
self.emit_indent(indent_level)?;
|
|
1051
|
+
write!(self.buffer, "{} ", keyword)?;
|
|
1052
|
+
|
|
1053
|
+
// Emit condition
|
|
1054
|
+
if let Some(predicate) = node.children.first() {
|
|
1055
|
+
self.write_source_text(predicate)?;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
write!(self.buffer, " then ")?;
|
|
1059
|
+
|
|
1060
|
+
// Emit statement
|
|
1061
|
+
if let Some(statements) = node.children.get(1) {
|
|
1062
|
+
self.write_source_text_trimmed(statements)?;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
write!(self.buffer, " end")?;
|
|
1066
|
+
self.emit_trailing_comments(node.location.end_line)?;
|
|
1031
1067
|
return Ok(());
|
|
1032
1068
|
}
|
|
1033
1069
|
|
|
@@ -1046,14 +1082,7 @@ impl Emitter {
|
|
|
1046
1082
|
|
|
1047
1083
|
// Emit predicate (condition) - first child
|
|
1048
1084
|
if let Some(predicate) = node.children.first() {
|
|
1049
|
-
|
|
1050
|
-
if !self.source.is_empty() {
|
|
1051
|
-
let start = predicate.location.start_offset;
|
|
1052
|
-
let end = predicate.location.end_offset;
|
|
1053
|
-
if let Some(text) = self.source.get(start..end) {
|
|
1054
|
-
write!(self.buffer, "{}", text)?;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1085
|
+
self.write_source_text(predicate)?;
|
|
1057
1086
|
}
|
|
1058
1087
|
|
|
1059
1088
|
// Emit trailing comment on same line as if/unless/elsif
|
|
@@ -1169,6 +1198,16 @@ impl Emitter {
|
|
|
1169
1198
|
if let Some(text) = self.source.get(start..end) {
|
|
1170
1199
|
// Trim trailing whitespace but preserve the content
|
|
1171
1200
|
write!(self.buffer, "{}", text.trim_end())?;
|
|
1201
|
+
|
|
1202
|
+
// Mark comments within the extracted range as emitted
|
|
1203
|
+
for (idx, comment) in self.all_comments.iter().enumerate() {
|
|
1204
|
+
if !self.emitted_comment_indices.contains(&idx)
|
|
1205
|
+
&& comment.location.start_offset >= start
|
|
1206
|
+
&& comment.location.end_offset <= end
|
|
1207
|
+
{
|
|
1208
|
+
self.emitted_comment_indices.insert(idx);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1172
1211
|
}
|
|
1173
1212
|
}
|
|
1174
1213
|
|
|
@@ -1193,14 +1232,27 @@ impl Emitter {
|
|
|
1193
1232
|
let mut last_stmt_end_line = block_start_line;
|
|
1194
1233
|
|
|
1195
1234
|
for child in &block_node.children {
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1235
|
+
match &child.node_type {
|
|
1236
|
+
NodeType::StatementsNode => {
|
|
1237
|
+
self.emit_statements(child, indent_level + 1)?;
|
|
1238
|
+
// Track the last statement's end line for blank line preservation
|
|
1239
|
+
if let Some(last_child) = child.children.last() {
|
|
1240
|
+
last_stmt_end_line = last_child.location.end_line;
|
|
1241
|
+
}
|
|
1242
|
+
self.buffer.push('\n');
|
|
1243
|
+
break;
|
|
1244
|
+
}
|
|
1245
|
+
NodeType::BeginNode => {
|
|
1246
|
+
// Block with rescue/ensure/else - delegate to emit_begin
|
|
1247
|
+
// which handles implicit begin (no "begin" keyword)
|
|
1248
|
+
self.emit_begin(child, indent_level + 1)?;
|
|
1249
|
+
self.buffer.push('\n');
|
|
1250
|
+
last_stmt_end_line = child.location.end_line;
|
|
1251
|
+
break;
|
|
1252
|
+
}
|
|
1253
|
+
_ => {
|
|
1254
|
+
// Skip parameter nodes
|
|
1201
1255
|
}
|
|
1202
|
-
self.buffer.push('\n');
|
|
1203
|
-
break;
|
|
1204
1256
|
}
|
|
1205
1257
|
}
|
|
1206
1258
|
|
|
@@ -1236,6 +1288,7 @@ impl Emitter {
|
|
|
1236
1288
|
fn emit_brace_block(&mut self, block_node: &Node, indent_level: usize) -> Result<()> {
|
|
1237
1289
|
// Determine if block should be inline or multiline
|
|
1238
1290
|
let is_multiline = block_node.location.start_line != block_node.location.end_line;
|
|
1291
|
+
let block_end_line = block_node.location.end_line;
|
|
1239
1292
|
|
|
1240
1293
|
if is_multiline {
|
|
1241
1294
|
// Multiline brace block
|
|
@@ -1254,6 +1307,7 @@ impl Emitter {
|
|
|
1254
1307
|
|
|
1255
1308
|
self.emit_indent(indent_level)?;
|
|
1256
1309
|
write!(self.buffer, "}}")?;
|
|
1310
|
+
self.emit_trailing_comments(block_end_line)?;
|
|
1257
1311
|
} else {
|
|
1258
1312
|
// Inline brace block - extract from source to preserve spacing
|
|
1259
1313
|
write!(self.buffer, " ")?;
|
|
@@ -1263,6 +1317,7 @@ impl Emitter {
|
|
|
1263
1317
|
{
|
|
1264
1318
|
write!(self.buffer, "{}", text)?;
|
|
1265
1319
|
}
|
|
1320
|
+
self.emit_trailing_comments(block_end_line)?;
|
|
1266
1321
|
}
|
|
1267
1322
|
|
|
1268
1323
|
Ok(())
|
|
@@ -1571,7 +1626,6 @@ impl Emitter {
|
|
|
1571
1626
|
match &child.node_type {
|
|
1572
1627
|
NodeType::InNode => {
|
|
1573
1628
|
self.emit_in(child, indent_level)?;
|
|
1574
|
-
self.buffer.push('\n');
|
|
1575
1629
|
}
|
|
1576
1630
|
NodeType::ElseNode => {
|
|
1577
1631
|
self.emit_indent(indent_level)?;
|
|
@@ -1607,19 +1661,25 @@ impl Emitter {
|
|
|
1607
1661
|
|
|
1608
1662
|
// First child is the pattern
|
|
1609
1663
|
if let Some(pattern) = node.children.first() {
|
|
1610
|
-
|
|
1611
|
-
let end = pattern.location.end_offset;
|
|
1612
|
-
if let Some(text) = self.source.get(start..end) {
|
|
1613
|
-
write!(self.buffer, "{}", text)?;
|
|
1614
|
-
}
|
|
1664
|
+
self.write_source_text(pattern)?;
|
|
1615
1665
|
}
|
|
1616
1666
|
|
|
1617
|
-
self.
|
|
1667
|
+
if self.is_single_line(node) {
|
|
1668
|
+
// Inline style: in X then Y
|
|
1669
|
+
if let Some(statements) = node.children.get(1) {
|
|
1670
|
+
write!(self.buffer, " then ")?;
|
|
1671
|
+
self.write_source_text(statements)?;
|
|
1672
|
+
}
|
|
1673
|
+
self.buffer.push('\n');
|
|
1674
|
+
} else {
|
|
1675
|
+
// Multi-line style: in X\n Y
|
|
1676
|
+
self.buffer.push('\n');
|
|
1618
1677
|
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1678
|
+
// Second child is the statements body
|
|
1679
|
+
if let Some(statements) = node.children.get(1) {
|
|
1680
|
+
if matches!(statements.node_type, NodeType::StatementsNode) {
|
|
1681
|
+
self.emit_statements(statements, indent_level + 1)?;
|
|
1682
|
+
}
|
|
1623
1683
|
}
|
|
1624
1684
|
}
|
|
1625
1685
|
|
data/ext/rfmt/src/lib.rs
CHANGED
|
@@ -10,7 +10,7 @@ use policy::SecurityPolicy;
|
|
|
10
10
|
|
|
11
11
|
use config::Config;
|
|
12
12
|
use emitter::Emitter;
|
|
13
|
-
use magnus::{
|
|
13
|
+
use magnus::{function, prelude::*, Error, Ruby};
|
|
14
14
|
use parser::{PrismAdapter, RubyParser};
|
|
15
15
|
|
|
16
16
|
fn format_ruby_code(ruby: &Ruby, source: String, json: String) -> Result<String, Error> {
|
|
@@ -45,10 +45,10 @@ fn rust_version() -> String {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
#[magnus::init]
|
|
48
|
-
fn init(
|
|
48
|
+
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
49
49
|
logging::RfmtLogger::init();
|
|
50
50
|
|
|
51
|
-
let module = define_module("Rfmt")?;
|
|
51
|
+
let module = ruby.define_module("Rfmt")?;
|
|
52
52
|
|
|
53
53
|
module.define_singleton_method("format_code", function!(format_ruby_code, 2))?;
|
|
54
54
|
module.define_singleton_method("parse_to_json", function!(parse_to_json, 1))?;
|
data/lib/rfmt/prism_bridge.rb
CHANGED
|
@@ -108,16 +108,69 @@ module Rfmt
|
|
|
108
108
|
# Extract location information from node
|
|
109
109
|
def self.extract_location(node)
|
|
110
110
|
loc = node.location
|
|
111
|
+
|
|
112
|
+
# For heredoc nodes, the location only covers the opening tag (<<~CSV)
|
|
113
|
+
# We need to find the maximum end_offset including closing_loc
|
|
114
|
+
end_offset = loc.end_offset
|
|
115
|
+
end_line = loc.end_line
|
|
116
|
+
end_column = loc.end_column
|
|
117
|
+
|
|
118
|
+
# Check this node's closing_loc
|
|
119
|
+
if node.respond_to?(:closing_loc) && node.closing_loc
|
|
120
|
+
closing = node.closing_loc
|
|
121
|
+
if closing.end_offset > end_offset
|
|
122
|
+
end_offset = closing.end_offset
|
|
123
|
+
end_line = closing.end_line
|
|
124
|
+
end_column = closing.end_column
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
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]
|
|
136
|
+
end
|
|
137
|
+
|
|
111
138
|
{
|
|
112
139
|
start_line: loc.start_line,
|
|
113
140
|
start_column: loc.start_column,
|
|
114
|
-
end_line:
|
|
115
|
-
end_column:
|
|
141
|
+
end_line: end_line,
|
|
142
|
+
end_column: end_column,
|
|
116
143
|
start_offset: loc.start_offset,
|
|
117
|
-
end_offset:
|
|
144
|
+
end_offset: end_offset
|
|
118
145
|
}
|
|
119
146
|
end
|
|
120
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
|
+
|
|
121
174
|
# Extract child nodes
|
|
122
175
|
def self.extract_children(node)
|
|
123
176
|
children = []
|
|
@@ -176,6 +229,7 @@ module Rfmt
|
|
|
176
229
|
[
|
|
177
230
|
node.statements,
|
|
178
231
|
node.rescue_clause,
|
|
232
|
+
node.else_clause,
|
|
179
233
|
node.ensure_clause
|
|
180
234
|
].compact
|
|
181
235
|
when Prism::EnsureNode
|
data/lib/rfmt/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rfmt
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- fujitani sora
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: diff-lcs
|
|
@@ -113,7 +112,6 @@ metadata:
|
|
|
113
112
|
source_code_uri: https://github.com/fs0414/rfmt
|
|
114
113
|
changelog_uri: https://github.com/fs0414/rfmt/releases
|
|
115
114
|
ruby_lsp_addon: 'true'
|
|
116
|
-
post_install_message:
|
|
117
115
|
rdoc_options: []
|
|
118
116
|
require_paths:
|
|
119
117
|
- lib
|
|
@@ -128,8 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
128
126
|
- !ruby/object:Gem::Version
|
|
129
127
|
version: 3.0.0
|
|
130
128
|
requirements: []
|
|
131
|
-
rubygems_version: 3.
|
|
132
|
-
signing_key:
|
|
129
|
+
rubygems_version: 3.6.9
|
|
133
130
|
specification_version: 4
|
|
134
131
|
summary: Ruby Formatter impl Rust lang.
|
|
135
132
|
test_files: []
|