use std::path::Path;
pub fn extract_reference_paths(source: &str) -> Vec<(String, usize, usize)> {
let mut references = Vec::new();
for (line_num, line) in source.lines().enumerate() {
let trimmed = line.trim();
if !trimmed.starts_with("///") {
continue;
}
if !trimmed.contains("<reference") || !trimmed.contains("path=") {
continue;
}
if let Some((path, offset)) = extract_quoted_path_with_offset(line) {
references.push((path, line_num, offset));
}
}
references
}
pub fn extract_reference_types(source: &str) -> Vec<(String, Option<String>, usize)> {
let mut references = Vec::new();
for (line_num, line) in source.lines().enumerate() {
let trimmed = line.trim();
if !trimmed.starts_with("///") {
continue;
}
if !trimmed.contains("<reference") || !trimmed.contains("types=") {
continue;
}
if let Some(name) = extract_quoted_attr(trimmed, "types") {
let resolution_mode = extract_quoted_attr(trimmed, "resolution-mode");
references.push((name, resolution_mode, line_num));
}
}
references
}
pub fn extract_amd_module_names(source: &str) -> Vec<(String, usize)> {
let mut amd_modules = Vec::new();
for (line_num, line) in source.lines().enumerate() {
let trimmed = line.trim();
if !trimmed.starts_with("///") {
continue;
}
if !trimmed.contains("<amd-module") || !trimmed.contains("name=") {
continue;
}
if let Some(name) = extract_quoted_attr(trimmed, "name") {
amd_modules.push((name, line_num));
}
}
amd_modules
}
fn extract_quoted_attr(line: &str, attr: &str) -> Option<String> {
let idx = line.find(attr)?;
let after_attr = &line[idx + attr.len()..];
let eq_idx = after_attr.find('=')?;
let after_equals = &after_attr[eq_idx + 1..];
let first_char = after_equals.trim_start().chars().next()?;
if first_char != '"' && first_char != '\'' {
return None;
}
let quote_char = first_char;
let after_open_quote = &after_equals[after_equals.find(quote_char)? + 1..];
let end_pos = after_open_quote.find(quote_char)?;
Some(after_open_quote[..end_pos].to_string())
}
fn extract_quoted_path_with_offset(line: &str) -> Option<(String, usize)> {
extract_quoted_attr_with_offset(line, "path")
}
fn extract_quoted_attr_with_offset(line: &str, attr: &str) -> Option<(String, usize)> {
let attr_idx = line.find(attr)?;
let after_attr = &line[attr_idx + attr.len()..];
let eq_idx = after_attr.find('=')?;
let after_equals = &after_attr[eq_idx + 1..];
let trimmed = after_equals.trim_start();
let first_char = trimmed.chars().next()?;
if first_char != '"' && first_char != '\'' {
return None;
}
let value_offset =
attr_idx + attr.len() + eq_idx + 1 + (after_equals.len() - trimmed.len()) + 1;
let after_open_quote = &trimmed[1..];
let end_pos = after_open_quote.find(first_char)?;
Some((after_open_quote[..end_pos].to_string(), value_offset))
}
pub fn validate_reference_path(source_file: &Path, reference_path: &str) -> bool {
if let Some(parent) = source_file.parent() {
let base_path = parent.join(reference_path);
if base_path.exists() {
return true;
}
if reference_path.contains('.') {
return false;
}
let extensions = [".ts", ".tsx", ".d.ts"];
for ext in &extensions {
let path_with_ext = parent.join(format!("{reference_path}{ext}"));
if path_with_ext.exists() {
return true;
}
}
false
} else {
false
}
}
#[cfg(test)]
#[path = "../tests/triple_slash_validator.rs"]
mod tests;