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