fix_getters_utils/collectors/
doc_code.rs

1//! A generic [`Getter`](crate::Getter)s collector visting documentation.
2
3use std::path::{Path, PathBuf};
4
5use crate::{GetterCollection, IdentificationMode, Scope, TokenStreamGetterCollector};
6
7/// A generic [`Getter`](crate::Getter)s collector visting documentation.
8///
9/// When parsing Rust code with [`syn`], documentation lines are
10/// passed as [`Attribute`](syn::Attribute)s, one line at a time.
11/// The generic [`DocCodeGetterCollector`] allows gathering the
12/// documentation code lines together and collecting the
13/// [`Getter`](crate::Getter)s using the provided
14/// [`TokenStreamGetterCollector`](super::TokenStreamGetterCollector).
15#[derive(Debug)]
16pub struct DocCodeGetterCollector<P: TokenStreamGetterCollector> {
17    code: String,
18    state: State,
19    identification_mode: IdentificationMode,
20    getter_collection: P::GetterCollection,
21    path: PathBuf,
22}
23
24impl<P: TokenStreamGetterCollector> DocCodeGetterCollector<P> {
25    /// Builds a [`DocCodeGetterCollector`].
26    ///
27    /// [`Getter`](crate::Getter)s will be added to the provided [`GetterCollection`].
28    /// Documentation alias attributes will be discarded.
29    pub fn new(
30        path: &Path,
31        identification_mode: IdentificationMode,
32        getter_collection: &P::GetterCollection,
33    ) -> Self {
34        let mut getter_collection = P::GetterCollection::clone(getter_collection);
35        getter_collection.disable_doc_alias();
36
37        DocCodeGetterCollector {
38            code: String::with_capacity(512),
39            state: State::None,
40            identification_mode,
41            getter_collection,
42            path: path.to_owned(),
43        }
44    }
45
46    /// Analyses the documentation in the provided [`Attribute`](syn::Attribute).
47    ///
48    /// Note that documentation code is parsed by [`syn`] one line at a time,
49    /// this method will take care of parsing any code found in the provided
50    /// [`Attribute`](syn::Attribute)s and feeding the [`GetterCollection`].
51    pub fn have_attribute(&mut self, node: &syn::Attribute) {
52        if let Some((_, cursor)) = syn::buffer::TokenBuffer::new2(node.tokens.clone())
53            .begin()
54            .punct()
55        {
56            if let Some((literal, _)) = cursor.literal() {
57                self.process(
58                    &literal.to_string().trim_matches('"').trim(),
59                    literal.span().start().line,
60                );
61            }
62        }
63    }
64
65    fn process(&mut self, doc_line: &str, offset: usize) {
66        if doc_line.starts_with("```") {
67            if !self.state.is_code_block() {
68                // starting a doc code block
69                self.getter_collection.set_offset(offset);
70                if doc_line.len() == 3 || doc_line.ends_with("rust") {
71                    self.state = State::RustCodeBlock;
72                } else {
73                    self.state = State::CodeBlock;
74                };
75            } else {
76                // terminating a doc code block
77                if self.state.is_rust() {
78                    self.collect();
79                }
80                self.state = State::None;
81            }
82        } else if self.state.is_rust() && !doc_line.starts_with('#') {
83            self.code.push_str(&doc_line.replace('\\', &""));
84            self.code.push('\n');
85        }
86    }
87
88    fn collect(&mut self) {
89        match syn::parse_str::<proc_macro2::TokenStream>(&self.code) {
90            Ok(syntax_tree) => P::collect(
91                &self.path,
92                &Scope::Documentation,
93                &syntax_tree,
94                self.identification_mode,
95                &self.getter_collection,
96            ),
97            Err(_err) => {
98                #[cfg(feature = "log")]
99                log::warn!(
100                    "{:?} doc @ {}: {:?}",
101                    self.path,
102                    self.getter_collection.offset(),
103                    _err
104                );
105            }
106        }
107
108        self.code.clear();
109    }
110}
111
112#[derive(Debug)]
113enum State {
114    None,
115    CodeBlock,
116    RustCodeBlock,
117}
118
119impl State {
120    fn is_code_block(&self) -> bool {
121        matches!(self, State::RustCodeBlock | State::CodeBlock)
122    }
123
124    fn is_rust(&self) -> bool {
125        matches!(self, State::RustCodeBlock)
126    }
127}