mod analyze;
use analyze::analyze_expression_nesting;
mod operators;
pub mod scan;
use scan::{
SpeculativeTypeAngleOpen, keyword_allows_regex_after, skip_block_comment, skip_identifier,
skip_line_comment, skip_number, skip_quoted, skip_regex, skip_template_text,
speculative_arrow_default_paren, speculative_type_angle_open_kind,
starts_valid_identifier_escape,
};
pub use scan::is_expression_trailing_trivia;
pub const MAX_EXPRESSION_NESTING_DEPTH: usize = 31;
const CUMULATIVE_SPECULATIVE_TYPE_ANGLE_MIN_DEPTH: usize = MAX_EXPRESSION_NESTING_DEPTH / 2;
const MAX_NUMERIC_TOKEN_BYTES: usize = 4096;
const MAX_ARROW_DEFAULT_ANGLE_PRODUCT: usize =
8 * MAX_EXPRESSION_NESTING_DEPTH * MAX_EXPRESSION_NESTING_DEPTH;
struct ExpressionNestingAnalysis {
max_depth: usize,
delimiters_balanced: bool,
cumulative_speculative_type_angle_depth: usize,
excessive_speculative_type_angle_opens: bool,
excessive_arrow_default_speculation: bool,
oversized_numeric_token: bool,
}
fn flush_speculative_type_angle_segment(cumulative: &mut usize, segment_depth: &mut usize) {
if *segment_depth >= CUMULATIVE_SPECULATIVE_TYPE_ANGLE_MIN_DEPTH {
*cumulative += *segment_depth;
}
*segment_depth = 0;
}
fn arrow_default_angle_product_exceeds(frames: &[(usize, usize)]) -> bool {
if frames.iter().filter(|&&(_, count)| count >= 2).count() < 2 {
return false;
}
let mut product = 1usize;
for &(_, count) in frames {
product = product.saturating_mul(count.max(1));
if product > MAX_ARROW_DEFAULT_ANGLE_PRODUCT {
return true;
}
}
false
}
pub fn expression_nesting_depth(content: &str) -> usize {
analyze_expression_nesting(content).max_depth
}
pub fn expression_has_balanced_delimiters(content: &str) -> bool {
analyze_expression_nesting(content).delimiters_balanced
}
pub fn expression_is_safe_to_parse(content: &str) -> bool {
if is_short_plain_identifier(content) {
return true;
}
let analysis = analyze_expression_nesting(content);
analysis.delimiters_balanced
&& analysis.max_depth <= MAX_EXPRESSION_NESTING_DEPTH
&& analysis.cumulative_speculative_type_angle_depth <= MAX_EXPRESSION_NESTING_DEPTH
&& !analysis.excessive_speculative_type_angle_opens
&& !analysis.excessive_arrow_default_speculation
&& !analysis.oversized_numeric_token
&& !operators::has_excessive_prefix_operator_run(content)
}
#[inline]
pub fn expression_exceeds_max_depth(content: &str) -> bool {
expression_nesting_depth(content) > MAX_EXPRESSION_NESTING_DEPTH
}
fn is_short_plain_identifier(content: &str) -> bool {
let bytes = content.as_bytes();
if bytes.len() > 16 {
return false;
}
let [first, rest @ ..] = bytes else {
return false;
};
is_ascii_ident_start(*first) && rest.iter().copied().all(is_ascii_ident_continue)
}
fn is_ascii_ident_start(byte: u8) -> bool {
matches!(byte, b'a'..=b'z' | b'A'..=b'Z' | b'_' | b'$')
}
fn is_ascii_ident_continue(byte: u8) -> bool {
matches!(byte, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | b'$')
}