lintspec_core/definitions/
error.rs1use crate::{
3 lint::{CheckNoticeAndDev, CheckParams, ItemDiagnostics},
4 natspec::NatSpec,
5};
6
7use super::{Identifier, ItemType, Parent, SourceItem, TextRange, Validate, ValidationOptions};
8
9#[derive(Debug, Clone, bon::Builder)]
11#[non_exhaustive]
12#[builder(on(String, into))]
13pub struct ErrorDefinition {
14 pub parent: Option<Parent>,
16
17 pub name: String,
19
20 pub span: TextRange,
22
23 pub params: Vec<Identifier>,
25
26 pub natspec: Option<NatSpec>,
28}
29
30impl SourceItem for ErrorDefinition {
31 fn item_type(&self) -> ItemType {
32 ItemType::Error
33 }
34
35 fn parent(&self) -> Option<Parent> {
36 self.parent.clone()
37 }
38
39 fn name(&self) -> String {
40 self.name.clone()
41 }
42
43 fn span(&self) -> TextRange {
44 self.span.clone()
45 }
46}
47
48impl Validate for ErrorDefinition {
49 fn validate(&self, options: &ValidationOptions) -> ItemDiagnostics {
50 let opts = &options.errors;
51 let mut out = ItemDiagnostics {
52 parent: self.parent(),
53 item_type: self.item_type(),
54 name: self.name(),
55 span: self.span(),
56 diags: vec![],
57 };
58 out.diags.extend(
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(),
67 );
68 out.diags.extend(
69 CheckParams::builder()
70 .natspec(&self.natspec)
71 .rule(opts.param)
72 .params(&self.params)
73 .default_span(self.span())
74 .build()
75 .check(),
76 );
77 out
78 }
79}
80
81#[cfg(test)]
82#[cfg(feature = "solar")]
83mod tests {
84 use std::sync::LazyLock;
85
86 use similar_asserts::assert_eq;
87
88 use crate::{
89 definitions::Definition,
90 parser::{Parse as _, solar::SolarParser},
91 };
92
93 use super::*;
94
95 static OPTIONS: LazyLock<ValidationOptions> =
96 LazyLock::new(|| ValidationOptions::builder().inheritdoc(false).build());
97
98 fn parse_file(contents: &str) -> ErrorDefinition {
99 let mut parser = SolarParser::default();
100 let doc = parser
101 .parse_document(contents.as_bytes(), None::<std::path::PathBuf>, false)
102 .unwrap();
103 doc.definitions
104 .into_iter()
105 .find_map(Definition::to_error)
106 .unwrap()
107 }
108
109 #[test]
110 fn test_error() {
111 let contents = "contract Test {
112 /// @notice An error
113 /// @param a The first
114 /// @param b The second
115 error Foobar(uint256 a, uint256 b);
116 }";
117 let res = parse_file(contents).validate(&OPTIONS);
118 assert!(res.diags.is_empty(), "{:#?}", res.diags);
119 }
120
121 #[test]
122 fn test_error_no_natspec() {
123 let contents = "contract Test {
124 error Foobar(uint256 a, uint256 b);
125 }";
126 let res = parse_file(contents).validate(&OPTIONS);
127 assert_eq!(res.diags.len(), 3);
128 assert_eq!(res.diags[0].message, "@notice is missing");
129 assert_eq!(res.diags[1].message, "@param a is missing");
130 assert_eq!(res.diags[2].message, "@param b is missing");
131 }
132
133 #[test]
134 fn test_error_only_notice() {
135 let contents = "contract Test {
136 /// @notice An error
137 error Foobar(uint256 a, uint256 b);
138 }";
139 let res = parse_file(contents).validate(&OPTIONS);
140 assert_eq!(res.diags.len(), 2);
141 assert_eq!(res.diags[0].message, "@param a is missing");
142 assert_eq!(res.diags[1].message, "@param b is missing");
143 }
144
145 #[test]
146 fn test_error_one_missing() {
147 let contents = "contract Test {
148 /// @notice An error
149 /// @param a The first
150 error Foobar(uint256 a, uint256 b);
151 }";
152 let res = parse_file(contents).validate(&OPTIONS);
153 assert_eq!(res.diags.len(), 1);
154 assert_eq!(res.diags[0].message, "@param b is missing");
155 }
156
157 #[test]
158 fn test_error_multiline() {
159 let contents = "contract Test {
160 /**
161 * @notice An error
162 * @param a The first
163 * @param b The second
164 */
165 error Foobar(uint256 a, uint256 b);
166 }";
167 let res = parse_file(contents).validate(&OPTIONS);
168 assert!(res.diags.is_empty(), "{:#?}", res.diags);
169 }
170
171 #[test]
172 fn test_error_duplicate() {
173 let contents = "contract Test {
174 /// @notice An error
175 /// @param a The first
176 /// @param a The first again
177 error Foobar(uint256 a);
178 }";
179 let res = parse_file(contents).validate(&OPTIONS);
180 assert_eq!(res.diags.len(), 1);
181 assert_eq!(res.diags[0].message, "@param a is present more than once");
182 }
183
184 #[test]
185 fn test_error_no_params() {
186 let contents = "contract Test {
187 error Foobar();
188 }";
189 let res = parse_file(contents).validate(&OPTIONS);
190 assert_eq!(res.diags.len(), 1);
191 assert_eq!(res.diags[0].message, "@notice is missing");
192 }
193
194 #[test]
195 fn test_error_inheritdoc() {
196 let contents = "contract Test {
198 /// @inheritdoc ITest
199 error Foobar(uint256 a);
200 }";
201 let res = parse_file(contents).validate(&ValidationOptions::default());
202 assert_eq!(res.diags.len(), 2);
203 assert_eq!(res.diags[0].message, "@notice is missing");
204 assert_eq!(res.diags[1].message, "@param a is missing");
205 }
206
207 #[test]
208 fn test_error_no_contract() {
209 let contents = "
210 /// @notice An error
211 /// @param a The first
212 /// @param b The second
213 error Foobar(uint256 a, uint256 b);
214 ";
215 let res = parse_file(contents).validate(&OPTIONS);
216 assert!(res.diags.is_empty(), "{:#?}", res.diags);
217 }
218
219 #[test]
220 fn test_error_no_contract_missing() {
221 let contents = "error Foobar(uint256 a, uint256 b);";
222 let res = parse_file(contents).validate(&OPTIONS);
223 assert_eq!(res.diags.len(), 3);
224 assert_eq!(res.diags[0].message, "@notice is missing");
225 assert_eq!(res.diags[1].message, "@param a is missing");
226 assert_eq!(res.diags[2].message, "@param b is missing");
227 }
228
229 #[test]
230 fn test_error_extra() {
231 let contents = "
232 /// @notice An error
233 /// @param a A param
234 error Foobar();
235 ";
236 let res = parse_file(contents).validate(&OPTIONS);
237 assert_eq!(res.diags.len(), 1);
238 assert_eq!(res.diags[0].message, "extra @param a");
239 }
240}