rfmt 1.3.1 → 1.3.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 +4 -4
- data/CHANGELOG.md +14 -0
- data/Cargo.lock +1 -1
- data/ext/rfmt/Cargo.toml +1 -1
- data/ext/rfmt/src/ast/mod.rs +13 -4
- data/ext/rfmt/src/emitter/mod.rs +11 -13
- data/ext/rfmt/src/logging/logger.rs +11 -2
- data/ext/rfmt/src/parser/prism_adapter.rs +54 -32
- data/ext/rfmt/src/policy/validation.rs +35 -0
- data/lib/rfmt/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 79f5e6c24f82f6552874aefda2a6b97d8f4612419810d76ebd9381e862edded4
|
|
4
|
+
data.tar.gz: f193dab59e9a02f62d3e599c8f0349ebbf6e7fda070ba55990e9a8d399990f72
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6ebfd2c04793445912e1edc28db74e5cfa218fcd3fc28f6e3b42a88b1794fe0bfc6643890fa34477291c7ad8624696f3bb7207436ecfeb6f756439be5d9065ae
|
|
7
|
+
data.tar.gz: e209cf0a8671786af5e1560979f70f5fe8bb4e2de3912613a59e6d6269effe54f123407ca8989b50578f330d16e8a0d2fa7a1b47c8c07d5731e893664892fe1a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.3.2] - 2026-01-09
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Implement `std::str::FromStr` trait for `NodeType`
|
|
7
|
+
- Unit tests for validation module
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Use serde enum for comment type deserialization (type-safe JSON parsing)
|
|
11
|
+
- Convert recursive `find_last_code_line` to iterative approach (prevent stack overflow)
|
|
12
|
+
- Use BTreeMap index for comment lookup in `emit_statements` (O(n) → O(log n))
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Remove panic-prone `unwrap()` on Mutex lock in logger (prevent Ruby VM crash)
|
|
16
|
+
|
|
3
17
|
## [1.3.1] - 2026-01-08
|
|
4
18
|
|
|
5
19
|
### Added
|
data/Cargo.lock
CHANGED
data/ext/rfmt/Cargo.toml
CHANGED
data/ext/rfmt/src/ast/mod.rs
CHANGED
|
@@ -235,9 +235,11 @@ pub enum NodeType {
|
|
|
235
235
|
Unknown(String),
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
impl NodeType {
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
impl std::str::FromStr for NodeType {
|
|
239
|
+
type Err = std::convert::Infallible;
|
|
240
|
+
|
|
241
|
+
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
242
|
+
Ok(match s {
|
|
241
243
|
"program_node" => Self::ProgramNode,
|
|
242
244
|
"statements_node" => Self::StatementsNode,
|
|
243
245
|
"class_node" => Self::ClassNode,
|
|
@@ -383,7 +385,14 @@ impl NodeType {
|
|
|
383
385
|
"implicit_node" => Self::ImplicitNode,
|
|
384
386
|
"implicit_rest_node" => Self::ImplicitRestNode,
|
|
385
387
|
_ => Self::Unknown(s.to_string()),
|
|
386
|
-
}
|
|
388
|
+
})
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
impl NodeType {
|
|
393
|
+
/// Parse a node type from a string (convenience wrapper for `FromStr`)
|
|
394
|
+
pub fn from_str(s: &str) -> Self {
|
|
395
|
+
s.parse().unwrap()
|
|
387
396
|
}
|
|
388
397
|
|
|
389
398
|
/// Check if this node type is a definition (class, module, or method)
|
data/ext/rfmt/src/emitter/mod.rs
CHANGED
|
@@ -79,12 +79,13 @@ impl Emitter {
|
|
|
79
79
|
/// Find the last line of code in the AST (excluding comments)
|
|
80
80
|
fn find_last_code_line(ast: &Node) -> usize {
|
|
81
81
|
let mut max_line = ast.location.end_line;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
let mut stack = vec![ast];
|
|
83
|
+
|
|
84
|
+
while let Some(node) = stack.pop() {
|
|
85
|
+
max_line = max_line.max(node.location.end_line);
|
|
86
|
+
stack.extend(node.children.iter());
|
|
87
87
|
}
|
|
88
|
+
|
|
88
89
|
max_line
|
|
89
90
|
}
|
|
90
91
|
|
|
@@ -426,15 +427,12 @@ impl Emitter {
|
|
|
426
427
|
let next_start_line = next_child.location.start_line;
|
|
427
428
|
|
|
428
429
|
// Find the first comment between current and next node (if any)
|
|
430
|
+
// Uses BTreeMap range for O(log n) lookup instead of O(n) iteration
|
|
429
431
|
let first_comment_line = self
|
|
430
|
-
.
|
|
431
|
-
.
|
|
432
|
-
.
|
|
433
|
-
|
|
434
|
-
&& c.location.end_line < next_start_line
|
|
435
|
-
})
|
|
436
|
-
.map(|c| c.location.start_line)
|
|
437
|
-
.min();
|
|
432
|
+
.comments_by_line
|
|
433
|
+
.range((current_end_line + 1)..next_start_line)
|
|
434
|
+
.next()
|
|
435
|
+
.map(|(line, _)| *line);
|
|
438
436
|
|
|
439
437
|
// Calculate line diff based on whether there's a comment
|
|
440
438
|
let effective_next_line = first_comment_line.unwrap_or(next_start_line);
|
|
@@ -54,7 +54,12 @@ impl Log for RfmtLogger {
|
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
// Use unwrap_or_else to recover from poisoned mutex.
|
|
58
|
+
// Logging should never cause a panic, even if another thread panicked while holding the lock.
|
|
59
|
+
let mut output = self
|
|
60
|
+
.output
|
|
61
|
+
.lock()
|
|
62
|
+
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
|
58
63
|
|
|
59
64
|
writeln!(
|
|
60
65
|
output,
|
|
@@ -67,7 +72,11 @@ impl Log for RfmtLogger {
|
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
fn flush(&self) {
|
|
70
|
-
|
|
75
|
+
// Recover from poisoned mutex - flushing should not panic
|
|
76
|
+
let mut output = self
|
|
77
|
+
.output
|
|
78
|
+
.lock()
|
|
79
|
+
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
|
71
80
|
output.flush().ok();
|
|
72
81
|
}
|
|
73
82
|
}
|
|
@@ -13,8 +13,8 @@ impl PrismAdapter {
|
|
|
13
13
|
Self
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/// Parse JSON from Ruby's PrismBridge
|
|
17
|
-
fn parse_json(
|
|
16
|
+
/// Parse JSON from Ruby's `PrismBridge`
|
|
17
|
+
fn parse_json(json: &str) -> Result<(PrismNode, Vec<PrismComment>)> {
|
|
18
18
|
// Try to parse as new format with comments first
|
|
19
19
|
if let Ok(wrapper) = serde_json::from_str::<PrismWrapper>(json) {
|
|
20
20
|
return Ok((wrapper.ast, wrapper.comments));
|
|
@@ -26,8 +26,8 @@ impl PrismAdapter {
|
|
|
26
26
|
Ok((node, Vec::new()))
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
/// Convert PrismNode to internal Node representation
|
|
30
|
-
fn convert_node(
|
|
29
|
+
/// Convert `PrismNode` to internal `Node` representation
|
|
30
|
+
fn convert_node(prism_node: &PrismNode) -> Result<Node> {
|
|
31
31
|
// Convert node type (always succeeds, returns Unknown for unsupported types)
|
|
32
32
|
let node_type = NodeType::from_str(&prism_node.node_type);
|
|
33
33
|
|
|
@@ -42,18 +42,15 @@ impl PrismAdapter {
|
|
|
42
42
|
);
|
|
43
43
|
|
|
44
44
|
// Convert children recursively
|
|
45
|
-
let children: Result<Vec<Node>> =
|
|
46
|
-
.children
|
|
47
|
-
.iter()
|
|
48
|
-
.map(|child| self.convert_node(child))
|
|
49
|
-
.collect();
|
|
45
|
+
let children: Result<Vec<Node>> =
|
|
46
|
+
prism_node.children.iter().map(Self::convert_node).collect();
|
|
50
47
|
let children = children?;
|
|
51
48
|
|
|
52
49
|
// Convert comments
|
|
53
50
|
let comments: Vec<Comment> = prism_node
|
|
54
51
|
.comments
|
|
55
52
|
.iter()
|
|
56
|
-
.map(
|
|
53
|
+
.map(Self::convert_comment)
|
|
57
54
|
.collect();
|
|
58
55
|
|
|
59
56
|
// Convert formatting info
|
|
@@ -76,21 +73,8 @@ impl PrismAdapter {
|
|
|
76
73
|
})
|
|
77
74
|
}
|
|
78
75
|
|
|
79
|
-
/// Convert PrismComment to internal Comment
|
|
80
|
-
fn convert_comment(
|
|
81
|
-
let comment_type = match comment.comment_type.as_str() {
|
|
82
|
-
"line" => CommentType::Line,
|
|
83
|
-
"block" => CommentType::Block,
|
|
84
|
-
_ => CommentType::Line, // default to line comment
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
let position = match comment.position.as_str() {
|
|
88
|
-
"leading" => CommentPosition::Leading,
|
|
89
|
-
"trailing" => CommentPosition::Trailing,
|
|
90
|
-
"inner" => CommentPosition::Inner,
|
|
91
|
-
_ => CommentPosition::Leading, // default to leading
|
|
92
|
-
};
|
|
93
|
-
|
|
76
|
+
/// Convert `PrismComment` to internal `Comment`
|
|
77
|
+
fn convert_comment(comment: &PrismComment) -> Comment {
|
|
94
78
|
Comment {
|
|
95
79
|
text: comment.text.clone(),
|
|
96
80
|
location: Location::new(
|
|
@@ -101,21 +85,21 @@ impl PrismAdapter {
|
|
|
101
85
|
comment.location.start_offset,
|
|
102
86
|
comment.location.end_offset,
|
|
103
87
|
),
|
|
104
|
-
comment_type,
|
|
105
|
-
position,
|
|
88
|
+
comment_type: comment.comment_type.into(),
|
|
89
|
+
position: comment.position.into(),
|
|
106
90
|
}
|
|
107
91
|
}
|
|
108
92
|
}
|
|
109
93
|
|
|
110
94
|
impl RubyParser for PrismAdapter {
|
|
111
95
|
fn parse(&self, json: &str) -> Result<Node> {
|
|
112
|
-
let (prism_ast, top_level_comments) =
|
|
113
|
-
let mut node =
|
|
96
|
+
let (prism_ast, top_level_comments) = Self::parse_json(json)?;
|
|
97
|
+
let mut node = Self::convert_node(&prism_ast)?;
|
|
114
98
|
|
|
115
99
|
// Attach top-level comments to the root node
|
|
116
100
|
if !top_level_comments.is_empty() {
|
|
117
101
|
node.comments
|
|
118
|
-
.extend(top_level_comments.iter().map(
|
|
102
|
+
.extend(top_level_comments.iter().map(Self::convert_comment));
|
|
119
103
|
}
|
|
120
104
|
|
|
121
105
|
Ok(node)
|
|
@@ -160,8 +144,46 @@ pub struct PrismLocation {
|
|
|
160
144
|
pub struct PrismComment {
|
|
161
145
|
pub text: String,
|
|
162
146
|
pub location: PrismLocation,
|
|
163
|
-
|
|
164
|
-
pub
|
|
147
|
+
#[serde(rename = "type", default)]
|
|
148
|
+
pub comment_type: PrismCommentType,
|
|
149
|
+
#[serde(default)]
|
|
150
|
+
pub position: PrismCommentPosition,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
|
154
|
+
#[serde(rename_all = "lowercase")]
|
|
155
|
+
pub enum PrismCommentType {
|
|
156
|
+
#[default]
|
|
157
|
+
Line,
|
|
158
|
+
Block,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
impl From<PrismCommentType> for CommentType {
|
|
162
|
+
fn from(t: PrismCommentType) -> Self {
|
|
163
|
+
match t {
|
|
164
|
+
PrismCommentType::Line => CommentType::Line,
|
|
165
|
+
PrismCommentType::Block => CommentType::Block,
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
|
171
|
+
#[serde(rename_all = "lowercase")]
|
|
172
|
+
pub enum PrismCommentPosition {
|
|
173
|
+
#[default]
|
|
174
|
+
Leading,
|
|
175
|
+
Trailing,
|
|
176
|
+
Inner,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
impl From<PrismCommentPosition> for CommentPosition {
|
|
180
|
+
fn from(p: PrismCommentPosition) -> Self {
|
|
181
|
+
match p {
|
|
182
|
+
PrismCommentPosition::Leading => CommentPosition::Leading,
|
|
183
|
+
PrismCommentPosition::Trailing => CommentPosition::Trailing,
|
|
184
|
+
PrismCommentPosition::Inner => CommentPosition::Inner,
|
|
185
|
+
}
|
|
186
|
+
}
|
|
165
187
|
}
|
|
166
188
|
|
|
167
189
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
@@ -16,3 +16,38 @@ pub fn validate_source_size(source: &str, max_size: u64) -> Result<()> {
|
|
|
16
16
|
|
|
17
17
|
Ok(())
|
|
18
18
|
}
|
|
19
|
+
|
|
20
|
+
#[cfg(test)]
|
|
21
|
+
mod tests {
|
|
22
|
+
use super::*;
|
|
23
|
+
|
|
24
|
+
#[test]
|
|
25
|
+
fn test_validate_source_size_ok() {
|
|
26
|
+
assert!(validate_source_size("small", 1000).is_ok());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[test]
|
|
30
|
+
fn test_validate_source_size_at_limit() {
|
|
31
|
+
let source = "a".repeat(1000);
|
|
32
|
+
assert!(validate_source_size(&source, 1000).is_ok());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#[test]
|
|
36
|
+
fn test_validate_source_size_exceeds_limit() {
|
|
37
|
+
let source = "a".repeat(1001);
|
|
38
|
+
assert!(validate_source_size(&source, 1000).is_err());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#[test]
|
|
42
|
+
fn test_validate_source_size_empty() {
|
|
43
|
+
assert!(validate_source_size("", 1000).is_ok());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#[test]
|
|
47
|
+
fn test_validate_source_size_unicode() {
|
|
48
|
+
// "日本語" = 9 bytes in UTF-8
|
|
49
|
+
let source = "日本語";
|
|
50
|
+
assert!(validate_source_size(source, 9).is_ok());
|
|
51
|
+
assert!(validate_source_size(source, 8).is_err());
|
|
52
|
+
}
|
|
53
|
+
}
|
data/lib/rfmt/version.rb
CHANGED
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: 1.3.
|
|
4
|
+
version: 1.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- fujitani sora
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-01-
|
|
11
|
+
date: 2026-01-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rb_sys
|