use super::{
ArmPath, BuildVariant, ContentNorm, FileContext, FileFeatures, IrNode, Resolution,
ResolvedTypes, Shape, SyntaxIrFile, TestCodeEvidence, Token, Unit, UnitEvidence, UnitKind,
UnitView, boilerplate, stable_id, test_code, verify,
};
pub(super) fn flatten_units(
files: &[SyntaxIrFile],
variant: &BuildVariant,
literals: crate::engine::LiteralNorm,
resolved: &ResolvedTypes,
) -> (Vec<Unit>, Vec<usize>) {
let mut units = Vec::new();
let mut offsets = Vec::with_capacity(files.len());
let mut next_conditional = 0u32;
for (file_index, file) in files.iter().enumerate() {
offsets.push(units.len());
let mut walk = UnitWalk {
file: file_index,
source: file,
context: FileContext {
frontend_version: file.frontend_version,
language: file.language,
},
variant,
literals,
resolution: resolved.names_for(file_index),
local: 0,
next_conditional: &mut next_conditional,
units: &mut units,
};
for root in &file.roots {
walk.visit(root, file.test_module, &ArmPath::default());
}
}
(units, offsets)
}
struct UnitWalk<'a> {
file: usize,
source: &'a SyntaxIrFile,
context: FileContext<'a>,
variant: &'a BuildVariant,
literals: crate::engine::LiteralNorm,
resolution: Option<&'a Resolution>,
local: usize,
next_conditional: &'a mut u32,
units: &'a mut Vec<Unit>,
}
impl UnitWalk<'_> {
fn visit(&mut self, node: &IrNode, test_code: bool, arms: &ArmPath) {
let end = node.token_end.min(self.source.tokens.len());
let start = node.token_start.min(end);
let tokens = &self.source.tokens[start..end];
let test_code = test_code || test_code::is_marked(self.source.language, tokens);
let test_code_evidence = test_code.then_some(TestCodeEvidence::Marker);
let descended = arms.descend(node, self.next_conditional);
let arms = descended.as_ref().unwrap_or(arms);
if let Some(kind) = unit_kind(&node.shape) {
let fingerprint =
stable_id::unit_fingerprint(self.variant, &self.context, tokens, ContentNorm::Raw);
let content = stable_id::fragment_fingerprint(
self.variant,
&self.context,
"unit",
tokens,
ContentNorm::Raw,
);
let normalized_content = stable_id::resolved_fragment_fingerprint(
self.variant,
&self.context,
"unit",
tokens,
ContentNorm::ResolvedNormalized(self.literals),
self.resolution,
);
self.units.push(Unit {
file: self.file,
local: self.local,
kind,
statements: verify::statement_sequence(node, &self.source.tokens),
fingerprint,
content,
normalized_content,
range: node.range,
lines: line_range(tokens),
tokens: (start, end),
name: node.name.clone(),
boilerplate: boilerplate::classify(node),
test_code,
test_code_evidence,
arms: arms.clone(),
});
self.local += 1;
}
for child in &node.children {
self.visit(child, test_code, arms);
}
}
}
const fn unit_kind(shape: &Shape) -> Option<UnitKind> {
match *shape {
Shape::Function => Some(UnitKind::Function),
Shape::Method => Some(UnitKind::Method),
Shape::Closure => Some(UnitKind::Closure),
_ => None,
}
}
pub(super) fn line_range(tokens: &[Token]) -> (u32, u32) {
let (Some(first), Some(last)) = (tokens.first(), tokens.last()) else {
return (0, 0);
};
let newlines = u32::try_from(last.text.matches('\n').count()).unwrap_or(0);
(
first.span.start_line,
last.span.start_line.saturating_add(newlines),
)
}
pub(super) fn view<'a>(
index: usize,
units: &'a [Unit],
files: &'a [SyntaxIrFile],
feature_files: &'a [FileFeatures],
evidence: &'a UnitEvidence,
) -> UnitView<'a> {
let unit = &units[index];
UnitView {
statements: &unit.statements,
tokens: &files[unit.file].tokens,
content: unit.content,
features: &feature_files[unit.file].units[unit.local],
types: evidence.types.get(index).and_then(Option::as_ref),
apis: evidence.apis.get(index).and_then(Option::as_ref),
}
}