1use proc_macro2::Span;
5use std::{
6 error, fmt, io,
7 path::{Path, PathBuf},
8};
9use syn::spanned::Spanned;
10use syn::ItemMod;
11
12mod mod_path;
13mod resolver;
14mod visitor;
15
16pub(crate) use mod_path::*;
17pub(crate) use resolver::*;
18pub(crate) use visitor::Visitor;
19
20pub fn parse_and_inline_modules(src_file: &Path) -> syn::File {
35 InlinerBuilder::default()
36 .parse_and_inline_modules(src_file)
37 .unwrap()
38 .output
39}
40
41#[derive(Debug)]
47pub struct InlinerBuilder {
48 root: bool,
49}
50
51impl Default for InlinerBuilder {
52 fn default() -> Self {
53 InlinerBuilder { root: true }
54 }
55}
56
57impl InlinerBuilder {
58 pub fn new() -> Self {
60 Self::default()
61 }
62
63 pub fn root(&mut self, root: bool) -> &mut Self {
70 self.root = root;
71 self
72 }
73
74 pub fn parse_and_inline_modules(&self, src_file: &Path) -> Result<InliningResult, Error> {
77 self.parse_internal(src_file, &mut FsResolver::new(|_: &Path, _| {}))
78 }
79
80 pub fn inline_with_callback(
84 &self,
85 src_file: &Path,
86 on_load: impl FnMut(&Path, String),
87 ) -> Result<InliningResult, Error> {
88 self.parse_internal(src_file, &mut FsResolver::new(on_load))
89 }
90
91 fn parse_internal<R: FileResolver>(
92 &self,
93 src_file: &Path,
94 resolver: &mut R,
95 ) -> Result<InliningResult, Error> {
96 let mut errors = Some(vec![]);
100 let result = Visitor::<R>::new(src_file, self.root, errors.as_mut(), resolver).visit()?;
101 Ok(InliningResult::new(result, errors.unwrap_or_default()))
102 }
103}
104
105#[derive(Debug)]
110pub enum Error {
111 Io(io::Error),
113
114 Parse(syn::Error),
116}
117
118impl error::Error for Error {
119 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
120 match self {
121 Error::Io(err) => Some(err),
122 Error::Parse(err) => Some(err),
123 }
124 }
125}
126
127impl From<io::Error> for Error {
128 fn from(err: io::Error) -> Self {
129 Error::Io(err)
130 }
131}
132
133impl From<syn::Error> for Error {
134 fn from(err: syn::Error) -> Self {
135 Error::Parse(err)
136 }
137}
138
139impl fmt::Display for Error {
140 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
141 match self {
142 Error::Io(_) => write!(f, "IO error"),
143 Error::Parse(_) => write!(f, "parse error"),
144 }
145 }
146}
147
148pub struct InliningResult {
153 output: syn::File,
154 errors: Vec<InlineError>,
155}
156
157impl InliningResult {
158 pub(crate) fn new(output: syn::File, errors: Vec<InlineError>) -> Self {
161 InliningResult { output, errors }
162 }
163
164 pub fn output(&self) -> &syn::File {
166 &self.output
167 }
168
169 pub fn errors(&self) -> &[InlineError] {
171 &self.errors
172 }
173
174 pub fn has_errors(&self) -> bool {
177 !self.errors.is_empty()
178 }
179
180 pub fn into_output_and_errors(self) -> (syn::File, Vec<InlineError>) {
202 (self.output, self.errors)
203 }
204}
205
206impl fmt::Debug for InliningResult {
207 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
208 self.errors.fmt(f)
209 }
210}
211
212impl fmt::Display for InliningResult {
213 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
214 writeln!(f, "Inlining partially completed before errors:")?;
215 for error in &self.errors {
216 writeln!(f, " * {}", error)?;
217 }
218
219 Ok(())
220 }
221}
222
223#[derive(Debug)]
225pub struct InlineError {
226 src_path: PathBuf,
227 module_name: String,
228 src_span: Span,
229 path: PathBuf,
230 kind: Error,
231}
232
233impl InlineError {
234 pub(crate) fn new(
235 src_path: impl Into<PathBuf>,
236 item_mod: &ItemMod,
237 path: impl Into<PathBuf>,
238 kind: Error,
239 ) -> Self {
240 Self {
241 src_path: src_path.into(),
242 module_name: item_mod.ident.to_string(),
243 src_span: item_mod.span(),
244 path: path.into(),
245 kind,
246 }
247 }
248
249 pub fn src_path(&self) -> &Path {
253 &self.src_path
254 }
255
256 pub fn module_name(&self) -> &str {
258 &self.module_name
259 }
260
261 pub fn src_span(&self) -> proc_macro2::Span {
264 self.src_span
265 }
266
267 pub fn path(&self) -> &Path {
271 &self.path
272 }
273
274 pub fn kind(&self) -> &Error {
276 &self.kind
277 }
278}
279
280impl fmt::Display for InlineError {
281 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
282 let start = self.src_span.start();
283 write!(
284 f,
285 "{}:{}:{}: error while including {}: {}",
286 self.src_path.display(),
287 start.line,
288 start.column,
289 self.path.display(),
290 self.kind
291 )
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use quote::{quote, ToTokens};
298
299 use super::*;
300
301 fn make_test_env() -> TestResolver {
302 let mut env = TestResolver::default();
303 env.register("src/lib.rs", "mod first;");
304 env.register("src/first/mod.rs", "mod second;");
305 env.register(
306 "src/first/second.rs",
307 r#"
308 #[doc = " Documentation"]
309 mod third {
310 mod fourth;
311 }
312
313 pub fn sample() -> usize { 4 }
314 "#,
315 );
316 env.register(
317 "src/first/second/third/fourth.rs",
318 "pub fn another_fn() -> bool { true }",
319 );
320 env
321 }
322
323 #[test]
325 fn happy_path() {
326 let result = InlinerBuilder::default()
327 .parse_internal(Path::new("src/lib.rs"), &mut make_test_env())
328 .unwrap()
329 .output;
330
331 assert_eq!(
332 result.into_token_stream().to_string(),
333 quote! {
334 mod first {
335 mod second {
336 #[doc = " Documentation"]
337 mod third {
338 mod fourth {
339 pub fn another_fn() -> bool {
340 true
341 }
342 }
343 }
344
345 pub fn sample() -> usize {
346 4
347 }
348 }
349 }
350 }
351 .to_string()
352 );
353 }
354
355 #[test]
357 fn missing_module() {
358 let mut env = TestResolver::default();
359 env.register("src/lib.rs", "mod missing;\nmod invalid;");
360 env.register("src/invalid.rs", "this-is-not-valid-rust!");
361
362 let result = InlinerBuilder::default().parse_internal(Path::new("src/lib.rs"), &mut env);
363
364 if let Ok(r) = result {
365 let errors = &r.errors;
366 assert_eq!(errors.len(), 2, "expected 2 errors");
367
368 let error = &errors[0];
369 assert_eq!(
370 error.src_path(),
371 Path::new("src/lib.rs"),
372 "correct source path"
373 );
374 assert_eq!(error.module_name(), "missing");
375 assert_eq!(error.src_span().start().line, 1);
376 assert_eq!(error.src_span().start().column, 0);
377 assert_eq!(error.src_span().end().line, 1);
378 assert_eq!(error.src_span().end().column, 12);
379 assert_eq!(error.path(), Path::new("src/missing/mod.rs"));
380 let io_err = match error.kind() {
381 Error::Io(err) => err,
382 _ => panic!("expected ErrorKind::Io, found {}", error.kind()),
383 };
384 assert_eq!(io_err.kind(), io::ErrorKind::NotFound);
385
386 let error = &errors[1];
387 assert_eq!(
388 error.src_path(),
389 Path::new("src/lib.rs"),
390 "correct source path"
391 );
392 assert_eq!(error.module_name(), "invalid");
393 assert_eq!(error.src_span().start().line, 2);
394 assert_eq!(error.src_span().start().column, 0);
395 assert_eq!(error.src_span().end().line, 2);
396 assert_eq!(error.src_span().end().column, 12);
397 assert_eq!(error.path(), Path::new("src/invalid.rs"));
398 match error.kind() {
399 Error::Parse(_) => {}
400 Error::Io(_) => panic!("expected ErrorKind::Parse, found {}", error.kind()),
401 }
402 } else {
403 unreachable!();
404 }
405 }
406
407 #[test]
416 #[should_panic]
417 fn cfg_attrs() {
418 let mut env = TestResolver::default();
419 env.register(
420 "src/lib.rs",
421 r#"
422 #[cfg(feature = "m1")]
423 mod m1;
424
425 #[cfg_attr(feature = "m2", path = "m2.rs")]
426 #[cfg_attr(not(feature = "m2"), path = "empty.rs")]
427 mod placeholder;
428 "#,
429 );
430 env.register("src/m1.rs", "struct M1;");
431 env.register(
432 "src/m2.rs",
433 "
434 //! module level doc comment
435
436 struct M2;
437 ",
438 );
439 env.register("src/empty.rs", "");
440
441 let result = InlinerBuilder::default()
442 .parse_internal(Path::new("src/lib.rs"), &mut env)
443 .unwrap()
444 .output;
445
446 assert_eq!(
447 result.into_token_stream().to_string(),
448 quote! {
449 #[cfg(feature = "m1")]
450 mod m1 {
451 struct M1;
452 }
453
454 #[cfg(feature = "m2")]
455 mod placeholder {
456 struct M2;
459 }
460
461 #[cfg(not(feature = "m2"))]
462 mod placeholder {
463
464 }
465 }
466 .to_string()
467 )
468 }
469
470 #[test]
471 fn cfg_attrs_revised() {
472 let mut env = TestResolver::default();
473 env.register(
474 "src/lib.rs",
475 r#"
476 #[cfg(feature = "m1")]
477 mod m1;
478
479 #[cfg(feature = "m2")]
480 #[path = "m2.rs"]
481 mod placeholder;
482
483 #[cfg(not(feature = "m2"))]
484 #[path = "empty.rs"]
485 mod placeholder;
486 "#,
487 );
488 env.register("src/m1.rs", "struct M1;");
489 env.register(
490 "src/m2.rs",
491 r#"
492 #![doc = " module level doc comment"]
493
494 struct M2;
495 "#,
496 );
497 env.register("src/empty.rs", "");
498
499 let result = InlinerBuilder::default()
500 .parse_internal(Path::new("src/lib.rs"), &mut env)
501 .unwrap()
502 .output;
503
504 assert_eq!(
505 result.into_token_stream().to_string(),
506 quote! {
507 #[cfg(feature = "m1")]
508 mod m1 {
509 struct M1;
510 }
511
512 #[cfg(feature = "m2")]
513 #[path = "m2.rs"]
514 mod placeholder {
515 #![doc = " module level doc comment"]
516
517 struct M2;
518 }
519
520 #[cfg(not(feature = "m2"))]
521 #[path = "empty.rs"]
522 mod placeholder {
523
524 }
525 }
526 .to_string()
527 )
528 }
529}