#![allow(dead_code)]
use std::path::{Path, PathBuf};
pub(crate) fn rust_sources(root: &Path) -> Vec<(String, PathBuf)> {
let mut found = Vec::new();
let mut pending = vec![root.to_path_buf()];
while let Some(directory) = pending.pop() {
let entries = std::fs::read_dir(&directory).expect("library src should be readable");
for entry in entries {
let path = entry.expect("directory entry").path();
if path.is_dir() {
pending.push(path);
} else if path.extension().is_some_and(|extension| extension == "rs") {
let relative = path
.strip_prefix(root)
.expect("walked paths live under src")
.to_string_lossy()
.replace('\\', "/");
found.push((relative, path));
}
}
}
found.sort();
found
}
pub(crate) fn test_gated_spans(source: &str, code: &str) -> Vec<(usize, usize)> {
let bytes = code.as_bytes();
if let Some(start) = code.find("#![")
&& code[..start].trim().is_empty()
&& balanced_end(bytes, start + 2, b'[', b']')
.is_some_and(|end| is_test_gate(&source[start..end]))
{
return vec![(0, code.len())];
}
let mut spans = Vec::new();
let mut cursor = 0;
while let Some(offset) = code[cursor..].find("#[") {
let start = cursor + offset;
let Some(attribute_end) = balanced_end(bytes, start + 1, b'[', b']') else {
break;
};
if is_test_gate(&source[start..attribute_end]) {
let end = gated_item_end(bytes, attribute_end);
spans.push((start, end));
cursor = end;
} else {
cursor = attribute_end;
}
}
spans
}
pub(crate) fn is_test_gate(attribute: &str) -> bool {
let normalized = normalize(attribute);
(normalized.starts_with("#[cfg(") || normalized.starts_with("#![cfg("))
&& !normalized.contains("not(")
&& contains_word(&normalized, "test")
}
pub(crate) fn contains_word(haystack: &str, word: &str) -> bool {
haystack.match_indices(word).any(|(index, _)| {
let before = haystack[..index].chars().next_back();
let after = haystack[index + word.len()..].chars().next();
!before.is_some_and(|character| character.is_alphanumeric() || character == '_')
&& !after.is_some_and(|character| character.is_alphanumeric() || character == '_')
})
}
pub(crate) fn gated_item_end(bytes: &[u8], from: usize) -> usize {
let mut braces = 0usize;
let mut nesting = 0usize;
let mut body_started = false;
for (index, &byte) in bytes.iter().enumerate().skip(from) {
match byte {
b'{' => {
braces += 1;
body_started = true;
}
b'}' => {
braces = braces.saturating_sub(1);
if body_started && braces == 0 {
return index + 1;
}
}
b'(' | b'[' => nesting += 1,
b')' | b']' => nesting = nesting.saturating_sub(1),
b';' if braces == 0 && nesting == 0 => return index + 1,
_ => {}
}
}
bytes.len()
}
pub(crate) fn balanced_end(bytes: &[u8], open: usize, opener: u8, closer: u8) -> Option<usize> {
let mut depth = 0usize;
for (index, &byte) in bytes.iter().enumerate().skip(open) {
if byte == opener {
depth += 1;
} else if byte == closer {
depth = depth.checked_sub(1)?;
if depth == 0 {
return Some(index + 1);
}
}
}
None
}
pub(crate) fn blanked(source: &str, blank_literals: bool) -> String {
let bytes = source.as_bytes();
let mut mask = bytes.to_vec();
let mut index = 0;
while index < bytes.len() {
if bytes[index] == b'/' && bytes.get(index + 1) == Some(&b'/') {
let end = source[index..]
.find('\n')
.map_or(bytes.len(), |at| index + at);
blank(&mut mask, index, end);
index = end;
continue;
}
if bytes[index] == b'/' && bytes.get(index + 1) == Some(&b'*') {
let end = block_comment_end(bytes, index + 2);
blank(&mut mask, index, end);
index = end;
continue;
}
if let Some(end) = literal_end(bytes, index) {
if blank_literals {
blank(&mut mask, index, end);
}
index = end;
continue;
}
index += 1;
}
String::from_utf8(mask).expect("blanking replaces whole ASCII-delimited regions")
}
pub(crate) fn blank(mask: &mut [u8], from: usize, to: usize) {
let to = to.min(mask.len());
for byte in &mut mask[from..to] {
if *byte != b'\n' {
*byte = b' ';
}
}
}
pub(crate) fn block_comment_end(bytes: &[u8], from: usize) -> usize {
let mut depth = 1usize;
let mut index = from;
while index < bytes.len() {
if bytes[index] == b'/' && bytes.get(index + 1) == Some(&b'*') {
depth += 1;
index += 2;
} else if bytes[index] == b'*' && bytes.get(index + 1) == Some(&b'/') {
depth -= 1;
index += 2;
if depth == 0 {
return index;
}
} else {
index += 1;
}
}
bytes.len()
}
pub(crate) fn literal_end(bytes: &[u8], index: usize) -> Option<usize> {
let after_identifier = index
.checked_sub(1)
.is_some_and(|before| bytes[before].is_ascii_alphanumeric() || bytes[before] == b'_');
let mut cursor = index;
if !after_identifier {
if bytes.get(cursor) == Some(&b'b') {
cursor += 1;
}
if bytes.get(cursor) == Some(&b'r') {
cursor += 1;
let hashes = hash_run(bytes, cursor);
cursor += hashes;
if bytes.get(cursor) != Some(&b'"') {
return None;
}
return Some(raw_string_end(bytes, cursor + 1, hashes));
}
}
match bytes.get(cursor) {
Some(&b'"') => Some(string_end(bytes, cursor + 1)),
Some(&b'\'') => character_end(bytes, cursor),
_ => None,
}
}
pub(crate) fn string_end(bytes: &[u8], from: usize) -> usize {
let mut index = from;
while index < bytes.len() {
match bytes[index] {
b'\\' => index += 2,
b'"' => return index + 1,
_ => index += 1,
}
}
bytes.len()
}
pub(crate) fn raw_string_end(bytes: &[u8], from: usize, hashes: usize) -> usize {
let mut index = from;
while index < bytes.len() {
if bytes[index] == b'"' && hash_run(bytes, index + 1) >= hashes {
return (index + 1 + hashes).min(bytes.len());
}
index += 1;
}
bytes.len()
}
pub(crate) fn hash_run(bytes: &[u8], from: usize) -> usize {
bytes[from..]
.iter()
.take_while(|byte| **byte == b'#')
.count()
}
pub(crate) fn character_end(bytes: &[u8], quote: usize) -> Option<usize> {
let mut index = quote + 1;
if bytes.get(index) == Some(&b'\\') {
index += 2;
while index < bytes.len() && bytes[index] != b'\'' {
index += 1;
}
return (index < bytes.len()).then_some(index + 1);
}
let leading = *bytes.get(index)?;
index += match leading {
0x00..=0x7f => 1,
0xc0..=0xdf => 2,
0xe0..=0xef => 3,
_ => 4,
};
(bytes.get(index) == Some(&b'\'')).then_some(index + 1)
}
pub(crate) fn normalize(text: &str) -> String {
let mut spaced = String::with_capacity(text.len());
for character in text.chars() {
if character.is_whitespace() {
if !spaced.ends_with(' ') {
spaced.push(' ');
}
} else {
spaced.push(character);
}
}
let mut tightened = String::with_capacity(spaced.len());
for character in spaced.chars() {
match character {
' ' if tightened.ends_with(['(', '[']) || tightened.is_empty() => {}
')' | ']' | ',' | ';' => {
if tightened.ends_with(' ') {
tightened.pop();
}
if matches!(character, ')' | ']') && tightened.ends_with(',') {
tightened.pop();
}
tightened.push(character);
}
_ => tightened.push(character),
}
}
tightened.trim_end().to_owned()
}