@@ -19,12 +19,12 @@ pub(super) fn find_annotations(
19
19
rule_cache : & mut HashMap < String , Rc < String > > ,
20
20
) {
21
21
// Use Rc for file path
22
- let file_rc = Rc :: new ( file. to_string ( ) ) ;
22
+ let file_rc = Rc :: new ( file. to_owned ( ) ) ;
23
23
24
24
for line in content. lines ( ) {
25
25
if let Some ( captures) = regex. captures ( line) {
26
26
if let Some ( rule_match) = captures. get ( 1 ) {
27
- let rule_str = rule_match. as_str ( ) . to_string ( ) ;
27
+ let rule_str = rule_match. as_str ( ) . to_owned ( ) ;
28
28
29
29
// Get or create Rc for this rule
30
30
let rule_rc = match rule_cache. get ( & rule_str) {
@@ -73,7 +73,7 @@ pub(super) fn count_annotations_by_crate(
73
73
Some ( name) => name. clone ( ) ,
74
74
None => {
75
75
let name = Rc :: new ( get_crate_for_file ( file_path) . to_owned ( ) ) ;
76
- crate_cache. insert ( file_path. to_string ( ) , Rc :: clone ( & name) ) ;
76
+ crate_cache. insert ( file_path. to_owned ( ) , Rc :: clone ( & name) ) ;
77
77
78
78
name
79
79
}
@@ -87,6 +87,10 @@ pub(super) fn count_annotations_by_crate(
87
87
88
88
/// Create a regex for matching clippy allow annotations
89
89
pub ( super ) fn create_annotation_regex ( rules : & [ String ] ) -> Result < Regex > {
90
+ if rules. is_empty ( ) {
91
+ return Err ( anyhow:: anyhow!( "Cannot create regex with empty rules list" ) ) ;
92
+ }
93
+
90
94
let rule_pattern = rules. join ( "|" ) ;
91
95
let regex = Regex :: new ( & format ! (
92
96
r"#\s*\[\s*allow\s*\(\s*clippy\s*::\s*({})\s*\)\s*\]" ,
@@ -96,3 +100,227 @@ pub(super) fn create_annotation_regex(rules: &[String]) -> Result<Regex> {
96
100
97
101
Ok ( regex)
98
102
}
103
+
104
+ #[ cfg( test) ]
105
+ mod tests {
106
+ use super :: * ;
107
+ use std:: rc:: Rc ;
108
+
109
+ #[ test]
110
+ fn test_count_annotations_by_rule ( ) {
111
+ // Create test annotations
112
+ let rule1 = Rc :: new ( "clippy::unwrap_used" . to_owned ( ) ) ;
113
+ let rule2 = Rc :: new ( "clippy::match_bool" . to_owned ( ) ) ;
114
+ let file = Rc :: new ( "src/main.rs" . to_owned ( ) ) ;
115
+
116
+ let annotations = vec ! [
117
+ ClippyAnnotation {
118
+ file: file. clone( ) ,
119
+ rule: rule1. clone( ) ,
120
+ } ,
121
+ ClippyAnnotation {
122
+ file: file. clone( ) ,
123
+ rule: rule1. clone( ) ,
124
+ } ,
125
+ ClippyAnnotation {
126
+ file: file. clone( ) ,
127
+ rule: rule2. clone( ) ,
128
+ } ,
129
+ ClippyAnnotation {
130
+ file: file. clone( ) ,
131
+ rule: rule1. clone( ) ,
132
+ } ,
133
+ ] ;
134
+
135
+ let counts = count_annotations_by_rule ( & annotations) ;
136
+
137
+ assert_eq ! ( counts. len( ) , 2 , "Should have counts for 2 rules" ) ;
138
+ assert_eq ! ( counts[ & rule1] , 3 , "Rule1 should have 3 annotations" ) ;
139
+ assert_eq ! ( counts[ & rule2] , 1 , "Rule2 should have 1 annotation" ) ;
140
+ }
141
+
142
+ #[ test]
143
+ fn test_count_annotations_by_rule_empty ( ) {
144
+ // Test with empty annotations
145
+ let annotations: Vec < ClippyAnnotation > = vec ! [ ] ;
146
+ let counts = count_annotations_by_rule ( & annotations) ;
147
+
148
+ assert_eq ! (
149
+ counts. len( ) ,
150
+ 0 ,
151
+ "Empty annotations should produce empty counts"
152
+ ) ;
153
+ }
154
+
155
+ #[ test]
156
+ fn test_count_annotations_by_crate ( ) {
157
+ use std:: fs:: { self , File } ;
158
+ use std:: io:: Write ;
159
+ use tempfile:: TempDir ;
160
+
161
+ // Create a temporary directory structure for testing
162
+ let temp_dir = TempDir :: new ( ) . expect ( "Failed to create temp directory" ) ;
163
+ let temp_path = temp_dir. path ( ) ;
164
+
165
+ // Create a directory structure with two different crates
166
+ // crate1
167
+ // ├── Cargo.toml (package.name = "crate1")
168
+ // └── src
169
+ // ├── lib.rs
170
+ // └── module.rs
171
+ // crate2
172
+ // ├── Cargo.toml (package.name = "crate2")
173
+ // └── src
174
+ // └── main.rs
175
+
176
+ // Create directories
177
+ let crate1_dir = temp_path. join ( "crate1" ) ;
178
+ let crate1_src_dir = crate1_dir. join ( "src" ) ;
179
+ let crate2_dir = temp_path. join ( "crate2" ) ;
180
+ let crate2_src_dir = crate2_dir. join ( "src" ) ;
181
+
182
+ fs:: create_dir_all ( & crate1_src_dir) . expect ( "Failed to create crate1/src directory" ) ;
183
+ fs:: create_dir_all ( & crate2_src_dir) . expect ( "Failed to create crate2/src directory" ) ;
184
+
185
+ // Create Cargo.toml files with specific package names
186
+ let crate1_cargo = crate1_dir. join ( "Cargo.toml" ) ;
187
+ let mut cargo1_file =
188
+ File :: create ( & crate1_cargo) . expect ( "Failed to create crate1 Cargo.toml" ) ;
189
+ writeln ! (
190
+ cargo1_file,
191
+ r#"[package]
192
+ name = "crate1"
193
+ version = "0.1.0"
194
+ edition = "2021"
195
+ "#
196
+ )
197
+ . expect ( "Failed to write to crate1 Cargo.toml" ) ;
198
+
199
+ let crate2_cargo = crate2_dir. join ( "Cargo.toml" ) ;
200
+ let mut cargo2_file =
201
+ File :: create ( & crate2_cargo) . expect ( "Failed to create crate2 Cargo.toml" ) ;
202
+ writeln ! (
203
+ cargo2_file,
204
+ r#"[package]
205
+ name = "crate2"
206
+ version = "0.1.0"
207
+ edition = "2021"
208
+ "#
209
+ )
210
+ . expect ( "Failed to write to crate2 Cargo.toml" ) ;
211
+
212
+ // Create source files
213
+ let crate1_lib = crate1_src_dir. join ( "lib.rs" ) ;
214
+ let mut lib_file = File :: create ( & crate1_lib) . expect ( "Failed to create lib.rs" ) ;
215
+ writeln ! ( lib_file, "// Empty lib file" ) . expect ( "Failed to write to lib.rs" ) ;
216
+
217
+ let crate1_module = crate1_src_dir. join ( "module.rs" ) ;
218
+ let mut module_file = File :: create ( & crate1_module) . expect ( "Failed to create module.rs" ) ;
219
+ writeln ! ( module_file, "// Empty module file" ) . expect ( "Failed to write to module.rs" ) ;
220
+
221
+ let crate2_main = crate2_src_dir. join ( "main.rs" ) ;
222
+ let mut main_file = File :: create ( & crate2_main) . expect ( "Failed to create main.rs" ) ;
223
+ writeln ! ( main_file, "// Empty main file" ) . expect ( "Failed to write to main.rs" ) ;
224
+
225
+ // Create test annotations with the real file paths
226
+ let rule = Rc :: new ( "clippy::unwrap_used" . to_owned ( ) ) ;
227
+
228
+ let crate1_lib_path = Rc :: new ( crate1_lib. to_string_lossy ( ) . to_string ( ) ) ;
229
+ let crate1_module_path = Rc :: new ( crate1_module. to_string_lossy ( ) . to_string ( ) ) ;
230
+ let crate2_main_path = Rc :: new ( crate2_main. to_string_lossy ( ) . to_string ( ) ) ;
231
+
232
+ let annotations = vec ! [
233
+ ClippyAnnotation {
234
+ file: crate1_lib_path. clone( ) ,
235
+ rule: rule. clone( ) ,
236
+ } ,
237
+ ClippyAnnotation {
238
+ file: crate1_module_path. clone( ) ,
239
+ rule: rule. clone( ) ,
240
+ } ,
241
+ ClippyAnnotation {
242
+ file: crate1_module_path. clone( ) , // Another annotation in the same file
243
+ rule: rule. clone( ) ,
244
+ } ,
245
+ ClippyAnnotation {
246
+ file: crate2_main_path. clone( ) ,
247
+ rule: rule. clone( ) ,
248
+ } ,
249
+ ] ;
250
+
251
+ let counts = count_annotations_by_crate ( & annotations) ;
252
+
253
+ assert_eq ! ( counts. len( ) , 2 , "Should have counts for 2 crates" ) ;
254
+
255
+ let crate1_count = counts
256
+ . iter ( )
257
+ . find ( |( k, _) | k. contains ( "crate1" ) )
258
+ . map ( |( _, v) | * v)
259
+ . unwrap_or ( 0 ) ;
260
+
261
+ let crate2_count = counts
262
+ . iter ( )
263
+ . find ( |( k, _) | k. contains ( "crate2" ) )
264
+ . map ( |( _, v) | * v)
265
+ . unwrap_or ( 0 ) ;
266
+
267
+ assert_eq ! ( crate1_count, 3 , "crate1 should have 3 annotations" ) ;
268
+ assert_eq ! ( crate2_count, 1 , "crate2 should have 1 annotation" ) ;
269
+ }
270
+
271
+ #[ test]
272
+ fn test_count_annotations_by_crate_empty ( ) {
273
+ // Test with empty annotations
274
+ let annotations: Vec < ClippyAnnotation > = vec ! [ ] ;
275
+ let counts = count_annotations_by_crate ( & annotations) ;
276
+
277
+ assert_eq ! (
278
+ counts. len( ) ,
279
+ 0 ,
280
+ "Empty annotations should produce empty counts"
281
+ ) ;
282
+ }
283
+
284
+ #[ test]
285
+ fn test_create_annotation_regex_single_rule ( ) {
286
+ let rules = vec ! [ "unwrap_used" . to_owned( ) ] ; // Rule without clippy:: prefix
287
+ let regex = create_annotation_regex ( & rules) . expect ( "Failed to create regex" ) ;
288
+
289
+ // Test matching
290
+ assert ! ( regex. is_match( "#[allow(clippy::unwrap_used)]" ) ) ;
291
+ assert ! ( regex. is_match( "#[allow(clippy:: unwrap_used )]" ) ) ; // With spaces
292
+ assert ! ( regex. is_match( "# [ allow ( clippy :: unwrap_used ) ]" ) ) ; // With more spaces
293
+
294
+ // Test non-matching
295
+ assert ! ( !regex. is_match( "#[allow(clippy::unused_imports)]" ) ) ;
296
+ assert ! ( !regex. is_match( "#[allow(unwrap_used)]" ) ) ; // Missing clippy::
297
+ assert ! ( !regex. is_match( "clippy::unwrap_used" ) ) ; // Missing #[allow()]
298
+ }
299
+ #[ test]
300
+ fn test_create_annotation_regex_multiple_rules ( ) {
301
+ let rules = vec ! [ "unwrap_used" . to_owned( ) , "match_bool" . to_owned( ) ] ;
302
+ let regex = create_annotation_regex ( & rules) . expect ( "Failed to create regex" ) ;
303
+
304
+ assert ! ( regex. is_match( "#[allow(clippy::unwrap_used)]" ) ) ;
305
+ assert ! ( regex. is_match( "#[allow(clippy::match_bool)]" ) ) ;
306
+
307
+ // Test mixed spacing and formatting
308
+ assert ! ( regex. is_match( "#[allow(clippy:: unwrap_used )]" ) ) ; // With spaces
309
+ assert ! ( regex. is_match( "# [ allow ( clippy :: match_bool ) ]" ) ) ; // With more spaces
310
+
311
+ // Test non-matching
312
+ assert ! ( !regex. is_match( "#[allow(clippy::unused_imports)]" ) ) ;
313
+ assert ! ( !regex. is_match( "#[allow(unwrap_used)]" ) ) ; // Missing clippy::
314
+ }
315
+
316
+ #[ test]
317
+ fn test_create_annotation_regex_empty_rules ( ) {
318
+ let rules: Vec < String > = vec ! [ ] ;
319
+ let result = create_annotation_regex ( & rules) ;
320
+
321
+ assert ! (
322
+ result. is_err( ) ,
323
+ "Creating regex with empty rules should fail"
324
+ ) ;
325
+ }
326
+ }
0 commit comments