1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
// SPDX-License-Identifier: GPL-3.0
//! This module provides utilities to “preserve” portions of a Rust source file
//! during parsing so that empty lines, comments, and certain attributes are not lost.
//!
//! The preservation process converts these elements into temporary doc comments (starting with
//! ///TEMP_DOC) and injects helper markers. This approach better preserves the original source code
//! structure, ensuring that all parts of the source (including non-code elements) are present in
//! the parsed AST.
//!
//! The public API consists of two functions:
//!
//! - [`preserve_and_parse`]: Reads a source file, applies preservation via a list of provided [`Preserver`], and
//! parses the resulting code into a [`syn::File`](https://docs.rs/syn/latest/syn/struct.File.html).
//! This function ensures that the full source code structure is retained during parsing.
//!
//! - [`resolve_preserved`]: Takes a preserved AST, un-parses it back to source code using [`prettyplease::unparse`](https://docs.rs/prettyplease/latest/prettyplease/fn.unparse.html),
//! and then restores the preserved comments and removes temporary markers.
//!
//! # Terminology
//!
//! The `Preserver` type specifies which parts of the code should remain unchanged, those parts are
//! called "preserved code". All the rest is considered non-preserved code.
//!
//! Preserved code is the code that will be parse in the AST in its original form, ie, it's
//! preserved for the AST.
//!
//! The naming may be a bit confusing, cause in practice, non-preserved code becomes a doc comment
//! in the way described above, so non-preserved code may be considered preserved in the sense that
//! its structure will be untouched when parsing the AST, but this isn't the sense in which this
//! term is applied in this crate.
//!
//! # Example
//!
//! ```rust
//! use test_builder::TestBuilder;
//! use rust_writer::preserver::Preserver;
//!
//! TestBuilder::default()
//! .with_complete_file()
//! .with_resolved_file()
//! .with_preserved_file_ast()
//! .execute(|builder| {
//! let complete_file_path = builder.tempfile_path("complete_file.rs")
//! .expect("This exists; qed;");
//!
//! let preserved_ast = builder.get_ref_ast_file("preserved_file.rs")
//! .expect("This exists; qed;");
//!
//! let preserver1 = Preserver::new("struct MyStruct");
//! let mut preserver2 = Preserver::new("impl MyTrait for MyStruct");
//! preserver2.add_inners(&["fn trait_method"]);
//! let preserver3 = Preserver::new("fn main");
//!
//! assert_eq!(
//! *preserved_ast,
//! rust_writer::preserver::preserve_and_parse(
//! builder.tempfile_path("complete_file.rs").expect("This exists; qed;"),
//! &[&preserver1, &preserver2, &preserver3]
//! )
//! .expect("This should be Ok; qed;")
//! );
//!
//! // While the code in "resolved_file" and "complete_file" is the same, the formatting
//! // isn't the same cause there's a preserved declarative macro with enough
//! // complexity, so we cannot compare the resolved directly with the original.
//! // Check both files in the repo to see their formatting differences.
//! let resolved_file_path = builder.tempfile_path("resolved_file.rs")
//! .expect("This exists; qed;");
//!
//! let expected_code = std::fs::read_to_string(&resolved_file_path)
//! .expect("File should be readable");
//!
//! assert!(rust_writer::preserver::resolve_preserved(
//! builder.get_ref_ast_file("preserved_file.rs")
//! .expect("This exists; qed;"),
//! complete_file_path
//! )
//! .is_ok());
//!
//! let actual_code = std::fs::read_to_string(complete_file_path)
//! .expect("File should be readable");
//!
//! assert_eq!(actual_code, expected_code);
//! });
//! ```
use crateError;
use ;
use Path;
use File;
use DelimitersCount;
pub use Preserver;
/// Reads the Rust source file at the given `code` path, applies the specified
/// preservation strategies (via the list of [`Preserver`]), and parses the resulting
/// code into a [`syn::File`]. All the preserved code becomes a doc comment starting by
/// ///TEMP_DOC.
///
/// All the code that isn't inside a preserved block (identified by a `Preserved`) becomes
/// non-preserved code, which means that this code becomes a doc comment in the way described above.
///
/// This function is useful to ensure that non-code elements such as comments,
/// empty lines, and global attributes are not lost during parsing. By converting these parts
/// into doc comment tokens, the overall source code structure is better preserved, which can
/// simplify later processing and transformations.
///
/// # Non preservable code
///
/// Inside preserved code, empty lines and comments become doc comments in order to keep them in
/// the AST. Those doc comments must document something to stay in the AST, so a temporal marker
/// `type temp_marker = ();` is added to achieve that. While this is enough in many cases, it has a
/// counterpart.
///
/// Imagine that this struct is preserved code:
///
/// ```no_compile
/// struct MyStruct {
/// // Invalid comment
/// field1: i32,
/// field2: String,
/// }
/// ```
///
/// It will result in the following struct:
///
/// ```no_compile
/// struct MyStruct {
/// ///TEMP_DOC // Invalid comment
/// type temp_marker = ();
/// field1: i32,
/// field2: String,
/// }
/// ```
///
/// which is invalid Rust code, impossible to be parsed into an AST. A file containing this lines
/// would be deemed as "non preservable code" if those lines are preserved. But it's perfectly
/// valid if those lines are non-preserved, so just pay attention to preserved code.
///
/// The following snippet shows illustrates it:
///
/// ```rust
/// use test_builder::TestBuilder;
/// use rust_writer::{preserver::Preserver, Error};
/// TestBuilder::default().with_non_preservable_file().execute(|builder| {
/// let preserver1 = Preserver::new("struct MyStruct");
/// let mut preserver2 = Preserver::new("impl MyTrait for MyStruct");
/// preserver2.add_inners(&["fn trait_method"]);
/// let preserver3 = Preserver::new("fn main");
///
/// assert!(matches!(
/// rust_writer::preserver::preserve_and_parse(
/// builder.tempfile_path("non_preservable_file.rs").expect("This exists; qed;"),
/// &[&preserver1, &preserver2, &preserver3]
/// ),
/// Err(Error::NonPreservableCode)
/// ));
///
/// assert!(rust_writer::preserver::preserve_and_parse(
/// builder.tempfile_path("non_preservable_file.rs").expect("This exists;qed"),
/// &[&preserver2, &preserver3]
/// ).is_ok());
/// });
/// ```
/// Resolves a previously preserved and parsed AST back into source code and writes it to the
/// specified `path`.
///
/// This function un-parses the given AST using [`prettyplease::unparse`] and resolves all the
/// changes applied by [`preserve_and_parse`], cleaning up all temporary doc comments and markers.
///
/// # Known limitations
///
/// The resolution process uses [`prettyplease::unparse`] to convert
/// the AST back into source code. While this approach generally preserves the overall structure of
/// the code, unparsing preserved declarative macro invocations (especially those that are
/// complex) can sometimes lead to formatting differences from the original source. This is a
/// well-known challenge in the Rust parsing ecosystem, and something to keep in mind.