1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//! Triple-slash reference directive validation
//!
//! Validates /// <reference path="..." /> directives in TypeScript files.
use std::path::Path;
/// Extract triple-slash reference paths from source text
///
/// Returns a vector of (path, `line_number`) tuples for each reference directive found.
pub fn extract_reference_paths(source: &str) -> Vec<(String, usize)> {
let mut references = Vec::new();
for (line_num, line) in source.lines().enumerate() {
let trimmed = line.trim();
// Check if line starts with ///
if !trimmed.starts_with("///") {
continue;
}
// Check if it contains <reference path=
if !trimmed.contains("<reference") || !trimmed.contains("path=") {
continue;
}
// Extract the path value between quotes
if let Some(path) = extract_quoted_path(trimmed) {
references.push((path, line_num));
}
}
references
}
/// Extract `/// <reference types="..." />` directives from source text.
///
/// Returns a vector of (`type_name`, `resolution_mode`, `line_number`) tuples.
/// `resolution_mode` is `Some("import")` or `Some("require")` if specified.
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
}
/// Extract `/// <amd-module name="..." />` directives from source text.
///
/// Returns a vector of (`module_name`, `line_number`) tuples for each amd-module directive found.
/// Used to detect multiple AMD module name assignments (TS2458).
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();
// Check if line starts with ///
if !trimmed.starts_with("///") {
continue;
}
// Check if it contains <amd-module name=
if !trimmed.contains("<amd-module") || !trimmed.contains("name=") {
continue;
}
// Extract the name value between quotes
if let Some(name) = extract_quoted_attr(trimmed, "name") {
amd_modules.push((name, line_num));
}
}
amd_modules
}
/// Extract the value of a named attribute from a reference directive line.
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())
}
/// Extract path from a reference directive line
fn extract_quoted_path(line: &str) -> Option<String> {
extract_quoted_attr(line, "path")
}
/// Check if a referenced file exists relative to the source file
///
/// Returns true if the file exists, false otherwise.
/// Follows TypeScript's resolution strategy:
/// 1. Try exact path first
/// 2. If no extension or not found, try .ts, .tsx, .d.ts extensions
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);
// Try exact path first
if base_path.exists() {
return true;
}
// If the path already has an extension, don't try others
if reference_path.contains('.') {
return false;
}
// Try TypeScript extensions in order: .ts, .tsx, .d.ts
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;