Skip to main content

lintspec_core/definitions/
event.rs

1//! Parsing and validation of event definitions.
2use crate::{
3    interner::{INTERNER, Symbol},
4    lint::{CheckNoticeAndDev, CheckParams, ItemDiagnostics},
5    natspec::NatSpec,
6};
7
8use super::{Identifier, ItemType, Parent, SourceItem, TextRange, Validate, ValidationOptions};
9
10/// An event definition
11#[derive(Debug, Clone, bon::Builder)]
12#[non_exhaustive]
13#[builder(on(String, into))]
14pub struct EventDefinition {
15    /// The parent for the event definition, if any
16    pub parent: Option<Parent>,
17
18    /// The name of the event
19    pub name: Symbol,
20
21    /// The span of the event definition
22    pub span: TextRange,
23
24    /// The name and span of the event's parameters
25    pub params: Vec<Identifier>,
26
27    /// The [`NatSpec`] associated with the event definition, if any
28    pub natspec: Option<NatSpec>,
29}
30
31impl SourceItem for EventDefinition {
32    fn item_type(&self) -> ItemType {
33        ItemType::Event
34    }
35
36    fn parent(&self) -> Option<Parent> {
37        self.parent.clone()
38    }
39
40    fn name(&self) -> Symbol {
41        self.name
42    }
43
44    fn span(&self) -> TextRange {
45        self.span.clone()
46    }
47}
48
49impl Validate for EventDefinition {
50    fn validate(&self, options: &ValidationOptions) -> ItemDiagnostics {
51        let opts = &options.events;
52        let mut out = ItemDiagnostics {
53            parent: self.parent(),
54            item_type: self.item_type(),
55            name: self.name().resolve_with(&INTERNER),
56            span: self.span(),
57            diags: vec![],
58        };
59        CheckNoticeAndDev::builder()
60            .natspec(&self.natspec)
61            .notice_rule(opts.notice)
62            .dev_rule(opts.dev)
63            .notice_or_dev(options.notice_or_dev)
64            .span(&self.span)
65            .build()
66            .check_into(&mut out.diags);
67        CheckParams::builder()
68            .natspec(&self.natspec)
69            .rule(opts.param)
70            .params(&self.params)
71            .default_span(self.span())
72            .build()
73            .check_into(&mut out.diags);
74        out
75    }
76}
77
78#[cfg(test)]
79#[cfg(feature = "solar")]
80mod tests {
81    use std::sync::LazyLock;
82
83    use similar_asserts::assert_eq;
84
85    use crate::{
86        definitions::Definition,
87        parser::{Parse as _, solar::SolarParser},
88    };
89
90    use super::*;
91
92    static OPTIONS: LazyLock<ValidationOptions> =
93        LazyLock::new(|| ValidationOptions::builder().inheritdoc(false).build());
94
95    fn parse_file(contents: &str) -> EventDefinition {
96        let mut parser = SolarParser::default();
97        let doc = parser
98            .parse_document(contents.as_bytes(), None::<std::path::PathBuf>, false)
99            .unwrap();
100        doc.definitions
101            .into_iter()
102            .find_map(Definition::to_event)
103            .unwrap()
104    }
105
106    #[test]
107    fn test_event() {
108        let contents = "contract Test {
109            /// @notice An event
110            /// @param a The first
111            /// @param b The second
112            event Foobar(uint256 a, uint256 b);
113        }";
114        let res = parse_file(contents).validate(&OPTIONS);
115        assert!(res.diags.is_empty(), "{:#?}", res.diags);
116    }
117
118    #[test]
119    fn test_event_no_natspec() {
120        let contents = "contract Test {
121            event Foobar(uint256 a, uint256 b);
122        }";
123        let res = parse_file(contents).validate(&OPTIONS);
124        assert_eq!(res.diags.len(), 3);
125        assert_eq!(res.diags[0].message, "@notice is missing");
126        assert_eq!(res.diags[1].message, "@param a is missing");
127        assert_eq!(res.diags[2].message, "@param b is missing");
128    }
129
130    #[test]
131    fn test_event_only_notice() {
132        let contents = "contract Test {
133            /// @notice An event
134            event Foobar(uint256 a, uint256 b);
135        }";
136        let res = parse_file(contents).validate(&OPTIONS);
137        assert_eq!(res.diags.len(), 2);
138        assert_eq!(res.diags[0].message, "@param a is missing");
139        assert_eq!(res.diags[1].message, "@param b is missing");
140    }
141
142    #[test]
143    fn test_event_multiline() {
144        let contents = "contract Test {
145            /**
146             * @notice An event
147             * @param a The first
148             * @param b The second
149             */
150            event Foobar(uint256 a, uint256 b);
151        }";
152        let res = parse_file(contents).validate(&OPTIONS);
153        assert!(res.diags.is_empty(), "{:#?}", res.diags);
154    }
155
156    #[test]
157    fn test_event_duplicate() {
158        let contents = "contract Test {
159            /// @notice An event
160            /// @param a The first
161            /// @param a The first again
162            event Foobar(uint256 a);
163        }";
164        let res = parse_file(contents).validate(&OPTIONS);
165        assert_eq!(res.diags.len(), 1);
166        assert_eq!(res.diags[0].message, "@param a is present more than once");
167    }
168
169    #[test]
170    fn test_event_no_params() {
171        let contents = "contract Test {
172            event Foobar();
173        }";
174        let res = parse_file(contents).validate(&OPTIONS);
175        assert_eq!(res.diags.len(), 1);
176        assert_eq!(res.diags[0].message, "@notice is missing");
177    }
178
179    #[test]
180    fn test_event_inheritdoc() {
181        // inheritdoc should be ignored as it doesn't apply to events
182        let contents = "contract Test {
183            /// @inheritdoc ITest
184            event Foobar(uint256 a);
185        }";
186        let res = parse_file(contents).validate(&ValidationOptions::default());
187        assert_eq!(res.diags.len(), 2);
188        assert_eq!(res.diags[0].message, "@notice is missing");
189        assert_eq!(res.diags[1].message, "@param a is missing");
190    }
191
192    #[test]
193    fn test_event_no_contract() {
194        let contents = "
195            /// @notice An event
196            /// @param a The first
197            /// @param b The second
198            event Foobar(uint256 a, uint256 b);
199            ";
200        let res = parse_file(contents).validate(&OPTIONS);
201        assert!(res.diags.is_empty(), "{:#?}", res.diags);
202    }
203
204    #[test]
205    fn test_event_no_contract_missing() {
206        let contents = "event Foobar(uint256 a, uint256 b);";
207        let res = parse_file(contents).validate(&OPTIONS);
208        assert_eq!(res.diags.len(), 3);
209        assert_eq!(res.diags[0].message, "@notice is missing");
210        assert_eq!(res.diags[1].message, "@param a is missing");
211        assert_eq!(res.diags[2].message, "@param b is missing");
212    }
213}