rfmt 1.5.3 → 1.6.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 +31 -0
- data/Cargo.lock +1 -1
- data/README.md +22 -18
- data/ext/rfmt/Cargo.toml +4 -1
- data/ext/rfmt/src/doc/builders.rs +528 -0
- data/ext/rfmt/src/doc/mod.rs +220 -0
- data/ext/rfmt/src/doc/printer.rs +684 -0
- data/ext/rfmt/src/format/context.rs +448 -0
- data/ext/rfmt/src/format/formatter.rs +226 -0
- data/ext/rfmt/src/format/mod.rs +35 -0
- data/ext/rfmt/src/format/registry.rs +195 -0
- data/ext/rfmt/src/format/rule.rs +555 -0
- data/ext/rfmt/src/format/rules/begin.rs +295 -0
- data/ext/rfmt/src/format/rules/body_end.rs +109 -0
- data/ext/rfmt/src/format/rules/call.rs +409 -0
- data/ext/rfmt/src/format/rules/case.rs +359 -0
- data/ext/rfmt/src/format/rules/class.rs +160 -0
- data/ext/rfmt/src/format/rules/def.rs +216 -0
- data/ext/rfmt/src/format/rules/fallback.rs +116 -0
- data/ext/rfmt/src/format/rules/if_unless.rs +407 -0
- data/ext/rfmt/src/format/rules/loops.rs +325 -0
- data/ext/rfmt/src/format/rules/mod.rs +31 -0
- data/ext/rfmt/src/format/rules/module.rs +150 -0
- data/ext/rfmt/src/format/rules/singleton_class.rs +202 -0
- data/ext/rfmt/src/format/rules/statements.rs +122 -0
- data/ext/rfmt/src/format/rules/variable_write.rs +296 -0
- data/ext/rfmt/src/lib.rs +8 -5
- data/ext/rfmt/src/parser/prism_adapter.rs +157 -2
- data/lib/rfmt/version.rb +1 -1
- data/lib/ruby_lsp/rfmt/formatter_runner.rb +2 -0
- metadata +23 -2
- data/ext/rfmt/src/emitter/mod.rs +0 -1844
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
//! Doc IR (Intermediate Representation) for rfmt
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides a Prettier/Biome-style intermediate representation
|
|
4
|
+
//! for code formatting. The Doc IR separates formatting intent from string generation,
|
|
5
|
+
//! enabling automatic line breaking and consistent formatting.
|
|
6
|
+
//!
|
|
7
|
+
//! # Architecture
|
|
8
|
+
//!
|
|
9
|
+
//! ```text
|
|
10
|
+
//! AST → DocBuilder → Doc IR → Printer → String
|
|
11
|
+
//! ```
|
|
12
|
+
//!
|
|
13
|
+
//! # Example
|
|
14
|
+
//!
|
|
15
|
+
//! ```rust
|
|
16
|
+
//! use rfmt::doc::builders::*;
|
|
17
|
+
//! use rfmt::doc::Printer;
|
|
18
|
+
//!
|
|
19
|
+
//! let doc = concat(vec![
|
|
20
|
+
//! text("def foo"),
|
|
21
|
+
//! hardline(),
|
|
22
|
+
//! indent(text("body")),
|
|
23
|
+
//! hardline(),
|
|
24
|
+
//! text("end"),
|
|
25
|
+
//! ]);
|
|
26
|
+
//!
|
|
27
|
+
//! let mut printer = Printer::new(config);
|
|
28
|
+
//! let output = printer.print(&doc);
|
|
29
|
+
//! // => "def foo\n body\nend"
|
|
30
|
+
//! ```
|
|
31
|
+
|
|
32
|
+
pub mod builders;
|
|
33
|
+
pub mod printer;
|
|
34
|
+
|
|
35
|
+
pub use builders::*;
|
|
36
|
+
pub use printer::Printer;
|
|
37
|
+
|
|
38
|
+
/// Document intermediate representation for code formatting.
|
|
39
|
+
///
|
|
40
|
+
/// Doc is a tree structure that describes how code should be formatted
|
|
41
|
+
/// without committing to specific line breaks. The Printer converts
|
|
42
|
+
/// Doc to a string, making line break decisions based on line width.
|
|
43
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
44
|
+
pub enum Doc {
|
|
45
|
+
/// A text literal that is printed as-is.
|
|
46
|
+
///
|
|
47
|
+
/// # Example
|
|
48
|
+
/// ```rust
|
|
49
|
+
/// Doc::Text("hello".to_string())
|
|
50
|
+
/// ```
|
|
51
|
+
Text(String),
|
|
52
|
+
|
|
53
|
+
/// Concatenation of multiple documents.
|
|
54
|
+
///
|
|
55
|
+
/// # Example
|
|
56
|
+
/// ```rust
|
|
57
|
+
/// Doc::Concat(vec![Doc::Text("a".into()), Doc::Text("b".into())])
|
|
58
|
+
/// // Prints: "ab"
|
|
59
|
+
/// ```
|
|
60
|
+
Concat(Vec<Doc>),
|
|
61
|
+
|
|
62
|
+
/// A group that tries to fit on one line.
|
|
63
|
+
///
|
|
64
|
+
/// If the contents fit within the remaining line width, Line docs
|
|
65
|
+
/// inside are replaced with spaces. Otherwise, they become newlines.
|
|
66
|
+
///
|
|
67
|
+
/// # Fields
|
|
68
|
+
/// - `contents`: The document to group
|
|
69
|
+
/// - `break_parent`: If true, forces parent groups to break
|
|
70
|
+
/// - `id`: Optional identifier for IfBreak references
|
|
71
|
+
Group {
|
|
72
|
+
contents: Box<Doc>,
|
|
73
|
+
break_parent: bool,
|
|
74
|
+
id: Option<GroupId>,
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/// A potential line break.
|
|
78
|
+
///
|
|
79
|
+
/// # Variants (controlled by fields)
|
|
80
|
+
/// - `soft=false, hard=false`: Space if flat, newline if broken
|
|
81
|
+
/// - `soft=true, hard=false`: Nothing if flat, newline if broken
|
|
82
|
+
/// - `hard=true`: Always a newline
|
|
83
|
+
/// - `literal=true`: Newline without indentation (for heredocs)
|
|
84
|
+
Line {
|
|
85
|
+
soft: bool,
|
|
86
|
+
hard: bool,
|
|
87
|
+
literal: bool,
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
/// Increases indentation for the contents.
|
|
91
|
+
///
|
|
92
|
+
/// # Example
|
|
93
|
+
/// ```rust
|
|
94
|
+
/// concat(vec![
|
|
95
|
+
/// text("if true"),
|
|
96
|
+
/// hardline(),
|
|
97
|
+
/// indent(text("body")),
|
|
98
|
+
/// hardline(),
|
|
99
|
+
/// text("end"),
|
|
100
|
+
/// ])
|
|
101
|
+
/// // Prints:
|
|
102
|
+
/// // if true
|
|
103
|
+
/// // body
|
|
104
|
+
/// // end
|
|
105
|
+
/// ```
|
|
106
|
+
Indent(Box<Doc>),
|
|
107
|
+
|
|
108
|
+
/// Conditional content based on group break state.
|
|
109
|
+
///
|
|
110
|
+
/// If the group is broken (multi-line), prints `break_contents`.
|
|
111
|
+
/// If the group is flat (single line), prints `flat_contents`.
|
|
112
|
+
///
|
|
113
|
+
/// # Fields
|
|
114
|
+
/// - `break_contents`: Printed when group is broken
|
|
115
|
+
/// - `flat_contents`: Printed when group is flat
|
|
116
|
+
/// - `group_id`: Optional reference to a specific group
|
|
117
|
+
IfBreak {
|
|
118
|
+
break_contents: Box<Doc>,
|
|
119
|
+
flat_contents: Box<Doc>,
|
|
120
|
+
group_id: Option<GroupId>,
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
/// An empty document that prints nothing.
|
|
124
|
+
Empty,
|
|
125
|
+
|
|
126
|
+
/// A trailing comment that appears at the end of a line.
|
|
127
|
+
///
|
|
128
|
+
/// # Example
|
|
129
|
+
/// ```rust
|
|
130
|
+
/// concat(vec![text("code"), Doc::TrailingComment("# comment".into())])
|
|
131
|
+
/// // Prints: "code # comment"
|
|
132
|
+
/// ```
|
|
133
|
+
TrailingComment(String),
|
|
134
|
+
|
|
135
|
+
/// A leading comment that appears before code.
|
|
136
|
+
///
|
|
137
|
+
/// # Fields
|
|
138
|
+
/// - `text`: The comment text
|
|
139
|
+
/// - `hard_line_after`: Whether to add a hard line after the comment
|
|
140
|
+
LeadingComment { text: String, hard_line_after: bool },
|
|
141
|
+
|
|
142
|
+
/// Aligns content to a specific column offset.
|
|
143
|
+
///
|
|
144
|
+
/// Used for aligning continuation lines or heredoc content.
|
|
145
|
+
Align { n: usize, contents: Box<Doc> },
|
|
146
|
+
|
|
147
|
+
/// Content to be appended at the end of the line.
|
|
148
|
+
///
|
|
149
|
+
/// Useful for trailing commas that should only appear in multi-line mode.
|
|
150
|
+
LineSuffix(Box<Doc>),
|
|
151
|
+
|
|
152
|
+
/// Fill: packs content into lines as tightly as possible.
|
|
153
|
+
///
|
|
154
|
+
/// Used for array elements, argument lists, etc.
|
|
155
|
+
Fill(Vec<Doc>),
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// Identifier for referencing groups in IfBreak.
|
|
159
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
160
|
+
pub struct GroupId(pub u32);
|
|
161
|
+
|
|
162
|
+
impl Doc {
|
|
163
|
+
/// Returns true if this doc is empty.
|
|
164
|
+
pub fn is_empty(&self) -> bool {
|
|
165
|
+
matches!(self, Doc::Empty)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// Returns true if this doc contains only text (no line breaks possible).
|
|
169
|
+
pub fn is_flat(&self) -> bool {
|
|
170
|
+
match self {
|
|
171
|
+
Doc::Text(_) => true,
|
|
172
|
+
Doc::Concat(docs) => docs.iter().all(|d| d.is_flat()),
|
|
173
|
+
Doc::Group { contents, .. } => contents.is_flat(),
|
|
174
|
+
Doc::Indent(contents) => contents.is_flat(),
|
|
175
|
+
Doc::IfBreak { flat_contents, .. } => flat_contents.is_flat(),
|
|
176
|
+
Doc::Empty => true,
|
|
177
|
+
Doc::TrailingComment(_) | Doc::LeadingComment { .. } => true,
|
|
178
|
+
Doc::Line { .. } => false,
|
|
179
|
+
Doc::Align { contents, .. } => contents.is_flat(),
|
|
180
|
+
Doc::LineSuffix(_) => true,
|
|
181
|
+
Doc::Fill(docs) => docs.iter().all(|d| d.is_flat()),
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#[cfg(test)]
|
|
187
|
+
mod tests {
|
|
188
|
+
use super::*;
|
|
189
|
+
|
|
190
|
+
#[test]
|
|
191
|
+
fn test_doc_is_empty() {
|
|
192
|
+
assert!(Doc::Empty.is_empty());
|
|
193
|
+
assert!(!Doc::Text("hello".into()).is_empty());
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[test]
|
|
197
|
+
fn test_doc_is_flat() {
|
|
198
|
+
assert!(Doc::Text("hello".into()).is_flat());
|
|
199
|
+
assert!(Doc::Empty.is_flat());
|
|
200
|
+
assert!(Doc::Concat(vec![Doc::Text("a".into()), Doc::Text("b".into())]).is_flat());
|
|
201
|
+
|
|
202
|
+
// Line is not flat
|
|
203
|
+
assert!(!Doc::Line {
|
|
204
|
+
soft: false,
|
|
205
|
+
hard: false,
|
|
206
|
+
literal: false
|
|
207
|
+
}
|
|
208
|
+
.is_flat());
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
#[test]
|
|
212
|
+
fn test_group_id_equality() {
|
|
213
|
+
let id1 = GroupId(1);
|
|
214
|
+
let id2 = GroupId(1);
|
|
215
|
+
let id3 = GroupId(2);
|
|
216
|
+
|
|
217
|
+
assert_eq!(id1, id2);
|
|
218
|
+
assert_ne!(id1, id3);
|
|
219
|
+
}
|
|
220
|
+
}
|