lintspec/definitions/
error.rs1use crate::{
3 lint::{check_notice_and_dev, check_params, 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(check_notice_and_dev(
59 &self.natspec,
60 opts.notice,
61 opts.dev,
62 options.notice_or_dev,
63 self.span(),
64 ));
65 out.diags.extend(check_params(
66 &self.natspec,
67 opts.param,
68 &self.params,
69 self.span(),
70 ));
71 out
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use std::sync::LazyLock;
78
79 use semver::Version;
80 use similar_asserts::assert_eq;
81 use slang_solidity::{cst::NonterminalKind, parser::Parser};
82
83 use crate::parser::slang::Extract as _;
84
85 use super::*;
86
87 static OPTIONS: LazyLock<ValidationOptions> =
88 LazyLock::new(|| ValidationOptions::builder().inheritdoc(false).build());
89
90 fn parse_file(contents: &str) -> ErrorDefinition {
91 let parser = Parser::create(Version::new(0, 8, 26)).unwrap();
92 let output = parser.parse(NonterminalKind::SourceUnit, contents);
93 assert!(output.is_valid(), "{:?}", output.errors());
94 let cursor = output.create_tree_cursor();
95 let m = cursor.query(vec![ErrorDefinition::query()]).next().unwrap();
96 let def = ErrorDefinition::extract(m).unwrap();
97 def.to_error().unwrap()
98 }
99
100 #[test]
101 fn test_error() {
102 let contents = "contract Test {
103 /// @notice An error
104 /// @param a The first
105 /// @param b The second
106 error Foobar(uint256 a, uint256 b);
107 }";
108 let res = parse_file(contents).validate(&OPTIONS);
109 assert!(res.diags.is_empty(), "{:#?}", res.diags);
110 }
111
112 #[test]
113 fn test_error_no_natspec() {
114 let contents = "contract Test {
115 error Foobar(uint256 a, uint256 b);
116 }";
117 let res = parse_file(contents).validate(&OPTIONS);
118 assert_eq!(res.diags.len(), 3);
119 assert_eq!(res.diags[0].message, "@notice is missing");
120 assert_eq!(res.diags[1].message, "@param a is missing");
121 assert_eq!(res.diags[2].message, "@param b is missing");
122 }
123
124 #[test]
125 fn test_error_only_notice() {
126 let contents = "contract Test {
127 /// @notice An error
128 error Foobar(uint256 a, uint256 b);
129 }";
130 let res = parse_file(contents).validate(&OPTIONS);
131 assert_eq!(res.diags.len(), 2);
132 assert_eq!(res.diags[0].message, "@param a is missing");
133 assert_eq!(res.diags[1].message, "@param b is missing");
134 }
135
136 #[test]
137 fn test_error_one_missing() {
138 let contents = "contract Test {
139 /// @notice An error
140 /// @param a The first
141 error Foobar(uint256 a, uint256 b);
142 }";
143 let res = parse_file(contents).validate(&OPTIONS);
144 assert_eq!(res.diags.len(), 1);
145 assert_eq!(res.diags[0].message, "@param b is missing");
146 }
147
148 #[test]
149 fn test_error_multiline() {
150 let contents = "contract Test {
151 /**
152 * @notice An error
153 * @param a The first
154 * @param b The second
155 */
156 error Foobar(uint256 a, uint256 b);
157 }";
158 let res = parse_file(contents).validate(&OPTIONS);
159 assert!(res.diags.is_empty(), "{:#?}", res.diags);
160 }
161
162 #[test]
163 fn test_error_duplicate() {
164 let contents = "contract Test {
165 /// @notice An error
166 /// @param a The first
167 /// @param a The first again
168 error Foobar(uint256 a);
169 }";
170 let res = parse_file(contents).validate(&OPTIONS);
171 assert_eq!(res.diags.len(), 1);
172 assert_eq!(res.diags[0].message, "@param a is present more than once");
173 }
174
175 #[test]
176 fn test_error_no_params() {
177 let contents = "contract Test {
178 error Foobar();
179 }";
180 let res = parse_file(contents).validate(&OPTIONS);
181 assert_eq!(res.diags.len(), 1);
182 assert_eq!(res.diags[0].message, "@notice is missing");
183 }
184
185 #[test]
186 fn test_error_inheritdoc() {
187 let contents = "contract Test {
189 /// @inheritdoc ITest
190 error Foobar(uint256 a);
191 }";
192 let res = parse_file(contents).validate(&ValidationOptions::default());
193 assert_eq!(res.diags.len(), 2);
194 assert_eq!(res.diags[0].message, "@notice is missing");
195 assert_eq!(res.diags[1].message, "@param a is missing");
196 }
197
198 #[test]
199 fn test_error_no_contract() {
200 let contents = "
201 /// @notice An error
202 /// @param a The first
203 /// @param b The second
204 error Foobar(uint256 a, uint256 b);
205 ";
206 let res = parse_file(contents).validate(&OPTIONS);
207 assert!(res.diags.is_empty(), "{:#?}", res.diags);
208 }
209
210 #[test]
211 fn test_error_no_contract_missing() {
212 let contents = "error Foobar(uint256 a, uint256 b);";
213 let res = parse_file(contents).validate(&OPTIONS);
214 assert_eq!(res.diags.len(), 3);
215 assert_eq!(res.diags[0].message, "@notice is missing");
216 assert_eq!(res.diags[1].message, "@param a is missing");
217 assert_eq!(res.diags[2].message, "@param b is missing");
218 }
219}