• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

xd009642 / tarpaulin / #800

29 Jun 2026 09:37AM UTC coverage: 88.955% (+2.9%) from 86.061%
#800

push

xd009642
Release 0.36.0

459 of 500 new or added lines in 22 files covered. (91.8%)

6 existing lines in 2 files now uncovered.

6975 of 7841 relevant lines covered (88.96%)

203510.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.27
/src/source_analysis/items.rs
1
use crate::source_analysis::prelude::*;
2
use syn::*;
3

4
impl SourceAnalysis {
5
    pub(crate) fn process_items(&mut self, items: &[Item], ctx: &Context) -> SubResult {
526✔
6
        let mut res = SubResult::Ok;
1,052✔
7
        for item in items.iter() {
2,076✔
8
            match item {
1,024✔
9
                Item::ExternCrate(i) => {
4✔
10
                    let analysis = self.get_line_analysis(ctx.file.to_path_buf());
12✔
11
                    analysis.ignore_tokens(i);
4✔
12
                }
13
                Item::Use(i) => {
194✔
14
                    let analysis = self.get_line_analysis(ctx.file.to_path_buf());
582✔
15
                    analysis.ignore_tokens(i);
194✔
16
                }
17
                Item::Mod(i) => self.visit_mod(i, ctx),
1,205✔
18
                Item::Fn(i) => self.visit_fn(i, ctx, false),
2,995✔
19
                Item::Struct(i) => {
56✔
20
                    let analysis = self.get_line_analysis(ctx.file.to_path_buf());
168✔
21
                    analysis.ignore_tokens(i);
56✔
22
                }
23
                Item::Enum(i) => {
8✔
24
                    let analysis = self.get_line_analysis(ctx.file.to_path_buf());
24✔
25
                    analysis.ignore_tokens(i);
8✔
26
                }
27
                Item::Union(i) => {
2✔
28
                    let analysis = self.get_line_analysis(ctx.file.to_path_buf());
6✔
29
                    analysis.ignore_tokens(i);
2✔
30
                }
31
                Item::Trait(i) => self.visit_trait(i, ctx),
55✔
32
                Item::Impl(i) => self.visit_impl(i, ctx),
110✔
NEW
33
                Item::Macro(i) => {
×
34
                    if self.visit_macro_call(&i.mac, ctx).is_unreachable() {
×
35
                        res = SubResult::Unreachable;
×
36
                    }
37
                }
38
                Item::Const(c) => {
38✔
39
                    let analysis = self.get_line_analysis(ctx.file.to_path_buf());
114✔
40
                    analysis.ignore_tokens(c);
38✔
41
                }
42
                _ => {}
×
43
            }
44
        }
45
        res
526✔
46
    }
47

48
    fn ignore_nested_modules(&mut self, items: &[Item], ctx: &Context) {
19✔
49
        for item in items.iter() {
69✔
50
            if let Item::Mod(m) = item {
36✔
51
                if let Some((_, ref items)) = m.content {
7✔
52
                    let _guard = ctx.push_to_symbol_stack(m.ident.to_string());
6✔
53
                    self.ignore_nested_modules(items, ctx);
3✔
54
                } else {
55
                    // ignore whole ass module
56
                    let mut p = if let Some(parent) = ctx.file.parent() {
12✔
57
                        parent.to_path_buf()
8✔
58
                    } else {
59
                        PathBuf::new()
×
60
                    };
61
                    match ctx.file.file_stem().and_then(|x| x.to_str()) {
20✔
62
                        Some(name) if !["lib", "mod"].contains(&name) => {
18✔
63
                            p.push(name);
4✔
64
                        }
65
                        _ => {}
2✔
66
                    }
67
                    for s in ctx.symbol_stack.borrow().iter() {
14✔
68
                        p.push(s);
10✔
69
                    }
70
                    p.push(m.ident.to_string());
16✔
71
                    if !p.exists() {
8✔
72
                        p.set_extension("rs");
4✔
73
                    }
74
                    ctx.ignore_mods.borrow_mut().insert(p);
8✔
75
                }
76
            }
77
        }
78
    }
79

80
    fn visit_mod(&mut self, module: &ItemMod, ctx: &Context) {
241✔
81
        let _guard = ctx.push_to_symbol_stack(module.ident.to_string());
1,205✔
82
        let analysis = self.get_line_analysis(ctx.file.to_path_buf());
1,205✔
83
        analysis.ignore_tokens(&module.mod_token);
723✔
84
        let should_cover = self.check_attr_list(&module.attrs, ctx);
1,205✔
85
        if should_cover {
241✔
86
            if let Some((_, ref items)) = module.content {
380✔
87
                self.process_items(items, ctx);
288✔
88
            }
89
        } else {
90
            if let Some((ref braces, _)) = module.content {
71✔
91
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
90✔
92
                analysis.ignore_span(braces.span.join());
72✔
93
                if let Some((_, ref items)) = module.content {
54✔
94
                    self.ignore_nested_modules(items, ctx);
54✔
95
                }
96
            } else {
97
                // This item was determined not to be covered, so we ignore it.
98
                // Do a best-effort calculation of the path of the module
99
                // corresponding to the mod item to ignore it.
100

101
                // Get the file or directory name of the module containing the mod item
102
                let mut p = if let Some(parent) = ctx.file.parent() {
105✔
103
                    parent.to_path_buf()
70✔
104
                } else {
105
                    PathBuf::new()
×
106
                };
107
                match ctx.file.file_stem().and_then(|x| x.to_str()) {
175✔
108
                    Some(name) if ["lib", "mod", "main"].contains(&name) => {
207✔
109
                        // skip because these segments are transparent in module tree
110
                    }
111
                    Some(name) => p.push(name),
4✔
112
                    None => warn!(
×
113
                        "Could not read file stem of {:?}; sub-module path may be wrong",
114
                        ctx.file
115
                    ),
116
                }
117
                let stack = ctx.symbol_stack.borrow();
105✔
118
                for s in stack.iter().take(stack.len() - 1) {
107✔
119
                    p.push(s);
2✔
120
                }
121
                p.push(module.ident.to_string());
140✔
122
                if !p.exists() {
70✔
123
                    p.set_extension("rs");
35✔
124
                }
125
                ctx.ignore_mods.borrow_mut().insert(p);
70✔
126
            }
127
        }
128
    }
129

130
    fn visit_fn(&mut self, func: &ItemFn, ctx: &Context, force_cover: bool) {
627✔
131
        let _guard = ctx.push_to_symbol_stack(func.sig.ident.to_string());
3,135✔
132
        {
133
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
3,762✔
134
            let span = func.span();
2,508✔
135
            analysis.functions.insert(
1,881✔
136
                ctx.get_qualified_name(),
1,881✔
137
                (func.sig.span().start().line, span.end().line),
1,254✔
138
            );
139
        }
140
        let mut test_func = false;
1,254✔
141
        let mut ignored_attr = false;
1,254✔
142
        let mut is_inline = false;
1,254✔
143
        let mut ignore_span = false;
1,254✔
144
        let is_generic = is_sig_generic(&func.sig);
1,881✔
145
        for attr in &func.attrs {
1,177✔
146
            let id = attr.path();
1,650✔
147
            if id.is_ident("test") || id.segments.last().is_some_and(|seg| seg.ident == "test") {
3,397✔
148
                test_func = true;
257✔
149
            } else if id.is_ident("derive") {
1,136✔
150
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
151
                analysis.ignore_span(attr.span());
×
152
            } else if id.is_ident("inline") {
881✔
153
                is_inline = true;
2✔
154
            } else if id.is_ident("ignore") {
875✔
155
                ignored_attr = true;
×
156
            } else if check_cfg_attr(&attr.meta) {
582✔
157
                ignore_span = true;
15✔
158
                break;
15✔
159
            }
160
        }
161
        if ignore_span
627✔
162
            || (test_func && !ctx.config.include_tests())
865✔
163
            || (ignored_attr && !ctx.config.run_ignored)
599✔
164
        {
165
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
168✔
166
            analysis.ignore_tokens(func);
56✔
167
        } else {
168
            if is_inline || is_generic || force_cover {
1,806✔
169
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
288✔
170
                // We need to force cover!
171
                analysis.cover_span(func.block.span(), Some(ctx.file_contents));
192✔
172
            }
173
            if self
1,198✔
174
                .process_statements(&func.block.stmts, ctx)
1,198✔
175
                .is_unreachable()
176
            {
177
                // if the whole body of the function is unreachable, that means the function itself
178
                // cannot be called, so is unreachable as a whole
179
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
25✔
180
                analysis.ignore_tokens(func);
15✔
181
                return;
5✔
182
            }
183
            self.visit_generics(&func.sig.generics, ctx);
2,376✔
184
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
2,970✔
185
            let line_number = func.sig.fn_token.span().start().line;
1,188✔
186
            let mut start_line = line_number;
1,188✔
187
            for attr in &func.attrs {
1,630✔
188
                start_line = start_line.min(attr.span().start().line);
1,554✔
189
            }
190
            if start_line < line_number {
904✔
191
                analysis.add_to_ignore(start_line..line_number);
930✔
192
            }
193
            analysis.ignore.remove(&Lines::Line(line_number));
1,782✔
194
            // Ignore multiple lines of fn decl
195
            let decl_start = func.sig.fn_token.span().start().line + 1;
1,188✔
196
            let stmts_start = func.block.span().start().line;
1,188✔
197
            let lines = decl_start..=stmts_start;
1,188✔
198
            analysis.add_to_ignore(lines);
1,782✔
199
        }
200
    }
201

202
    fn visit_trait(&mut self, trait_item: &ItemTrait, ctx: &Context) {
11✔
203
        let _guard = ctx.push_to_symbol_stack(trait_item.ident.to_string());
55✔
204
        let check_cover = self.check_attr_list(&trait_item.attrs, ctx);
55✔
205
        if check_cover {
11✔
206
            for item in &trait_item.items {
22✔
207
                if let TraitItem::Fn(ref i) = *item {
23✔
208
                    if self.check_attr_list(&i.attrs, ctx) {
44✔
209
                        let item = i.clone();
27✔
210
                        if let Some(block) = item.default {
23✔
211
                            let item_fn = ItemFn {
212
                                attrs: item.attrs,
21✔
213
                                // Trait functions inherit visibility from the trait
214
                                vis: trait_item.vis.clone(),
28✔
215
                                sig: item.sig,
21✔
216
                                block: Box::new(block),
14✔
217
                            };
218
                            // We visit the function and force cover it
219
                            self.visit_fn(&item_fn, ctx, true);
21✔
220
                        } else {
221
                            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
12✔
222
                            analysis.ignore_tokens(i);
4✔
223
                        }
224
                    } else {
225
                        let analysis = self.get_line_analysis(ctx.file.to_path_buf());
12✔
226
                        analysis.ignore_tokens(i);
4✔
227
                    }
228
                    let analysis = self.get_line_analysis(ctx.file.to_path_buf());
55✔
229
                    for a in &i.attrs {
17✔
230
                        analysis.ignore_tokens(a);
6✔
231
                    }
232
                }
233
            }
234
            self.visit_generics(&trait_item.generics, ctx);
40✔
235
        } else {
236
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
6✔
237
            analysis.ignore_tokens(trait_item);
2✔
238
        }
239
    }
240

241
    fn visit_impl(&mut self, impl_blk: &ItemImpl, ctx: &Context) {
22✔
242
        let self_ty_name = impl_blk
66✔
243
            .self_ty
44✔
244
            .to_token_stream()
245
            .to_string()
246
            .replace(' ', "");
247
        let _guard = match &impl_blk.trait_ {
44✔
248
            Some((_, path, _)) => {
6✔
249
                let trait_name = path
12✔
250
                    .segments
6✔
251
                    .last()
252
                    .map(|x| x.ident.to_string())
18✔
253
                    .unwrap_or_else(|| path.to_token_stream().to_string());
6✔
254
                let name = format!("<impl {} for {}>", trait_name, self_ty_name);
12✔
255
                ctx.push_to_symbol_stack(name)
18✔
256
            }
257
            None => ctx.push_to_symbol_stack(self_ty_name),
48✔
258
        };
259
        let check_cover = self.check_attr_list(&impl_blk.attrs, ctx);
110✔
260
        if check_cover {
22✔
261
            for item in &impl_blk.items {
43✔
262
                match *item {
22✔
263
                    ImplItem::Fn(ref i) => {
42✔
264
                        let item = i.clone();
84✔
265
                        let item_fn = ItemFn {
266
                            attrs: item.attrs,
63✔
267
                            vis: item.vis,
63✔
268
                            sig: item.sig,
63✔
269
                            block: Box::new(item.block),
42✔
270
                        };
271

272
                        // If the impl is on a generic, we need to force cover
273
                        let force_cover = !impl_blk.generics.params.is_empty();
63✔
274

275
                        self.visit_fn(&item_fn, ctx, force_cover);
84✔
276
                    }
277
                    ImplItem::Type(_) => {
1✔
278
                        let analysis = self.get_line_analysis(ctx.file.to_path_buf());
6✔
279
                        analysis.ignore_span(item.span());
3✔
280
                    }
281
                    _ => {}
×
282
                }
283
            }
284
            self.visit_generics(&impl_blk.generics, ctx);
84✔
285
        } else {
286
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
6✔
287
            analysis.ignore_tokens(impl_blk);
2✔
288
        }
289
    }
290
}
291

292
fn has_generic_arg<'a>(args: impl Iterator<Item = &'a FnArg>) -> bool {
593✔
293
    for arg in args {
785✔
294
        if let FnArg::Typed(pat) = arg {
377✔
295
            if matches!(*pat.ty, Type::ImplTrait(_)) {
369✔
296
                return true;
1✔
297
            }
298
        }
299
    }
300
    false
592✔
301
}
302

303
fn is_sig_generic(sig: &Signature) -> bool {
627✔
304
    !sig.generics.params.is_empty() || has_generic_arg(sig.inputs.iter())
2,406✔
305
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc