Skip to main content

lintspec_core/
definitions.rs

1//! Source item definitions
2//!
3//! This module contains structs for each of the source item types that can be documented with `NatSpec`.
4//! The [`Definition`] type provides a unified interface to interact with the various types.
5use constructor::ConstructorDefinition;
6use contract::ContractDefinition;
7use derive_more::{Display, From, FromStr, IsVariant, TryInto};
8use enumeration::EnumDefinition;
9use error::ErrorDefinition;
10use event::EventDefinition;
11use function::FunctionDefinition;
12use lintspec_macros::AsToVariant;
13use modifier::ModifierDefinition;
14use serde::{Deserialize, Serialize};
15use structure::StructDefinition;
16use variable::VariableDeclaration;
17
18use crate::{
19    definitions::{interface::InterfaceDefinition, library::LibraryDefinition},
20    error::Error,
21    interner::Symbol,
22    lint::{Diagnostic, ItemDiagnostics, Validate, ValidationOptions},
23    textindex::TextRange,
24};
25
26pub mod constructor;
27pub mod contract;
28pub mod enumeration;
29pub mod error;
30pub mod event;
31pub mod function;
32pub mod interface;
33pub mod library;
34pub mod modifier;
35pub mod structure;
36pub mod variable;
37
38/// Source-related information about a [`Definition`]
39pub trait SourceItem {
40    fn item_type(&self) -> ItemType;
41
42    /// Retrieve the parent contract, interface, or library's name
43    fn parent(&self) -> Option<Parent>;
44
45    /// Retrieve the name of the source item
46    fn name(&self) -> Symbol;
47
48    /// Retrieve the span of the source item
49    fn span(&self) -> TextRange;
50}
51
52/// An identifier (named or unnamed) and its span
53///
54/// Unnamed identifiers are used for unnamed return params.
55#[derive(Debug, Clone, bon::Builder)]
56#[builder(on(String, into))]
57pub struct Identifier {
58    pub name: Option<Symbol>,
59    pub span: TextRange,
60}
61
62/// The visibility modifier for a function-like item
63///
64/// If no modifier is present, the default is `Internal`.
65#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
66pub enum Visibility {
67    External,
68    #[default]
69    Internal,
70    Private,
71    Public,
72}
73
74/// Attributes for a function or state variable (visibility and override)
75#[derive(Debug, Clone, Copy, Default, bon::Builder)]
76#[non_exhaustive]
77pub struct Attributes {
78    pub visibility: Visibility,
79    pub r#override: bool,
80}
81
82/// The name and type of a source item's parent
83#[derive(Debug, Clone, Display, Serialize, PartialEq, Eq, IsVariant)]
84#[serde(untagged)]
85pub enum Parent {
86    Contract(&'static str),
87    Interface(&'static str),
88    Library(&'static str),
89}
90
91/// A source item's definition
92#[derive(Debug, From, TryInto, IsVariant, AsToVariant)]
93pub enum Definition {
94    Contract(ContractDefinition),
95    Interface(InterfaceDefinition),
96    Library(LibraryDefinition),
97    Constructor(ConstructorDefinition),
98    Enumeration(EnumDefinition),
99    Error(ErrorDefinition),
100    Event(EventDefinition),
101    Function(FunctionDefinition),
102    Modifier(ModifierDefinition),
103    Struct(StructDefinition),
104    Variable(VariableDeclaration),
105    NatspecParsingError(Error),
106}
107
108impl PartialEq for Definition {
109    /// Compare two definitions based on their underlying node
110    ///
111    /// If two instances of the same node generate two definitions (due to quantifiers in the query), this can be
112    /// used to deduplicate the instances.
113    fn eq(&self, other: &Self) -> bool {
114        match (self, other) {
115            (Self::Contract(a), Self::Contract(b)) => a.span.start == b.span.start,
116            (Self::Interface(a), Self::Interface(b)) => a.span.start == b.span.start,
117            (Self::Library(a), Self::Library(b)) => a.span.start == b.span.start,
118            (Self::Constructor(a), Self::Constructor(b)) => a.span.start == b.span.start,
119            (Self::Enumeration(a), Self::Enumeration(b)) => a.span.start == b.span.start,
120            (Self::Error(a), Self::Error(b)) => a.span.start == b.span.start,
121            (Self::Event(a), Self::Event(b)) => a.span.start == b.span.start,
122            (Self::Function(a), Self::Function(b)) => a.span.start == b.span.start,
123            (Self::Modifier(a), Self::Modifier(b)) => a.span.start == b.span.start,
124            (Self::Struct(a), Self::Struct(b)) => a.span.start == b.span.start,
125            (Self::Variable(a), Self::Variable(b)) => a.span.start == b.span.start,
126            (
127                Self::NatspecParsingError(Error::NatspecParsingError { span: span_a, .. }),
128                Self::NatspecParsingError(Error::NatspecParsingError { span: span_b, .. }),
129            ) => span_a.start == span_b.start,
130            (Self::NatspecParsingError(a), Self::NatspecParsingError(b)) => {
131                a.to_string() == b.to_string() // try to compare on error message
132            }
133            _ => false,
134        }
135    }
136}
137
138impl Definition {
139    /// Retrieve the span of a definition
140    #[must_use]
141    pub fn span(&self) -> Option<TextRange> {
142        match self {
143            Definition::Contract(d) => Some(d.span()),
144            Definition::Interface(d) => Some(d.span()),
145            Definition::Library(d) => Some(d.span()),
146            Definition::Constructor(d) => Some(d.span()),
147            Definition::Enumeration(d) => Some(d.span()),
148            Definition::Error(d) => Some(d.span()),
149            Definition::Event(d) => Some(d.span()),
150            Definition::Function(d) => Some(d.span()),
151            Definition::Modifier(d) => Some(d.span()),
152            Definition::Struct(d) => Some(d.span()),
153            Definition::Variable(d) => Some(d.span()),
154            Definition::NatspecParsingError(Error::NatspecParsingError { span, .. }) => {
155                Some(span.clone())
156            }
157            Definition::NatspecParsingError(_) => None,
158        }
159    }
160
161    /// Mutably borrow the span of a definition
162    pub fn span_mut(&mut self) -> Option<&mut TextRange> {
163        match self {
164            Definition::Contract(d) => Some(&mut d.span),
165            Definition::Interface(d) => Some(&mut d.span),
166            Definition::Library(d) => Some(&mut d.span),
167            Definition::Constructor(d) => Some(&mut d.span),
168            Definition::Enumeration(d) => Some(&mut d.span),
169            Definition::Error(d) => Some(&mut d.span),
170            Definition::Event(d) => Some(&mut d.span),
171            Definition::Function(d) => Some(&mut d.span),
172            Definition::Modifier(d) => Some(&mut d.span),
173            Definition::Struct(d) => Some(&mut d.span),
174            Definition::Variable(d) => Some(&mut d.span),
175            Definition::NatspecParsingError(Error::NatspecParsingError { span, .. }) => Some(span),
176            Definition::NatspecParsingError(_) => None,
177        }
178    }
179}
180
181impl Validate for Definition {
182    /// Validate a definition and generate [`Diagnostic`]s for errors
183    fn validate(&self, options: &ValidationOptions) -> ItemDiagnostics {
184        match self {
185            // if there was an error while parsing the NatSpec comments, a special diagnostic is generated
186            Definition::NatspecParsingError(error) => {
187                let (parent, span, message) = match error {
188                    Error::NatspecParsingError {
189                        parent,
190                        span,
191                        message,
192                    } => (parent.clone(), span.clone(), message.clone()),
193                    _ => (None, TextRange::default(), error.to_string()),
194                };
195                ItemDiagnostics {
196                    parent,
197                    item_type: ItemType::ParsingError,
198                    name: "",
199                    span: span.clone(),
200                    diags: vec![Diagnostic { span, message }],
201                }
202            }
203            Definition::Contract(def) => def.validate(options),
204            Definition::Interface(def) => def.validate(options),
205            Definition::Library(def) => def.validate(options),
206            Definition::Constructor(def) => def.validate(options),
207            Definition::Enumeration(def) => def.validate(options),
208            Definition::Error(def) => def.validate(options),
209            Definition::Event(def) => def.validate(options),
210            Definition::Function(def) => def.validate(options),
211            Definition::Modifier(def) => def.validate(options),
212            Definition::Struct(def) => def.validate(options),
213            Definition::Variable(def) => def.validate(options),
214        }
215    }
216}
217
218/// A type of source item (function, struct, etc.)
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Display, FromStr)]
220#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
221#[serde(rename_all = "snake_case")]
222#[display(rename_all = "kebab-case")] // to match ValueEnum's behavior
223pub enum ContractType {
224    Contract,
225    Interface,
226    Library,
227}
228
229/// A type of source item (function, struct, etc.)
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Display, FromStr)]
231#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
232#[serde(rename_all = "snake_case")]
233#[display(rename_all = "snake_case")]
234#[from_str(rename_all = "kebab-case")] // to match ValueEnum's behavior
235pub enum ItemType {
236    Contract,
237    Interface,
238    Library,
239    Constructor,
240    Enum,
241    Error,
242    Event,
243    #[display("function")]
244    PrivateFunction,
245    #[display("function")]
246    InternalFunction,
247    #[display("function")]
248    PublicFunction,
249    #[display("function")]
250    ExternalFunction,
251    Modifier,
252    ParsingError,
253    Struct,
254    #[display("variable")]
255    PrivateVariable,
256    #[display("variable")]
257    InternalVariable,
258    #[display("variable")]
259    PublicVariable,
260}
261
262#[cfg(test)]
263mod tests {
264    use std::str::FromStr;
265
266    use super::*;
267
268    #[test]
269    fn test_contract_type_display() {
270        assert_eq!(ContractType::Contract.to_string(), "contract");
271        assert_eq!(ContractType::Interface.to_string(), "interface");
272        assert_eq!(ContractType::Library.to_string(), "library");
273    }
274
275    #[test]
276    fn test_contract_type_from_str() {
277        assert_eq!(
278            ContractType::from_str("contract").unwrap(),
279            ContractType::Contract
280        );
281        assert_eq!(
282            ContractType::from_str("interface").unwrap(),
283            ContractType::Interface
284        );
285        assert_eq!(
286            ContractType::from_str("library").unwrap(),
287            ContractType::Library
288        );
289    }
290
291    #[test]
292    fn test_contract_type_from_str_case_insensitive() {
293        // FromStr is case-insensitive
294        assert_eq!(
295            ContractType::from_str("Contract").unwrap(),
296            ContractType::Contract
297        );
298        assert_eq!(
299            ContractType::from_str("INTERFACE").unwrap(),
300            ContractType::Interface
301        );
302        assert_eq!(
303            ContractType::from_str("Library").unwrap(),
304            ContractType::Library
305        );
306    }
307
308    #[test]
309    fn test_contract_type_from_str_invalid() {
310        assert!(ContractType::from_str("invalid").is_err());
311        assert!(ContractType::from_str("").is_err());
312    }
313
314    #[test]
315    fn test_item_type_display() {
316        assert_eq!(ItemType::Contract.to_string(), "contract");
317        assert_eq!(ItemType::Interface.to_string(), "interface");
318        assert_eq!(ItemType::Library.to_string(), "library");
319        assert_eq!(ItemType::Constructor.to_string(), "constructor");
320        assert_eq!(ItemType::Enum.to_string(), "enum");
321        assert_eq!(ItemType::Error.to_string(), "error");
322        assert_eq!(ItemType::Event.to_string(), "event");
323        assert_eq!(ItemType::Modifier.to_string(), "modifier");
324        assert_eq!(ItemType::ParsingError.to_string(), "parsing_error");
325        assert_eq!(ItemType::Struct.to_string(), "struct");
326        assert_eq!(ItemType::PrivateFunction.to_string(), "function");
327        assert_eq!(ItemType::InternalFunction.to_string(), "function");
328        assert_eq!(ItemType::PublicFunction.to_string(), "function");
329        assert_eq!(ItemType::ExternalFunction.to_string(), "function");
330        assert_eq!(ItemType::PrivateVariable.to_string(), "variable");
331        assert_eq!(ItemType::InternalVariable.to_string(), "variable");
332        assert_eq!(ItemType::PublicVariable.to_string(), "variable");
333    }
334
335    #[test]
336    fn test_item_type_from_str() {
337        assert_eq!(ItemType::from_str("contract").unwrap(), ItemType::Contract);
338        assert_eq!(
339            ItemType::from_str("interface").unwrap(),
340            ItemType::Interface
341        );
342        assert_eq!(ItemType::from_str("library").unwrap(), ItemType::Library);
343        assert_eq!(
344            ItemType::from_str("constructor").unwrap(),
345            ItemType::Constructor
346        );
347        assert_eq!(ItemType::from_str("enum").unwrap(), ItemType::Enum);
348        assert_eq!(ItemType::from_str("error").unwrap(), ItemType::Error);
349        assert_eq!(ItemType::from_str("event").unwrap(), ItemType::Event);
350        assert_eq!(ItemType::from_str("modifier").unwrap(), ItemType::Modifier);
351        assert_eq!(
352            ItemType::from_str("parsing-error").unwrap(),
353            ItemType::ParsingError
354        );
355        assert_eq!(ItemType::from_str("struct").unwrap(), ItemType::Struct);
356        assert_eq!(
357            ItemType::from_str("private-function").unwrap(),
358            ItemType::PrivateFunction
359        );
360        assert_eq!(
361            ItemType::from_str("internal-function").unwrap(),
362            ItemType::InternalFunction
363        );
364        assert_eq!(
365            ItemType::from_str("public-function").unwrap(),
366            ItemType::PublicFunction
367        );
368        assert_eq!(
369            ItemType::from_str("external-function").unwrap(),
370            ItemType::ExternalFunction
371        );
372        assert_eq!(
373            ItemType::from_str("private-variable").unwrap(),
374            ItemType::PrivateVariable
375        );
376        assert_eq!(
377            ItemType::from_str("internal-variable").unwrap(),
378            ItemType::InternalVariable
379        );
380        assert_eq!(
381            ItemType::from_str("public-variable").unwrap(),
382            ItemType::PublicVariable
383        );
384    }
385
386    #[test]
387    fn test_item_type_from_str_case_sensitive() {
388        assert!(ItemType::from_str("Contract").is_err());
389        assert!(ItemType::from_str("PRIVATE_FUNCTION").is_err());
390        assert!(ItemType::from_str("PrivateFunction").is_err());
391    }
392
393    #[test]
394    fn test_item_type_from_str_invalid() {
395        assert!(ItemType::from_str("invalid").is_err());
396        assert!(ItemType::from_str("function").is_err()); // ambiguous
397        assert!(ItemType::from_str("variable").is_err()); // ambiguous
398        assert!(ItemType::from_str("").is_err());
399    }
400}