lintspec_core/definitions/
event.rs1use 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#[derive(Debug, Clone, bon::Builder)]
12#[non_exhaustive]
13#[builder(on(String, into))]
14pub struct EventDefinition {
15 pub parent: Option<Parent>,
17
18 pub name: Symbol,
20
21 pub span: TextRange,
23
24 pub params: Vec<Identifier>,
26
27 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 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}