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
// SPDX-License-Identifier: GPL-3.0
//! This module leverages the [syn](https://docs.rs/syn/latest/syn/index.html) crate and provides an easy mechanism
//! to interact with a rust AST represented as a [`syn::File`](https://docs.rs/syn/latest/syn/struct.File.html) with
//! just a few lines of code.
//!
//! At the heart of this module are the [`Finder`](https://docs.rs/rust_writer/latest/rust_writer/ast/finder/struct.Finder.html)
//! and [`Mutator`](https://docs.rs/rust_writer/latest/rust_writer/ast/mutator/struct.Mutator.html)
//! structs, which can be thought of a game console:
//! - Their purpose is clear: they either find something in an AST or mutate parts of the AST itself
//! (similar to how a game console's purpose is to run games).
//! - They can be customized to find or mutate specific parts of the AST in concrete ways (just as
//! each game has its own unique narrative).
//!
//! These structs "load" the **implementors** (the games, following the previous analogy), which
//! specify exactly where and how the structs should act. For example, they may be used to locate a
//! specific item within a particular trait or to mutate a macro invocation, as the following
//! example shows:
//!
//! ```rust
//! use test_builder::TestBuilder;
//! use rust_writer::ast::{
//! mutator::{Mutator, ToMutate},
//! finder::{Finder, ToFind},
//! implementors::{ItemToTrait, TokenStreamToMacro}
//! };
//! use syn::{
//! visit::Visit,
//! visit_mut::VisitMut,
//! parse_quote,
//! TraitItem
//! };
//!
//! TestBuilder::default()
//! .with_trait_ast()
//! .with_macro_ast()
//! .execute(|mut builder|{
//! // Define the ItemToTrait implementor. This implementor means:
//! // 1. We're looking inside a trait called `MyTrait` .
//! // 2. We're interested in a type called `Type1` with trait bound `From<String>`.
//! let item_to_trait: ItemToTrait =
//! ("MyTrait", parse_quote! {type Type1: From<String>;}).into();
//!
//! let ast = builder.get_ref_ast_file("trait.rs").expect("This exists; qed;");
//!
//! // 'Load' the implementor into a `Finder` using `to_find`.
//! let mut finder = Finder::default().to_find(&item_to_trait);
//!
//! // Use the new finder variable to check if there's a trait called `MyTrait` in the AST
//! // containing a type called `Type1` whose trait bound is `From<String>`.
//! assert!(finder.find(ast));
//!
//! // Define the TokenStreamToMacro implementor. This implementor means:
//! // 1. We're looking inside a macro called `my_macro`.
//! // 2. We're interested in a `TokenStream` composed by the token `D`.
//! let token_to_macro: TokenStreamToMacro =
//! (parse_quote! { my_macro }, None, parse_quote! { D }).into();
//!
//! let ast = builder.get_mut_ast_file("macro.rs").expect("This exists; qed;");
//!
//! let mut finder = Finder::default().to_find(&token_to_macro);
//!
//! // The token `D` isn't in the macro at the beginning.
//! assert!(!finder.find(ast));
//!
//! // 'Load' the implementor into a 'Mutator' using `to_mutate` and mutate the ast.
//! let mut mutator = Mutator::default().to_mutate(&token_to_macro);
//! assert!(mutator.mutate(ast).is_ok());
//!
//! // We can now find the token `D` inside `my_macro`.
//! let mut finder = Finder::default().to_find(&token_to_macro);
//! assert!(finder.find(ast));
//! });
//! ```
//!
//! This crate comes with a set of predefined implementors, which can be found in the
//! [implementors](https://docs.rs/rust_writer/latest/rust_writer/ast/implementors/index.html) module.
//! This set may be extended in the future to include additional implementors as the crate evolves.
//! All the included implementors comes with a `From` implementation that
//!
//! # Combining implementors
//!
//! It was so good up to this point, but creating a new `Finder`/`Mutator` for each AST operation
//! may feel a bit cumbersome in some situations. The
//! [`#[finder]`](https://docs.rs/rust_writer/latest/rust_writer/ast/attr.finder.html) and
//! [`#[mutator]`](https://docs.rs/rust_writer/latest/rust_writer/ast/attr.mutator.html) macros come in to combine several
//! implementors into a new one capable of executing all operations simultaneously.
//!
//! It's recomended to go through their docs to fully understand what those macros are doing and
//! how to use them. As a teaser, let's replicate the example of the item added to a trait and the
//! token stream added to a macro using this handy approach.
//!
//! ```rust
//! use test_builder::TestBuilder;
//! use rust_writer::ast::{
//! mutator::{Mutator, ToMutate},
//! finder::{Finder, ToFind},
//! implementors::{ItemToTrait, TokenStreamToMacro},
//! mutator,
//! finder
//! };
//! use syn::{
//! visit::Visit,
//! visit_mut::VisitMut,
//! parse_quote,
//! };
//!
//! #[finder(ItemToTrait<'a>, TokenStreamToMacro)]
//! #[mutator(ItemToTrait<'a>, TokenStreamToMacro)]
//! #[impl_from]
//! struct CombinedImplementor;
//!
//! TestBuilder::default()
//! .with_trait_ast()
//! .with_macro_ast()
//! .execute(|mut builder|{
//! let combined_implementor: CombinedImplementor = (
//! ("MyTrait", parse_quote! { type Type1: From<String>; }).into(),
//! (parse_quote! { my_macro }, None, parse_quote! { D }).into()
//! ).into();
//!
//! let mut ast = builder.get_mut_ast_file("trait.rs").expect("This exists; qed;").clone();
//! ast.items.extend(builder.get_ref_ast_file("macro.rs").expect("This exists;
//! qed;").items.clone());
//!
//! let mut finder: CombinedImplementorFinderWrapper = Finder::default().to_find(&combined_implementor).into();
//!
//! // The `Finder` fails to find all the elements.
//! assert!(!finder.find(&ast, None));
//!
//! // But we can still check that the item_to_trait implementor succeeded in its research,
//! // thanks to the finder struct and the handy `get_missing_indexes` method that tell us which
//! // implementors couldn't find its target.
//! let missing_indexes = finder.get_missing_indexes();
//! assert_eq!(missing_indexes, Some(vec![1]));
//!
//! // And that it was the macro implementor who failed
//! assert!(!finder.0.found[1]);
//!
//! // Let's mutate and complete the ast
//! let mut mutator: CombinedImplementorMutatorWrapper = Mutator::default().to_mutate(&combined_implementor).into();
//!
//! // We can mutate just the elements that were not found, so we don't duplicate the rest.
//! assert!(mutator.mutate(&mut ast, missing_indexes.as_deref()).is_ok());
//!
//! let mut finder: CombinedImplementorFinderWrapper = Finder::default().to_find(&combined_implementor).into();
//!
//! // Now the finder can find both elements
//! assert!(finder.find(&ast, None));
//! });
//! ```
//! # Defining new implementors
//!
//! If the set of predefined implementors isn't enough, defining a new implementor is perfectly
//! possible. However, it's not directly feasible... Both `Finder` and
//! `Mutator` need to implement [`syn::visit::Visit`](https://docs.rs/syn/latest/syn/visit/trait.Visit.html)
//! and [`syn::visit_mut::VisitMut`](https://docs.rs/syn/latest/syn/visit_mut/trait.VisitMut.html) traits respectively
//! in order to become functional. Implementing a foreign trait in a foreign type is forbidden by
//! the orphan rule, so, what to do in this case? 🤔🤔🤔
//!
//! There's basically two approaches:
//! 1. If the implementor to define isn't quite linked to the project where it's needed, open a
//! **PR** to the rust_writer crate! That way everybody will benefit of the new implementor.
//! 1. Use the [`#[local_finder]`](https://docs.rs/rust_writer/latest/rust_writer/ast/attr.local_finder.html)
//! and [`#[local_mutator]`](https://docs.rs/rust_writer/latest/rust_writer/ast/attr.local_mutator.html)
//! macros to define a local implementor.
pub use *;