use js::token::{Keyword, Operation, ReservedChar, Token, Tokens};
pub(crate) struct VariableNameGenerator<'a> {
letter: char,
lower: Option<Box<VariableNameGenerator<'a>>>,
prepend: Option<&'a str>,
}
impl<'a> VariableNameGenerator<'a> {
pub(crate) fn new(prepend: Option<&'a str>, nb_letter: usize) -> VariableNameGenerator<'a> {
if nb_letter > 1 {
VariableNameGenerator {
letter: 'a',
lower: Some(Box::new(VariableNameGenerator::new(None, nb_letter - 1))),
prepend,
}
} else {
VariableNameGenerator {
letter: 'a',
lower: None,
prepend,
}
}
}
pub(crate) fn next(&mut self) {
self.incr_letters();
}
pub(crate) fn to_string(&self) -> String {
if let Some(ref lower) = self.lower {
format!("{}{}{}",
match self.prepend {
Some(ref p) => p,
None => "",
},
self.letter,
lower.to_string())
} else {
format!("{}{}",
match self.prepend {
Some(ref p) => p,
None => "",
},
self.letter)
}
}
#[allow(dead_code)]
pub(crate) fn len(&self) -> usize {
let first = match self.prepend {
Some(ref s) => s.len(),
None => 0,
} + 1;
first + match self.lower {
Some(ref s) => s.len(),
None => 0,
}
}
pub(crate) fn incr_letters(&mut self) {
let max = [('z', 'A'), ('Z', '0'), ('9', 'a')];
for (m, next) in &max {
if self.letter == *m {
self.letter = *next;
if self.letter == 'a' {
if let Some(ref mut lower) = self.lower {
lower.incr_letters();
} else {
self.lower = Some(Box::new(VariableNameGenerator::new(None, 1)));
}
}
return;
}
}
self.letter = ((self.letter as u8) + 1) as char;
}
}
#[inline]
pub fn replace_tokens_with<'a, 'b: 'a, F: Fn(&Token<'a>) -> Option<Token<'b>>>(
mut tokens: Tokens<'a>,
callback: F,
) -> Tokens<'a> {
for token in tokens.0.iter_mut() {
if let Some(t) = callback(token) {
*token = t;
}
}
tokens
}
#[inline]
pub fn replace_token_with<'a, 'b: 'a, F: Fn(&Token<'a>) -> Option<Token<'b>>>(
token: Token<'a>,
callback: &F,
) -> Token<'a> {
if let Some(t) = callback(&token) {
t
} else {
token
}
}
#[inline]
pub fn get_variable_name_and_value_positions<'a>(
tokens: &'a Tokens<'a>,
pos: usize,
) -> Option<(usize, Option<usize>)> {
if pos >= tokens.len() {
return None;
}
let mut tmp = pos;
match tokens[pos] {
Token::Keyword(Keyword::Let) |
Token::Keyword(Keyword::Var) => {
tmp += 1;
}
Token::Other(_) if pos > 0 => {
let mut pos = pos - 1;
while pos > 0 {
if tokens[pos].is_comment() || tokens[pos].is_white_character() {
pos -= 1;
} else if tokens[pos] == Token::Char(ReservedChar::Comma) ||
tokens[pos] == Token::Keyword(Keyword::Let) ||
tokens[pos] == Token::Keyword(Keyword::Var) {
break;
} else {
return None;
}
}
}
_ => return None,
}
while tmp < tokens.len() {
if tokens[tmp].is_other() {
let mut tmp2 = tmp + 1;
'big: while tmp2 < tokens.len() {
if tokens[tmp2] == Token::Operation(Operation::Equal) {
tmp2 += 1;
while tmp2 < tokens.len() {
let token = &tokens[tmp2];
if token.is_string() || token.is_other() || token.is_regex() ||
token.is_number() || token.is_floating_number() {
return Some((tmp, Some(tmp2)));
} else if !tokens[tmp2].is_comment() &&
!tokens[tmp2].is_white_character() {
break;
}
tmp2 += 1;
}
break;
} else if match tokens[tmp2].get_char() {
Some(ReservedChar::Comma) | Some(ReservedChar::SemiColon) => true,
_ => false,
} {
return Some((tmp, None));
} else if !tokens[tmp2].is_comment() &&
!(tokens[tmp2].is_white_character() &&
tokens[tmp2].get_char() != Some(ReservedChar::Backline)) {
break;
}
tmp2 += 1;
}
} else {
}
tmp += 1;
}
None
}
#[inline]
pub fn clean_tokens<'a>(mut tokens: Tokens<'a>) -> Tokens<'a> {
tokens.0.retain(clean_token);
tokens
}
#[inline]
pub fn clean_token<'a>(token: &Token<'a>) -> bool {
!token.is_comment() && {
if let Some(x) = token.get_char() {
!x.is_useless()
} else {
true
}
}
}
#[inline]
pub fn clean_tokens_except<'a, F: Fn(&Token<'a>) -> bool>(
mut tokens: Tokens<'a>,
f: F,
) -> Tokens<'a> {
tokens.0.retain(|c| clean_token_except(c, &f));
tokens
}
#[inline]
pub fn clean_token_except<'a, F: Fn(&Token<'a>) -> bool>(
token: &Token<'a>,
f: &F,
) -> bool {
let res = !token.is_comment() && {
if let Some(x) = token.get_char() {
!x.is_useless()
} else {
true
}
};
if !res {
!f(token)
} else {
true
}
}
#[inline]
pub(crate) fn get_array<'a>(
tokens: &'a Tokens<'a>,
array_name: &str,
) -> Option<(Vec<usize>, usize)> {
let mut ret = Vec::new();
let mut looking_for_var = false;
let mut looking_for_equal = false;
let mut looking_for_array_start = false;
let mut getting_values = false;
for pos in 0..tokens.len() {
if looking_for_var {
match tokens[pos] {
Token::Other(s) => {
looking_for_var = false;
if s == array_name {
looking_for_equal = true;
}
}
ref s => {
looking_for_var = s.is_comment() || s.is_white_character();
}
}
} else if looking_for_equal {
match tokens[pos] {
Token::Operation(Operation::Equal) => {
looking_for_equal = false;
looking_for_array_start = true;
}
ref s => {
looking_for_equal = s.is_comment() || s.is_white_character();
}
}
} else if looking_for_array_start {
match tokens[pos] {
Token::Char(ReservedChar::OpenBracket) => {
looking_for_array_start = false;
getting_values = true;
}
ref s => {
looking_for_array_start = s.is_comment() || s.is_white_character();
}
}
} else if getting_values {
match &tokens[pos] {
Token::Char(ReservedChar::CloseBracket) => {
return Some((ret, pos));
}
s if s.is_comment() || s.is_white_character() => {}
_ => {
ret.push(pos);
}
}
} else {
match tokens[pos] {
Token::Keyword(Keyword::Let) |
Token::Keyword(Keyword::Var) => {
looking_for_var = true;
}
_ => {}
}
}
}
None
}
#[test]
fn check_get_array() {
let source = r#"var x = [ ]; var y = ['hello',
12]; var z = []; var w = 12;"#;
let tokens = ::js::token::tokenize(source);
let ar = get_array(&tokens, "x");
assert!(ar.is_some());
assert_eq!(ar.unwrap().1, 9);
let ar = get_array(&tokens, "y");
assert!(ar.is_some());
assert_eq!(ar.unwrap().1, 27);
let ar = get_array(&tokens, "z");
assert!(ar.is_some());
assert_eq!(ar.unwrap().1, 37);
let ar = get_array(&tokens, "w");
assert!(ar.is_none());
let ar = get_array(&tokens, "W");
assert!(ar.is_none());
}
#[test]
fn check_get_variable_name_and_value_positions() {
let source = r#"var x = 1;var y = "2",we=4;"#;
let mut result = Vec::new();
let mut pos = 0;
let tokens = ::js::token::tokenize(source);
while pos < tokens.len() {
if let Some(x) = get_variable_name_and_value_positions(&tokens, pos) {
result.push(x);
pos = x.0;
}
pos += 1;
}
assert_eq!(result, vec![(2, Some(6)), (10, Some(18)), (20, Some(22))]);
let mut result = Vec::new();
let tokens = ::js::clean_tokens(tokens);
pos = 0;
while pos < tokens.len() {
if let Some(x) = get_variable_name_and_value_positions(&tokens, pos) {
result.push(x);
pos = x.0;
}
pos += 1;
}
assert_eq!(result, vec![(1, Some(3)), (6, Some(8)), (10, Some(12))]);
}
#[test]
fn replace_tokens() {
let source = r#"
var x = ['a', 'b', null, 'd', {'x': null, 'e': null, 'z': 'w'}];
var n = null;
"#;
let expected_result = "var x=['a','b',N,'d',{'x':N,'e':N,'z':'w'}];var n=N;";
let res = ::js::simple_minify(source)
.apply(::js::clean_tokens)
.apply(|f| {
replace_tokens_with(f, |t| {
if *t == Token::Keyword(Keyword::Null) {
Some(Token::Other("N"))
} else {
None
}
})
});
assert_eq!(res.to_string(), expected_result);
}
#[test]
fn check_iterator() {
let source = r#"
var x = ['a', 'b', null, 'd', {'x': null, 'e': null, 'z': 'w'}];
var n = null;
"#;
let expected_result = "var x=['a','b',N,'d',{'x':N,'e':N,'z':'w'}];var n=N;";
let res: Tokens = ::js::simple_minify(source)
.into_iter()
.filter(::js::clean_token)
.map(|t| {
if t == Token::Keyword(Keyword::Null) {
Token::Other("N")
} else {
t
}
})
.collect::<Vec<_>>()
.into();
assert_eq!(res.to_string(), expected_result);
}