Skip to main content

_diffctx/edges/structural/
containment.rs

1use std::path::Path;
2
3use rustc_hash::FxHashMap;
4
5use crate::config::weights::EDGE_WEIGHTS;
6use crate::types::Fragment;
7
8use super::super::EdgeDict;
9use super::super::base::{EdgeBuilder, add_edge};
10
11pub struct ContainmentEdgeBuilder;
12
13impl EdgeBuilder for ContainmentEdgeBuilder {
14    fn build(&self, fragments: &[Fragment], _repo_root: Option<&Path>) -> EdgeDict {
15        let weight = EDGE_WEIGHTS["containment"].forward;
16        let reverse_factor = EDGE_WEIGHTS["containment"].reverse_factor;
17
18        let mut by_path: FxHashMap<&str, Vec<&Fragment>> = FxHashMap::default();
19        for f in fragments {
20            by_path.entry(f.path()).or_default().push(f);
21        }
22
23        let mut edges: EdgeDict = FxHashMap::default();
24
25        let reps = super::super::base::file_representatives(fragments);
26
27        for (path, frags) in &by_path {
28            if frags.len() < 2 {
29                continue;
30            }
31            let mut sorted = frags.clone();
32            sorted.sort_by(|a, b| {
33                a.start_line()
34                    .cmp(&b.start_line())
35                    .then(b.end_line().cmp(&a.end_line()))
36            });
37
38            let mut stack: Vec<&Fragment> = Vec::new();
39
40            for f in &sorted {
41                while let Some(top) = stack.last() {
42                    if f.start_line() > top.end_line() {
43                        stack.pop();
44                    } else {
45                        break;
46                    }
47                }
48
49                if let Some(parent) = stack.last() {
50                    if parent.start_line() <= f.start_line()
51                        && f.end_line() <= parent.end_line()
52                        && parent.id != f.id
53                    {
54                        add_edge(&mut edges, &f.id, &parent.id, weight, reverse_factor);
55                    }
56                }
57
58                stack.push(f);
59            }
60
61            // The nesting pass only connects a fragment to what encloses it,
62            // so a file's top-level fragments were mutually unreachable. The
63            // star through the file representative gives every file an
64            // intra-file spine — linear in fragment count — which is what
65            // lets file-level relations (includes, path references, pairing)
66            // link representatives instead of full fragment cross products.
67            if std::env::var_os("DIFFCTX_FILE_STAR").is_some() {
68                if let Some(rep) = reps.get(*path) {
69                    for f in &sorted {
70                        if f.id != *rep {
71                            add_edge(&mut edges, &f.id, rep, weight, reverse_factor);
72                        }
73                    }
74                }
75            }
76        }
77
78        edges
79    }
80}