use super::policy::Literal;
use super::position::PositionIndex;
use super::{Found, fallback, format, json, render, source};
pub(crate) fn locate(text: &str, format_key: &str, values: &[Literal]) -> Vec<Found> {
let index = PositionIndex::new(text);
let key = format::canonical(format_key);
let spans: Option<Vec<(Literal, usize)>> = match key {
"json" => Some(json::extract_spanned(text)),
"unknown" => Some(fallback::spanned(text)),
_ if format::is_source(key) => Some(source::spanned(text, key)),
_ => None,
};
if let Some(spans) = spans {
return spans
.into_iter()
.map(|(literal, offset)| Found {
value: render::js_number(literal.value),
notation: literal.notation,
position: Some(index.at(offset)),
})
.collect();
}
let runs = fallback::spanned(text);
let mut cursor = 0;
values
.iter()
.map(|literal| {
let found = runs[cursor..]
.iter()
.position(|(candidate, _)| candidate.value.to_bits() == literal.value.to_bits());
let position = found.map(|offset| {
cursor += offset + 1;
index.at(runs[cursor - 1].1)
});
Found {
value: render::js_number(literal.value),
notation: literal.notation,
position,
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn at(text: &str, format: &str, values: &[f64]) -> Vec<Option<(usize, usize)>> {
let literals: Vec<Literal> = values.iter().copied().map(Literal::decimal).collect();
locate(text, format, &literals)
.into_iter()
.map(|found| found.position.map(|p| (p.line, p.column)))
.collect()
}
#[test]
fn a_number_is_found_where_it_appears() {
assert_eq!(at("port = 8080\n", "toml", &[8080.0]), [Some((1, 8))]);
}
#[test]
fn repeated_values_take_successive_runs() {
let text = "a = 5\nb = 9\nc = 5\n";
assert_eq!(
at(text, "toml", &[5.0, 9.0, 5.0]),
[Some((1, 5)), Some((2, 5)), Some((3, 5))]
);
}
#[test]
fn a_value_the_scanner_cannot_see_gets_no_position() {
assert_eq!(at("a = 0x1A\n", "toml", &[26.0]), [None]);
}
#[test]
fn a_value_written_differently_from_how_it_prints_still_matches() {
assert_eq!(at("A=+7\n", "env", &[7.0]), [Some((1, 3))]);
assert_eq!(at("a = 1.5e3\n", "ini", &[1500.0]), [Some((1, 5))]);
}
#[test]
fn a_miss_does_not_move_the_cursor() {
let text = "a = 1\nb = 3\n";
assert_eq!(
at(text, "toml", &[1.0, 2.0, 3.0]),
[Some((1, 5)), None, Some((2, 5))]
);
}
#[test]
fn json_is_placed_by_its_parser() {
assert_eq!(at(r#"{"a":1e21}"#, "json", &[1e21]), [Some((1, 6))]);
}
#[test]
fn the_fallback_places_its_own_runs() {
assert_eq!(at("rate 0.0825 here", "unknown", &[0.0825]), [Some((1, 6))]);
}
#[test]
fn a_source_language_places_a_literal_the_text_scanner_cannot_see() {
let found = locate("let mask = 0xFF;\n", "rust", &[Literal::decimal(255.0)]);
assert_eq!(found[0].value, "255");
assert_eq!(found[0].position.map(|p| (p.line, p.column)), Some((1, 12)));
}
#[test]
fn a_run_in_a_key_can_take_the_match() {
assert_eq!(at("k26 = 0x1A\n", "toml", &[26.0]), [Some((1, 2))]);
}
#[test]
fn nothing_to_locate_is_nothing_returned() {
assert!(locate("anything", "toml", &[]).is_empty());
}
#[test]
fn a_column_after_a_multibyte_character_is_counted_in_utf16() {
assert_eq!(at("café = 42\n", "ini", &[42.0]), [Some((1, 8))]);
}
}