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::ErrorKind,
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(ErrorKind),
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(ErrorKind::NatspecParsingError { span: span_a, .. }),
128                Self::NatspecParsingError(ErrorKind::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(ErrorKind::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(ErrorKind::NatspecParsingError { span, .. }) => {
176                Some(span)
177            }
178            Definition::NatspecParsingError(_) => None,
179        }
180    }
181}
182
183impl Validate for Definition {
184    /// Validate a definition and generate [`Diagnostic`]s for errors
185    fn validate(&self, options: &ValidationOptions) -> ItemDiagnostics {
186        match self {
187            // if there was an error while parsing the NatSpec comments, a special diagnostic is generated
188            Definition::NatspecParsingError(error) => {
189                let (parent, span, message) = match error {
190                    ErrorKind::NatspecParsingError {
191                        parent,
192                        span,
193                        message,
194                    } => (parent.clone(), span.clone(), message.clone()),
195                    _ => (None, TextRange::default(), error.to_string()),
196                };
197                ItemDiagnostics {
198                    parent,
199                    item_type: ItemType::ParsingError,
200                    name: "",
201                    span: span.clone(),
202                    diags: vec![Diagnostic { span, message }],
203                }
204            }
205            Definition::Contract(def) => def.validate(options),
206            Definition::Interface(def) => def.validate(options),
207            Definition::Library(def) => def.validate(options),
208            Definition::Constructor(def) => def.validate(options),
209            Definition::Enumeration(def) => def.validate(options),
210            Definition::Error(def) => def.validate(options),
211            Definition::Event(def) => def.validate(options),
212            Definition::Function(def) => def.validate(options),
213            Definition::Modifier(def) => def.validate(options),
214            Definition::Struct(def) => def.validate(options),
215            Definition::Variable(def) => def.validate(options),
216        }
217    }
218}
219
220/// A type of source item (function, struct, etc.)
221#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Display, FromStr)]
222#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
223#[serde(rename_all = "snake_case")]
224#[display(rename_all = "kebab-case")] // to match ValueEnum's behavior
225pub enum ContractType {
226    Contract,
227    Interface,
228    Library,
229}
230
231/// A type of source item (function, struct, etc.)
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Display, FromStr)]
233#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
234#[serde(rename_all = "snake_case")]
235#[display(rename_all = "snake_case")]
236#[from_str(rename_all = "kebab-case")] // to match ValueEnum's behavior
237pub enum ItemType {
238    Contract,
239    Interface,
240    Library,
241    Constructor,
242    Enum,
243    Error,
244    Event,
245    #[display("function")]
246    PrivateFunction,
247    #[display("function")]
248    InternalFunction,
249    #[display("function")]
250    PublicFunction,
251    #[display("function")]
252    ExternalFunction,
253    Modifier,
254    ParsingError,
255    Struct,
256    #[display("variable")]
257    PrivateVariable,
258    #[display("variable")]
259    InternalVariable,
260    #[display("variable")]
261    PublicVariable,
262}
263
264#[cfg(test)]
265mod tests {
266    use std::str::FromStr;
267
268    use super::*;
269
270    #[test]
271    fn test_contract_type_display() {
272        assert_eq!(ContractType::Contract.to_string(), "contract");
273        assert_eq!(ContractType::Interface.to_string(), "interface");
274        assert_eq!(ContractType::Library.to_string(), "library");
275    }
276
277    #[test]
278    fn test_contract_type_from_str() {
279        assert_eq!(
280            ContractType::from_str("contract").unwrap(),
281            ContractType::Contract
282        );
283        assert_eq!(
284            ContractType::from_str("interface").unwrap(),
285            ContractType::Interface
286        );
287        assert_eq!(
288            ContractType::from_str("library").unwrap(),
289            ContractType::Library
290        );
291    }
292
293    #[test]
294    fn test_contract_type_from_str_case_insensitive() {
295        // FromStr is case-insensitive
296        assert_eq!(
297            ContractType::from_str("Contract").unwrap(),
298            ContractType::Contract
299        );
300        assert_eq!(
301            ContractType::from_str("INTERFACE").unwrap(),
302            ContractType::Interface
303        );
304        assert_eq!(
305            ContractType::from_str("Library").unwrap(),
306            ContractType::Library
307        );
308    }
309
310    #[test]
311    fn test_contract_type_from_str_invalid() {
312        assert!(ContractType::from_str("invalid").is_err());
313        assert!(ContractType::from_str("").is_err());
314    }
315
316    #[test]
317    fn test_item_type_display() {
318        assert_eq!(ItemType::Contract.to_string(), "contract");
319        assert_eq!(ItemType::Interface.to_string(), "interface");
320        assert_eq!(ItemType::Library.to_string(), "library");
321        assert_eq!(ItemType::Constructor.to_string(), "constructor");
322        assert_eq!(ItemType::Enum.to_string(), "enum");
323        assert_eq!(ItemType::Error.to_string(), "error");
324        assert_eq!(ItemType::Event.to_string(), "event");
325        assert_eq!(ItemType::Modifier.to_string(), "modifier");
326        assert_eq!(ItemType::ParsingError.to_string(), "parsing_error");
327        assert_eq!(ItemType::Struct.to_string(), "struct");
328        assert_eq!(ItemType::PrivateFunction.to_string(), "function");
329        assert_eq!(ItemType::InternalFunction.to_string(), "function");
330        assert_eq!(ItemType::PublicFunction.to_string(), "function");
331        assert_eq!(ItemType::ExternalFunction.to_string(), "function");
332        assert_eq!(ItemType::PrivateVariable.to_string(), "variable");
333        assert_eq!(ItemType::InternalVariable.to_string(), "variable");
334        assert_eq!(ItemType::PublicVariable.to_string(), "variable");
335    }
336
337    #[test]
338    fn test_item_type_from_str() {
339        assert_eq!(ItemType::from_str("contract").unwrap(), ItemType::Contract);
340        assert_eq!(
341            ItemType::from_str("interface").unwrap(),
342            ItemType::Interface
343        );
344        assert_eq!(ItemType::from_str("library").unwrap(), ItemType::Library);
345        assert_eq!(
346            ItemType::from_str("constructor").unwrap(),
347            ItemType::Constructor
348        );
349        assert_eq!(ItemType::from_str("enum").unwrap(), ItemType::Enum);
350        assert_eq!(ItemType::from_str("error").unwrap(), ItemType::Error);
351        assert_eq!(ItemType::from_str("event").unwrap(), ItemType::Event);
352        assert_eq!(ItemType::from_str("modifier").unwrap(), ItemType::Modifier);
353        assert_eq!(
354            ItemType::from_str("parsing-error").unwrap(),
355            ItemType::ParsingError
356        );
357        assert_eq!(ItemType::from_str("struct").unwrap(), ItemType::Struct);
358        assert_eq!(
359            ItemType::from_str("private-function").unwrap(),
360            ItemType::PrivateFunction
361        );
362        assert_eq!(
363            ItemType::from_str("internal-function").unwrap(),
364            ItemType::InternalFunction
365        );
366        assert_eq!(
367            ItemType::from_str("public-function").unwrap(),
368            ItemType::PublicFunction
369        );
370        assert_eq!(
371            ItemType::from_str("external-function").unwrap(),
372            ItemType::ExternalFunction
373        );
374        assert_eq!(
375            ItemType::from_str("private-variable").unwrap(),
376            ItemType::PrivateVariable
377        );
378        assert_eq!(
379            ItemType::from_str("internal-variable").unwrap(),
380            ItemType::InternalVariable
381        );
382        assert_eq!(
383            ItemType::from_str("public-variable").unwrap(),
384            ItemType::PublicVariable
385        );
386    }
387
388    #[test]
389    fn test_item_type_from_str_case_sensitive() {
390        assert!(ItemType::from_str("Contract").is_err());
391        assert!(ItemType::from_str("PRIVATE_FUNCTION").is_err());
392        assert!(ItemType::from_str("PrivateFunction").is_err());
393    }
394
395    #[test]
396    fn test_item_type_from_str_invalid() {
397        assert!(ItemType::from_str("invalid").is_err());
398        assert!(ItemType::from_str("function").is_err()); // ambiguous
399        assert!(ItemType::from_str("variable").is_err()); // ambiguous
400        assert!(ItemType::from_str("").is_err());
401    }
402}