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