mod handlers;
mod normal;
mod state;
#[cfg(test)]
mod tests;
pub use state::{ScanResult, ScanState};
use crate::interpolation::Segment;
pub fn scan_js_source(source: &str) -> (Vec<Segment>, bool) {
let mut scanner = Scanner::new(source);
scanner.scan()
}
pub fn create_validation_source(source: &str) -> String {
let mut scanner = Scanner::new(source);
scanner.create_validation_source()
}
pub(crate) struct Scanner<'a> {
source: &'a str,
pub(super) chars: std::iter::Peekable<std::str::CharIndices<'a>>,
pub(super) state: ScanState,
pub(super) template_depth_stack: Vec<usize>,
pub(super) brace_depth: usize,
pub(super) last_token_allows_regex: bool,
}
impl<'a> Scanner<'a> {
fn new(source: &'a str) -> Self {
Self {
source,
chars: source.char_indices().peekable(),
state: ScanState::Normal,
template_depth_stack: Vec::new(),
brace_depth: 0,
last_token_allows_regex: true,
}
}
fn scan(&mut self) -> (Vec<Segment>, bool) {
let mut segments = Vec::new();
let mut has_interpolation = false;
let mut current_literal = String::new();
while let Some((idx, c)) = self.chars.next() {
if let Some(result) = self.process_char(c, idx, &mut current_literal) {
match result {
ScanResult::ValueInterpolation(tokens) => {
if !current_literal.is_empty() {
segments.push(Segment::Literal(std::mem::take(&mut current_literal)));
}
segments.push(Segment::ValueInterpolation(tokens));
has_interpolation = true;
}
ScanResult::RawInterpolation(tokens) => {
if !current_literal.is_empty() {
segments.push(Segment::Literal(std::mem::take(&mut current_literal)));
}
segments.push(Segment::RawInterpolation(tokens));
has_interpolation = true;
}
ScanResult::Continue => {}
}
}
}
if !current_literal.is_empty() {
segments.push(Segment::Literal(current_literal));
}
(segments, has_interpolation)
}
fn create_validation_source(&mut self) -> String {
let mut result = String::new();
while let Some((idx, c)) = self.chars.next() {
if let Some(scan_result) = self.process_char_for_validation(c, idx, &mut result) {
match scan_result {
ScanResult::ValueInterpolation(_) | ScanResult::RawInterpolation(_) => {
result.push_str("null");
}
ScanResult::Continue => {}
}
}
}
result
}
fn process_char(&mut self, c: char, idx: usize, literal: &mut String) -> Option<ScanResult> {
match self.state {
ScanState::Normal => self.handle_normal(c, idx, literal),
ScanState::DoubleString => {
literal.push(c);
self.handle_double_string(c);
None
}
ScanState::SingleString => {
literal.push(c);
self.handle_single_string(c);
None
}
ScanState::TemplateString => self.handle_template_string(c, idx, literal),
ScanState::TemplateExpr => self.handle_template_expr(c, idx, literal),
ScanState::Regex => {
literal.push(c);
self.handle_regex(c);
None
}
ScanState::RegexCharClass => {
literal.push(c);
self.handle_regex_char_class(c);
None
}
ScanState::LineComment => {
literal.push(c);
self.handle_line_comment(c);
None
}
ScanState::BlockComment => {
literal.push(c);
self.handle_block_comment(c, literal);
None
}
ScanState::EscapeDouble => {
literal.push(c);
self.state = ScanState::DoubleString;
None
}
ScanState::EscapeSingle => {
literal.push(c);
self.state = ScanState::SingleString;
None
}
ScanState::EscapeTemplate => {
literal.push(c);
self.state = ScanState::TemplateString;
None
}
ScanState::EscapeRegex => {
literal.push(c);
self.state = ScanState::Regex;
None
}
ScanState::EscapeRegexCharClass => {
literal.push(c);
self.state = ScanState::RegexCharClass;
None
}
}
}
fn process_char_for_validation(
&mut self,
c: char,
idx: usize,
result: &mut String,
) -> Option<ScanResult> {
match self.state {
ScanState::Normal => self.handle_normal_validation(c, idx, result),
ScanState::DoubleString => {
result.push(c);
self.handle_double_string(c);
None
}
ScanState::SingleString => {
result.push(c);
self.handle_single_string(c);
None
}
ScanState::TemplateString => self.handle_template_string_validation(c, idx, result),
ScanState::TemplateExpr => self.handle_template_expr_validation(c, idx, result),
ScanState::Regex => {
result.push(c);
self.handle_regex(c);
None
}
ScanState::RegexCharClass => {
result.push(c);
self.handle_regex_char_class(c);
None
}
ScanState::LineComment => {
result.push(c);
self.handle_line_comment(c);
None
}
ScanState::BlockComment => {
result.push(c);
self.handle_block_comment(c, result);
None
}
ScanState::EscapeDouble => {
result.push(c);
self.state = ScanState::DoubleString;
None
}
ScanState::EscapeSingle => {
result.push(c);
self.state = ScanState::SingleString;
None
}
ScanState::EscapeTemplate => {
result.push(c);
self.state = ScanState::TemplateString;
None
}
ScanState::EscapeRegex => {
result.push(c);
self.state = ScanState::Regex;
None
}
ScanState::EscapeRegexCharClass => {
result.push(c);
self.state = ScanState::RegexCharClass;
None
}
}
}
}