use crate::facts::{
Declaration, DeclarationKind, Facts, Import, ImportBinding, Reference, ReferenceKind, Span,
};
use crate::syntax::Language;
use crate::token::{Mode, Token, TokenKind, Tokenizer};
use std::collections::BTreeMap;
#[must_use]
pub fn extract(source: &str, language: Language) -> Facts {
let tokens = Tokenizer::new(source, language)
.mode(Mode::Lite)
.collect::<Vec<_>>();
Extractor {
source,
tokens: &tokens,
language,
facts: Facts::default(),
scopes: Vec::new(),
import_bindings: BTreeMap::new(),
depth: 0,
paren_depth: 0,
bracket_depth: 0,
}
.run()
}
struct Scope {
name: String,
depth: Option<i32>,
declaration: Option<usize>,
member_body: bool,
fields: bool,
paren_depth: i32,
bracket_depth: i32,
}
struct Extractor<'source, 'tokens> {
source: &'source str,
tokens: &'tokens [Token],
language: Language,
facts: Facts,
scopes: Vec<Scope>,
import_bindings: BTreeMap<String, (String, bool, String)>,
depth: i32,
paren_depth: i32,
bracket_depth: i32,
}
mod calls;
mod declarations;
mod modules;
mod traversal;
mod types;
fn is_method(name: &str) -> bool {
matches!(
name,
"GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | "ALL"
)
}
fn template_interpolation_ranges(template: &str, language: Language) -> Vec<(usize, usize)> {
let bytes = template.as_bytes();
let mut ranges = Vec::new();
let mut cursor = usize::from(bytes.first() == Some(&b'`'));
while cursor + 1 < bytes.len() {
if bytes[cursor] == b'\\' {
cursor = (cursor + 2).min(bytes.len());
continue;
}
if bytes[cursor] == b'`' {
break;
}
if bytes[cursor] != b'$' || bytes[cursor + 1] != b'{' {
cursor += 1;
continue;
}
let expression_start = cursor + 2;
let tail = &template[expression_start..];
let tokens = Tokenizer::new(tail, language)
.mode(Mode::Lite)
.collect::<Vec<_>>();
let mut depth = 1_i32;
let mut expression_end = None;
for token in tokens {
if token.kind != TokenKind::Punctuation {
continue;
}
match token.text(tail) {
"{" => depth += 1,
"}" => {
depth -= 1;
if depth == 0 {
expression_end = Some(expression_start + token.start);
cursor = expression_start + token.end;
break;
}
}
_ => {}
}
}
let Some(expression_end) = expression_end else {
break;
};
ranges.push((expression_start, expression_end));
}
ranges
}
fn position_at(source: &str, offset: usize) -> (u32, u32) {
let prefix = source.get(..offset).unwrap_or(source);
let line = u32::try_from(prefix.bytes().filter(|byte| *byte == b'\n').count())
.unwrap_or(u32::MAX)
.saturating_add(1);
let column = u32::try_from(
prefix
.rsplit_once('\n')
.map_or(prefix, |(_, suffix)| suffix)
.chars()
.count(),
)
.unwrap_or(u32::MAX)
.saturating_add(1);
(line, column)
}
#[cfg(test)]
mod tests;