├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── jsbeautifier ├── flags.go ├── jsbeautifier.go ├── jsbeautifier_test.go ├── output.go └── outputline.go ├── main.go ├── optargs └── optargs.go ├── tokenizer ├── acorn.go ├── characters.go ├── token.go ├── tokenizer.go └── tokenstack.go ├── unpackers ├── packer.go └── unpackers.go └── utils └── utils.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.3 4 | * Various bug fixes, passes most of the original jsbeautifier tests 5 | 6 | ## v0.2 7 | * Semi-Tested Unicode support, converted most of the code to use Go's rune parsing methods, seems to be fine but needs more testing 8 | * Untested Regex parsing 9 | * Some edge cases fixed 10 | 11 | ## v0.1 12 | * Initial release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ditashi Sayomi 4 | 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: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 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 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | jsbeautifier-go [![Travis build status](https://api.travis-ci.org/ditashi/jsbeautifier-go.svg?branch=master)](https://travis-ci.org/ditashi/jsbeautifier-go) 4 | =============== 5 | 6 | A golang port of the awesome jsbeautifier tool 7 | 8 | This is a port of the awesome [jsbeautifier](http://jsbeautifier.org/) tool to golang. 9 | 10 | Granted this is a very basic and naive attempt since this is one of my first takes with golang. 11 | 12 | ## Current state 13 | Implemented most of the original features from jsbeautifier (missing features are listed in the following section). 14 | 15 | ## What doesn't work 16 | * Unpacking & Deobfuscation (and with that code eval) 17 | * Reading from stdin 18 | 19 | ## Priority 20 | 1. Finish implementing features to match the current state of JsBeautifier 21 | 2. Unpacking & Deobfuscation 22 | 23 | ## Usage 24 | ``` go get && go run main.go test.js ``` 25 | 26 | ### Testing 27 | From within the source folder run: 28 | ``` go test ./... ``` or use something like [goconvey](https://github.com/smartystreets/goconvey) 29 | 30 | ## Options 31 | 32 | ``` 33 | usage: 34 | jsbeautifier [options] PATH 35 | 36 | options: 37 | 38 | 39 | --indent-size=SIZE Indentation Size [default: 4] 40 | 41 | --indent-char=CHAR Indentation Char [default: space] 42 | --brace-style=STYLE Brace Style (expand, collapse, end-exapnd or none) [default: collapse] 43 | --outfile=OUTPUTFILE Output results to a file [default: stdout] 44 | -z --eval-code Eval JS code (dangerous!) 45 | -f --keep-function-indentation Keep original function indentation 46 | -t --indent-with-tabs Use tabs to indent 47 | -d --disable-preserve-newlines Don't preserve newlines 48 | -P --space-in-paren Use spaces in parenthesis 49 | -E --space-in-empty-paren Use spaces in empty parenthesis 50 | -j --jslint-happy Keep JSLint happy :) 51 | -a --space_after_anon_function Use spaces after anonymous functions 52 | -x --unescape-strings Unescape strings 53 | -X --e4x Parse XML by e4x standard 54 | -n --end-with-newline End output with newline 55 | -w --wrap-line-length Wrap line length 56 | -i --stdin Use stdin as input 57 | -h --help Show this screen 58 | --version Show version 59 | ```` 60 | 61 | ## License 62 | You are free to use this in any way you want, in case you find this useful or working for you but you must keep the copyright notice and license. (MIT) 63 | 64 | ## Credits 65 | * Einar Lielmanis, einar@jsbeautifier.org | Original author of the jsbeautifier tool 66 | * [docopt](http://docopt.org) / [go-docopt](https://github.com/docopt/docopt.go) / [flynn](https://github.com/flynn/go-docopt) | For writing, porting and supporting docopt 67 | -------------------------------------------------------------------------------- /jsbeautifier/flags.go: -------------------------------------------------------------------------------- 1 | package jsbeautifier 2 | 3 | // Copyright (c) 2014 Ditashi Sayomi 4 | 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: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 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 21 | // SOFTWARE. 22 | 23 | type Flags struct { 24 | mode Mode 25 | parent *Flags 26 | last_text string 27 | 28 | multiline_frame bool 29 | start_line_index int 30 | indentation_level int 31 | line_indent_level int 32 | if_block bool 33 | do_block bool 34 | in_case bool 35 | declaration_statement bool 36 | declaration_assignment bool 37 | do_while bool 38 | ternary_depth int 39 | last_word string 40 | else_block bool 41 | in_case_statement bool 42 | case_body bool 43 | } 44 | 45 | func (self *Flags) apply_base(flags_base *Flags, added_newline bool) { 46 | next_indent_level := flags_base.indentation_level 47 | if !added_newline && flags_base.line_indent_level > next_indent_level { 48 | next_indent_level = flags_base.line_indent_level 49 | } 50 | 51 | self.parent = flags_base 52 | self.last_text = flags_base.last_text 53 | self.last_word = flags_base.last_word 54 | self.indentation_level = next_indent_level 55 | } 56 | 57 | func NewFlags(mode Mode) *Flags { 58 | flags := new(Flags) 59 | flags.mode = mode 60 | return flags 61 | } 62 | -------------------------------------------------------------------------------- /jsbeautifier/jsbeautifier.go: -------------------------------------------------------------------------------- 1 | package jsbeautifier 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "strings" 7 | 8 | "github.com/ditashi/jsbeautifier-go/optargs" 9 | "github.com/ditashi/jsbeautifier-go/tokenizer" 10 | "github.com/ditashi/jsbeautifier-go/unpackers" 11 | "github.com/ditashi/jsbeautifier-go/utils" 12 | ) 13 | 14 | // Copyright (c) 2014 Ditashi Sayomi 15 | 16 | // Permission is hereby granted, free of charge, to any person obtaining a copy 17 | // of this software and associated documentation files (the "Software"), to deal 18 | // in the Software without restriction, including without limitation the rights 19 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | // copies of the Software, and to permit persons to whom the Software is 21 | // furnished to do so, subject to the following conditions: 22 | 23 | // The above copyright notice and this permission notice shall be included in all 24 | // copies or substantial portions of the Software. 25 | 26 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | // SOFTWARE. 33 | 34 | var default_options = map[string]interface{}{ 35 | "indent_size": 4, 36 | "indent_char": " ", 37 | "indent_with_tabs": false, 38 | "preserve_newlines": true, 39 | "max_preserve_newlines": 10, 40 | "space_in_paren": false, 41 | "space_in_empty_paren": false, 42 | "e4x": false, 43 | "jslint_happy": false, 44 | "space_after_anon_function": false, 45 | "brace_style": "collapse", 46 | "keep_array_indentation": false, 47 | "keep_function_indentation": false, 48 | "eval_code": false, 49 | "unescape_strings": false, 50 | "wrap_line_length": 0, 51 | "break_chained_methods": false, 52 | "end_with_newline": false, 53 | } 54 | 55 | var handlers = map[string]func(*jsbeautifier, *tokenizer.Token){ 56 | "TK_START_EXPR": (*jsbeautifier).handle_start_expr, 57 | "TK_END_EXPR": (*jsbeautifier).handle_end_expr, 58 | "TK_START_BLOCK": (*jsbeautifier).handle_start_block, 59 | "TK_END_BLOCK": (*jsbeautifier).handle_end_block, 60 | "TK_WORD": (*jsbeautifier).handle_word, 61 | "TK_RESERVED": (*jsbeautifier).handle_word, 62 | "TK_SEMICOLON": (*jsbeautifier).handle_semicolon, 63 | "TK_STRING": (*jsbeautifier).handle_string, 64 | "TK_EQUALS": (*jsbeautifier).handle_equals, 65 | "TK_OPERATOR": (*jsbeautifier).handle_operator, 66 | "TK_COMMA": (*jsbeautifier).handle_comma, 67 | "TK_BLOCK_COMMENT": (*jsbeautifier).handle_block_comment, 68 | "TK_INLINE_COMMENT": (*jsbeautifier).handle_inline_comment, 69 | "TK_COMMENT": (*jsbeautifier).handle_comment, 70 | "TK_DOT": (*jsbeautifier).handle_dot, 71 | "TK_UNKNOWN": (*jsbeautifier).handle_unknown, 72 | "TK_EOF": (*jsbeautifier).handle_eof, 73 | } 74 | 75 | type jsbeautifier struct { 76 | flags *Flags 77 | previous_flags *Flags 78 | flag_store []Flags 79 | tokens []string 80 | token_pos int 81 | options optargs.MapType 82 | output *output 83 | last_last_text string 84 | last_type string 85 | ternary_depth int 86 | indent_string string 87 | baseIndentString string 88 | token_queue tokenizer.TokenStack 89 | tkch chan tokenizer.Token 90 | } 91 | 92 | type Mode int 93 | 94 | const ( 95 | BlockStatement Mode = iota 96 | Statement 97 | ObjectLiteral 98 | ArrayLiteral 99 | ForInitializer 100 | Conditional 101 | Expression 102 | ) 103 | 104 | func (self *jsbeautifier) unpack(s *string, eval_code bool) *string { 105 | return unpackers.Run(s) 106 | } 107 | 108 | func (self *jsbeautifier) beautify(s *string) (string, error) { 109 | 110 | if !utils.InStrArray(self.options["brace_style"].(string), []string{"expand", "collapse", "end-expand", "none"}) { 111 | return "", errors.New("opts.brace-style must be \"expand\", \"collapse\", \"end-exapnd\", or \"none\".") 112 | } 113 | 114 | s = self.blank_state(s) 115 | 116 | input := self.unpack(s, self.options["eval_code"].(bool)) 117 | t := tokenizer.New(input, self.options, " ") 118 | self.tkch = t.Tokenize() 119 | self.token_pos = 0 120 | 121 | for token := range self.tkch { 122 | self.parse_token(token) 123 | 124 | for !self.token_queue.Empty() { 125 | qtoken := self.token_queue.Shift() 126 | self.parse_token(*qtoken) 127 | } 128 | } 129 | 130 | sweet_code := self.output.get_code() 131 | if self.options["end_with_newline"].(bool) { 132 | sweet_code += "\n" 133 | } 134 | 135 | return sweet_code, nil 136 | } 137 | 138 | func (self *jsbeautifier) parse_token(t tokenizer.Token) { 139 | for _, comment_token := range t.CommentsBefore() { 140 | self.handle_token(&comment_token) 141 | } 142 | self.handle_token(&t) 143 | 144 | self.last_last_text = self.flags.last_text 145 | self.last_type = t.Type() 146 | self.flags.last_text = t.Text() 147 | self.token_pos++ 148 | } 149 | 150 | func (self *jsbeautifier) handle_token(t *tokenizer.Token) { 151 | 152 | newlines := t.NewLines() 153 | keep_whitespace := self.options["keep_array_indentation"].(bool) && self.is_array(self.flags.mode) 154 | 155 | if keep_whitespace { 156 | for i := 0; i < newlines; i++ { 157 | self.print_newline(i > 0, false) 158 | } 159 | } else { 160 | if self.options["max_preserve_newlines"].(int) != 0 && newlines > self.options["max_preserve_newlines"].(int) { 161 | newlines = self.options["max_preserve_newlines"].(int) 162 | } 163 | if self.options["preserve_newlines"].(bool) && newlines > 1 { 164 | self.print_newline(false, false) 165 | for i := 1; i < newlines; i++ { 166 | self.print_newline(true, false) 167 | 168 | } 169 | } 170 | } 171 | 172 | handlers[t.Type()](self, t) 173 | 174 | } 175 | 176 | func (self *jsbeautifier) is_special_word(s string) bool { 177 | return utils.InStrArray(s, []string{"case", "return", "do", "if", "throw", "else"}) 178 | } 179 | 180 | func (self *jsbeautifier) is_array(mode Mode) bool { 181 | return mode == ArrayLiteral 182 | } 183 | 184 | func (self *jsbeautifier) is_expression(mode Mode) bool { 185 | return mode == Expression || mode == ForInitializer || mode == Conditional 186 | } 187 | 188 | func (self *jsbeautifier) allow_wrap_or_preserved_newline(current_token tokenizer.Token, force_linewrap bool) { 189 | 190 | if self.output.just_added_newline() { 191 | return 192 | } 193 | 194 | if (self.options["preserve_newlines"].(bool) && current_token.WantedNewLine()) || force_linewrap { 195 | self.print_newline(false, true) 196 | } else if self.options["wrap_line_length"].(int) > 0 { 197 | proposed_line_length := self.output.current_line.get_character_count() + len(current_token.Text()) 198 | 199 | if self.output.space_before_token { 200 | proposed_line_length += 1 201 | } 202 | 203 | if proposed_line_length >= self.options["wrap_line_length"].(int) { 204 | self.print_newline(false, true) 205 | } 206 | } 207 | } 208 | 209 | func (self *jsbeautifier) print_newline(force_newline bool, preserve_satatement_flags bool) { 210 | if !preserve_satatement_flags { 211 | 212 | if self.flags.last_text != ";" && self.flags.last_text != "," && self.flags.last_text != "=" && self.last_type != "TK_OPERATOR" { 213 | for self.flags.mode == Statement && !self.flags.if_block && !self.flags.do_block { 214 | self.restore_mode() 215 | } 216 | } 217 | } 218 | 219 | if self.output.add_new_line(force_newline) { 220 | self.flags.multiline_frame = true 221 | } 222 | } 223 | 224 | func (self *jsbeautifier) print_token_line_indentation(current_token tokenizer.Token) { 225 | 226 | if self.output.just_added_newline() { 227 | line := self.output.current_line 228 | if self.options["keep_array_indentation"].(bool) && self.is_array(self.flags.mode) && current_token.WantedNewLine() { 229 | line.push(current_token.WhitespaceBefore()) 230 | self.output.space_before_token = false 231 | } else if self.output.set_indent(self.flags.indentation_level) { 232 | self.flags.line_indent_level = self.flags.indentation_level 233 | } 234 | } 235 | 236 | } 237 | 238 | func (self *jsbeautifier) print_token(current_token tokenizer.Token, s string) { 239 | if s == "" { 240 | s = current_token.Text() 241 | } 242 | self.print_token_line_indentation(current_token) 243 | self.output.add_token(s) 244 | } 245 | 246 | func (self *jsbeautifier) indent() { 247 | self.flags.indentation_level += 1 248 | } 249 | 250 | func (self *jsbeautifier) deindent() { 251 | allow_deindent := self.flags.indentation_level > 0 && ((self.flags.parent == nil) || self.flags.indentation_level > self.flags.parent.indentation_level) 252 | if allow_deindent { 253 | self.flags.indentation_level -= 1 254 | } 255 | } 256 | 257 | func (self *jsbeautifier) set_mode(mode Mode) { 258 | if self.flags != nil { 259 | self.flag_store = append(self.flag_store, *self.flags) 260 | self.previous_flags = self.flags 261 | } else { 262 | self.previous_flags = NewFlags(mode) 263 | } 264 | 265 | self.flags = NewFlags(mode) 266 | self.flags.apply_base(self.previous_flags, self.output.just_added_newline()) 267 | 268 | self.flags.start_line_index = self.output.get_line_number() 269 | } 270 | 271 | func (self *jsbeautifier) restore_mode() { 272 | if len(self.flag_store) > 0 { 273 | self.previous_flags = self.flags 274 | 275 | self.flags = &self.flag_store[len(self.flag_store)-1] 276 | self.flag_store = self.flag_store[:len(self.flag_store)-1] 277 | 278 | if self.previous_flags.mode == Statement { 279 | self.output.remove_redundant_indentation(*self.previous_flags) 280 | } 281 | } 282 | } 283 | 284 | func (self *jsbeautifier) start_of_object_property() bool { 285 | return self.flags.parent.mode == ObjectLiteral && self.flags.mode == Statement && ((self.flags.last_text == ":" && self.flags.ternary_depth == 0) || (self.last_type == "TK_RESERVED" && (self.flags.last_text == "get" || self.flags.last_text == "set"))) 286 | } 287 | 288 | func (self *jsbeautifier) start_of_statement(current_token tokenizer.Token) bool { 289 | 290 | if (self.last_type == "TK_RESERVED" && utils.InStrArray(self.flags.last_text, []string{"var", "let", "const"}) && current_token.Type() == "TK_WORD") || (self.last_type == "TK_RESERVED" && self.flags.last_text == "do") || (self.last_type == "TK_RESERVED" && self.flags.last_text == "return" && !current_token.WantedNewLine()) || (self.last_type == "TK_RESERVED" && self.flags.last_text == "else" && !(current_token.Type() == "TK_RESERVED" && current_token.Text() == "if")) || (self.last_type == "TK_END_EXPR" && (self.previous_flags.mode == ForInitializer || self.previous_flags.mode == Conditional)) || (self.last_type == "TK_WORD" && self.flags.mode == BlockStatement && !self.flags.in_case && !(current_token.Text() == "--" || current_token.Text() == "++") && current_token.Type() != "TK_WORD" && current_token.Type() != "TK_RESERVED") || (self.flags.mode == ObjectLiteral && ((self.flags.last_text == ":" && self.flags.ternary_depth == 0) || (self.last_type == "TK_RESERVED" && utils.InStrArray(self.flags.last_text, []string{"get", "set"})))) { 291 | 292 | self.set_mode(Statement) 293 | self.indent() 294 | if self.last_type == "TK_RESERVED" && utils.InStrArray(self.flags.last_text, []string{"var", "let", "const"}) && current_token.Type() == "TK_WORD" { 295 | self.flags.declaration_statement = true 296 | } 297 | 298 | if !self.start_of_object_property() { 299 | self.allow_wrap_or_preserved_newline(current_token, (current_token.Type() == "TK_RESERVED" && utils.InStrArray(current_token.Text(), []string{"do", "for", "if", "while"}))) 300 | } 301 | 302 | return true 303 | } else { 304 | return false 305 | } 306 | } 307 | 308 | func (self *jsbeautifier) get_token() (tokenizer.Token, bool) { 309 | token, more := <-self.tkch 310 | 311 | if more { 312 | self.token_queue.Append(token) 313 | } 314 | 315 | return token, more 316 | } 317 | 318 | func (self *jsbeautifier) handle_start_expr(current_token *tokenizer.Token) { 319 | if self.start_of_statement(*current_token) { 320 | 321 | } 322 | next_mode := Expression 323 | if current_token.Text() == "[" { 324 | if self.last_type == "TK_WORD" || self.flags.last_text == ")" { 325 | if self.last_type == "TK_RESERVED" && utils.InStrArray(self.flags.last_text, tokenizer.GetLineStarters()) { 326 | self.output.space_before_token = true 327 | } 328 | self.set_mode(next_mode) 329 | self.print_token(*current_token, "") 330 | self.indent() 331 | if self.options["space_in_paren"].(bool) { 332 | self.output.space_before_token = true 333 | } 334 | return 335 | } 336 | 337 | next_mode = ArrayLiteral 338 | if self.is_array(self.flags.mode) { 339 | if self.flags.last_text == "[" || (self.flags.last_text == "," && (self.last_last_text == "]" || self.last_last_text == "}")) { 340 | if !self.options["keep_array_indentation"].(bool) { 341 | self.print_newline(false, false) 342 | } 343 | } 344 | } 345 | } else { 346 | if self.last_type == "TK_RESERVED" && self.flags.last_text == "for" { 347 | next_mode = ForInitializer 348 | } else if self.last_type == "TK_RESERVED" && (self.flags.last_text == "if" || self.flags.last_text == "while") { 349 | next_mode = Conditional 350 | } else { 351 | next_mode = Expression 352 | } 353 | } 354 | 355 | if self.flags.last_text == ";" || self.last_type == "TK_START_BLOCK" { 356 | self.print_newline(false, false) 357 | } else if utils.InStrArray(self.last_type, []string{"TK_END_EXPR", "TK_START_EXPR", "TK_END_BLOCK"}) || self.flags.last_text == "." { 358 | self.allow_wrap_or_preserved_newline(*current_token, current_token.WantedNewLine()) 359 | } else if !(self.last_type == "TK_RESERVED" && current_token.Text() == "(") && !utils.InStrArray(self.last_type, []string{"TK_WORD", "TK_OPERATOR"}) { 360 | self.output.space_before_token = true 361 | } else if (self.last_type == "TK_RESERVED" && (self.flags.last_word == "function" || self.flags.last_word == "typeof")) || (self.flags.last_text == "*" && self.last_last_text == "function") { 362 | if self.options["space_after_anon_function"].(bool) { 363 | self.output.space_before_token = true 364 | } 365 | } else if self.last_type == "TK_RESERVED" && (utils.InStrArray(self.flags.last_text, tokenizer.GetLineStarters()) || self.flags.last_text == "catch") { 366 | self.output.space_before_token = true 367 | } 368 | 369 | if self.last_type == "TK_EQUALS" || self.last_type == "TK_OPERATOR" { 370 | if !self.start_of_object_property() { 371 | self.allow_wrap_or_preserved_newline(*current_token, false) 372 | } 373 | } 374 | 375 | self.set_mode(next_mode) 376 | self.print_token(*current_token, "") 377 | 378 | if self.options["space_in_paren"].(bool) { 379 | self.output.space_before_token = true 380 | } 381 | self.indent() 382 | } 383 | 384 | func (self *jsbeautifier) handle_start_block(current_token *tokenizer.Token) { 385 | next_token, nmore := self.get_token() 386 | second_token, smore := self.get_token() 387 | if smore && ((second_token.Text() == ":" && utils.InStrArray(next_token.Type(), []string{"TK_STRING", "TK_WORD", "TK_RESERVED"})) || (utils.InStrArray(next_token.Text(), []string{"get", "set"}) && utils.InStrArray(second_token.Type(), []string{"TK_WORD", "TK_RESE$RVED"}))) { 388 | if !utils.InStrArray(self.last_last_text, []string{"class", "interface"}) { 389 | self.set_mode(ObjectLiteral) 390 | } else { 391 | self.set_mode(BlockStatement) 392 | } 393 | } else { 394 | self.set_mode(BlockStatement) 395 | } 396 | 397 | empty_braces := (nmore) && len(next_token.CommentsBefore()) == 0 && next_token.Text() == "}" 398 | empty_anonymous_function := empty_braces && self.flags.last_word == "function" && self.last_type == "TK_END_EXPR" 399 | 400 | if self.options["brace_style"].(string) == "expand" || (self.options["brace_style"].(string) == "none" && current_token.WantedNewLine()) { 401 | if self.last_type != "TK_OPERATOR" && (empty_anonymous_function || self.last_type == "TK_EQUALS" || (self.last_type == "TK_RESERVED" && self.is_special_word(self.flags.last_text) && self.flags.last_text != "else")) { 402 | self.output.space_before_token = true 403 | } else { 404 | self.print_newline(false, true) 405 | } 406 | } else { 407 | if !utils.InStrArray(self.last_type, []string{"TK_OPERATOR", "TK_START_EXPR"}) { 408 | if self.last_type == "TK_START_BLOCK" { 409 | self.print_newline(false, false) 410 | } else { 411 | self.output.space_before_token = true 412 | } 413 | } else { 414 | if self.is_array(self.previous_flags.mode) && self.flags.last_text == "," { 415 | if self.last_last_text == "}" { 416 | self.output.space_before_token = true 417 | } else { 418 | self.print_newline(false, false) 419 | } 420 | } 421 | } 422 | } 423 | 424 | self.print_token(*current_token, "") 425 | self.indent() 426 | } 427 | 428 | func (self *jsbeautifier) handle_end_expr(current_token *tokenizer.Token) { 429 | for self.flags.mode == Statement { 430 | self.restore_mode() 431 | } 432 | 433 | if self.flags.multiline_frame { 434 | self.allow_wrap_or_preserved_newline(*current_token, current_token.Text() == "]" && self.is_array(self.flags.mode) && !self.options["keep_array_indentation"].(bool)) 435 | } 436 | 437 | if self.options["space_in_paren"].(bool) { 438 | if self.last_type == "TK_START_EXPR" && !self.options["space_in_empty_paren"].(bool) { 439 | self.output.space_before_token = false 440 | self.output.trim(false) 441 | } else { 442 | self.output.space_before_token = true 443 | } 444 | } 445 | 446 | if current_token.Text() == "]" && self.options["keep_array_indentation"].(bool) { 447 | self.print_token(*current_token, "") 448 | self.restore_mode() 449 | } else { 450 | self.restore_mode() 451 | self.print_token(*current_token, "") 452 | } 453 | 454 | self.output.remove_redundant_indentation(*self.previous_flags) 455 | 456 | if self.flags.do_while && self.previous_flags.mode == Conditional { 457 | self.previous_flags.mode = Expression 458 | self.flags.do_block = false 459 | self.flags.do_block = false 460 | } 461 | } 462 | 463 | func (self *jsbeautifier) handle_end_block(current_token *tokenizer.Token) { 464 | for self.flags.mode == Statement { 465 | self.restore_mode() 466 | } 467 | 468 | empty_braces := self.last_type == "TK_START_BLOCK" 469 | 470 | if self.options["brace_style"].(string) == "expand" { 471 | if !empty_braces { 472 | self.print_newline(false, false) 473 | } 474 | } else { 475 | if !empty_braces { 476 | if self.is_array(self.flags.mode) && self.options["keep_array_indentation"].(bool) { 477 | self.options["keep_array_indentation"] = false 478 | self.print_newline(false, false) 479 | self.options["keep_array_indentation"] = true 480 | } else { 481 | self.print_newline(false, false) 482 | } 483 | } 484 | } 485 | 486 | self.restore_mode() 487 | self.print_token(*current_token, "") 488 | } 489 | 490 | func (self *jsbeautifier) handle_word(current_token *tokenizer.Token) { 491 | 492 | if current_token.Type() == "TK_RESERVED" && self.flags.mode != ObjectLiteral && (current_token.Text() == "set" || current_token.Text() == "get") { 493 | current_token.SetType("TK_WORD") 494 | } 495 | 496 | if current_token.Type() == "TK_RESERVED" && self.flags.mode == ObjectLiteral { 497 | next_token, _ := self.get_token() 498 | if next_token.Text() == ":" { 499 | current_token.SetType("TK_WORD") 500 | } 501 | } 502 | 503 | if self.start_of_statement(*current_token) { 504 | } else if current_token.WantedNewLine() && !self.is_expression(self.flags.mode) && (self.last_type != "TK_OPERATOR" || (self.flags.last_text == "--" || self.flags.last_text == "++")) && self.last_type != "TK_EQUALS" && (self.options["preserve_newlines"].(bool) || !(self.last_type == "TK_RESERVED" && utils.InStrArray(self.flags.last_text, []string{"var", "let", "const", "set", "get"}))) { 505 | self.print_newline(false, false) 506 | } 507 | 508 | if self.flags.do_block && !self.flags.do_while { 509 | if current_token.Type() == "TK_RESERVED" && current_token.Text() == "while" { 510 | self.output.space_before_token = true 511 | self.print_token(*current_token, "") 512 | self.output.space_before_token = true 513 | self.flags.do_while = true 514 | return 515 | } else { 516 | self.print_newline(false, false) 517 | self.flags.do_block = false 518 | } 519 | } 520 | 521 | if self.flags.if_block { 522 | if (!self.flags.else_block) && (current_token.Type() == "TK_RESERVED" && current_token.Text() == "else") { 523 | self.flags.else_block = true 524 | } else { 525 | for self.flags.mode == Statement { 526 | self.restore_mode() 527 | } 528 | self.flags.if_block = false 529 | } 530 | } 531 | 532 | if current_token.Type() == "TK_RESERVED" && (current_token.Text() == "case" || (current_token.Text() == "default" && self.flags.in_case_statement)) { 533 | self.print_newline(false, false) 534 | 535 | if self.flags.case_body || self.options["jslint_happy"].(bool) { 536 | self.flags.case_body = false 537 | self.deindent() 538 | } 539 | 540 | self.print_token(*current_token, "") 541 | self.flags.in_case = true 542 | self.flags.in_case_statement = true 543 | return 544 | } 545 | 546 | if current_token.Type() == "TK_RESERVED" && current_token.Text() == "function" { 547 | 548 | if (self.flags.last_text == "}" || self.flags.last_text == ";") || (self.output.just_added_newline() && !utils.InStrArray(self.flags.last_text, []string{"[", "{", ":", "=", ","})) { 549 | if !self.output.just_added_blankline() && len(current_token.CommentsBefore()) == 0 { 550 | self.print_newline(false, false) 551 | self.print_newline(true, false) 552 | } 553 | } 554 | 555 | if self.last_type == "TK_RESERVED" || self.last_type == "TK_WORD" { 556 | if self.last_type == "TK_RESERVED" && utils.InStrArray(self.flags.last_text, []string{"get", "set", "new", "return", "export"}) { 557 | self.output.space_before_token = true 558 | } else if self.last_type == "TK_RESERVED" && self.flags.last_text == "default" && self.last_last_text == "export" { 559 | self.output.space_before_token = true 560 | } else { 561 | self.print_newline(false, false) 562 | } 563 | } else if self.last_type == "TK_OPERATOR" || self.flags.last_text == "=" { 564 | self.output.space_before_token = true 565 | } else if !self.flags.multiline_frame && (self.is_expression(self.flags.mode) || self.is_array(self.flags.mode)) { 566 | 567 | } else { 568 | self.print_newline(false, false) 569 | } 570 | } 571 | 572 | if utils.InStrArray(self.last_type, []string{"TK_COMMA", "TK_START_EXPR", "TK_EQUALS", "TK_OPERATOR"}) { 573 | 574 | if !self.start_of_object_property() { 575 | self.allow_wrap_or_preserved_newline(*current_token, false) 576 | } 577 | } 578 | 579 | if current_token.Type() == "TK_RESERVED" && utils.InStrArray(current_token.Text(), []string{"function", "get", "set"}) { 580 | self.print_token(*current_token, "") 581 | self.flags.last_word = current_token.Text() 582 | return 583 | } 584 | 585 | prefix := "NONE" 586 | if self.last_type == "TK_END_BLOCK" { 587 | if !(current_token.Type() == "TK_RESERVED" && utils.InStrArray(current_token.Text(), []string{"else", "catch", "finally"})) { 588 | prefix = "NEWLINE" 589 | } else { 590 | if utils.InStrArray(self.options["brace_style"].(string), []string{"expand", "end-expand"}) || (self.options["brace_style"].(string) == "none" && current_token.WantedNewLine()) { 591 | prefix = "NEWLINE" 592 | } else { 593 | prefix = "SPACE" 594 | self.output.space_before_token = true 595 | } 596 | } 597 | } else if self.last_type == "TK_SEMICOLON" && self.flags.mode == BlockStatement { 598 | prefix = "NEWLINE" 599 | } else if self.last_type == "TK_SEMICOLON" && self.is_expression(self.flags.mode) { 600 | prefix = "SPACE" 601 | } else if self.last_type == "TK_STRING" { 602 | prefix = "NEWLINE" 603 | } else if self.last_type == "TK_RESERVED" || self.last_type == "TK_WORD" || (self.flags.last_text == "*" && self.last_last_text == "function") { 604 | prefix = "SPACE" 605 | } else if self.last_type == "TK_START_BLOCK" { 606 | prefix = "NEWLINE" 607 | } else if self.last_type == "TK_END_EXPR" { 608 | self.output.space_before_token = true 609 | prefix = "NEWLINE" 610 | } 611 | 612 | if current_token.Type() == "TK_RESERVED" && utils.InStrArray(current_token.Text(), tokenizer.GetLineStarters()) && self.flags.last_text != ")" { 613 | 614 | if self.flags.last_text == "else" || self.flags.last_text == "export" { 615 | prefix = "SPACE" 616 | } else { 617 | prefix = "NEWLINE" 618 | } 619 | } 620 | 621 | if current_token.Type() == "TK_RESERVED" && utils.InStrArray(current_token.Text(), []string{"else", "catch", "finally"}) { 622 | if self.last_type != "TK_END_BLOCK" || self.options["brace_style"].(string) == "expand" || self.options["brace_style"].(string) == "end-expand" || (self.options["brace_style"].(string) == "none" && current_token.WantedNewLine()) { 623 | self.print_newline(false, false) 624 | } else { 625 | self.output.trim(true) 626 | 627 | if self.output.current_line.last() != "}" { 628 | self.print_newline(false, false) 629 | } 630 | 631 | self.output.space_before_token = true 632 | } 633 | } else if prefix == "NEWLINE" { 634 | if self.last_type == "TK_RESERVED" && self.is_special_word(self.flags.last_text) { 635 | self.output.space_before_token = true 636 | } else if self.last_type != "TK_END_EXPR" { 637 | if (self.last_type != "TK_START_EXPR" || !(current_token.Type() == "TK_RESERVED" && utils.InStrArray(current_token.Text(), []string{"var", "let", "const"}))) && self.flags.last_text != ":" { 638 | if current_token.Type() == "TK_RESERVED" && current_token.Text() == "if" && self.flags.last_text == "else" { 639 | self.output.space_before_token = true 640 | } else { 641 | self.print_newline(false, false) 642 | } 643 | } 644 | } else if current_token.Type() == "TK_RESERVED" && utils.InStrArray(current_token.Text(), tokenizer.GetLineStarters()) && self.flags.last_text != ")" { 645 | self.print_newline(false, false) 646 | } 647 | } else if self.flags.multiline_frame && self.is_array(self.flags.mode) && self.flags.last_text == "," && self.last_last_text == "}" { 648 | self.print_newline(false, false) 649 | } else if prefix == "SPACE" { 650 | self.output.space_before_token = true 651 | } 652 | 653 | self.print_token(*current_token, "") 654 | self.flags.last_word = current_token.Text() 655 | 656 | if current_token.Type() == "TK_RESERVED" && current_token.Text() == "do" { 657 | self.flags.do_block = true 658 | } 659 | 660 | if current_token.Type() == "TK_RESERVED" && current_token.Text() == "if" { 661 | self.flags.if_block = true 662 | } 663 | 664 | } 665 | 666 | func (self *jsbeautifier) handle_semicolon(current_token *tokenizer.Token) { 667 | if self.start_of_statement(*current_token) { 668 | self.output.space_before_token = false 669 | } 670 | for self.flags.mode == Statement && !self.flags.if_block && !self.flags.do_block { 671 | self.restore_mode() 672 | } 673 | 674 | self.print_token(*current_token, "") 675 | } 676 | 677 | func (self *jsbeautifier) handle_string(current_token *tokenizer.Token) { 678 | if self.start_of_statement(*current_token) { 679 | self.output.space_before_token = true 680 | } else if self.last_type == "TK_RESERVED" || self.last_type == "TK_WORD" { 681 | self.output.space_before_token = true 682 | } else if utils.InStrArray(self.last_type, []string{"TK_COMMA", "TK_START_EXPR", "TK_EQUALS", "TK_OPERATOR"}) { 683 | if !self.start_of_object_property() { 684 | self.allow_wrap_or_preserved_newline(*current_token, false) 685 | } 686 | } else { 687 | self.print_newline(false, false) 688 | } 689 | 690 | self.print_token(*current_token, "") 691 | } 692 | 693 | func (self *jsbeautifier) handle_equals(current_token *tokenizer.Token) { 694 | if self.start_of_statement(*current_token) { 695 | 696 | } 697 | 698 | if self.flags.declaration_statement { 699 | self.flags.declaration_assignment = true 700 | } 701 | 702 | self.output.space_before_token = true 703 | self.print_token(*current_token, "") 704 | self.output.space_before_token = true 705 | } 706 | 707 | func (self *jsbeautifier) handle_comma(current_token *tokenizer.Token) { 708 | if self.flags.declaration_statement { 709 | if self.is_expression(self.flags.parent.mode) { 710 | self.flags.declaration_assignment = false 711 | } 712 | self.print_token(*current_token, "") 713 | 714 | if self.flags.declaration_assignment { 715 | self.flags.declaration_assignment = false 716 | self.print_newline(false, true) 717 | } else { 718 | self.output.space_before_token = true 719 | } 720 | 721 | return 722 | } 723 | 724 | self.print_token(*current_token, "") 725 | 726 | if self.flags.mode == ObjectLiteral || (self.flags.mode == Statement && self.flags.parent.mode == ObjectLiteral) { 727 | if self.flags.mode == Statement { 728 | self.restore_mode() 729 | } 730 | 731 | self.print_newline(false, false) 732 | } else { 733 | self.output.space_before_token = true 734 | } 735 | } 736 | 737 | func (self *jsbeautifier) handle_operator(current_token *tokenizer.Token) { 738 | if self.start_of_statement(*current_token) { 739 | 740 | } 741 | 742 | if self.last_type == "TK_RESERVED" && self.is_special_word(self.flags.last_text) { 743 | self.output.space_before_token = true 744 | self.print_token(*current_token, "") 745 | return 746 | } 747 | 748 | if current_token.Text() == "*" && self.last_type == "TK_DOT" { 749 | self.print_token(*current_token, "") 750 | return 751 | } 752 | 753 | if current_token.Text() == ":" && self.flags.in_case { 754 | self.flags.case_body = true 755 | self.indent() 756 | self.print_token(*current_token, "") 757 | self.print_newline(false, false) 758 | self.flags.in_case = false 759 | return 760 | } 761 | 762 | if current_token.Text() == "::" { 763 | self.print_token(*current_token, "") 764 | return 765 | } 766 | 767 | if current_token.WantedNewLine() && (current_token.Text() == "--" || current_token.Text() == "++") { 768 | self.print_newline(false, true) 769 | } 770 | 771 | if self.last_type == "TK_OPERATOR" { 772 | self.allow_wrap_or_preserved_newline(*current_token, false) 773 | } 774 | 775 | space_before := true 776 | space_after := true 777 | 778 | if utils.InStrArray(current_token.Text(), []string{"--", "++", "!", "~"}) || ((current_token.Text() == "+" || current_token.Text() == "-") && (utils.InStrArray(self.last_type, []string{"TK_START_BLOCK", "TK_START_EXPR", "TK_EQUALS", "TK_OPERATOR"}) || utils.InStrArray(self.flags.last_text, tokenizer.GetLineStarters()) || self.flags.last_text == ",")) { 779 | space_after = false 780 | space_before = false 781 | 782 | if self.flags.last_text == ";" && self.is_expression(self.flags.mode) { 783 | space_before = true 784 | } 785 | 786 | if self.last_type == "TK_RESERVED" || self.last_type == "TK_END_EXPR" { 787 | space_before = true 788 | } else if self.last_type == "TK_OPERATOR" { 789 | space_before = (utils.InStrArray(current_token.Text(), []string{"--", "-"}) && utils.InStrArray(self.flags.last_text, []string{"--", "-"})) || (utils.InStrArray(current_token.Text(), []string{"++", "+"}) && utils.InStrArray(self.flags.last_text, []string{"++", "+"})) 790 | } 791 | 792 | if self.flags.mode == BlockStatement && (self.flags.last_text == "{" || self.flags.last_text == ";") { 793 | self.print_newline(false, false) 794 | } 795 | } else if current_token.Text() == ":" { 796 | if self.flags.ternary_depth == 0 { 797 | space_before = false 798 | } else { 799 | self.flags.ternary_depth -= 1 800 | } 801 | } else if current_token.Text() == "?" { 802 | self.flags.ternary_depth += 1 803 | } else if current_token.Text() == "*" && self.last_type == "TK_RESERVED" && self.flags.last_text == "function" { 804 | space_before = false 805 | space_after = false 806 | } 807 | 808 | if space_before { 809 | self.output.space_before_token = true 810 | } 811 | 812 | self.print_token(*current_token, "") 813 | 814 | if space_after { 815 | self.output.space_before_token = true 816 | } 817 | } 818 | 819 | func (self *jsbeautifier) handle_block_comment(current_token *tokenizer.Token) { 820 | lines := strings.Split(strings.Replace(current_token.Text(), "\x0d", "", -1), "\x0a") 821 | 822 | javadoc := true 823 | starless := true 824 | last_indent := current_token.WhitespaceBefore() 825 | last_indent_length := len(last_indent) 826 | 827 | self.print_newline(false, true) 828 | 829 | if len(lines) > 1 { 830 | for _, l := range lines[1:] { 831 | trims := strings.TrimSpace(l) 832 | if trims == "" || trims[0] != '*' { 833 | javadoc = false 834 | break 835 | } 836 | } 837 | 838 | if !javadoc { 839 | for _, l := range lines[1:] { 840 | trims := strings.TrimSpace(l) 841 | if trims != "" && !strings.HasPrefix(l, last_indent) { 842 | starless = false 843 | break 844 | } 845 | } 846 | } 847 | } else { 848 | javadoc = false 849 | starless = false 850 | } 851 | 852 | self.print_token(*current_token, lines[0]) 853 | for _, l := range lines[1:] { 854 | self.print_newline(false, true) 855 | if javadoc { 856 | self.print_token(*current_token, " "+strings.TrimSpace(l)) 857 | } else if starless && len(l) > last_indent_length { 858 | self.print_token(*current_token, l[last_indent_length:]) 859 | } else { 860 | self.output.add_token(l) 861 | } 862 | } 863 | self.print_newline(false, true) 864 | } 865 | 866 | func (self *jsbeautifier) handle_inline_comment(current_token *tokenizer.Token) { 867 | self.output.space_before_token = true 868 | self.print_token(*current_token, "") 869 | self.output.space_before_token = true 870 | } 871 | 872 | func (self *jsbeautifier) handle_comment(current_token *tokenizer.Token) { 873 | if current_token.WantedNewLine() { 874 | self.print_newline(false, true) 875 | } 876 | if !current_token.WantedNewLine() { 877 | self.output.trim(true) 878 | } 879 | 880 | self.output.space_before_token = true 881 | self.print_token(*current_token, "") 882 | self.print_newline(false, true) 883 | } 884 | 885 | func (self *jsbeautifier) handle_dot(current_token *tokenizer.Token) { 886 | if self.start_of_statement(*current_token) { 887 | } 888 | 889 | if self.last_type == "TK_RESERVED" && self.is_special_word(self.flags.last_text) { 890 | self.output.space_before_token = true 891 | } else { 892 | self.allow_wrap_or_preserved_newline(*current_token, self.flags.last_text == ")" && self.options["break_chained_methods"].(bool)) 893 | } 894 | self.print_token(*current_token, "") 895 | } 896 | 897 | func (self *jsbeautifier) handle_unknown(current_token *tokenizer.Token) { 898 | self.print_token(*current_token, "") 899 | if current_token.Text()[len(current_token.Text())-1:] == "\n" { 900 | self.print_newline(false, false) 901 | } 902 | } 903 | 904 | func (self *jsbeautifier) handle_eof(current_token *tokenizer.Token) { 905 | for self.flags.mode == Statement { 906 | self.restore_mode() 907 | } 908 | } 909 | 910 | func (self *jsbeautifier) blank_state(s *string) *string { 911 | if self.options["jslint_happy"].(bool) { 912 | self.options["space_after_anon_function"] = true 913 | } 914 | 915 | if self.options["indent_with_tabs"].(bool) { 916 | self.options["indent_char"] = "\t" 917 | self.options["indent_size"] = 1 918 | } 919 | 920 | self.indent_string = "" 921 | 922 | for i := 0; i < self.options["indent_size"].(int); i++ { 923 | self.indent_string += self.options["indent_char"].(string) 924 | } 925 | 926 | self.baseIndentString = "" 927 | self.last_type = "TK_START_BLOCK" 928 | self.last_last_text = "" 929 | 930 | preindent_index := 0 931 | 932 | if s != nil && len(*s) > 0 { 933 | for preindent_index < len(*s) && (string((*s)[preindent_index]) == " " || string((*s)[preindent_index]) == "\t") { 934 | self.baseIndentString += string((*s)[preindent_index]) 935 | preindent_index++ 936 | } 937 | temps := string((*s)[preindent_index:]) 938 | s = &temps 939 | } 940 | 941 | self.output = NewOutput(self.indent_string, self.baseIndentString) 942 | 943 | self.set_mode(BlockStatement) 944 | return s 945 | } 946 | 947 | func DefaultOptions() map[string]interface{} { 948 | return default_options 949 | } 950 | func New(options optargs.MapType) *jsbeautifier { 951 | b := new(jsbeautifier) 952 | 953 | b.options.Copy(options) 954 | b.blank_state(nil) 955 | return b 956 | } 957 | 958 | func Beautify(data *string, options optargs.MapType) (string, error) { 959 | b := New(options) 960 | return b.beautify(data) 961 | } 962 | 963 | func BeautifyFile(file string, options optargs.MapType) *string { 964 | if file == "-" { 965 | panic("Reading stdin not implemented yet") 966 | } 967 | 968 | data, _ := ioutil.ReadFile(file) 969 | sdata := string(data) 970 | val, _ := Beautify(&sdata, options) 971 | return &val 972 | } 973 | -------------------------------------------------------------------------------- /jsbeautifier/jsbeautifier_test.go: -------------------------------------------------------------------------------- 1 | package jsbeautifier 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ditashi/jsbeautifier-go/optargs" 7 | ) 8 | 9 | // Copyright (c) 2014 Ditashi Sayomi 10 | 11 | // Permission is hereby granted, free of charge, to any person obtaining a copy 12 | // of this software and associated documentation files (the "Software"), to deal 13 | // in the Software without restriction, including without limitation the rights 14 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | // copies of the Software, and to permit persons to whom the Software is 16 | // furnished to do so, subject to the following conditions: 17 | 18 | // The above copyright notice and this permission notice shall be included in all 19 | // copies or substantial portions of the Software. 20 | 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | // SOFTWARE. 28 | 29 | var ts *testing.T 30 | 31 | var test_options optargs.MapType 32 | 33 | func TestBeautifier(t *testing.T) { 34 | ts = t 35 | 36 | test_options = default_options 37 | 38 | test_options["indent_size"] = 4 39 | test_options["indent_char"] = " " 40 | test_options["preserve_newlines"] = true 41 | test_options["jslint_happy"] = false 42 | test_options["keep_array_indentation"] = false 43 | test_options["brace_style"] = "collapse" 44 | 45 | // Unicode Support 46 | test("var \u0CA0_\u0CA0 = \"hi\";") 47 | test("var \u00e4 x = {\n \u00e4rgerlich: true\n};") 48 | 49 | // End With Newline - (eof = "\n") 50 | test_options["end_with_newline"] = true 51 | test("", "\n") 52 | test(" return .5", " return .5\n") 53 | test(" \n\nreturn .5\n\n\n\n", " return .5\n") 54 | test("\n") 55 | 56 | // End With Newline - (eof = "") 57 | test_options["end_with_newline"] = false 58 | test("") 59 | test(" return .5") 60 | test(" \n\nreturn .5\n\n\n\n", " return .5") 61 | test("\n", "") 62 | 63 | // New Test Suite 64 | 65 | // Old tests 66 | test("") 67 | test(" return .5") 68 | test(" return .5;\n a();") 69 | test(" return .5;\n a();") 70 | test(" return .5;\n a();") 71 | test(" < div") 72 | test("a = 1", "a = 1") 73 | test("a=1", "a = 1") 74 | test("(3) / 2") 75 | test("[\"a\", \"b\"].join(\"\")") 76 | test("a();\n\nb();") 77 | test("var a = 1 var b = 2", "var a = 1\nvar b = 2") 78 | test("var a=1, b=c[d], e=6;", "var a = 1,\n b = c[d],\n e = 6;") 79 | test("var a,\n b,\n c;") 80 | test("let a = 1 let b = 2", "let a = 1\nlet b = 2") 81 | test("let a=1, b=c[d], e=6;", "let a = 1,\n b = c[d],\n e = 6;") 82 | test("let a,\n b,\n c;") 83 | test("const a = 1 const b = 2", "const a = 1\nconst b = 2") 84 | test("const a=1, b=c[d], e=6;", "const a = 1,\n b = c[d],\n e = 6;") 85 | test("const a,\n b,\n c;") 86 | test("a = \" 12345 \"") 87 | test("a = ' 12345 '") 88 | test("if (a == 1) b = 2;") 89 | test("if(1){2}else{3}", "if (1) {\n 2\n} else {\n 3\n}") 90 | test("if(1||2);", "if (1 || 2);") 91 | test("(a==1)||(b==2)", "(a == 1) || (b == 2)") 92 | test("var a = 1 if (2) 3;", "var a = 1\nif (2) 3;") 93 | test("a = a + 1") 94 | test("a = a == 1") 95 | test("/12345[^678]*9+/.match(a)") 96 | test("a /= 5") 97 | test("a = 0.5 * 3") 98 | test("a *= 10.55") 99 | test("a < .5") 100 | test("a <= .5") 101 | test("a<.5", "a < .5") 102 | test("a<=.5", "a <= .5") 103 | test("a = 0xff;") 104 | test("a=0xff+4", "a = 0xff + 4") 105 | test("a = [1, 2, 3, 4]") 106 | test("F*(g/=f)*g+b", "F * (g /= f) * g + b") 107 | test("a.b({c:d})", "a.b({\n c: d\n})") 108 | test("a.b\n(\n{\nc:\nd\n}\n)", "a.b({\n c: d\n})") 109 | test("a.b({c:\"d\"})", "a.b({\n c: \"d\"\n})") 110 | test("a.b\n(\n{\nc:\n\"d\"\n}\n)", "a.b({\n c: \"d\"\n})") 111 | test("a=!b", "a = !b") 112 | test("a=!!b", "a = !!b") 113 | test("a?b:c", "a ? b : c") 114 | test("a?1:2", "a ? 1 : 2") 115 | test("a?(b):c", "a ? (b) : c") 116 | test("x={a:1,b:w==\"foo\"?x:y,c:z}", "x = {\n a: 1,\n b: w == \"foo\" ? x : y,\n c: z\n}") 117 | test("x=a?b?c?d:e:f:g;", "x = a ? b ? c ? d : e : f : g;") 118 | test("x=a?b?c?d:{e1:1,e2:2}:f:g;", "x = a ? b ? c ? d : {\n e1: 1,\n e2: 2\n} : f : g;") 119 | test("function void(void) {}") 120 | test("if(!a)foo();", "if (!a) foo();") 121 | test("a=~a", "a = ~a") 122 | test("a;/*comment*/b;", "a; /*comment*/\nb;") 123 | test("a;/* comment */b;", "a; /* comment */\nb;") 124 | 125 | // simple comments don't get touched at all 126 | test("a;/*\ncomment\n*/b;", "a;\n/*\ncomment\n*/\nb;") 127 | test("a;/**\n* javadoc\n*/b;", "a;\n/**\n * javadoc\n */\nb;") 128 | test("a;/**\n\nno javadoc\n*/b;", "a;\n/**\n\nno javadoc\n*/\nb;") 129 | 130 | // comment blocks detected and reindented even w/o javadoc starter 131 | test("a;/*\n* javadoc\n*/b;", "a;\n/*\n * javadoc\n */\nb;") 132 | test("if(a)break;", "if (a) break;") 133 | test("if(a){break}", "if (a) {\n break\n}") 134 | test("if((a))foo();", "if ((a)) foo();") 135 | test("for(var i=0;;) a", "for (var i = 0;;) a") 136 | test("for(var i=0;;)\na", "for (var i = 0;;)\n a") 137 | test("a++;") 138 | test("for(;;i++)a()", "for (;; i++) a()") 139 | test("for(;;i++)\na()", "for (;; i++)\n a()") 140 | test("for(;;++i)a", "for (;; ++i) a") 141 | test("return(1)", "return (1)") 142 | test("try{a();}catch(b){c();}finally{d();}", "try {\n a();\n} catch (b) {\n c();\n} finally {\n d();\n}") 143 | 144 | // magic function call 145 | test("(xx)()") 146 | 147 | // another magic function call 148 | test("a[1]()") 149 | test("if(a){b();}else if(c) foo();", "if (a) {\n b();\n} else if (c) foo();") 150 | test("switch(x) {case 0: case 1: a(); break; default: break}", "switch (x) {\n case 0:\n case 1:\n a();\n break;\n default:\n break\n}") 151 | test("switch(x){case -1:break;case !y:break;}", "switch (x) {\n case -1:\n break;\n case !y:\n break;\n}") 152 | test("a !== b") 153 | test("if (a) b(); else c();", "if (a) b();\nelse c();") 154 | 155 | // typical greasemonkey start 156 | test("// comment\n(function something() {})") 157 | 158 | // duplicating newlines 159 | test("{\n\n x();\n\n}") 160 | test("if (a in b) foo();") 161 | test("if(X)if(Y)a();else b();else c();", "if (X)\n if (Y) a();\n else b();\nelse c();") 162 | test("if (foo) bar();\nelse break") 163 | test("var a, b;") 164 | test("var a = new function();") 165 | test("new function") 166 | test("var a, b") 167 | test("{a:1, b:2}", "{\n a: 1,\n b: 2\n}") 168 | test("a={1:[-1],2:[+1]}", "a = {\n 1: [-1],\n 2: [+1]\n}") 169 | test("var l = {'a':'1', 'b':'2'}", "var l = {\n 'a': '1',\n 'b': '2'\n}") 170 | test("if (template.user[n] in bk) foo();") 171 | test("return 45") 172 | test("return this.prevObject ||\n\n this.constructor(null);") 173 | test("If[1]") 174 | test("Then[1]") 175 | test("a = 1e10") 176 | test("a = 1.3e10") 177 | test("a = 1.3e-10") 178 | test("a = -1.3e-10") 179 | test("a = 1e-10") 180 | test("a = e - 10") 181 | test("a = 11-10", "a = 11 - 10") 182 | test("a = 1;// comment", "a = 1; // comment") 183 | test("a = 1; // comment") 184 | test("a = 1;\n // comment", "a = 1;\n// comment") 185 | test("a = [-1, -1, -1]") 186 | 187 | // The exact formatting these should have is open for discussion, but they are at least reasonable 188 | test("a = [ // comment\n -1, -1, -1\n]") 189 | test("var a = [ // comment\n -1, -1, -1\n]") 190 | test("a = [ // comment\n -1, // comment\n -1, -1\n]") 191 | test("var a = [ // comment\n -1, // comment\n -1, -1\n]") 192 | test("o = [{a:b},{c:d}]", "o = [{\n a: b\n}, {\n c: d\n}]") 193 | 194 | // was: extra space appended 195 | test("if (a) {\n do();\n}") 196 | 197 | // if/else statement with empty body 198 | test("if (a) {\n// comment\n}else{\n// comment\n}", "if (a) {\n // comment\n} else {\n // comment\n}") 199 | 200 | // multiple comments indentation 201 | test("if (a) {\n// comment\n// comment\n}", "if (a) {\n // comment\n // comment\n}") 202 | test("if (a) b() else c();", "if (a) b()\nelse c();") 203 | test("if (a) b() else if c() d();", "if (a) b()\nelse if c() d();") 204 | test("{}") 205 | test("{\n\n}") 206 | test("do { a(); } while ( 1 );", "do {\n a();\n} while (1);") 207 | test("do {} while (1);") 208 | test("do {\n} while (1);", "do {} while (1);") 209 | test("do {\n\n} while (1);") 210 | test("var a = x(a, b, c)") 211 | test("delete x if (a) b();", "delete x\nif (a) b();") 212 | test("delete x[x] if (a) b();", "delete x[x]\nif (a) b();") 213 | test("for(var a=1,b=2)d", "for (var a = 1, b = 2) d") 214 | test("for(var a=1,b=2,c=3) d", "for (var a = 1, b = 2, c = 3) d") 215 | test("for(var a=1,b=2,c=3;d<3;d++)\ne", "for (var a = 1, b = 2, c = 3; d < 3; d++)\n e") 216 | test("function x(){(a||b).c()}", "function x() {\n (a || b).c()\n}") 217 | test("function x(){return - 1}", "function x() {\n return -1\n}") 218 | test("function x(){return ! a}", "function x() {\n return !a\n}") 219 | test("x => x") 220 | test("(x) => x") 221 | test("x => { x }", "x => {\n x\n}") 222 | test("(x) => { x }", "(x) => {\n x\n}") 223 | 224 | // a common snippet in jQuery plugins 225 | test("settings = $.extend({},defaults,settings);", "settings = $.extend({}, defaults, settings);") 226 | test("$http().then().finally().default()") 227 | test("$http()\n.then()\n.finally()\n.default()", "$http()\n .then()\n .finally()\n .default()") 228 | test("$http().when.in.new.catch().throw()") 229 | test("$http()\n.when\n.in\n.new\n.catch()\n.throw()", "$http()\n .when\n .in\n .new\n .catch()\n .throw()") 230 | test("{xxx;}()", "{\n xxx;\n}()") 231 | test("a = 'a'\nb = 'b'") 232 | test("a = /reg/exp") 233 | test("a = /reg/") 234 | test("/abc/.test()") 235 | test("/abc/i.test()") 236 | test("{/abc/i.test()}", "{\n /abc/i.test()\n}") 237 | test("var x=(a)/a;", "var x = (a) / a;") 238 | test("x != -1") 239 | test("for (; s-->0;)t", "for (; s-- > 0;) t") 240 | test("for (; s++>0;)u", "for (; s++ > 0;) u") 241 | test("a = s++>s--;", "a = s++ > s--;") 242 | test("a = s++>--s;", "a = s++ > --s;") 243 | test("{x=#1=[]}", "{\n x = #1=[]\n}") 244 | test("{a:#1={}}", "{\n a: #1={}\n}") 245 | test("{a:#1#}", "{\n a: #1#\n}") 246 | test("\"incomplete-string") 247 | test("'incomplete-string") 248 | test("/incomplete-regex") 249 | test("`incomplete-template-string") 250 | test("{a:1},{a:2}", "{\n a: 1\n}, {\n a: 2\n}") 251 | test("var ary=[{a:1}, {a:2}];", "var ary = [{\n a: 1\n}, {\n a: 2\n}];") 252 | // incomplete 253 | test("{a:#1", "{\n a: #1") 254 | 255 | // incomplete 256 | test("{a:#", "{\n a: #") 257 | 258 | // incomplete 259 | test("}}}", "}\n}\n}") 260 | test("") 261 | 262 | // incomplete regexp 263 | test("a=/regexp", "a = /regexp") 264 | test("{a:#1=[],b:#1#,c:#999999#}", "{\n a: #1=[],\n b: #1#,\n c: #999999#\n}") 265 | test("a = 1e+2") 266 | test("a = 1e-2") 267 | test("do{x()}while(a>1)", "do {\n x()\n} while (a > 1)") 268 | test("x(); /reg/exp.match(something)", "x();\n/reg/exp.match(something)") 269 | test("something();(", "something();\n(") 270 | test("#!she/bangs, she bangs\nf=1", "#!she/bangs, she bangs\n\nf = 1") 271 | test("#!she/bangs, she bangs\n\nf=1", "#!she/bangs, she bangs\n\nf = 1") 272 | test("#!she/bangs, she bangs\n\n/* comment */") 273 | test("#!she/bangs, she bangs\n\n\n/* comment */") 274 | test("#") 275 | test("#!") 276 | test("function namespace::something()") 277 | test("") 278 | test("", "") 279 | test("{foo();--bar;}", "{\n foo();\n --bar;\n}") 280 | test("{foo();++bar;}", "{\n foo();\n ++bar;\n}") 281 | test("{--bar;}", "{\n --bar;\n}") 282 | test("{++bar;}", "{\n ++bar;\n}") 283 | test("if(true)++a;", "if (true) ++a;") 284 | test("if(true)\n++a;", "if (true)\n ++a;") 285 | test("if(true)--a;", "if (true) --a;") 286 | test("if(true)\n--a;", "if (true)\n --a;") 287 | 288 | // Handling of newlines around unary ++ and -- operators 289 | test("{foo\n++bar;}", "{\n foo\n ++bar;\n}") 290 | test("{foo++\nbar;}", "{\n foo++\n bar;\n}") 291 | 292 | // This is invalid, but harder to guard against. Issue #203. 293 | test("{foo\n++\nbar;}", "{\n foo\n ++\n bar;\n}") 294 | 295 | // regexps 296 | test("a(/abc\\/\\/def/);b()", "a(/abc\\/\\/def/);\nb()") 297 | test("a(/a[b\\[\\]c]d/);b()", "a(/a[b\\[\\]c]d/);\nb()") 298 | 299 | // incomplete char class 300 | test("a(/a[b\\[") 301 | 302 | // allow unescaped / in char classes 303 | test("a(/[a/b]/);b()", "a(/[a/b]/);\nb()") 304 | test("typeof /foo\\//;") 305 | test("yield /foo\\//;") 306 | test("throw /foo\\//;") 307 | test("do /foo\\//;") 308 | test("return /foo\\//;") 309 | test("switch (a) {\n case /foo\\//:\n b\n}") 310 | test("if (a) /foo\\//\nelse /foo\\//;") 311 | test("if (foo) /regex/.test();") 312 | test("function foo() {\n return [\n \"one\",\n \"two\"\n ];\n}") 313 | test("a=[[1,2],[4,5],[7,8]]", "a = [\n [1, 2],\n [4, 5],\n [7, 8]\n]") 314 | test("a=[[1,2],[4,5],function(){},[7,8]]", "a = [\n [1, 2],\n [4, 5],\n function() {},\n [7, 8]\n]") 315 | test("a=[[1,2],[4,5],function(){},function(){},[7,8]]", "a = [\n [1, 2],\n [4, 5],\n function() {},\n function() {},\n [7, 8]\n]") 316 | test("a=[[1,2],[4,5],function(){},[7,8]]", "a = [\n [1, 2],\n [4, 5],\n function() {},\n [7, 8]\n]") 317 | test("a=[b,c,function(){},function(){},d]", "a = [b, c, function() {}, function() {}, d]") 318 | test("a=[b,c,\nfunction(){},function(){},d]", "a = [b, c,\n function() {},\n function() {},\n d\n]") 319 | test("a=[a[1],b[4],c[d[7]]]", "a = [a[1], b[4], c[d[7]]]") 320 | test("[1,2,[3,4,[5,6],7],8]", "[1, 2, [3, 4, [5, 6], 7], 8]") 321 | 322 | test("[[[\"1\",\"2\"],[\"3\",\"4\"]],[[\"5\",\"6\",\"7\"],[\"8\",\"9\",\"0\"]],[[\"1\",\"2\",\"3\"],[\"4\",\"5\",\"6\",\"7\"],[\"8\",\"9\",\"0\"]]]", "[\n [\n [\"1\", \"2\"],\n [\"3\", \"4\"]\n ],\n [\n [\"5\", \"6\", \"7\"],\n [\"8\", \"9\", \"0\"]\n ],\n [\n [\"1\", \"2\", \"3\"],\n [\"4\", \"5\", \"6\", \"7\"],\n [\"8\", \"9\", \"0\"]\n ]\n]") 323 | test("{[x()[0]];indent;}", "{\n [x()[0]];\n indent;\n}") 324 | 325 | test("{{}/z/}", "{\n {}\n /z/\n}") 326 | test("return ++i", "return ++i") 327 | test("return !!x", "return !!x") 328 | test("return !x", "return !x") 329 | test("return [1,2]", "return [1, 2]") 330 | test("return;", "return;") 331 | test("return\nfunc", "return\nfunc") 332 | test("catch(e)", "catch (e)") 333 | test("yield [1, 2]") 334 | 335 | test("var a=1,b={foo:2,bar:3},{baz:4,wham:5},c=4;", 336 | "var a = 1,\n b = {\n foo: 2,\n bar: 3\n },\n {\n baz: 4,\n wham: 5\n }, c = 4;") 337 | test("var a=1,b={foo:2,bar:3},{baz:4,wham:5},\nc=4;", 338 | "var a = 1,\n b = {\n foo: 2,\n bar: 3\n },\n {\n baz: 4,\n wham: 5\n },\n c = 4;") 339 | 340 | // inline comment 341 | test("function x(/*int*/ start, /*string*/ foo)", "function x( /*int*/ start, /*string*/ foo)") 342 | 343 | // javadoc comment 344 | test("/**\n* foo\n*/", "/**\n * foo\n */") 345 | test("{\n/**\n* foo\n*/\n}", "{\n /**\n * foo\n */\n}") 346 | 347 | // starless block comment 348 | test("/**\nfoo\n*/") 349 | test("/**\nfoo\n**/") 350 | test("/**\nfoo\nbar\n**/") 351 | test("/**\nfoo\n\nbar\n**/") 352 | test("/**\nfoo\n bar\n**/") 353 | test("{\n/**\nfoo\n*/\n}", "{\n /**\n foo\n */\n}") 354 | test("{\n/**\nfoo\n**/\n}", "{\n /**\n foo\n **/\n}") 355 | test("{\n/**\nfoo\nbar\n**/\n}", "{\n /**\n foo\n bar\n **/\n}") 356 | test("{\n/**\nfoo\n\nbar\n**/\n}", "{\n /**\n foo\n\n bar\n **/\n}") 357 | test("{\n/**\nfoo\n bar\n**/\n}", "{\n /**\n foo\n bar\n **/\n}") 358 | test("{\n /**\n foo\nbar\n **/\n}") 359 | 360 | test("var a,b,c=1,d,e,f=2;", "var a, b, c = 1,\n d, e, f = 2;") 361 | test("var a,b,c=[],d,e,f=2;", "var a, b, c = [],\n d, e, f = 2;") 362 | test("function() {\n var a, b, c, d, e = [],\n f;\n}") 363 | 364 | test("do/regexp/;\nwhile(1);", "do /regexp/;\nwhile (1);") 365 | 366 | test("var a = a,\na;\nb = {\nb\n}", "var a = a,\n a;\nb = {\n b\n}") 367 | 368 | test("var a = a,\n /* c */\n b;") 369 | test("var a = a,\n // c\n b;") 370 | 371 | test("foo.(\"bar\");") 372 | 373 | test("if (a) a()\nelse b()\nnewline()") 374 | test("if (a) a()\nnewline()") 375 | test("a=typeof(x)", "a = typeof(x)") 376 | 377 | test("var a = function() {\n return null;\n },\n b = false;") 378 | 379 | test("var a = function() {\n func1()\n}") 380 | test("var a = function() {\n func1()\n}\nvar b = function() {\n func2()\n}") 381 | 382 | // Code with and without semicolons 383 | test("var whatever = require(\"whatever\");\nfunction() {\n a = 6;\n}", 384 | "var whatever = require(\"whatever\");\n\nfunction() {\n a = 6;\n}") 385 | test("var whatever = require(\"whatever\")\nfunction() {\n a = 6\n}", "var whatever = require(\"whatever\")\n\nfunction() {\n a = 6\n}") 386 | 387 | test_options["space_after_anon_function"] = true 388 | 389 | test("switch(x) {case 0: case 1: a(); break; default: break}", 390 | "switch (x) {\n case 0:\n case 1:\n a();\n break;\n default:\n break\n}") 391 | test("switch(x){case -1:break;case !y:break;}", "switch (x) {\n case -1:\n break;\n case !y:\n break;\n}") 392 | test("a=typeof(x)", "a = typeof (x)") 393 | test("x();\n\nfunction(){}", "x();\n\nfunction () {}") 394 | test("function () {\n var a, b, c, d, e = [],\n f;\n}") 395 | test("// comment 1\n(function()", "// comment 1\n(function ()") 396 | test("var o1=$.extend(a);function(){alert(x);}", "var o1 = $.extend(a);\n\nfunction () {\n alert(x);\n}") 397 | test("function* () {\n yield 1;\n}") 398 | 399 | test_options["space_after_anon_function"] = false 400 | 401 | test_options["jslint_happy"] = true 402 | 403 | test("x();\n\nfunction(){}", "x();\n\nfunction () {}") 404 | test("function () {\n var a, b, c, d, e = [],\n f;\n}") 405 | test("switch(x) {case 0: case 1: a(); break; default: break}", "switch (x) {\ncase 0:\ncase 1:\n a();\n break;\ndefault:\n break\n}") 406 | test("switch(x){case -1:break;case !y:break;}", "switch (x) {\ncase -1:\n break;\ncase !y:\n break;\n}") 407 | test("// comment 1\n(function()", "// comment 1\n(function ()") 408 | test("var o1=$.extend(a);function(){alert(x);}", "var o1 = $.extend(a);\n\nfunction () {\n alert(x);\n}") 409 | test("a=typeof(x)", "a = typeof (x)") 410 | test("function* () {\n yield 1;\n}") 411 | 412 | test_options["jslint_happy"] = false 413 | test("switch(x) {case 0: case 1: a(); break; default: break}", "switch (x) {\n case 0:\n case 1:\n a();\n break;\n default:\n break\n}") 414 | test("switch(x){case -1:break;case !y:break;}", "switch (x) {\n case -1:\n break;\n case !y:\n break;\n}") 415 | 416 | test("// comment 2\n(function()", "// comment 2\n(function()") 417 | 418 | test("var a2, b2, c2, d2 = 0, c = function() {}, d = '';", "var a2, b2, c2, d2 = 0,\n c = function() {},\n d = '';") 419 | test("var a2, b2, c2, d2 = 0, c = function() {},\nd = '';", "var a2, b2, c2, d2 = 0,\n c = function() {},\n d = '';") 420 | test("var o2=$.extend(a);function(){alert(x);}", "var o2 = $.extend(a);\n\nfunction() {\n alert(x);\n}") 421 | test("function*() {\n yield 1;\n}") 422 | test("function* x() {\n yield 1;\n}") 423 | 424 | test("{\"x\":[{\"a\":1,\"b\":3},\n7,8,8,8,8,{\"b\":99},{\"a\":11}]}", "{\n \"x\": [{\n \"a\": 1,\n \"b\": 3\n },\n 7, 8, 8, 8, 8, {\n \"b\": 99\n }, {\n \"a\": 11\n }\n ]\n}") 425 | test("{\"x\":[{\"a\":1,\"b\":3},7,8,8,8,8,{\"b\":99},{\"a\":11}]}", "{\n \"x\": [{\n \"a\": 1,\n \"b\": 3\n }, 7, 8, 8, 8, 8, {\n \"b\": 99\n }, {\n \"a\": 11\n }]\n}") 426 | 427 | test("{\"1\":{\"1a\":\"1b\"},\"2\"}", "{\n \"1\": {\n \"1a\": \"1b\"\n },\n \"2\"\n}") 428 | test("{a:{a:b},c}", "{\n a: {\n a: b\n },\n c\n}") 429 | 430 | test("{[y[a]];keep_indent;}", "{\n [y[a]];\n keep_indent;\n}") 431 | 432 | test("if (x) {y} else { if (x) {y}}", "if (x) {\n y\n} else {\n if (x) {\n y\n }\n}") 433 | 434 | test("if (foo) one()\ntwo()\nthree()") 435 | test("if (1 + foo() && bar(baz()) / 2) one()\ntwo()\nthree()") 436 | test("if (1 + foo() && bar(baz()) / 2) one();\ntwo();\nthree();") 437 | 438 | test_options["indent_size"] = 1 439 | test_options["indent_char"] = " " 440 | test("{ one_char() }", "{\n one_char()\n}") 441 | 442 | test("var a,b=1,c=2", "var a, b = 1,\n c = 2") 443 | 444 | test_options["indent_size"] = 4 445 | test_options["indent_char"] = " " 446 | test("{ one_char() }", "{\n one_char()\n}") 447 | 448 | test_options["indent_size"] = 1 449 | test_options["indent_char"] = "\t" 450 | test("{ one_char() }", "{\n\tone_char()\n}") 451 | test("x = a ? b : c; x;", "x = a ? b : c;\nx;") 452 | 453 | test_options["indent_size"] = 5 454 | test_options["indent_char"] = " " 455 | test_options["indent_with_tabs"] = true 456 | 457 | test("{ one_char() }", "{\n\tone_char()\n}") 458 | test("x = a ? b : c; x;", "x = a ? b : c;\nx;") 459 | 460 | test_options["indent_size"] = 4 461 | test_options["indent_char"] = " " 462 | test_options["indent_with_tabs"] = false 463 | 464 | test_options["preserve_newlines"] = false 465 | test("var\na=dont_preserve_newlines;", "var a = dont_preserve_newlines;") 466 | 467 | test("function foo() {\n return 1;\n}\n\nfunction foo() {\n return 1;\n}") 468 | test("function foo() {\n return 1;\n}\nfunction foo() {\n return 1;\n}", "function foo() {\n return 1;\n}\n\nfunction foo() {\n return 1;\n}") 469 | test("function foo() {\n return 1;\n}\n\n\nfunction foo() {\n return 1;\n}", "function foo() {\n return 1;\n}\n\nfunction foo() {\n return 1;\n}") 470 | 471 | test_options["preserve_newlines"] = true 472 | test("var\na=do_preserve_newlines;", "var\n a = do_preserve_newlines;") 473 | test("// a\n// b\n\n// c\n// d") 474 | test("if (foo) // comment\n{\n bar();\n}") 475 | 476 | test_options["keep_array_indentation"] = false 477 | test("a = ['a', 'b', 'c',\n 'd', 'e', 'f']", 478 | "a = ['a', 'b', 'c',\n 'd', 'e', 'f'\n]") 479 | test("a = ['a', 'b', 'c',\n 'd', 'e', 'f',\n 'g', 'h', 'i']", 480 | "a = ['a', 'b', 'c',\n 'd', 'e', 'f',\n 'g', 'h', 'i'\n]") 481 | test("a = ['a', 'b', 'c',\n 'd', 'e', 'f',\n 'g', 'h', 'i']", 482 | "a = ['a', 'b', 'c',\n 'd', 'e', 'f',\n 'g', 'h', 'i'\n]") 483 | test("var x = [{}\n]", "var x = [{}]") 484 | test("var x = [{foo:bar}\n]", "var x = [{\n foo: bar\n}]") 485 | test("a = ['something',\n 'completely',\n 'different'];\nif (x);", 486 | "a = ['something',\n 'completely',\n 'different'\n];\nif (x);") 487 | test("a = ['a','b','c']", "a = ['a', 'b', 'c']") 488 | test("a = ['a', 'b','c']", "a = ['a', 'b', 'c']") 489 | test("x = [{'a':0}]", 490 | "x = [{\n 'a': 0\n}]") 491 | test("{a([[a1]], {b;});}", "{\n a([\n [a1]\n ], {\n b;\n });\n}") 492 | test("a();\n [\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ].toString();", 493 | "a();\n[\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n].toString();") 494 | test("a();\na = [\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ].toString();", 495 | "a();\na = [\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n].toString();") 496 | test("function() {\n Foo([\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ]);\n}", 497 | "function() {\n Foo([\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ]);\n}") 498 | test("function foo() {\n return [\n \"one\",\n \"two\"\n ];\n}") 499 | // 4 spaces per indent input, processed with 4-spaces per indent 500 | test("function foo() {\n"+ 501 | " return [\n"+ 502 | " {\n"+ 503 | " one: 'x',\n"+ 504 | " two: [\n"+ 505 | " {\n"+ 506 | " id: 'a',\n"+ 507 | " name: 'apple'\n"+ 508 | " }, {\n"+ 509 | " id: 'b',\n"+ 510 | " name: 'banana'\n"+ 511 | " }\n"+ 512 | " ]\n"+ 513 | " }\n"+ 514 | " ];\n"+ 515 | "}", 516 | "function foo() {\n"+ 517 | " return [{\n"+ 518 | " one: 'x',\n"+ 519 | " two: [{\n"+ 520 | " id: 'a',\n"+ 521 | " name: 'apple'\n"+ 522 | " }, {\n"+ 523 | " id: 'b',\n"+ 524 | " name: 'banana'\n"+ 525 | " }]\n"+ 526 | " }];\n"+ 527 | "}") 528 | 529 | test("function foo() {\n"+ 530 | " return [\n"+ 531 | " {\n"+ 532 | " one: 'x',\n"+ 533 | " two: [\n"+ 534 | " {\n"+ 535 | " id: 'a',\n"+ 536 | " name: 'apple'\n"+ 537 | " }, {\n"+ 538 | " id: 'b',\n"+ 539 | " name: 'banana'\n"+ 540 | " }\n"+ 541 | " ]\n"+ 542 | " }\n"+ 543 | " ];\n"+ 544 | "}", 545 | "function foo() {\n"+ 546 | " return [{\n"+ 547 | " one: 'x',\n"+ 548 | " two: [{\n"+ 549 | " id: 'a',\n"+ 550 | " name: 'apple'\n"+ 551 | " }, {\n"+ 552 | " id: 'b',\n"+ 553 | " name: 'banana'\n"+ 554 | " }]\n"+ 555 | " }];\n"+ 556 | "}") 557 | 558 | test_options["keep_array_indentation"] = true 559 | test("a = ['a', 'b', 'c',\n 'd', 'e', 'f']") 560 | test("a = ['a', 'b', 'c',\n 'd', 'e', 'f',\n 'g', 'h', 'i']") 561 | test("a = ['a', 'b', 'c',\n 'd', 'e', 'f',\n 'g', 'h', 'i']") 562 | test("var x = [{}\n]", "var x = [{}\n]") 563 | test("var x = [{foo:bar}\n]", "var x = [{\n foo: bar\n }\n]") 564 | test("a = ['something',\n 'completely',\n 'different'];\nif (x);") 565 | test("a = ['a','b','c']", "a = ['a', 'b', 'c']") 566 | test("a = ['a', 'b','c']", "a = ['a', 'b', 'c']") 567 | test("x = [{'a':0}]", 568 | "x = [{\n 'a': 0\n}]") 569 | test("{a([[a1]], {b;});}", "{\n a([[a1]], {\n b;\n });\n}") 570 | test("a();\n [\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ].toString();", 571 | "a();\n [\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ].toString();") 572 | test("a();\na = [\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ].toString();", 573 | "a();\na = [\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ].toString();") 574 | test("function() {\n Foo([\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ]);\n}", 575 | "function() {\n Foo([\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ]);\n}") 576 | test("function foo() {\n return [\n \"one\",\n \"two\"\n ];\n}") 577 | 578 | test("function foo() {\n" + 579 | " return [\n" + 580 | " {\n" + 581 | " one: 'x',\n" + 582 | " two: [\n" + 583 | " {\n" + 584 | " id: 'a',\n" + 585 | " name: 'apple'\n" + 586 | " }, {\n" + 587 | " id: 'b',\n" + 588 | " name: 'banana'\n" + 589 | " }\n" + 590 | " ]\n" + 591 | " }\n" + 592 | " ];\n" + 593 | "}") 594 | 595 | test_options["keep_array_indentation"] = false 596 | 597 | test("a = //comment\n /regex/;") 598 | 599 | test("/*\n * X\n */") 600 | test("/*\r\n * X\r\n */", "/*\n * X\n */") 601 | 602 | test("if (a)\n{\nb;\n}\nelse\n{\nc;\n}", "if (a) {\n b;\n} else {\n c;\n}") 603 | 604 | test("var a = new function();") 605 | test("new function") 606 | 607 | test_options["brace_style"] = "expand" 608 | 609 | test("//case 1\nif (a == 1)\n{}\n//case 2\nelse if (a == 2)\n{}") 610 | test("if(1){2}else{3}", "if (1)\n{\n 2\n}\nelse\n{\n 3\n}") 611 | test("try{a();}catch(b){c();}catch(d){}finally{e();}", "try\n{\n a();\n}\ncatch (b)\n{\n c();\n}\ncatch (d)\n{}\nfinally\n{\n e();\n}") 612 | test("if(a){b();}else if(c) foo();", "if (a)\n{\n b();\n}\nelse if (c) foo();") 613 | test("if (a) {\n// comment\n}else{\n// comment\n}", 614 | "if (a)\n{\n // comment\n}\nelse\n{\n // comment\n}") 615 | test("if (x) {y} else { if (x) {y}}", "if (x)\n{\n y\n}\nelse\n{\n if (x)\n {\n y\n }\n}") 616 | test("if (a)\n{\nb;\n}\nelse\n{\nc;\n}", "if (a)\n{\n b;\n}\nelse\n{\n c;\n}") 617 | test(" /*\n* xx\n*/\n// xx\nif (foo) {\n bar();\n}", " /*\n * xx\n */\n // xx\n if (foo)\n {\n bar();\n }") 618 | test("if (foo)\n{}\nelse /regex/.test();") 619 | test("if (foo) {", "if (foo)\n{") 620 | test("foo {", "foo\n{") 621 | test("return {", "return {") 622 | test("return /* inline */ {", "return /* inline */ {") 623 | test("return;\n{", "return;\n{") 624 | test("throw {}") 625 | test("throw {\n foo;\n}") 626 | test("var foo = {}") 627 | test("function x() {\n foo();\n}zzz", "function x()\n{\n foo();\n}\nzzz") 628 | test("a: do {} while (); xxx", "a: do {} while ();\nxxx") 629 | test("{a: do {} while (); xxx}", "{\n a: do {} while ();xxx\n}") 630 | test("var a = new function() {};") 631 | test("var a = new function a() {};", "var a = new function a()\n{};") 632 | test("var a = new function()\n{};", "var a = new function() {};") 633 | test("var a = new function a()\n{};") 634 | test("var a = new function a()\n {},\n b = new function b()\n {};") 635 | test("foo({\n 'a': 1\n},\n10);", 636 | "foo(\n {\n 'a': 1\n },\n 10);") 637 | test("([\"foo\",\"bar\"]).each(function(i) {return i;});", "([\"foo\", \"bar\"]).each(function(i)\n{\n return i;\n});") 638 | test("(function(i) {return i;})();", "(function(i)\n{\n return i;\n})();") 639 | test("test( /*Argument 1*/ {\n"+ 640 | " 'Value1': '1'\n"+ 641 | "}, /*Argument 2\n"+ 642 | " */ {\n"+ 643 | " 'Value2': '2'\n"+ 644 | "});", 645 | 646 | "test( /*Argument 1*/\n"+ 647 | " {\n"+ 648 | " 'Value1': '1'\n"+ 649 | " },\n"+ 650 | " /*Argument 2\n"+ 651 | " */\n"+ 652 | " {\n"+ 653 | " 'Value2': '2'\n"+ 654 | " });") 655 | test("test(\n"+ 656 | "/*Argument 1*/ {\n"+ 657 | " 'Value1': '1'\n"+ 658 | "},\n"+ 659 | "/*Argument 2\n"+ 660 | " */ {\n"+ 661 | " 'Value2': '2'\n"+ 662 | "});", 663 | 664 | "test(\n"+ 665 | " /*Argument 1*/\n"+ 666 | " {\n"+ 667 | " 'Value1': '1'\n"+ 668 | " },\n"+ 669 | " /*Argument 2\n"+ 670 | " */\n"+ 671 | " {\n"+ 672 | " 'Value2': '2'\n"+ 673 | " });") 674 | test("test( /*Argument 1*/\n"+ 675 | "{\n"+ 676 | " 'Value1': '1'\n"+ 677 | "}, /*Argument 2\n"+ 678 | " */\n"+ 679 | "{\n"+ 680 | " 'Value2': '2'\n"+ 681 | "});", 682 | 683 | "test( /*Argument 1*/\n"+ 684 | " {\n"+ 685 | " 'Value1': '1'\n"+ 686 | " },\n"+ 687 | " /*Argument 2\n"+ 688 | " */\n"+ 689 | " {\n"+ 690 | " 'Value2': '2'\n"+ 691 | " });") 692 | 693 | test_options["brace_style"] = "collapse" 694 | 695 | test("//case 1\nif (a == 1) {}\n//case 2\nelse if (a == 2) {}") 696 | test("if(1){2}else{3}", "if (1) {\n 2\n} else {\n 3\n}") 697 | test("try{a();}catch(b){c();}catch(d){}finally{e();}", "try {\n a();\n} catch (b) {\n c();\n} catch (d) {} finally {\n e();\n}") 698 | test("if(a){b();}else if(c) foo();", "if (a) {\n b();\n} else if (c) foo();") 699 | test("if (a) {\n// comment\n}else{\n// comment\n}", 700 | "if (a) {\n // comment\n} else {\n // comment\n}") 701 | test("if (x) {y} else { if (x) {y}}", "if (x) {\n y\n} else {\n if (x) {\n y\n }\n}") 702 | test("if (a)\n{\nb;\n}\nelse\n{\nc;\n}", "if (a) {\n b;\n} else {\n c;\n}") 703 | test(" /*\n* xx\n*/\n// xx\nif (foo) {\n bar();\n}", " /*\n * xx\n */\n // xx\n if (foo) {\n bar();\n }") 704 | test("if (foo) {} else /regex/.test();") 705 | test("if (foo) {", "if (foo) {") 706 | test("foo {", "foo {") 707 | test("return {", "return {") 708 | test("return /* inline */ {", "return /* inline */ {") 709 | test("return;\n{", "return; {") 710 | test("throw {}") 711 | test("throw {\n foo;\n}") 712 | test("var foo = {}") 713 | test("function x() {\n foo();\n}zzz", "function x() {\n foo();\n}\nzzz") 714 | test("a: do {} while (); xxx", "a: do {} while ();\nxxx") 715 | test("{a: do {} while (); xxx}", "{\n a: do {} while ();xxx\n}") 716 | test("var a = new function() {};") 717 | test("var a = new function a() {};") 718 | test("var a = new function()\n{};", "var a = new function() {};") 719 | test("var a = new function a()\n{};", "var a = new function a() {};") 720 | test("var a = new function a()\n {},\n b = new function b()\n {};", "var a = new function a() {},\n b = new function b() {};") 721 | test("foo({\n 'a': 1\n},\n10);", 722 | "foo({\n 'a': 1\n },\n 10);") 723 | test("([\"foo\",\"bar\"]).each(function(i) {return i;});", "([\"foo\", \"bar\"]).each(function(i) {\n return i;\n});") 724 | test("(function(i) {return i;})();", "(function(i) {\n return i;\n})();") 725 | test("test( /*Argument 1*/ {\n"+ 726 | " 'Value1': '1'\n"+ 727 | "}, /*Argument 2\n"+ 728 | " */ {\n"+ 729 | " 'Value2': '2'\n"+ 730 | "});", 731 | 732 | "test( /*Argument 1*/ {\n"+ 733 | " 'Value1': '1'\n"+ 734 | " },\n"+ 735 | " /*Argument 2\n"+ 736 | " */\n"+ 737 | " {\n"+ 738 | " 'Value2': '2'\n"+ 739 | " });") 740 | test("test(\n"+ 741 | "/*Argument 1*/ {\n"+ 742 | " 'Value1': '1'\n"+ 743 | "},\n"+ 744 | "/*Argument 2\n"+ 745 | " */ {\n"+ 746 | " 'Value2': '2'\n"+ 747 | "});", 748 | 749 | "test(\n"+ 750 | " /*Argument 1*/\n"+ 751 | " {\n"+ 752 | " 'Value1': '1'\n"+ 753 | " },\n"+ 754 | " /*Argument 2\n"+ 755 | " */\n"+ 756 | " {\n"+ 757 | " 'Value2': '2'\n"+ 758 | " });") 759 | test("test( /*Argument 1*/\n"+ 760 | "{\n"+ 761 | " 'Value1': '1'\n"+ 762 | "}, /*Argument 2\n"+ 763 | " */\n"+ 764 | "{\n"+ 765 | " 'Value2': '2'\n"+ 766 | "});", 767 | 768 | "test( /*Argument 1*/ {\n"+ 769 | " 'Value1': '1'\n"+ 770 | " },\n"+ 771 | " /*Argument 2\n"+ 772 | " */\n"+ 773 | " {\n"+ 774 | " 'Value2': '2'\n"+ 775 | " });") 776 | 777 | test_options["brace_style"] = "end-expand" 778 | 779 | test("//case 1\nif (a == 1) {}\n//case 2\nelse if (a == 2) {}") 780 | test("if(1){2}else{3}", "if (1) {\n 2\n}\nelse {\n 3\n}") 781 | test("try{a();}catch(b){c();}catch(d){}finally{e();}", "try {\n a();\n}\ncatch (b) {\n c();\n}\ncatch (d) {}\nfinally {\n e();\n}") 782 | test("if(a){b();}else if(c) foo();", "if (a) {\n b();\n}\nelse if (c) foo();") 783 | test("if (a) {\n// comment\n}else{\n// comment\n}", 784 | "if (a) {\n // comment\n}\nelse {\n // comment\n}") 785 | test("if (x) {y} else { if (x) {y}}", "if (x) {\n y\n}\nelse {\n if (x) {\n y\n }\n}") 786 | test("if (a)\n{\nb;\n}\nelse\n{\nc;\n}", "if (a) {\n b;\n}\nelse {\n c;\n}") 787 | test(" /*\n* xx\n*/\n// xx\nif (foo) {\n bar();\n}", " /*\n * xx\n */\n // xx\n if (foo) {\n bar();\n }") 788 | test("if (foo) {}\nelse /regex/.test();") 789 | test("if (foo) {", "if (foo) {") 790 | test("foo {", "foo {") 791 | test("return {", "return {") 792 | test("return /* inline */ {", "return /* inline */ {") 793 | test("return;\n{", "return; {") 794 | test("throw {}") 795 | test("throw {\n foo;\n}") 796 | test("var foo = {}") 797 | test("function x() {\n foo();\n}zzz", "function x() {\n foo();\n}\nzzz") 798 | test("a: do {} while (); xxx", "a: do {} while ();\nxxx") 799 | test("{a: do {} while (); xxx}", "{\n a: do {} while ();xxx\n}") 800 | test("var a = new function() {};") 801 | test("var a = new function a() {};") 802 | test("var a = new function()\n{};", "var a = new function() {};") 803 | test("var a = new function a()\n{};", "var a = new function a() {};") 804 | test("var a = new function a()\n {},\n b = new function b()\n {};", "var a = new function a() {},\n b = new function b() {};") 805 | test("foo({\n 'a': 1\n},\n10);", 806 | "foo({\n 'a': 1\n },\n 10);") 807 | test("([\"foo\",\"bar\"]).each(function(i) {return i;});", "([\"foo\", \"bar\"]).each(function(i) {\n return i;\n});") 808 | test("(function(i) {return i;})();", "(function(i) {\n return i;\n})();") 809 | test("test( /*Argument 1*/ {\n"+ 810 | " 'Value1': '1'\n"+ 811 | "}, /*Argument 2\n"+ 812 | " */ {\n"+ 813 | " 'Value2': '2'\n"+ 814 | "});", 815 | 816 | "test( /*Argument 1*/ {\n"+ 817 | " 'Value1': '1'\n"+ 818 | " },\n"+ 819 | " /*Argument 2\n"+ 820 | " */\n"+ 821 | " {\n"+ 822 | " 'Value2': '2'\n"+ 823 | " });") 824 | test("test(\n"+ 825 | "/*Argument 1*/ {\n"+ 826 | " 'Value1': '1'\n"+ 827 | "},\n"+ 828 | "/*Argument 2\n"+ 829 | " */ {\n"+ 830 | " 'Value2': '2'\n"+ 831 | "});", 832 | 833 | "test(\n"+ 834 | " /*Argument 1*/\n"+ 835 | " {\n"+ 836 | " 'Value1': '1'\n"+ 837 | " },\n"+ 838 | " /*Argument 2\n"+ 839 | " */\n"+ 840 | " {\n"+ 841 | " 'Value2': '2'\n"+ 842 | " });") 843 | test("test( /*Argument 1*/\n"+ 844 | "{\n"+ 845 | " 'Value1': '1'\n"+ 846 | "}, /*Argument 2\n"+ 847 | " */\n"+ 848 | "{\n"+ 849 | " 'Value2': '2'\n"+ 850 | "});", 851 | 852 | "test( /*Argument 1*/ {\n"+ 853 | " 'Value1': '1'\n"+ 854 | " },\n"+ 855 | " /*Argument 2\n"+ 856 | " */\n"+ 857 | " {\n"+ 858 | " 'Value2': '2'\n"+ 859 | " });") 860 | 861 | test_options["brace_style"] = "none" 862 | 863 | test("//case 1\nif (a == 1)\n{}\n//case 2\nelse if (a == 2)\n{}") 864 | test("if(1){2}else{3}", "if (1) {\n 2\n} else {\n 3\n}") 865 | test("try{a();}catch(b){c();}catch(d){}finally{e();}", "try {\n a();\n} catch (b) {\n c();\n} catch (d) {} finally {\n e();\n}") 866 | test("if(a){b();}else if(c) foo();", "if (a) {\n b();\n} else if (c) foo();") 867 | test("if (a) {\n// comment\n}else{\n// comment\n}", 868 | "if (a) {\n // comment\n} else {\n // comment\n}") 869 | test("if (x) {y} else { if (x) {y}}", "if (x) {\n y\n} else {\n if (x) {\n y\n }\n}") 870 | test("if (a)\n{\nb;\n}\nelse\n{\nc;\n}", "if (a)\n{\n b;\n}\nelse\n{\n c;\n}") 871 | test(" /*\n* xx\n*/\n// xx\nif (foo) {\n bar();\n}", " /*\n * xx\n */\n // xx\n if (foo) {\n bar();\n }") 872 | test("if (foo)\n{}\nelse /regex/.test();") 873 | test("if (foo) {") 874 | test("foo {") 875 | test("return {") 876 | test("return /* inline */ {") 877 | test("return;\n{") 878 | test("throw {}") 879 | test("throw {\n foo;\n}") 880 | test("var foo = {}") 881 | test("function x() {\n foo();\n}zzz", "function x() {\n foo();\n}\nzzz") 882 | test("a: do {} while (); xxx", "a: do {} while ();\nxxx") 883 | test("{a: do {} while (); xxx}", "{\n a: do {} while ();xxx\n}") 884 | test("var a = new function() {};") 885 | test("var a = new function a() {};") 886 | test("var a = new function()\n{};", "var a = new function() {};") 887 | test("var a = new function a()\n{};") 888 | test("var a = new function a()\n {},\n b = new function b()\n {};") 889 | test("foo({\n 'a': 1\n},\n10);", 890 | "foo({\n 'a': 1\n },\n 10);") 891 | test("([\"foo\",\"bar\"]).each(function(i) {return i;});", "([\"foo\", \"bar\"]).each(function(i) {\n return i;\n});") 892 | test("(function(i) {return i;})();", "(function(i) {\n return i;\n})();") 893 | test("test( /*Argument 1*/ {\n"+ 894 | " 'Value1': '1'\n"+ 895 | "}, /*Argument 2\n"+ 896 | " */ {\n"+ 897 | " 'Value2': '2'\n"+ 898 | "});", 899 | 900 | "test( /*Argument 1*/ {\n"+ 901 | " 'Value1': '1'\n"+ 902 | " },\n"+ 903 | " /*Argument 2\n"+ 904 | " */\n"+ 905 | " {\n"+ 906 | " 'Value2': '2'\n"+ 907 | " });") 908 | test("test(\n"+ 909 | "/*Argument 1*/ {\n"+ 910 | " 'Value1': '1'\n"+ 911 | "},\n"+ 912 | "/*Argument 2\n"+ 913 | " */ {\n"+ 914 | " 'Value2': '2'\n"+ 915 | "});", 916 | 917 | "test(\n"+ 918 | " /*Argument 1*/\n"+ 919 | " {\n"+ 920 | " 'Value1': '1'\n"+ 921 | " },\n"+ 922 | " /*Argument 2\n"+ 923 | " */\n"+ 924 | " {\n"+ 925 | " 'Value2': '2'\n"+ 926 | " });") 927 | test("test( /*Argument 1*/\n"+ 928 | "{\n"+ 929 | " 'Value1': '1'\n"+ 930 | "}, /*Argument 2\n"+ 931 | " */\n"+ 932 | "{\n"+ 933 | " 'Value2': '2'\n"+ 934 | "});", 935 | 936 | "test( /*Argument 1*/\n"+ 937 | " {\n"+ 938 | " 'Value1': '1'\n"+ 939 | " },\n"+ 940 | " /*Argument 2\n"+ 941 | " */\n"+ 942 | " {\n"+ 943 | " 'Value2': '2'\n"+ 944 | " });") 945 | 946 | test_options["brace_style"] = "collapse" 947 | 948 | test("a = ;") 949 | test("a = <%= external() %> ;") 950 | 951 | test("roo = {\n /*\n ****\n FOO\n ****\n */\n BAR: 0\n};") 952 | test("if (zz) {\n // ....\n}\n(function") 953 | 954 | test_options["preserve_newlines"] = true 955 | test("var a = 42; // foo\n\nvar b;") 956 | test("var a = 42; // foo\n\n\nvar b;") 957 | test("var a = 'foo' +\n 'bar';") 958 | test("var a = \"foo\" +\n \"bar\";") 959 | 960 | test("\"foo\"\"bar\"\"baz\"", "\"foo\"\n\"bar\"\n\"baz\"") 961 | test("'foo''bar''baz'", "'foo'\n'bar'\n'baz'") 962 | test("{\n get foo() {}\n}") 963 | test("{\n var a = get\n foo();\n}") 964 | test("{\n set foo() {}\n}") 965 | test("{\n var a = set\n foo();\n}") 966 | test("var x = {\n get function()\n}") 967 | test("var x = {\n set function()\n}") 968 | 969 | test("var x = set\n\na() {}", "var x = set\n\na() {}") 970 | test("var x = set\n\nfunction() {}", "var x = set\n\nfunction() {}") 971 | 972 | test("") 973 | test("" { 457 | self.in_html_comment = false 458 | self.parser_pos += 2 459 | return "-->", "TK_COMMENT" 460 | } 461 | 462 | if c == "." { 463 | return c, "TK_DOT" 464 | } 465 | 466 | if utils.InStrArray(c, punct) { 467 | for self.parser_pos < len(*self.input) && utils.InStrArray(c+self.GetNextChar(), punct) { 468 | c += self.AdvanceNextChar() 469 | if self.parser_pos >= len(*self.input) { 470 | break 471 | } 472 | } 473 | 474 | if c == "," { 475 | return c, "TK_COMMA" 476 | } 477 | 478 | if c == "=" { 479 | return c, "TK_EQUALS" 480 | } 481 | return c, "TK_OPERATOR" 482 | } 483 | 484 | return c, "TK_UNKNOWN" 485 | } 486 | 487 | func GetLineStarters() []string { 488 | return line_starters 489 | } 490 | 491 | func New(s *string, options optargs.MapType, indent_string string) *tokenizer { 492 | t := new(tokenizer) 493 | t.input = s 494 | t.options = options 495 | t.indent_string = indent_string 496 | t.acorn = NewAcorn() 497 | return t 498 | } 499 | -------------------------------------------------------------------------------- /tokenizer/tokenstack.go: -------------------------------------------------------------------------------- 1 | package tokenizer 2 | 3 | // Copyright (c) 2014 Ditashi Sayomi 4 | 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: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 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 21 | // SOFTWARE. 22 | 23 | type TokenStack struct { 24 | tokens []Token 25 | size int 26 | } 27 | 28 | func (self *TokenStack) Append(t Token) { 29 | self.tokens = append(self.tokens, t) 30 | self.size++ 31 | } 32 | 33 | func (self *TokenStack) Pop() *Token { 34 | if self.size == 0 { 35 | return nil 36 | } 37 | t := self.tokens[self.size-1] 38 | self.tokens = self.tokens[:self.size-1] 39 | self.size-- 40 | return &t 41 | } 42 | 43 | func (self *TokenStack) Shift() *Token { 44 | if self.size == 0 { 45 | return nil 46 | } 47 | 48 | t := self.tokens[0] 49 | if self.size == 1 { 50 | self.tokens = nil 51 | } else { 52 | self.tokens = self.tokens[1:] 53 | } 54 | self.size-- 55 | return &t 56 | } 57 | 58 | func (self *TokenStack) Empty() bool { 59 | return self.size == 0 60 | } 61 | -------------------------------------------------------------------------------- /unpackers/packer.go: -------------------------------------------------------------------------------- 1 | package unpackers 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | "unicode/utf8" 10 | ) 11 | 12 | // Copyright (c) 2014 Ditashi Sayomi 13 | 14 | // Permission is hereby granted, free of charge, to any person obtaining a copy 15 | // of this software and associated documentation files (the "Software"), to deal 16 | // in the Software without restriction, including without limitation the rights 17 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | // copies of the Software, and to permit persons to whom the Software is 19 | // furnished to do so, subject to the following conditions: 20 | 21 | // The above copyright notice and this permission notice shall be included in all 22 | // copies or substantial portions of the Software. 23 | 24 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | // SOFTWARE. 31 | 32 | type DotPacker struct{} 33 | 34 | func (self *DotPacker) unpack(source *string) (*string, error) { 35 | payload, symtab, radix, count, _ := self.getargs(source) 36 | 37 | if count != len(symtab) { 38 | return nil, errors.New("Malformed p.a.c.k.e.r symtab") 39 | } 40 | unbaser, _ := newunbaser(radix) 41 | 42 | lookup := func(match string) string { 43 | res := symtab[unbaser.unbase(&match)] 44 | if res != "" { 45 | return res 46 | } 47 | 48 | return match 49 | } 50 | 51 | *source = regexp.MustCompile(`\b\w+\b`).ReplaceAllStringFunc(payload, lookup) 52 | 53 | return self.replacestrings(source), nil 54 | } 55 | 56 | func (self *DotPacker) replacestrings(source *string) *string { 57 | match := regexp.MustCompile(`(?s)var *(_\w+)\=\["(.*?)"\];`).FindStringSubmatch(*source) 58 | 59 | if match != nil { 60 | varname, string_values := match[1], match[2] 61 | 62 | startpoint := len(match[0]) 63 | lookup := strings.Split(string_values, `","`) 64 | variable := fmt.Sprintf("%s[%%d]", varname) 65 | for index, value := range lookup { 66 | *source = strings.Replace(*source, fmt.Sprintf(variable, index), `"`+value+`"`, -1) 67 | } 68 | 69 | *source = (*source)[startpoint:] 70 | return source 71 | } 72 | return source 73 | } 74 | func (self *DotPacker) detect(source *string) bool { 75 | return strings.HasPrefix(strings.Replace(*source, " ", "", -1), "eval(function(p,a,c,k,e,") 76 | } 77 | 78 | func (self *DotPacker) getargs(source *string) (string, []string, int, int, error) { 79 | juicers := []*regexp.Regexp{regexp.MustCompile(`(?s)}\('(.*)', *(\d+), *(\d+), *'(.*)'\.split\('\|'\), *(\d+), *(.*)\)\)`), 80 | regexp.MustCompile(`(?s)}\('(.*)', *(\d+), *(\d+), *'(.*)'\.split\('\|'\)`)} 81 | 82 | for _, juicer := range juicers { 83 | args := juicer.FindStringSubmatch(*source) 84 | 85 | if args != nil { 86 | arg1, _ := strconv.Atoi(args[2]) 87 | arg2, _ := strconv.Atoi(args[3]) 88 | return args[1], strings.Split(args[4], "|"), arg1, arg2, nil 89 | } else { 90 | return "", []string{}, 0, 0, errors.New("Corrupted p.a.c.k.e.r data") 91 | } 92 | } 93 | return "", []string{}, 0, 0, errors.New("Could not make sense of p.a.c.k.e.r data (unexpected code structure)") 94 | } 95 | 96 | type unbaser struct { 97 | base int 98 | unbase func(s *string) int 99 | dict map[string]int 100 | } 101 | 102 | func newunbaser(base int) (*unbaser, error) { 103 | ALPHABET := map[int]string{62: `0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`, 104 | 95: ` !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ' 105 | '[\]^_` + "`" + `abcdefghijklmnopqrstuvwxyz{|}~`, 106 | } 107 | 108 | unbaser := new(unbaser) 109 | 110 | if 2 <= base && base <= 36 { 111 | unbaser.unbase = func(s *string) int { val, _ := strconv.ParseInt(*s, base, 0); return int(val) } 112 | } else { 113 | if _, ok := ALPHABET[base]; !ok { 114 | return nil, errors.New("Unsupported base encoding") 115 | } 116 | 117 | unbaser.dict = make(map[string]int) 118 | for index, cipher := range ALPHABET[base] { 119 | unbaser.dict[string(cipher)] = index 120 | } 121 | 122 | unbaser.unbase = unbaser.dictunbaser 123 | 124 | } 125 | 126 | unbaser.base = base 127 | return unbaser, nil 128 | } 129 | 130 | func (self *unbaser) dictunbaser(s *string) int { 131 | ret := 0 132 | for index, cipher := range reverse(*s) { 133 | ret += pow(self.base, index) * self.dict[string(cipher)] 134 | } 135 | 136 | return ret 137 | } 138 | 139 | func reverse(s string) string { 140 | o := make([]rune, utf8.RuneCountInString(s)) 141 | i := len(o) 142 | for _, c := range s { 143 | i-- 144 | o[i] = c 145 | } 146 | return string(o) 147 | } 148 | 149 | func pow(a, b int) int { 150 | var result int = 1 151 | 152 | for 0 != b { 153 | if 0 != (b & 1) { 154 | result *= a 155 | } 156 | b >>= 1 157 | a *= a 158 | } 159 | 160 | return result 161 | } 162 | -------------------------------------------------------------------------------- /unpackers/unpackers.go: -------------------------------------------------------------------------------- 1 | package unpackers 2 | 3 | // Copyright (c) 2014 Ditashi Sayomi 4 | 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: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 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 21 | // SOFTWARE. 22 | 23 | type Unpacker interface { 24 | unpack(*string) (*string, error) 25 | detect(*string) bool 26 | } 27 | 28 | var unpackers = []Unpacker{new(DotPacker)} 29 | 30 | func GetUnpackers() []Unpacker { 31 | return unpackers 32 | } 33 | 34 | func Run(source *string) *string { 35 | for _, unpacker := range unpackers { 36 | if unpacker.detect(source) { 37 | source, _ = unpacker.unpack(source) 38 | } 39 | } 40 | return source 41 | } 42 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // Copyright (c) 2014 Ditashi Sayomi 4 | 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: 11 | 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | 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 21 | // SOFTWARE. 22 | 23 | func InStrArray(s string, arr []string) bool { 24 | for _, val := range arr { 25 | if s == val { 26 | return true 27 | } 28 | } 29 | return false 30 | } 31 | --------------------------------------------------------------------------------