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

xd009642 / tarpaulin / #713

01 Jan 2026 11:44AM UTC coverage: 85.116% (+0.8%) from 84.27%
#713

push

xd009642
Release 0.35.0

124 of 133 new or added lines in 8 files covered. (93.23%)

5 existing lines in 2 files now uncovered.

4695 of 5516 relevant lines covered (85.12%)

252346.46 hits per line

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

94.74
/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 {
421✔
6
        let mut res = SubResult::Ok;
842✔
7
        for item in items.iter() {
1,621✔
8
            match item {
779✔
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) => {
114✔
14
                    let analysis = self.get_line_analysis(ctx.file.to_path_buf());
342✔
15
                    analysis.ignore_tokens(i);
114✔
16
                }
17
                Item::Mod(i) => self.visit_mod(i, ctx),
850✔
18
                Item::Fn(i) => self.visit_fn(i, ctx, false),
2,420✔
19
                Item::Struct(i) => {
50✔
20
                    let analysis = self.get_line_analysis(ctx.file.to_path_buf());
150✔
21
                    analysis.ignore_tokens(i);
50✔
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),
95✔
33
                Item::Macro(ref i) => {
×
34
                    if self.visit_macro_call(&i.mac, ctx).is_unreachable() {
×
35
                        res = SubResult::Unreachable;
×
36
                    }
37
                }
38
                Item::Const(c) => {
12✔
39
                    let analysis = self.get_line_analysis(ctx.file.to_path_buf());
36✔
40
                    analysis.ignore_tokens(c);
12✔
41
                }
42
                _ => {}
×
43
            }
44
        }
45
        res
421✔
46
    }
47

48
    fn ignore_nested_modules(&mut self, items: &[Item], ctx: &Context) {
43✔
49
        for item in items.iter() {
177✔
50
            if let Item::Mod(m) = item {
96✔
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 {
NEW
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) {
170✔
81
        let _guard = ctx.push_to_symbol_stack(module.ident.to_string());
850✔
82
        let analysis = self.get_line_analysis(ctx.file.to_path_buf());
850✔
83
        analysis.ignore_tokens(module.mod_token);
510✔
84
        let check_insides = self.check_attr_list(&module.attrs, ctx);
850✔
85
        if check_insides {
170✔
86
            if let Some((_, ref items)) = module.content {
251✔
87
                self.process_items(items, ctx);
186✔
88
            }
89
        } else {
90
            if let Some((ref braces, _)) = module.content {
85✔
91
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
210✔
92
                analysis.ignore_span(braces.span.join());
168✔
93
                if let Some((_, ref items)) = module.content {
126✔
94
                    self.ignore_nested_modules(items, ctx);
126✔
95
                }
96
            } else {
97
                // Get the file or directory name of the module
98
                let mut p = if let Some(parent) = ctx.file.parent() {
3✔
99
                    parent.to_path_buf()
2✔
100
                } else {
NEW
101
                    PathBuf::new()
×
102
                };
103
                if let Some(s) = ctx.file.file_stem().and_then(|x| x.to_str()) {
7✔
104
                    p.push(s);
2✔
105
                }
106
                let stack = ctx.symbol_stack.borrow();
3✔
107
                for s in stack.iter().take(stack.len() - 1) {
5✔
108
                    p.push(s);
2✔
109
                }
110
                p.push(module.ident.to_string());
4✔
111
                if !p.exists() {
2✔
112
                    p.set_extension("rs");
1✔
113
                }
114
                ctx.ignore_mods.borrow_mut().insert(p);
2✔
115
            }
116
        }
117
    }
118

119
    fn visit_fn(&mut self, func: &ItemFn, ctx: &Context, force_cover: bool) {
509✔
120
        let _guard = ctx.push_to_symbol_stack(func.sig.ident.to_string());
2,545✔
121
        {
122
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
3,054✔
123
            let span = func.span();
2,036✔
124
            analysis.functions.insert(
1,527✔
125
                ctx.get_qualified_name(),
1,527✔
126
                (func.sig.span().start().line, span.end().line),
1,018✔
127
            );
128
        }
129
        let mut test_func = false;
1,018✔
130
        let mut ignored_attr = false;
1,018✔
131
        let mut is_inline = false;
1,018✔
132
        let mut ignore_span = false;
1,018✔
133
        let is_generic = is_sig_generic(&func.sig);
1,527✔
134
        for attr in &func.attrs {
976✔
135
            let id = attr.path();
1,401✔
136
            if id.is_ident("test") || id.segments.last().is_some_and(|seg| seg.ident == "test") {
3,013✔
137
                test_func = true;
187✔
138
            } else if id.is_ident("derive") {
1,027✔
139
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
×
140
                analysis.ignore_span(attr.span());
×
141
            } else if id.is_ident("inline") {
841✔
142
                is_inline = true;
1✔
143
            } else if id.is_ident("ignore") {
838✔
144
                ignored_attr = true;
×
145
            } else if check_cfg_attr(&attr.meta) {
558✔
146
                ignore_span = true;
15✔
147
                break;
15✔
148
            }
149
        }
150
        if ignore_span
509✔
151
            || (test_func && !ctx.config.include_tests())
677✔
152
            || (ignored_attr && !ctx.config.run_ignored)
463✔
153
        {
154
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
276✔
155
            analysis.ignore_tokens(func);
92✔
156
        } else {
157
            if is_inline || is_generic || force_cover {
1,398✔
158
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
120✔
159
                // We need to force cover!
160
                analysis.cover_span(func.block.span(), Some(ctx.file_contents));
80✔
161
            }
162
            if self
926✔
163
                .process_statements(&func.block.stmts, ctx)
926✔
164
                .is_unreachable()
165
            {
166
                // if the whole body of the function is unreachable, that means the function itself
167
                // cannot be called, so is unreachable as a whole
168
                let analysis = self.get_line_analysis(ctx.file.to_path_buf());
25✔
169
                analysis.ignore_tokens(func);
15✔
170
                return;
5✔
171
            }
172
            self.visit_generics(&func.sig.generics, ctx);
1,832✔
173
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
2,290✔
174
            let line_number = func.sig.fn_token.span().start().line;
916✔
175
            let mut start_line = line_number;
916✔
176
            for attr in &func.attrs {
1,268✔
177
                start_line = start_line.min(attr.span().start().line);
1,215✔
178
            }
179
            if start_line < line_number {
679✔
180
                analysis.add_to_ignore(start_line..line_number);
663✔
181
            }
182
            analysis.ignore.remove(&Lines::Line(line_number));
1,374✔
183
            // Ignore multiple lines of fn decl
184
            let decl_start = func.sig.fn_token.span().start().line + 1;
916✔
185
            let stmts_start = func.block.span().start().line;
916✔
186
            let lines = decl_start..=stmts_start;
916✔
187
            analysis.add_to_ignore(lines);
1,374✔
188
        }
189
    }
190

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

230
    fn visit_impl(&mut self, impl_blk: &ItemImpl, ctx: &Context) {
19✔
231
        let self_ty_name = impl_blk
57✔
232
            .self_ty
38✔
233
            .to_token_stream()
234
            .to_string()
235
            .replace(' ', "");
236
        let _guard = match &impl_blk.trait_ {
38✔
237
            Some((_, path, _)) => {
6✔
238
                let trait_name = path
12✔
239
                    .segments
6✔
240
                    .last()
241
                    .map(|x| x.ident.to_string())
18✔
242
                    .unwrap_or_else(|| path.to_token_stream().to_string());
6✔
243
                let name = format!("<impl {} for {}>", trait_name, self_ty_name);
12✔
244
                ctx.push_to_symbol_stack(name)
18✔
245
            }
246
            None => ctx.push_to_symbol_stack(self_ty_name),
39✔
247
        };
248
        let check_cover = self.check_attr_list(&impl_blk.attrs, ctx);
95✔
249
        if check_cover {
19✔
250
            for item in &impl_blk.items {
37✔
251
                match *item {
19✔
252
                    ImplItem::Fn(ref i) => {
36✔
253
                        let item = i.clone();
72✔
254
                        let item_fn = ItemFn {
255
                            attrs: item.attrs,
54✔
256
                            vis: item.vis,
54✔
257
                            sig: item.sig,
54✔
258
                            block: Box::new(item.block),
36✔
259
                        };
260

261
                        // If the impl is on a generic, we need to force cover
262
                        let force_cover = !impl_blk.generics.params.is_empty();
54✔
263

264
                        self.visit_fn(&item_fn, ctx, force_cover);
72✔
265
                    }
266
                    ImplItem::Type(_) => {
1✔
267
                        let analysis = self.get_line_analysis(ctx.file.to_path_buf());
6✔
268
                        analysis.ignore_span(item.span());
3✔
269
                    }
270
                    _ => {}
×
271
                }
272
            }
273
            self.visit_generics(&impl_blk.generics, ctx);
72✔
274
        } else {
275
            let analysis = self.get_line_analysis(ctx.file.to_path_buf());
6✔
276
            analysis.ignore_tokens(impl_blk);
2✔
277
        }
278
    }
279
}
280

281
fn has_generic_arg<'a>(args: impl Iterator<Item = &'a FnArg>) -> bool {
501✔
282
    for arg in args {
690✔
283
        if let FnArg::Typed(pat) = arg {
372✔
284
            if matches!(*pat.ty, Type::ImplTrait(_)) {
365✔
285
                return true;
1✔
286
            }
287
        }
288
    }
289
    false
500✔
290
}
291

292
fn is_sig_generic(sig: &Signature) -> bool {
509✔
293
    !sig.generics.params.is_empty() || has_generic_arg(sig.inputs.iter())
2,012✔
294
}
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