1use crate::js::token::{Keyword, Operation, ReservedChar, Token, Tokens};
4use std::vec::IntoIter;
5
6pub(crate) struct VariableNameGenerator<'a> {
7 letter: char,
8 lower: Option<Box<VariableNameGenerator<'a>>>,
9 prepend: Option<&'a str>,
10}
11
12impl<'a> VariableNameGenerator<'a> {
13 pub(crate) fn new(prepend: Option<&'a str>, nb_letter: usize) -> VariableNameGenerator<'a> {
14 if nb_letter > 1 {
15 VariableNameGenerator {
16 letter: 'a',
17 lower: Some(Box::new(VariableNameGenerator::new(None, nb_letter - 1))),
18 prepend,
19 }
20 } else {
21 VariableNameGenerator {
22 letter: 'a',
23 lower: None,
24 prepend,
25 }
26 }
27 }
28
29 pub(crate) fn next(&mut self) {
30 self.incr_letters();
31 }
32
33 #[allow(clippy::inherent_to_string)]
34 pub(crate) fn to_string(&self) -> String {
35 if let Some(ref lower) = self.lower {
36 format!(
37 "{}{}{}",
38 self.prepend.unwrap_or(""),
39 self.letter,
40 lower.to_string()
41 )
42 } else {
43 format!("{}{}", self.prepend.unwrap_or(""), self.letter)
44 }
45 }
46
47 #[allow(dead_code)]
48 pub(crate) fn len(&self) -> usize {
49 let first = match self.prepend {
50 Some(s) => s.len(),
51 None => 0,
52 } + 1;
53 first
54 + match self.lower {
55 Some(ref s) => s.len(),
56 None => 0,
57 }
58 }
59
60 pub(crate) fn incr_letters(&mut self) {
61 let max = [('z', 'A'), ('Z', '0'), ('9', 'a')];
62
63 for (m, next) in &max {
64 if self.letter == *m {
65 self.letter = *next;
66 if self.letter == 'a' {
67 if let Some(ref mut lower) = self.lower {
68 lower.incr_letters();
69 } else {
70 self.lower = Some(Box::new(VariableNameGenerator::new(None, 1)));
71 }
72 }
73 return;
74 }
75 }
76 self.letter = ((self.letter as u8) + 1) as char;
77 }
78}
79
80#[inline]
119pub fn replace_tokens_with<'a, 'b: 'a, F: Fn(&Token<'a>) -> Option<Token<'b>>>(
120 mut tokens: Tokens<'a>,
121 callback: F,
122) -> Tokens<'a> {
123 for token in tokens.0.iter_mut() {
124 if let Some(t) = callback(token) {
125 *token = t;
126 }
127 }
128 tokens
129}
130
131#[inline]
133pub fn replace_token_with<'a, 'b: 'a, F: Fn(&Token<'a>) -> Option<Token<'b>>>(
134 token: Token<'a>,
135 callback: &F,
136) -> Token<'a> {
137 if let Some(t) = callback(&token) {
138 t
139 } else {
140 token
141 }
142}
143
144pub fn get_variable_name_and_value_positions<'a>(
178 tokens: &'a Tokens<'a>,
179 pos: usize,
180) -> Option<(usize, Option<usize>)> {
181 if pos >= tokens.len() {
182 return None;
183 }
184 let mut tmp = pos;
185 match tokens[pos] {
186 Token::Keyword(Keyword::Let) | Token::Keyword(Keyword::Var) => {
187 tmp += 1;
188 }
189 Token::Other(_) if pos > 0 => {
190 let mut pos = pos - 1;
191 while pos > 0 {
192 if tokens[pos].is_comment() || tokens[pos].is_white_character() {
193 pos -= 1;
194 } else if tokens[pos] == Token::Char(ReservedChar::Comma)
195 || tokens[pos] == Token::Keyword(Keyword::Let)
196 || tokens[pos] == Token::Keyword(Keyword::Var)
197 {
198 break;
199 } else {
200 return None;
201 }
202 }
203 }
204 _ => return None,
205 }
206 while tmp < tokens.len() {
207 if tokens[tmp].is_other() {
208 let mut tmp2 = tmp + 1;
209 while tmp2 < tokens.len() {
210 if tokens[tmp2] == Token::Operation(Operation::Equal) {
211 tmp2 += 1;
212 while tmp2 < tokens.len() {
213 let token = &tokens[tmp2];
214 if token.is_string()
215 || token.is_other()
216 || token.is_regex()
217 || token.is_number()
218 || token.is_floating_number()
219 {
220 return Some((tmp, Some(tmp2)));
221 } else if !tokens[tmp2].is_comment() && !tokens[tmp2].is_white_character() {
222 break;
223 }
224 tmp2 += 1;
225 }
226 break;
227 } else if matches!(
228 tokens[tmp2].get_char(),
229 Some(ReservedChar::Comma) | Some(ReservedChar::SemiColon)
230 ) {
231 return Some((tmp, None));
232 } else if !(tokens[tmp2].is_comment()
233 || tokens[tmp2].is_white_character()
234 && tokens[tmp2].get_char() != Some(ReservedChar::Backline))
235 {
236 break;
237 }
238 tmp2 += 1;
239 }
240 } else {
241 }
243 tmp += 1;
244 }
245 None
246}
247
248#[inline]
249fn get_next<'a>(it: &mut IntoIter<Token<'a>>) -> Option<Token<'a>> {
250 for t in it {
251 if t.is_comment() || t.is_white_character() {
252 continue;
253 }
254 return Some(t);
255 }
256 None
257}
258
259#[allow(clippy::collapsible_if)]
278pub fn clean_tokens(tokens: Tokens<'_>) -> Tokens<'_> {
279 let mut v = Vec::with_capacity(tokens.len() / 3 * 2);
280 let mut it = tokens.0.into_iter();
281
282 loop {
283 let token = get_next(&mut it);
284 if token.is_none() {
285 break;
286 }
287 let token = token.unwrap();
288 if token.is_white_character() {
289 continue;
290 } else if token.get_char() == Some(ReservedChar::SemiColon) {
291 if v.is_empty() {
292 continue;
293 }
294 }
304 v.push(token);
305 }
306 v.into()
307}
308
309pub fn clean_token(token: &Token<'_>, next_token: &Option<&Token<'_>>) -> bool {
312 !token.is_comment() && {
313 if let Some(x) = token.get_char() {
314 !x.is_white_character()
315 && (x != ReservedChar::SemiColon
316 || *next_token != Some(&Token::Char(ReservedChar::CloseCurlyBrace)))
317 } else {
318 true
319 }
320 }
321}
322
323#[inline]
324fn get_next_except<'a, F: Fn(&Token<'a>) -> bool>(
325 it: &mut IntoIter<Token<'a>>,
326 f: &F,
327) -> Option<Token<'a>> {
328 for t in it {
329 if (t.is_comment() || t.is_white_character()) && f(&t) {
330 continue;
331 }
332 return Some(t);
333 }
334 None
335}
336
337pub fn clean_tokens_except<'a, F: Fn(&Token<'a>) -> bool>(tokens: Tokens<'a>, f: F) -> Tokens<'a> {
362 let mut v = Vec::with_capacity(tokens.len() / 3 * 2);
363 let mut it = tokens.0.into_iter();
364
365 loop {
366 let token = get_next_except(&mut it, &f);
367 if token.is_none() {
368 break;
369 }
370 let token = token.unwrap();
371 if token.is_white_character() {
372 if f(&token) {
373 continue;
374 }
375 } else if token.get_char() == Some(ReservedChar::SemiColon) {
376 if v.is_empty() {
377 if !f(&token) {
378 v.push(token);
379 }
380 continue;
381 }
382 if let Some(next) = get_next_except(&mut it, &f) {
383 if next != Token::Char(ReservedChar::CloseCurlyBrace) || !f(&token) {
384 v.push(token);
385 }
386 v.push(next);
387 } else if !f(&token) {
388 v.push(token);
389 }
390 continue;
391 }
392 v.push(token);
393 }
394 v.into()
395}
396
397#[inline]
400pub fn clean_token_except<'a, F: Fn(&Token<'a>) -> bool>(
401 token: &Token<'a>,
402 next_token: &Option<&Token<'_>>,
403 f: &F,
404) -> bool {
405 if !clean_token(token, next_token) {
406 !f(token)
407 } else {
408 true
409 }
410}
411
412pub(crate) fn get_array<'a>(
413 tokens: &'a Tokens<'a>,
414 array_name: &str,
415) -> Option<(Vec<usize>, usize)> {
416 let mut ret = Vec::new();
417
418 let mut looking_for_var = false;
419 let mut looking_for_equal = false;
420 let mut looking_for_array_start = false;
421 let mut getting_values = false;
422
423 for pos in 0..tokens.len() {
424 if looking_for_var {
425 match tokens[pos] {
426 Token::Other(s) => {
427 looking_for_var = false;
428 if s == array_name {
429 looking_for_equal = true;
430 }
431 }
432 ref s => {
433 looking_for_var = s.is_comment() || s.is_white_character();
434 }
435 }
436 } else if looking_for_equal {
437 match tokens[pos] {
438 Token::Operation(Operation::Equal) => {
439 looking_for_equal = false;
440 looking_for_array_start = true;
441 }
442 ref s => {
443 looking_for_equal = s.is_comment() || s.is_white_character();
444 }
445 }
446 } else if looking_for_array_start {
447 match tokens[pos] {
448 Token::Char(ReservedChar::OpenBracket) => {
449 looking_for_array_start = false;
450 getting_values = true;
451 }
452 ref s => {
453 looking_for_array_start = s.is_comment() || s.is_white_character();
454 }
455 }
456 } else if getting_values {
457 match &tokens[pos] {
458 Token::Char(ReservedChar::CloseBracket) => {
459 return Some((ret, pos));
460 }
461 s if s.is_comment() || s.is_white_character() => {}
462 _ => {
463 ret.push(pos);
464 }
465 }
466 } else {
467 match tokens[pos] {
468 Token::Keyword(Keyword::Let) | Token::Keyword(Keyword::Var) => {
469 looking_for_var = true;
470 }
471 _ => {}
472 }
473 }
474 }
475 None
476}
477
478#[test]
479fn check_get_array() {
480 let source = r#"var x = [ ]; var y = ['hello',
481 12]; var z = []; var w = 12;"#;
482
483 let tokens = crate::js::token::tokenize(source);
484
485 let ar = get_array(&tokens, "x");
486 assert!(ar.is_some());
487 assert_eq!(ar.unwrap().1, 9);
488
489 let ar = get_array(&tokens, "y");
490 assert!(ar.is_some());
491 assert_eq!(ar.unwrap().1, 27);
492
493 let ar = get_array(&tokens, "z");
494 assert!(ar.is_some());
495 assert_eq!(ar.unwrap().1, 37);
496
497 let ar = get_array(&tokens, "w");
498 assert!(ar.is_none());
499
500 let ar = get_array(&tokens, "W");
501 assert!(ar.is_none());
502}
503
504#[test]
505fn check_get_variable_name_and_value_positions() {
506 let source = r#"var x = 1;var y = "2",we=4;"#;
507 let mut result = Vec::new();
508 let mut pos = 0;
509
510 let tokens = crate::js::token::tokenize(source);
511
512 while pos < tokens.len() {
513 if let Some(x) = get_variable_name_and_value_positions(&tokens, pos) {
514 result.push(x);
515 pos = x.0;
516 }
517 pos += 1;
518 }
519 assert_eq!(result, vec![(2, Some(6)), (10, Some(18)), (20, Some(22))]);
520
521 let mut result = Vec::new();
522 let tokens = crate::js::clean_tokens(tokens);
523 pos = 0;
524
525 while pos < tokens.len() {
526 if let Some(x) = get_variable_name_and_value_positions(&tokens, pos) {
527 result.push(x);
528 pos = x.0;
529 }
530 pos += 1;
531 }
532 assert_eq!(result, vec![(1, Some(3)), (6, Some(8)), (10, Some(12))]);
533}
534
535#[test]
536fn replace_tokens() {
537 let source = r#"
538var x = ['a', 'b', null, 'd', {'x': null, 'e': null, 'z': 'w'}];
539var n = null;
540"#;
541 let expected_result = "var x=['a','b',N,'d',{'x':N,'e':N,'z':'w'}];var n=N;";
542
543 let res = crate::js::simple_minify(source)
544 .apply(crate::js::clean_tokens)
545 .apply(|f| {
546 replace_tokens_with(f, |t| {
547 if *t == Token::Keyword(Keyword::Null) {
548 Some(Token::Other("N"))
549 } else {
550 None
551 }
552 })
553 });
554 assert_eq!(res.to_string(), expected_result);
555}
556
557#[test]
558fn check_iterator() {
559 let source = r#"
560var x = ['a', 'b', null, 'd', {'x': null, 'e': null, 'z': 'w'}];
561var n = null;
562"#;
563 let expected_result = "var x=['a','b',N,'d',{'x':N,'e':N,'z':'w'}];var n=N;";
564
565 let mut iter = crate::js::simple_minify(source).into_iter().peekable();
566 let mut tokens = Vec::new();
567 while let Some(token) = iter.next() {
568 if crate::js::clean_token(&token, &iter.peek()) {
569 tokens.push(if token == Token::Keyword(Keyword::Null) {
570 Token::Other("N")
571 } else {
572 token
573 });
574 }
575 }
576 let tokens: Tokens = tokens.into();
577 assert_eq!(tokens.to_string(), expected_result);
578}