3 // Copyright (c) 2018 Guillaume Gomez
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
12 // The above copyright notice and this permission notice shall be included in all
13 // copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 use js
::token
::{Keyword, Operation, ReservedChar, Token, Tokens}
;
24 use std
::vec
::IntoIter
;
26 pub(crate) struct VariableNameGenerator
<'a
> {
28 lower
: Option
<Box
<VariableNameGenerator
<'a
>>>,
29 prepend
: Option
<&'a
str>,
32 impl<'a
> VariableNameGenerator
<'a
> {
33 pub(crate) fn new(prepend
: Option
<&'a
str>, nb_letter
: usize) -> VariableNameGenerator
<'a
> {
35 VariableNameGenerator
{
37 lower
: Some(Box
::new(VariableNameGenerator
::new(None
, nb_letter
- 1))),
41 VariableNameGenerator
{
49 pub(crate) fn next(&mut self) {
53 #[allow(clippy::inherent_to_string)]
54 pub(crate) fn to_string(&self) -> String
{
55 if let Some(ref lower
) = self.lower
{
58 self.prepend
.unwrap_or(""),
63 format
!("{}{}", self.prepend
.unwrap_or(""), self.letter
)
68 pub(crate) fn len(&self) -> usize {
69 let first
= match self.prepend
{
70 Some(ref s
) => s
.len(),
75 Some(ref s
) => s
.len(),
80 pub(crate) fn incr_letters(&mut self) {
81 let max
= [('z'
, 'A'
), ('Z'
, '
0'
), ('
9'
, 'a'
)];
83 for (m
, next
) in &max
{
84 if self.letter
== *m
{
86 if self.letter
== 'a'
{
87 if let Some(ref mut lower
) = self.lower
{
90 self.lower
= Some(Box
::new(VariableNameGenerator
::new(None
, 1)));
96 self.letter
= ((self.letter
as u8) + 1) as char;
100 /// Replace given tokens with others.
105 /// extern crate minifier;
106 /// use minifier::js::{Keyword, Token, replace_tokens_with, simple_minify};
110 /// function replaceByNull(data, func) {
111 /// for (var i = 0; i < data.length; ++i) {
112 /// if func(data[i]) {
118 /// let js_minified = simple_minify(js)
120 /// replace_tokens_with(f, |t| {
121 /// if *t == Token::Keyword(Keyword::Null) {
122 /// Some(Token::Other("N"))
128 /// println!("{}", js_minified.to_string());
132 /// The previous code will have all its `null` keywords replaced with `N`. In such cases,
133 /// don't forget to include the definition of `N` in the returned minified javascript:
139 pub fn replace_tokens_with
<'a
, 'b
: 'a
, F
: Fn(&Token
<'a
>) -> Option
<Token
<'b
>>>(
140 mut tokens
: Tokens
<'a
>,
143 for token
in tokens
.0.iter_mut
() {
144 if let Some(t
) = callback(token
) {
151 /// Replace a given token with another.
153 pub fn replace_token_with
<'a
, 'b
: 'a
, F
: Fn(&Token
<'a
>) -> Option
<Token
<'b
>>>(
157 if let Some(t
) = callback(&token
) {
164 /// When looping over `Tokens`, if you encounter `Keyword::Var`, `Keyword::Let` or
165 /// `Token::Other` using this function will allow you to get the variable name's
166 /// position and the variable value's position (if any).
170 /// It'll return the value only if there is an `Operation::Equal` found.
175 /// extern crate minifier;
176 /// use minifier::js::{Keyword, get_variable_name_and_value_positions, simple_minify};
179 /// let source = r#"var x = 1;var z;var y = "2";"#;
180 /// let mut result = Vec::new();
182 /// let tokens = simple_minify(source);
184 /// for pos in 0..tokens.len() {
185 /// match tokens[pos].get_keyword() {
186 /// Some(k) if k == Keyword::Let || k == Keyword::Var => {
187 /// if let Some(x) = get_variable_name_and_value_positions(&tokens, pos) {
194 /// assert_eq!(result, vec![(2, Some(6)), (10, None), (14, Some(22))]);
198 pub fn get_variable_name_and_value_positions
<'a
>(
199 tokens
: &'a Tokens
<'a
>,
201 ) -> Option
<(usize, Option
<usize>)> {
202 if pos
>= tokens
.len() {
207 Token
::Keyword(Keyword
::Let
) | Token
::Keyword(Keyword
::Var
) => {
210 Token
::Other(_
) if pos
> 0 => {
211 let mut pos
= pos
- 1;
213 if tokens
[pos
].is_comment() || tokens
[pos
].is_white_character() {
215 } else if tokens
[pos
] == Token
::Char(ReservedChar
::Comma
)
216 || tokens
[pos
] == Token
::Keyword(Keyword
::Let
)
217 || tokens
[pos
] == Token
::Keyword(Keyword
::Var
)
227 while tmp
< tokens
.len() {
228 if tokens
[tmp
].is_other() {
229 let mut tmp2
= tmp
+ 1;
230 while tmp2
< tokens
.len() {
231 if tokens
[tmp2
] == Token
::Operation(Operation
::Equal
) {
233 while tmp2
< tokens
.len() {
234 let token
= &tokens
[tmp2
];
239 || token
.is_floating_number()
241 return Some((tmp
, Some(tmp2
)));
242 } else if !tokens
[tmp2
].is_comment() && !tokens
[tmp2
].is_white_character() {
249 tokens
[tmp2
].get_char(),
250 Some(ReservedChar
::Comma
) | Some(ReservedChar
::SemiColon
)
252 return Some((tmp
, None
));
253 } else if !(tokens
[tmp2
].is_comment()
254 || tokens
[tmp2
].is_white_character()
255 && tokens
[tmp2
].get_char() != Some(ReservedChar
::Backline
))
262 // We don't care about syntax errors.
270 fn get_next
<'a
>(it
: &mut IntoIter
<Token
<'a
>>) -> Option
<Token
<'a
>> {
272 if t
.is_comment() || t
.is_white_character() {
280 /// Convenient function used to clean useless tokens in a token list.
285 /// extern crate minifier;
287 /// use minifier::js::{clean_tokens, simple_minify};
291 /// let content = fs::read("some_file.js").expect("file not found");
292 /// let source = String::from_utf8_lossy(&content);
293 /// let s = simple_minify(&source); // First we get the tokens list.
294 /// let s = s.apply(clean_tokens); // We now have a cleaned token list!
295 /// println!("result: {:?}", s);
299 pub fn clean_tokens(tokens
: Tokens
<'_
>) -> Tokens
<'_
> {
300 let mut v
= Vec
::with_capacity(tokens
.len() / 3 * 2);
301 let mut it
= tokens
.0.into_iter
();
304 let token
= get_next(&mut it
);
308 let token
= token
.unwrap();
309 if token
.is_white_character() {
311 } else if token
.get_char() == Some(ReservedChar
::SemiColon
) {
315 if let Some(next
) = get_next(&mut it
) {
316 if next
!= Token
::Char(ReservedChar
::CloseCurlyBrace
) {
328 /// Returns true if the token is a "useful" one (so not a comment or a "useless"
331 pub fn clean_token(token
: &Token
<'_
>, next_token
: &Option
<&Token
<'_
>>) -> bool
{
332 !token
.is_comment() && {
333 if let Some(x
) = token
.get_char() {
334 !x
.is_white_character()
335 && (x
!= ReservedChar
::SemiColon
336 || *next_token
!= Some(&Token
::Char(ReservedChar
::CloseCurlyBrace
)))
344 fn get_next_except
<'a
, F
: Fn(&Token
<'a
>) -> bool
>(
345 it
: &mut IntoIter
<Token
<'a
>>,
347 ) -> Option
<Token
<'a
>> {
349 if (t
.is_comment() || t
.is_white_character()) && f(&t
) {
357 /// Same as `clean_tokens` except that if a token is considered as not desired,
358 /// the callback is called. If the callback returns `false` as well, it will
364 /// extern crate minifier;
366 /// use minifier::js::{clean_tokens_except, simple_minify, ReservedChar};
370 /// let content = fs::read("some_file.js").expect("file not found");
371 /// let source = String::from_utf8_lossy(&content);
372 /// let s = simple_minify(&source); // First we get the tokens list.
373 /// let s = s.apply(|f| {
374 /// clean_tokens_except(f, |c| {
375 /// c.get_char() != Some(ReservedChar::Backline)
377 /// }); // We now have a cleaned token list which kept backlines!
378 /// println!("result: {:?}", s);
382 pub fn clean_tokens_except
<'a
, F
: Fn(&Token
<'a
>) -> bool
>(tokens
: Tokens
<'a
>, f
: F
) -> Tokens
<'a
> {
383 let mut v
= Vec
::with_capacity(tokens
.len() / 3 * 2);
384 let mut it
= tokens
.0.into_iter
();
387 let token
= get_next_except(&mut it
, &f
);
391 let token
= token
.unwrap();
392 if token
.is_white_character() {
396 } else if token
.get_char() == Some(ReservedChar
::SemiColon
) {
403 if let Some(next
) = get_next_except(&mut it
, &f
) {
404 if next
!= Token
::Char(ReservedChar
::CloseCurlyBrace
) || !f(&token
) {
408 } else if !f(&token
) {
418 /// Returns true if the token is a "useful" one (so not a comment or a "useless"
421 pub fn clean_token_except
<'a
, F
: Fn(&Token
<'a
>) -> bool
>(
423 next_token
: &Option
<&Token
<'_
>>,
426 if !clean_token(token
, next_token
) {
434 pub(crate) fn get_array
<'a
>(
435 tokens
: &'a Tokens
<'a
>,
437 ) -> Option
<(Vec
<usize>, usize)> {
438 let mut ret
= Vec
::new();
440 let mut looking_for_var
= false;
441 let mut looking_for_equal
= false;
442 let mut looking_for_array_start
= false;
443 let mut getting_values
= false;
445 for pos
in 0..tokens
.len() {
449 looking_for_var
= false;
451 looking_for_equal
= true;
455 looking_for_var
= s
.is_comment() || s
.is_white_character();
458 } else if looking_for_equal
{
460 Token
::Operation(Operation
::Equal
) => {
461 looking_for_equal
= false;
462 looking_for_array_start
= true;
465 looking_for_equal
= s
.is_comment() || s
.is_white_character();
468 } else if looking_for_array_start
{
470 Token
::Char(ReservedChar
::OpenBracket
) => {
471 looking_for_array_start
= false;
472 getting_values
= true;
475 looking_for_array_start
= s
.is_comment() || s
.is_white_character();
478 } else if getting_values
{
480 Token
::Char(ReservedChar
::CloseBracket
) => {
481 return Some((ret
, pos
));
483 s
if s
.is_comment() || s
.is_white_character() => {}
490 Token
::Keyword(Keyword
::Let
) | Token
::Keyword(Keyword
::Var
) => {
491 looking_for_var
= true;
501 fn check_get_array() {
502 let source
= r
#"var x = [ ]; var y = ['hello',
503 12]; var z = []; var w = 12;"#;
505 let tokens
= ::js
::token
::tokenize(source
);
507 let ar
= get_array(&tokens
, "x");
508 assert
!(ar
.is_some());
509 assert_eq
!(ar
.unwrap().1, 9);
511 let ar
= get_array(&tokens
, "y");
512 assert
!(ar
.is_some());
513 assert_eq
!(ar
.unwrap().1, 27);
515 let ar
= get_array(&tokens
, "z");
516 assert
!(ar
.is_some());
517 assert_eq
!(ar
.unwrap().1, 37);
519 let ar
= get_array(&tokens
, "w");
520 assert
!(ar
.is_none());
522 let ar
= get_array(&tokens
, "W");
523 assert
!(ar
.is_none());
527 fn check_get_variable_name_and_value_positions() {
528 let source
= r
#"var x = 1;var y = "2",we=4;"#;
529 let mut result
= Vec
::new();
532 let tokens
= ::js
::token
::tokenize(source
);
534 while pos
< tokens
.len() {
535 if let Some(x
) = get_variable_name_and_value_positions(&tokens
, pos
) {
541 assert_eq
!(result
, vec
![(2, Some(6)), (10, Some(18)), (20, Some(22))]);
543 let mut result
= Vec
::new();
544 let tokens
= ::js
::clean_tokens(tokens
);
547 while pos
< tokens
.len() {
548 if let Some(x
) = get_variable_name_and_value_positions(&tokens
, pos
) {
554 assert_eq
!(result
, vec
![(1, Some(3)), (6, Some(8)), (10, Some(12))]);
558 fn replace_tokens() {
560 var x = ['a', 'b', null, 'd', {'x': null, 'e': null, 'z': 'w'}];
563 let expected_result
= "var x=['a','b',N,'d',{'x':N,'e':N,'z':'w'}];var n=N";
565 let res
= ::js
::simple_minify(source
)
566 .apply(::js
::clean_tokens
)
568 replace_tokens_with(f
, |t
| {
569 if *t
== Token
::Keyword(Keyword
::Null
) {
570 Some(Token
::Other("N"))
576 assert_eq
!(res
.to_string(), expected_result
);
580 fn check_iterator() {
582 var x = ['a', 'b', null, 'd', {'x': null, 'e': null, 'z': 'w'}];
585 let expected_result
= "var x=['a','b',N,'d',{'x':N,'e':N,'z':'w'}];var n=N;";
587 let res
: Tokens
= ::js
::simple_minify(source
)
589 .filter(|(x
, next
)| ::js
::clean_token(x
, next
))
591 if t
== Token
::Keyword(Keyword
::Null
) {
599 assert_eq
!(res
.to_string(), expected_result
);