lintspec_core/definitions/
library.rs1use crate::{
3 lint::{CheckAuthor, CheckNoticeAndDev, CheckTitle, ItemDiagnostics},
4 natspec::NatSpec,
5};
6
7use super::{ItemType, Parent, SourceItem, TextRange, Validate, ValidationOptions};
8
9#[derive(Debug, Clone, bon::Builder)]
11#[non_exhaustive]
12#[builder(on(String, into))]
13pub struct LibraryDefinition {
14 pub name: String,
16
17 pub span: TextRange,
19
20 pub natspec: Option<NatSpec>,
22}
23
24impl SourceItem for LibraryDefinition {
25 fn item_type(&self) -> ItemType {
26 ItemType::Library
27 }
28
29 fn parent(&self) -> Option<Parent> {
30 None
31 }
32
33 fn name(&self) -> String {
34 self.name.clone()
35 }
36
37 fn span(&self) -> TextRange {
38 self.span.clone()
39 }
40}
41
42impl Validate for LibraryDefinition {
43 fn validate(&self, options: &ValidationOptions) -> ItemDiagnostics {
44 let opts = &options.libraries;
45 let mut out = ItemDiagnostics {
46 parent: self.parent(),
47 item_type: self.item_type(),
48 name: self.name(),
49 span: self.span(),
50 diags: vec![],
51 };
52 out.diags.extend(
53 CheckTitle::builder()
54 .natspec(&self.natspec)
55 .rule(opts.title)
56 .span(&self.span)
57 .build()
58 .check(),
59 );
60 out.diags.extend(
61 CheckAuthor::builder()
62 .natspec(&self.natspec)
63 .rule(opts.author)
64 .span(&self.span)
65 .build()
66 .check(),
67 );
68 out.diags.extend(
69 CheckNoticeAndDev::builder()
70 .natspec(&self.natspec)
71 .notice_rule(opts.notice)
72 .dev_rule(opts.dev)
73 .notice_or_dev(options.notice_or_dev)
74 .span(&self.span)
75 .build()
76 .check(),
77 );
78 out
79 }
80}
81
82#[cfg(test)]
83#[cfg(feature = "solar")]
84mod tests {
85 use std::sync::LazyLock;
86
87 use similar_asserts::assert_eq;
88
89 use crate::{
90 config::{ContractRules, Req},
91 definitions::Definition,
92 parser::{Parse as _, solar::SolarParser},
93 };
94
95 use super::*;
96
97 static OPTIONS: LazyLock<ValidationOptions> = LazyLock::new(|| {
98 ValidationOptions::builder()
99 .inheritdoc(false)
100 .libraries(
101 ContractRules::builder()
102 .title(Req::Required)
103 .author(Req::Required)
104 .notice(Req::Required)
105 .build(),
106 )
107 .build()
108 });
109
110 fn parse_file(contents: &str) -> LibraryDefinition {
111 let mut parser = SolarParser::default();
112 let doc = parser
113 .parse_document(contents.as_bytes(), None::<std::path::PathBuf>, false)
114 .unwrap();
115 doc.definitions
116 .into_iter()
117 .find_map(Definition::to_library)
118 .unwrap()
119 }
120
121 #[test]
122 fn test_library() {
123 let contents = "/// @title Library
124 /// @author Me
125 /// @notice This is a library
126 library Test {}";
127 let res = parse_file(contents).validate(&OPTIONS);
128 assert!(res.diags.is_empty(), "{:#?}", res.diags);
129 }
130
131 #[test]
132 fn test_library_no_natspec() {
133 let contents = "library Test {}";
134 let res = parse_file(contents).validate(&OPTIONS);
135 assert_eq!(res.diags.len(), 3);
136 assert_eq!(res.diags[0].message, "@title is missing");
137 assert_eq!(res.diags[1].message, "@author is missing");
138 assert_eq!(res.diags[2].message, "@notice is missing");
139 }
140
141 #[test]
142 fn test_library_multiline() {
143 let contents = "/**
144 * @title Library
145 * @author Me
146 * @notice This is a library
147 */
148 library Test {}";
149 let res = parse_file(contents).validate(&OPTIONS);
150 assert!(res.diags.is_empty(), "{:#?}", res.diags);
151 }
152
153 #[test]
154 fn test_library_inheritdoc() {
155 let contents = "/// @inheritdoc ITest
157 library Test {}";
158 let res = parse_file(contents).validate(
159 &ValidationOptions::builder()
160 .libraries(ContractRules::builder().title(Req::Required).build())
161 .build(),
162 );
163 assert_eq!(res.diags.len(), 1);
164 assert_eq!(res.diags[0].message, "@title is missing");
165 }
166}