lintspec_core/definitions/
error.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 ErrorDefinition {
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 ErrorDefinition {
32 fn item_type(&self) -> ItemType {
33 ItemType::Error
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 ErrorDefinition {
50 fn validate(&self, options: &ValidationOptions) -> ItemDiagnostics {
51 let opts = &options.errors;
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) -> ErrorDefinition {
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_error)
103 .unwrap()
104 }
105
106 #[test]
107 fn test_error() {
108 let contents = "contract Test {
109 /// @notice An error
110 /// @param a The first
111 /// @param b The second
112 error 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_error_no_natspec() {
120 let contents = "contract Test {
121 error 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_error_only_notice() {
132 let contents = "contract Test {
133 /// @notice An error
134 error 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_error_one_missing() {
144 let contents = "contract Test {
145 /// @notice An error
146 /// @param a The first
147 error Foobar(uint256 a, uint256 b);
148 }";
149 let res = parse_file(contents).validate(&OPTIONS);
150 assert_eq!(res.diags.len(), 1);
151 assert_eq!(res.diags[0].message, "@param b is missing");
152 }
153
154 #[test]
155 fn test_error_multiline() {
156 let contents = "contract Test {
157 /**
158 * @notice An error
159 * @param a The first
160 * @param b The second
161 */
162 error Foobar(uint256 a, uint256 b);
163 }";
164 let res = parse_file(contents).validate(&OPTIONS);
165 assert!(res.diags.is_empty(), "{:#?}", res.diags);
166 }
167
168 #[test]
169 fn test_error_duplicate() {
170 let contents = "contract Test {
171 /// @notice An error
172 /// @param a The first
173 /// @param a The first again
174 error Foobar(uint256 a);
175 }";
176 let res = parse_file(contents).validate(&OPTIONS);
177 assert_eq!(res.diags.len(), 1);
178 assert_eq!(res.diags[0].message, "@param a is present more than once");
179 }
180
181 #[test]
182 fn test_error_no_params() {
183 let contents = "contract Test {
184 error Foobar();
185 }";
186 let res = parse_file(contents).validate(&OPTIONS);
187 assert_eq!(res.diags.len(), 1);
188 assert_eq!(res.diags[0].message, "@notice is missing");
189 }
190
191 #[test]
192 fn test_error_inheritdoc() {
193 let contents = "contract Test {
195 /// @inheritdoc ITest
196 error Foobar(uint256 a);
197 }";
198 let res = parse_file(contents).validate(&ValidationOptions::default());
199 assert_eq!(res.diags.len(), 2);
200 assert_eq!(res.diags[0].message, "@notice is missing");
201 assert_eq!(res.diags[1].message, "@param a is missing");
202 }
203
204 #[test]
205 fn test_error_no_contract() {
206 let contents = "
207 /// @notice An error
208 /// @param a The first
209 /// @param b The second
210 error Foobar(uint256 a, uint256 b);
211 ";
212 let res = parse_file(contents).validate(&OPTIONS);
213 assert!(res.diags.is_empty(), "{:#?}", res.diags);
214 }
215
216 #[test]
217 fn test_error_no_contract_missing() {
218 let contents = "error Foobar(uint256 a, uint256 b);";
219 let res = parse_file(contents).validate(&OPTIONS);
220 assert_eq!(res.diags.len(), 3);
221 assert_eq!(res.diags[0].message, "@notice is missing");
222 assert_eq!(res.diags[1].message, "@param a is missing");
223 assert_eq!(res.diags[2].message, "@param b is missing");
224 }
225
226 #[test]
227 fn test_error_extra() {
228 let contents = "
229 /// @notice An error
230 /// @param a A param
231 error Foobar();
232 ";
233 let res = parse_file(contents).validate(&OPTIONS);
234 assert_eq!(res.diags.len(), 1);
235 assert_eq!(res.diags[0].message, "extra @param a");
236 }
237}