prettify_js/
prettyprint.rs

1/// Approach translated from
2/// https://github.com/mozilla/pretty-fast/blob/55a44e7217f37219e30490b290fac2a181e1d216/pretty-fast.js
3/// adapted to use the RESS JS tokenizer.
4///
5/// pretty-fast uses the Acorn tokenizer, which tokenizes into template strings. The RESS tokenizer
6/// returns the entire template string as a token, so template-string handling is a little different.
7/// In particular there are no ${ tokens.
8use std::cmp::max;
9
10use super::*;
11
12use log::debug;
13use ress::tokens::*;
14use ress::*;
15
16#[derive(Clone)]
17struct Tok<'a> {
18    token: Token<&'a str>,
19    start: SourceCoord,
20    end: SourceCoord,
21    starts_array_literal: bool,
22}
23
24type Stack<'a> = Vec<Tok<'a>>;
25
26fn top_token<'a, 'b>(s: &'b Stack<'a>) -> Option<&'b Token<&'a str>> {
27    s.last().map(|v| &v.token)
28}
29
30fn is_pre_array_literal_token(token: &Tok) -> bool {
31    match &token.token {
32        &Token::Keyword(Keyword::Case(_))
33        | &Token::Keyword(Keyword::Delete(_))
34        | &Token::Keyword(Keyword::Do(_))
35        | &Token::Keyword(Keyword::Else(_))
36        | &Token::Keyword(Keyword::In(_))
37        | &Token::Keyword(Keyword::InstanceOf(_))
38        | &Token::Keyword(Keyword::TypeOf(_))
39        | &Token::Keyword(Keyword::Void(_))
40        | &Token::Punct(Punct::Ampersand)
41        | &Token::Punct(Punct::AmpersandEqual)
42        | &Token::Punct(Punct::Asterisk)
43        | &Token::Punct(Punct::AsteriskEqual)
44        | &Token::Punct(Punct::Bang)
45        | &Token::Punct(Punct::BangDoubleEqual)
46        | &Token::Punct(Punct::BangEqual)
47        | &Token::Punct(Punct::Caret)
48        | &Token::Punct(Punct::CaretEqual)
49        | &Token::Punct(Punct::CloseBrace)
50        | &Token::Punct(Punct::Colon)
51        | &Token::Punct(Punct::Comma)
52        | &Token::Punct(Punct::Dash)
53        | &Token::Punct(Punct::DashEqual)
54        | &Token::Punct(Punct::DoubleAmpersand)
55        | &Token::Punct(Punct::DoubleAsterisk)
56        | &Token::Punct(Punct::DoubleAsteriskEqual)
57        | &Token::Punct(Punct::DoubleDash)
58        | &Token::Punct(Punct::DoubleEqual)
59        | &Token::Punct(Punct::DoubleGreaterThan)
60        | &Token::Punct(Punct::DoubleGreaterThanEqual)
61        | &Token::Punct(Punct::DoubleLessThan)
62        | &Token::Punct(Punct::DoubleLessThanEqual)
63        | &Token::Punct(Punct::DoublePipe)
64        | &Token::Punct(Punct::DoublePlus)
65        | &Token::Punct(Punct::Equal)
66        | &Token::Punct(Punct::ForwardSlash)
67        | &Token::Punct(Punct::ForwardSlashEqual)
68        | &Token::Punct(Punct::GreaterThan)
69        | &Token::Punct(Punct::GreaterThanEqual)
70        | &Token::Punct(Punct::LessThan)
71        | &Token::Punct(Punct::LessThanEqual)
72        | &Token::Punct(Punct::OpenBrace)
73        | &Token::Punct(Punct::Percent)
74        | &Token::Punct(Punct::PercentEqual)
75        | &Token::Punct(Punct::Pipe)
76        | &Token::Punct(Punct::PipeEqual)
77        | &Token::Punct(Punct::Plus)
78        | &Token::Punct(Punct::PlusEqual)
79        | &Token::Punct(Punct::QuestionMark)
80        | &Token::Punct(Punct::Tilde)
81        | &Token::Punct(Punct::TripleEqual)
82        | &Token::Punct(Punct::TripleGreaterThan)
83        | &Token::Punct(Punct::TripleGreaterThanEqual)
84        | &Token::Punct(Punct::SemiColon) => true,
85        _ => false,
86    }
87}
88
89fn starts_array_literal<'a>(token: &Tok, last_token: &Option<Tok>) -> bool {
90    if &token.token != &Token::Punct(Punct::OpenBracket) {
91        return false;
92    }
93    if let Some(ref t) = last_token {
94        is_pre_array_literal_token(t)
95    } else {
96        true
97    }
98}
99
100fn prevent_asi_after_token(token: &Tok) -> bool {
101    match &token.token {
102        &Token::Keyword(Keyword::Delete(_))
103        | &Token::Keyword(Keyword::In(_))
104        | &Token::Keyword(Keyword::InstanceOf(_))
105        | &Token::Keyword(Keyword::TypeOf(_))
106        | &Token::Keyword(Keyword::Void(_))
107        | &Token::Keyword(Keyword::New(_))
108        | &Token::Punct(Punct::Ampersand)
109        | &Token::Punct(Punct::AmpersandEqual)
110        | &Token::Punct(Punct::Asterisk)
111        | &Token::Punct(Punct::AsteriskEqual)
112        | &Token::Punct(Punct::Bang)
113        | &Token::Punct(Punct::BangDoubleEqual)
114        | &Token::Punct(Punct::BangEqual)
115        | &Token::Punct(Punct::Caret)
116        | &Token::Punct(Punct::CaretEqual)
117        | &Token::Punct(Punct::Comma)
118        | &Token::Punct(Punct::Dash)
119        | &Token::Punct(Punct::DashEqual)
120        | &Token::Punct(Punct::DoubleAmpersand)
121        | &Token::Punct(Punct::DoubleAsterisk)
122        | &Token::Punct(Punct::DoubleAsteriskEqual)
123        | &Token::Punct(Punct::DoubleGreaterThan)
124        | &Token::Punct(Punct::DoubleGreaterThanEqual)
125        | &Token::Punct(Punct::DoubleLessThan)
126        | &Token::Punct(Punct::DoubleLessThanEqual)
127        | &Token::Punct(Punct::DoublePipe)
128        | &Token::Punct(Punct::Equal)
129        | &Token::Punct(Punct::ForwardSlash)
130        | &Token::Punct(Punct::ForwardSlashEqual)
131        | &Token::Punct(Punct::GreaterThan)
132        | &Token::Punct(Punct::GreaterThanEqual)
133        | &Token::Punct(Punct::LessThan)
134        | &Token::Punct(Punct::LessThanEqual)
135        | &Token::Punct(Punct::OpenParen)
136        | &Token::Punct(Punct::Percent)
137        | &Token::Punct(Punct::PercentEqual)
138        | &Token::Punct(Punct::Period)
139        | &Token::Punct(Punct::Pipe)
140        | &Token::Punct(Punct::PipeEqual)
141        | &Token::Punct(Punct::Plus)
142        | &Token::Punct(Punct::PlusEqual)
143        | &Token::Punct(Punct::Tilde)
144        | &Token::Punct(Punct::TripleEqual)
145        | &Token::Punct(Punct::TripleGreaterThan)
146        | &Token::Punct(Punct::TripleGreaterThanEqual) => true,
147        _ => false,
148    }
149}
150
151fn prevent_asi_before_token(token: &Tok) -> bool {
152    match &token.token {
153        &Token::Keyword(Keyword::In(_))
154        | &Token::Keyword(Keyword::InstanceOf(_))
155        | &Token::Punct(Punct::Ampersand)
156        | &Token::Punct(Punct::AmpersandEqual)
157        | &Token::Punct(Punct::Asterisk)
158        | &Token::Punct(Punct::AsteriskEqual)
159        | &Token::Punct(Punct::Bang)
160        | &Token::Punct(Punct::BangDoubleEqual)
161        | &Token::Punct(Punct::BangEqual)
162        | &Token::Punct(Punct::Caret)
163        | &Token::Punct(Punct::CaretEqual)
164        | &Token::Punct(Punct::Comma)
165        | &Token::Punct(Punct::Dash)
166        | &Token::Punct(Punct::DashEqual)
167        | &Token::Punct(Punct::DoubleAmpersand)
168        | &Token::Punct(Punct::DoubleAsterisk)
169        | &Token::Punct(Punct::DoubleAsteriskEqual)
170        | &Token::Punct(Punct::DoubleGreaterThan)
171        | &Token::Punct(Punct::DoubleGreaterThanEqual)
172        | &Token::Punct(Punct::DoubleLessThan)
173        | &Token::Punct(Punct::DoubleLessThanEqual)
174        | &Token::Punct(Punct::DoublePipe)
175        | &Token::Punct(Punct::Equal)
176        | &Token::Punct(Punct::ForwardSlash)
177        | &Token::Punct(Punct::ForwardSlashEqual)
178        | &Token::Punct(Punct::GreaterThan)
179        | &Token::Punct(Punct::GreaterThanEqual)
180        | &Token::Punct(Punct::LessThan)
181        | &Token::Punct(Punct::LessThanEqual)
182        | &Token::Punct(Punct::OpenParen)
183        | &Token::Punct(Punct::Percent)
184        | &Token::Punct(Punct::PercentEqual)
185        | &Token::Punct(Punct::Period)
186        | &Token::Punct(Punct::Pipe)
187        | &Token::Punct(Punct::PipeEqual)
188        | &Token::Punct(Punct::Plus)
189        | &Token::Punct(Punct::PlusEqual)
190        | &Token::Punct(Punct::Tilde)
191        | &Token::Punct(Punct::TripleEqual)
192        | &Token::Punct(Punct::TripleGreaterThan)
193        | &Token::Punct(Punct::TripleGreaterThanEqual) => true,
194        _ => false,
195    }
196}
197
198fn is_identifier_like(token: &Tok) -> bool {
199    match &token.token {
200        &Token::Boolean(_)
201        | &Token::Ident(_)
202        | &Token::Keyword(_)
203        | &Token::Null
204        | &Token::Number(_) => true,
205        _ => false,
206    }
207}
208
209fn is_asi(token: &Tok, last_token: &Option<Tok>) -> bool {
210    let t = if let Some(ref t) = last_token {
211        t
212    } else {
213        return false;
214    };
215    if token.start.line == t.start.line {
216        return false;
217    }
218    match &t.token {
219        &Token::Keyword(Keyword::Return(_)) | &Token::Keyword(Keyword::Yield(_)) => return true,
220        _ => (),
221    }
222    if prevent_asi_after_token(t) || prevent_asi_before_token(token) {
223        return false;
224    }
225    true
226}
227
228fn is_line_delimiter(token: &Tok, stack: &Stack) -> bool {
229    if token.starts_array_literal {
230        return true;
231    }
232    match &token.token {
233        &Token::Punct(Punct::SemiColon) | &Token::Punct(Punct::Comma) => {
234            top_token(stack) != Some(&Token::Punct(Punct::OpenParen))
235        }
236        &Token::Punct(Punct::OpenBrace) => true,
237        &Token::Punct(Punct::Colon) => match stack.last().map(|v| &v.token) {
238            Some(&Token::Keyword(Keyword::Case(_)))
239            | Some(&Token::Keyword(Keyword::Default(_))) => true,
240            _ => false,
241        },
242        _ => false,
243    }
244}
245
246struct Writer {
247    buffer: String,
248    current: SourceCoord,
249    last_from: SourceCoord,
250    mappings: Vec<SourceMapping>,
251    indent: u32,
252}
253
254impl Writer {
255    fn new(indent: u32) -> Writer {
256        Writer {
257            buffer: String::new(),
258            current: SourceCoord {
259                line: SourceMapLine(0),
260                column: SourceMapColumn(0),
261            },
262            last_from: SourceCoord {
263                line: SourceMapLine(0),
264                column: SourceMapColumn(0),
265            },
266            mappings: Vec::new(),
267            indent: indent,
268        }
269    }
270    fn write_new(&mut self, s: &str) {
271        if self.mappings.is_empty() {
272            self.mappings.push(SourceMapping {
273                from: self.current,
274                to: self.last_from,
275            });
276        }
277        self.update_current(s);
278    }
279    fn write(&mut self, s: &str, from: SourceCoord) {
280        self.last_from = from;
281        self.mappings.push(SourceMapping {
282            from: self.current,
283            to: self.last_from,
284        });
285        self.update_current(s);
286    }
287    fn write_indent(&mut self, level: u32, from: SourceCoord) {
288        if level == 0 {
289            return;
290        }
291        self.last_from = from;
292        self.mappings.push(SourceMapping {
293            from: self.current,
294            to: self.last_from,
295        });
296        let count = level * self.indent;
297        for _ in 0..count {
298            self.buffer.push(' ');
299        }
300        self.current.column.0 += count;
301    }
302    fn update_current(&mut self, s: &str) {
303        self.buffer.push_str(s);
304        for ch in s.chars() {
305            if ch == '\n' {
306                self.current.line.0 += 1;
307                self.current.column.0 = 0;
308            } else {
309                self.current.column.0 += ch.len_utf16() as u32;
310            }
311        }
312    }
313}
314
315fn append_newline(token: &Tok, stack: &Stack, out: &mut Writer) -> bool {
316    if is_line_delimiter(token, stack) {
317        out.write("\n", token.start);
318        return true;
319    }
320    false
321}
322
323fn need_space_after(token: &Tok, last_token: &Option<Tok>) -> bool {
324    if let Some(t) = last_token.as_ref() {
325        match &t.token {
326            &Token::Keyword(Keyword::Do(_))
327            | &Token::Keyword(Keyword::For(_))
328            | &Token::Keyword(Keyword::While(_))
329            | &Token::Punct(Punct::Ampersand)
330            | &Token::Punct(Punct::AmpersandEqual)
331            | &Token::Punct(Punct::Asterisk)
332            | &Token::Punct(Punct::AsteriskEqual)
333            | &Token::Punct(Punct::BangDoubleEqual)
334            | &Token::Punct(Punct::BangEqual)
335            | &Token::Punct(Punct::Caret)
336            | &Token::Punct(Punct::CaretEqual)
337            | &Token::Punct(Punct::Colon)
338            | &Token::Punct(Punct::Comma)
339            | &Token::Punct(Punct::Dash)
340            | &Token::Punct(Punct::DashEqual)
341            | &Token::Punct(Punct::DoubleAmpersand)
342            | &Token::Punct(Punct::DoubleAsterisk)
343            | &Token::Punct(Punct::DoubleAsteriskEqual)
344            | &Token::Punct(Punct::DoubleEqual)
345            | &Token::Punct(Punct::DoubleGreaterThan)
346            | &Token::Punct(Punct::DoubleGreaterThanEqual)
347            | &Token::Punct(Punct::DoubleLessThan)
348            | &Token::Punct(Punct::DoubleLessThanEqual)
349            | &Token::Punct(Punct::DoublePipe)
350            | &Token::Punct(Punct::Equal)
351            | &Token::Punct(Punct::ForwardSlash)
352            | &Token::Punct(Punct::ForwardSlashEqual)
353            | &Token::Punct(Punct::GreaterThan)
354            | &Token::Punct(Punct::GreaterThanEqual)
355            | &Token::Punct(Punct::LessThan)
356            | &Token::Punct(Punct::LessThanEqual)
357            | &Token::Punct(Punct::Percent)
358            | &Token::Punct(Punct::PercentEqual)
359            | &Token::Punct(Punct::Pipe)
360            | &Token::Punct(Punct::PipeEqual)
361            | &Token::Punct(Punct::Plus)
362            | &Token::Punct(Punct::PlusEqual)
363            | &Token::Punct(Punct::QuestionMark)
364            | &Token::Punct(Punct::SemiColon)
365            | &Token::Punct(Punct::TripleEqual)
366            | &Token::Punct(Punct::TripleGreaterThan)
367            | &Token::Punct(Punct::TripleGreaterThanEqual) => return true,
368            &Token::Number(_) => {
369                if let &Token::Punct(Punct::Period) = &token.token {
370                    return true;
371                }
372            }
373            &Token::Keyword(Keyword::Break(_))
374            | &Token::Keyword(Keyword::Continue(_))
375            | &Token::Keyword(Keyword::Return(_)) => match &token.token {
376                &Token::Punct(Punct::Period) | &Token::Punct(Punct::SemiColon) => (),
377                _ => return true,
378            },
379            &Token::Keyword(Keyword::Debugger(_))
380            | &Token::Keyword(Keyword::Default(_))
381            | &Token::Keyword(Keyword::This(_)) => (),
382            &Token::Keyword(_) => match &token.token {
383                &Token::Punct(Punct::Period) => (),
384                _ => return true,
385            },
386            &Token::Punct(Punct::CloseParen) => match &token.token {
387                &Token::Punct(Punct::CloseParen)
388                | &Token::Punct(Punct::CloseBracket)
389                | &Token::Punct(Punct::SemiColon)
390                | &Token::Punct(Punct::Comma)
391                | &Token::Punct(Punct::Period) => (),
392                _ => return true,
393            },
394            &Token::Ident(_) => {
395                if let &Token::Punct(Punct::OpenBrace) = &token.token {
396                    return true;
397                }
398            }
399            _ => (),
400        }
401        if is_identifier_like(token) && is_identifier_like(t) {
402            return true;
403        }
404    }
405
406    match &token.token {
407        &Token::Punct(Punct::AmpersandEqual)
408        | &Token::Punct(Punct::AsteriskEqual)
409        | &Token::Punct(Punct::CaretEqual)
410        | &Token::Punct(Punct::DashEqual)
411        | &Token::Punct(Punct::DoubleAsteriskEqual)
412        | &Token::Punct(Punct::Equal)
413        | &Token::Punct(Punct::ForwardSlashEqual)
414        | &Token::Punct(Punct::PercentEqual)
415        | &Token::Punct(Punct::PipeEqual)
416        | &Token::Punct(Punct::PlusEqual)
417        | &Token::Punct(Punct::QuestionMark) => true,
418        &Token::Punct(Punct::Ampersand)
419        | &Token::Punct(Punct::Asterisk)
420        | &Token::Punct(Punct::BangDoubleEqual)
421        | &Token::Punct(Punct::BangEqual)
422        | &Token::Punct(Punct::Caret)
423        | &Token::Punct(Punct::Dash)
424        | &Token::Punct(Punct::DoubleAmpersand)
425        | &Token::Punct(Punct::DoubleAsterisk)
426        | &Token::Punct(Punct::DoubleEqual)
427        | &Token::Punct(Punct::DoubleGreaterThan)
428        | &Token::Punct(Punct::DoubleGreaterThanEqual)
429        | &Token::Punct(Punct::DoubleLessThan)
430        | &Token::Punct(Punct::DoubleLessThanEqual)
431        | &Token::Punct(Punct::DoublePipe)
432        | &Token::Punct(Punct::GreaterThan)
433        | &Token::Punct(Punct::GreaterThanEqual)
434        | &Token::Punct(Punct::LessThan)
435        | &Token::Punct(Punct::LessThanEqual)
436        | &Token::Punct(Punct::Percent)
437        | &Token::Punct(Punct::Pipe)
438        | &Token::Punct(Punct::Plus) => last_token.is_some(),
439        _ => false,
440    }
441}
442
443fn increments_indent(token: &Tok) -> bool {
444    match &token.token {
445        &Token::Punct(Punct::OpenBrace) | &Token::Keyword(Keyword::Switch(_)) => true,
446        _ => token.starts_array_literal,
447    }
448}
449
450fn decrements_indent(token: &Tok, stack: &Stack) -> bool {
451    match &token.token {
452        &Token::Punct(Punct::CloseBrace) => true,
453        &Token::Punct(Punct::CloseBracket) => stack
454            .last()
455            .map(|v| v.starts_array_literal)
456            .unwrap_or(false),
457        _ => false,
458    }
459}
460
461fn prepend_white_space(
462    token: &Tok,
463    last_token: &Option<Tok>,
464    stack: &Stack,
465    mut added_newline: bool,
466    mut added_space: bool,
467    indent_level: u32,
468    out: &mut Writer,
469) {
470    if let Some(&Token::Punct(Punct::CloseBrace)) = last_token.as_ref().map(|v| &v.token) {
471        let start = last_token.as_ref().unwrap().start;
472        match &token.token {
473            &Token::Keyword(Keyword::While(_)) => {
474                if let Some(&Token::Keyword(Keyword::Do(_))) = top_token(stack) {
475                    out.write(" ", start);
476                    added_space = true;
477                } else {
478                    out.write("\n", start);
479                    added_newline = true;
480                }
481            }
482            &Token::Keyword(Keyword::Else(_))
483            | &Token::Keyword(Keyword::Catch(_))
484            | &Token::Keyword(Keyword::Finally(_)) => {
485                out.write(" ", start);
486                added_space = true;
487            }
488            &Token::Punct(Punct::OpenParen)
489            | &Token::Punct(Punct::SemiColon)
490            | &Token::Punct(Punct::Comma)
491            | &Token::Punct(Punct::CloseParen)
492            | &Token::Punct(Punct::Period)
493            | &Token::Template(_) => (),
494            _ => {
495                out.write("\n", start);
496                added_newline = true;
497            }
498        }
499    }
500
501    match &token.token {
502        &Token::Punct(Punct::Colon) => {
503            if let Some(&Token::Punct(Punct::QuestionMark)) = top_token(stack) {
504                out.write(" ", last_token.as_ref().unwrap().start);
505                added_space = true;
506            }
507        }
508        &Token::Keyword(Keyword::Else(_)) => match last_token.as_ref().map(|v| &v.token) {
509            Some(&Token::Punct(Punct::CloseBrace)) | Some(&Token::Punct(Punct::Period)) => (),
510            Some(_) => {
511                out.write(" ", last_token.as_ref().unwrap().start);
512                added_space = true;
513            }
514            None => (),
515        },
516        _ => (),
517    }
518
519    if is_asi(token, last_token) || decrements_indent(token, stack) {
520        if !added_newline {
521            out.write("\n", last_token.as_ref().unwrap().start);
522            added_newline = true;
523        }
524    }
525
526    if added_newline {
527        match &token.token {
528            &Token::Keyword(Keyword::Case(_)) | &Token::Keyword(Keyword::Default(_)) => {
529                out.write_indent(max(1, indent_level) - 1, token.start);
530            }
531            _ => {
532                out.write_indent(indent_level, token.start);
533            }
534        }
535    } else if !added_space && need_space_after(token, last_token) {
536        out.write(" ", last_token.as_ref().unwrap().start);
537    }
538}
539
540fn add_token(token: &Tok, out: &mut Writer) {
541    out.write(&token.token.to_string(), token.start);
542}
543
544fn belongs_on_stack(token: &Tok) -> bool {
545    match &token.token {
546        &Token::Keyword(Keyword::Case(_))
547        | &Token::Keyword(Keyword::Default(_))
548        | &Token::Keyword(Keyword::Do(_))
549        | &Token::Keyword(Keyword::Switch(_))
550        | &Token::Punct(Punct::OpenBrace)
551        | &Token::Punct(Punct::OpenParen)
552        | &Token::Punct(Punct::OpenBracket)
553        | &Token::Punct(Punct::QuestionMark) => true,
554        _ => false,
555    }
556}
557
558fn should_pop_stack(token: &Tok, stack: &Stack) -> bool {
559    match &token.token {
560        &Token::Keyword(Keyword::While(_)) => match top_token(stack) {
561            Some(&Token::Keyword(Keyword::Do(_))) => true,
562            _ => false,
563        },
564        &Token::Punct(Punct::CloseBracket)
565        | &Token::Punct(Punct::CloseParen)
566        | &Token::Punct(Punct::CloseBrace) => true,
567        &Token::Punct(Punct::Colon) => match top_token(stack) {
568            Some(&Token::Keyword(Keyword::Case(_)))
569            | Some(&Token::Keyword(Keyword::Default(_)))
570            | Some(&Token::Punct(Punct::QuestionMark)) => true,
571            _ => false,
572        },
573        _ => false,
574    }
575}
576
577fn add_comment(token: &Tok, next_token: Option<&Tok>, indent_level: u32, out: &mut Writer) -> bool {
578    out.write_indent(indent_level, token.start);
579    let comment = if let &Token::Comment(ref c) = &token.token {
580        c
581    } else {
582        panic!("Must be a comment");
583    };
584    let need_new_line = match comment.kind {
585        CommentKind::Multi | CommentKind::Html => {
586            out.write_new("/*");
587            let mut first = true;
588            for line in comment.content.lines() {
589                if first {
590                    first = false;
591                } else {
592                    out.write_indent(indent_level, token.start);
593                    out.write_new("  ");
594                }
595                out.write_new(line);
596            }
597            out.write_indent(indent_level, token.start);
598            out.write_new("*/");
599            next_token
600                .map(|t| t.start.line != token.start.line)
601                .unwrap_or(false)
602        }
603        CommentKind::Hashbang | CommentKind::Single => {
604            out.write_new(&comment.to_string());
605            true
606        }
607    };
608    if need_new_line {
609        out.write_new("\n");
610    } else {
611        out.write_new(" ");
612    }
613    need_new_line
614}
615
616fn convert_position(pos: Position) -> SourceCoord {
617    SourceCoord {
618        line: SourceMapLine(pos.line as u32 - 1),
619        column: SourceMapColumn(pos.column as u32 - 1),
620    }
621}
622
623fn convert_token<'a>(item: Item<Token<&'a str>>) -> Tok<'a> {
624    debug!("token: {:?} -> {:?}", item.location.start, item.token);
625    Tok {
626        token: item.token,
627        start: convert_position(item.location.start),
628        end: convert_position(item.location.end),
629        starts_array_literal: false,
630    }
631}
632
633/// Prettyprint JS source code. Returns the prettyprinted code,
634/// plus a list of SourceMappings in source order (both in original and prettyprinted
635/// code ... we don't reorder code).
636///
637/// The SourceMapping 'from' coordinates are
638/// in the prettyprinted code, the 'to' coordinates are in the original (presumably
639/// minified/obfuscated code).
640///
641/// Example:
642/// ```
643/// let (pretty, _) = prettify_js::prettyprint("function x(a){return a;}");
644/// assert_eq!(pretty, "function x(a) {\n  return a;\n}\n");
645/// ```
646pub fn prettyprint(source: &str) -> (String, Vec<SourceMapping>) {
647    let mut indent_level = 0;
648    let mut out = Writer::new(2);
649    let mut added_newline = false;
650    let mut added_space = false;
651    let mut stack: Stack = Vec::new();
652    let mut scanner = Scanner::new(source)
653        .filter_map(|v| match v {
654            Ok(v) => Some(convert_token(v)),
655            Err(_) => None,
656        })
657        .peekable();
658    let mut last_token: Option<Tok> = None;
659
660    while let Some(mut token) = scanner.next() {
661        let next_token = scanner.peek();
662        match &token.token {
663            &Token::Comment(_) => {
664                let comment_indent_level = if last_token
665                    .as_ref()
666                    .map(|v| v.end.line == token.start.line)
667                    .unwrap_or(false)
668                {
669                    out.write_new(" ");
670                    0
671                } else {
672                    indent_level
673                };
674                added_newline = add_comment(&token, next_token, comment_indent_level, &mut out);
675                added_space = !added_newline;
676                continue;
677            }
678            &Token::EoF => break,
679            _ => (),
680        }
681
682        token.starts_array_literal = starts_array_literal(&token, &last_token);
683
684        if belongs_on_stack(&token) {
685            stack.push(token.clone());
686        }
687
688        if decrements_indent(&token, &stack) {
689            indent_level = max(1, indent_level) - 1;
690            if let &Token::Punct(Punct::CloseBrace) = &token.token {
691                if stack.len() >= 2 {
692                    if let &Token::Keyword(Keyword::Switch(_)) = &stack[stack.len() - 2].token {
693                        indent_level = max(1, indent_level) - 1;
694                    }
695                }
696            }
697        }
698
699        prepend_white_space(
700            &token,
701            &last_token,
702            &stack,
703            added_newline,
704            added_space,
705            indent_level,
706            &mut out,
707        );
708        add_token(&token, &mut out);
709        added_space = false;
710        let mut same_line_comment = false;
711        if let Some(&Token::Comment(_)) = next_token.as_ref().map(|v| &v.token) {
712            if next_token.unwrap().start.line == token.end.line {
713                same_line_comment = true;
714            }
715        }
716        if !same_line_comment {
717            added_newline = append_newline(&token, &stack, &mut out);
718        }
719
720        if should_pop_stack(&token, &stack) {
721            stack.pop();
722            if let &Token::Punct(Punct::CloseBrace) = &token.token {
723                if let Some(&Token::Keyword(Keyword::Switch(_))) = stack.last().map(|v| &v.token) {
724                    stack.pop();
725                }
726            }
727        }
728
729        if increments_indent(&token) {
730            indent_level += 1;
731        }
732
733        last_token = Some(token);
734    }
735
736    if !added_newline {
737        out.write_new("\n");
738    }
739
740    (out.buffer, out.mappings)
741}