dry4rust/
sub_unit_extractor.rs1use crate::code_unit::CodeUnitKind;
7use crate::extractor::SubUnit;
8use crate::node::NodeKind;
9use crate::node::NormalizedNode;
10use crate::node::count_nodes;
11use crate::node::reindex_placeholders;
12
13pub struct SubUnitExtractor {
21 min_node_count: usize,
22}
23
24impl SubUnitExtractor {
25 #[must_use]
26 pub const fn new(min_node_count: usize) -> Self {
27 Self { min_node_count }
28 }
29
30 #[must_use]
35 pub fn extract(&self, node: &NormalizedNode) -> Vec<SubUnit> {
36 let mut found = self.own_units(node);
37 for child in &node.children {
38 found.extend(self.extract(child));
39 }
40 found
41 }
42
43 fn own_units(&self, node: &NormalizedNode) -> Vec<SubUnit> {
44 match &node.kind {
45 NodeKind::If => self.if_units(node),
46 NodeKind::Match => self.match_units(node),
47 _ => self.bodied_unit(node),
48 }
49 }
50
51 fn if_units(&self, node: &NormalizedNode) -> Vec<SubUnit> {
55 let then_branch = node
56 .children
57 .get(1)
58 .and_then(|n| self.made(n, CodeUnitKind::IfBranch, "if-then branch"));
59 let else_branch = node
60 .children
61 .get(2)
62 .filter(|n| !n.is_none())
63 .and_then(|n| self.made(n, CodeUnitKind::IfBranch, "if-else branch"));
64 then_branch.into_iter().chain(else_branch).collect()
65 }
66
67 fn match_units(&self, node: &NormalizedNode) -> Vec<SubUnit> {
71 node.children
72 .iter()
73 .skip(1)
74 .enumerate()
75 .filter_map(|(i, arm)| {
76 let body = arm.children.get(2)?;
77 self.made(
78 body,
79 CodeUnitKind::MatchArm,
80 &format!("match arm {}", i + 1),
81 )
82 })
83 .collect()
84 }
85
86 fn bodied_unit(&self, node: &NormalizedNode) -> Vec<SubUnit> {
87 let Some(target) = Self::body_target(&node.kind) else {
88 return Vec::new();
89 };
90 let (index, kind, description) = target;
91 node.children
92 .get(index)
93 .and_then(|body| self.made(body, kind, description))
94 .into_iter()
95 .collect()
96 }
97
98 const fn body_target(kind: &NodeKind) -> Option<(usize, CodeUnitKind, &'static str)> {
101 match kind {
102 NodeKind::Loop => Some((0, CodeUnitKind::LoopBody, "loop body")),
103 NodeKind::While => Some((1, CodeUnitKind::LoopBody, "while body")),
104 NodeKind::ForLoop => Some((2, CodeUnitKind::LoopBody, "for body")),
105 NodeKind::Closure => Some((0, CodeUnitKind::Block, "closure body")),
106 _ => None,
107 }
108 }
109
110 fn made(
113 &self,
114 node: &NormalizedNode,
115 kind: CodeUnitKind,
116 description: &str,
117 ) -> Option<SubUnit> {
118 let reindexed = reindex_placeholders(node);
119 let node_count = count_nodes(&reindexed);
120 if node_count < self.min_node_count {
121 return None;
122 }
123 Some(SubUnit {
124 kind,
125 node: reindexed,
126 node_count,
127 description: description.to_string(),
128 })
129 }
130}