beautify.js 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077
  1. /*jslint onevar: false, plusplus: false */
  2. /*
  3. JS Beautifier
  4. ---------------
  5. Written by Einar Lielmanis, <einar@jsbeautifier.org>
  6. http://jsbeautifier.org/
  7. Originally converted to javascript by Vital, <vital76@gmail.com>
  8. You are free to use this in any way you want, in case you find this useful or working for you.
  9. Usage:
  10. js_beautify(js_source_text);
  11. js_beautify(js_source_text, options);
  12. The options are:
  13. indent_size (default 4) — indentation size,
  14. indent_char (default space) — character to indent with,
  15. preserve_newlines (default true) — whether existing line breaks should be preserved,
  16. indent_level (default 0) — initial indentation level, you probably won't need this ever,
  17. space_after_anon_function (default false) — if true, then space is added between "function ()"
  18. (jslint is happy about this); if false, then the common "function()" output is used.
  19. braces_on_own_line (default false) - ANSI / Allman brace style, each opening/closing brace gets its own line.
  20. e.g
  21. js_beautify(js_source_text, {indent_size: 1, indent_char: '\t'});
  22. */
  23. function js_beautify(js_source_text, options) {
  24. var input, output, token_text, last_type, last_text, last_last_text, last_word, flags, flag_store, indent_string;
  25. var whitespace, wordchar, punct, parser_pos, line_starters, digits;
  26. var prefix, token_type, do_block_just_closed;
  27. var wanted_newline, just_added_newline, n_newlines;
  28. // Some interpreters have unexpected results with foo = baz || bar;
  29. options = options ? options : {};
  30. var opt_braces_on_own_line = options.braces_on_own_line ? options.braces_on_own_line : false;
  31. var opt_indent_size = options.indent_size ? options.indent_size : 4;
  32. var opt_indent_char = options.indent_char ? options.indent_char : ' ';
  33. var opt_preserve_newlines = typeof options.preserve_newlines === 'undefined' ? true : options.preserve_newlines;
  34. var opt_indent_level = options.indent_level ? options.indent_level : 0; // starting indentation
  35. var opt_space_after_anon_function = options.space_after_anon_function === 'undefined' ? false : options.space_after_anon_function;
  36. var opt_keep_array_indentation = typeof options.keep_array_indentation === 'undefined' ? true : options.keep_array_indentation;
  37. just_added_newline = false;
  38. // cache the source's length.
  39. var input_length = js_source_text.length;
  40. function trim_output() {
  41. while (output.length && (output[output.length - 1] === ' ' || output[output.length - 1] === indent_string)) {
  42. output.pop();
  43. }
  44. }
  45. function print_newline(ignore_repeated) {
  46. flags.eat_next_space = false;
  47. if (opt_keep_array_indentation && is_array(flags.mode)) {
  48. return;
  49. }
  50. ignore_repeated = typeof ignore_repeated === 'undefined' ? true : ignore_repeated;
  51. flags.if_line = false;
  52. trim_output();
  53. if (!output.length) {
  54. return; // no newline on start of file
  55. }
  56. if (output[output.length - 1] !== "\n" || !ignore_repeated) {
  57. just_added_newline = true;
  58. output.push("\n");
  59. }
  60. for (var i = 0; i < flags.indentation_level; i += 1) {
  61. output.push(indent_string);
  62. }
  63. if (flags.var_line && flags.var_line_reindented) {
  64. if (opt_indent_char === ' ') {
  65. output.push(' '); // var_line always pushes 4 spaces, so that the variables would be one under another
  66. } else {
  67. output.push(indent_string); // skip space-stuffing, if indenting with a tab
  68. }
  69. }
  70. }
  71. function print_single_space() {
  72. if (flags.eat_next_space) {
  73. flags.eat_next_space = false;
  74. return;
  75. }
  76. var last_output = ' ';
  77. if (output.length) {
  78. last_output = output[output.length - 1];
  79. }
  80. if (last_output !== ' ' && last_output !== '\n' && last_output !== indent_string) { // prevent occassional duplicate space
  81. output.push(' ');
  82. }
  83. }
  84. function print_token() {
  85. just_added_newline = false;
  86. flags.eat_next_space = false;
  87. output.push(token_text);
  88. }
  89. function indent() {
  90. flags.indentation_level += 1;
  91. }
  92. function remove_indent() {
  93. if (output.length && output[output.length - 1] === indent_string) {
  94. output.pop();
  95. }
  96. }
  97. function set_mode(mode) {
  98. if (flags) {
  99. flag_store.push(flags);
  100. }
  101. flags = {
  102. previous_mode: flags ? flags.mode : 'BLOCK',
  103. mode: mode,
  104. var_line: false,
  105. var_line_tainted: false,
  106. var_line_reindented: false,
  107. in_html_comment: false,
  108. if_line: false,
  109. in_case: false,
  110. eat_next_space: false,
  111. indentation_baseline: -1,
  112. indentation_level: (flags ? flags.indentation_level + ((flags.var_line && flags.var_line_reindented) ? 1 : 0) : opt_indent_level)
  113. };
  114. }
  115. function is_array(mode) {
  116. return mode === '[EXPRESSION]' || mode === '[INDENTED-EXPRESSION]';
  117. }
  118. function is_expression(mode) {
  119. return mode === '[EXPRESSION]' || mode === '[INDENTED-EXPRESSION]' || mode === '(EXPRESSION)';
  120. }
  121. function restore_mode() {
  122. do_block_just_closed = flags.mode === 'DO_BLOCK';
  123. if (flag_store.length > 0) {
  124. flags = flag_store.pop();
  125. }
  126. }
  127. function in_array(what, arr) {
  128. for (var i = 0; i < arr.length; i += 1) {
  129. if (arr[i] === what) {
  130. return true;
  131. }
  132. }
  133. return false;
  134. }
  135. // Walk backwards from the colon to find a '?' (colon is part of a ternary op)
  136. // or a '{' (colon is part of a class literal). Along the way, keep track of
  137. // the blocks and expressions we pass so we only trigger on those chars in our
  138. // own level, and keep track of the colons so we only trigger on the matching '?'.
  139. function is_ternary_op() {
  140. var level = 0,
  141. colon_count = 0;
  142. for (var i = output.length - 1; i >= 0; i--) {
  143. switch (output[i]) {
  144. case ':':
  145. if (level === 0) {
  146. colon_count++;
  147. }
  148. break;
  149. case '?':
  150. if (level === 0) {
  151. if (colon_count === 0) {
  152. return true;
  153. } else {
  154. colon_count--;
  155. }
  156. }
  157. break;
  158. case '{':
  159. if (level === 0) {
  160. return false;
  161. }
  162. level--;
  163. break;
  164. case '(':
  165. case '[':
  166. level--;
  167. break;
  168. case ')':
  169. case ']':
  170. case '}':
  171. level++;
  172. break;
  173. }
  174. }
  175. }
  176. function get_next_token() {
  177. n_newlines = 0;
  178. if (parser_pos >= input_length) {
  179. return ['', 'TK_EOF'];
  180. }
  181. wanted_newline = false;
  182. var c = input.charAt(parser_pos);
  183. parser_pos += 1;
  184. var keep_whitespace = opt_keep_array_indentation && is_array(flags.mode);
  185. if (keep_whitespace) {
  186. //
  187. // slight mess to allow nice preservation of array indentation and reindent that correctly
  188. // first time when we get to the arrays:
  189. // var a = [
  190. // ....'something'
  191. // we make note of whitespace_count = 4 into flags.indentation_baseline
  192. // so we know that 4 whitespaces in original source match indent_level of reindented source
  193. //
  194. // and afterwards, when we get to
  195. // 'something,
  196. // .......'something else'
  197. // we know that this should be indented to indent_level + (7 - indentation_baseline) spaces
  198. //
  199. var whitespace_count = 0;
  200. while (in_array(c, whitespace)) {
  201. if (c === "\n") {
  202. trim_output();
  203. output.push("\n");
  204. just_added_newline = true;
  205. whitespace_count = 0;
  206. } else {
  207. if (c === '\t') {
  208. whitespace_count += 4;
  209. } else {
  210. whitespace_count += 1;
  211. }
  212. }
  213. if (parser_pos >= input_length) {
  214. return ['', 'TK_EOF'];
  215. }
  216. c = input.charAt(parser_pos);
  217. parser_pos += 1;
  218. }
  219. if (flags.indentation_baseline === -1) {
  220. flags.indentation_baseline = whitespace_count;
  221. }
  222. if (just_added_newline) {
  223. var i;
  224. for (i = 0; i < flags.indentation_level + 1; i += 1) {
  225. output.push(indent_string);
  226. }
  227. if (flags.indentation_baseline !== -1) {
  228. for (i = 0; i < whitespace_count - flags.indentation_baseline; i++) {
  229. output.push(' ');
  230. }
  231. }
  232. }
  233. } else {
  234. while (in_array(c, whitespace)) {
  235. if (c === "\n") {
  236. n_newlines += 1;
  237. }
  238. if (parser_pos >= input_length) {
  239. return ['', 'TK_EOF'];
  240. }
  241. c = input.charAt(parser_pos);
  242. parser_pos += 1;
  243. }
  244. if (opt_preserve_newlines) {
  245. if (n_newlines > 1) {
  246. for (i = 0; i < n_newlines; i += 1) {
  247. print_newline(i === 0);
  248. just_added_newline = true;
  249. }
  250. }
  251. }
  252. wanted_newline = n_newlines > 0;
  253. }
  254. if (in_array(c, wordchar)) {
  255. if (parser_pos < input_length) {
  256. while (in_array(input.charAt(parser_pos), wordchar)) {
  257. c += input.charAt(parser_pos);
  258. parser_pos += 1;
  259. if (parser_pos === input_length) {
  260. break;
  261. }
  262. }
  263. }
  264. // small and surprisingly unugly hack for 1E-10 representation
  265. if (parser_pos !== input_length && c.match(/^[0-9]+[Ee]$/) && (input.charAt(parser_pos) === '-' || input.charAt(parser_pos) === '+')) {
  266. var sign = input.charAt(parser_pos);
  267. parser_pos += 1;
  268. var t = get_next_token(parser_pos);
  269. c += sign + t[0];
  270. return [c, 'TK_WORD'];
  271. }
  272. if (c === 'in') { // hack for 'in' operator
  273. return [c, 'TK_OPERATOR'];
  274. }
  275. if (wanted_newline && last_type !== 'TK_OPERATOR' && !flags.if_line && (opt_preserve_newlines || last_text !== 'var')) {
  276. print_newline();
  277. }
  278. return [c, 'TK_WORD'];
  279. }
  280. if (c === '(' || c === '[') {
  281. return [c, 'TK_START_EXPR'];
  282. }
  283. if (c === ')' || c === ']') {
  284. return [c, 'TK_END_EXPR'];
  285. }
  286. if (c === '{') {
  287. return [c, 'TK_START_BLOCK'];
  288. }
  289. if (c === '}') {
  290. return [c, 'TK_END_BLOCK'];
  291. }
  292. if (c === ';') {
  293. return [c, 'TK_SEMICOLON'];
  294. }
  295. if (c === '/') {
  296. var comment = '';
  297. // peek for comment /* ... */
  298. var inline_comment = true;
  299. if (input.charAt(parser_pos) === '*') {
  300. parser_pos += 1;
  301. if (parser_pos < input_length) {
  302. while (! (input.charAt(parser_pos) === '*' && input.charAt(parser_pos + 1) && input.charAt(parser_pos + 1) === '/') && parser_pos < input_length) {
  303. c = input.charAt(parser_pos);
  304. comment += c;
  305. if (c === '\x0d' || c === '\x0a') {
  306. inline_comment = false;
  307. }
  308. parser_pos += 1;
  309. if (parser_pos >= input_length) {
  310. break;
  311. }
  312. }
  313. }
  314. parser_pos += 2;
  315. if (inline_comment) {
  316. return ['/*' + comment + '*/', 'TK_INLINE_COMMENT'];
  317. } else {
  318. return ['/*' + comment + '*/', 'TK_BLOCK_COMMENT'];
  319. }
  320. }
  321. // peek for comment // ...
  322. if (input.charAt(parser_pos) === '/') {
  323. comment = c;
  324. while (input.charAt(parser_pos) !== "\x0d" && input.charAt(parser_pos) !== "\x0a") {
  325. comment += input.charAt(parser_pos);
  326. parser_pos += 1;
  327. if (parser_pos >= input_length) {
  328. break;
  329. }
  330. }
  331. parser_pos += 1;
  332. if (wanted_newline) {
  333. print_newline();
  334. }
  335. return [comment, 'TK_COMMENT'];
  336. }
  337. }
  338. if (c === "'" || // string
  339. c === '"' || // string
  340. (c === '/' && ((last_type === 'TK_WORD' && in_array(last_text, ['return', 'do'])) || (last_type === 'TK_START_EXPR' || last_type === 'TK_START_BLOCK' || last_type === 'TK_END_BLOCK' || last_type === 'TK_OPERATOR' || last_type === 'TK_EQUALS' || last_type === 'TK_EOF' || last_type === 'TK_SEMICOLON')))) { // regexp
  341. var sep = c;
  342. var esc = false;
  343. var resulting_string = c;
  344. if (parser_pos < input_length) {
  345. if (sep === '/') {
  346. //
  347. // handle regexp separately...
  348. //
  349. var in_char_class = false;
  350. while (esc || in_char_class || input.charAt(parser_pos) !== sep) {
  351. resulting_string += input.charAt(parser_pos);
  352. if (!esc) {
  353. esc = input.charAt(parser_pos) === '\\';
  354. if (input.charAt(parser_pos) === '[') {
  355. in_char_class = true;
  356. } else if (input.charAt(parser_pos) === ']') {
  357. in_char_class = false;
  358. }
  359. } else {
  360. esc = false;
  361. }
  362. parser_pos += 1;
  363. if (parser_pos >= input_length) {
  364. // incomplete string/rexp when end-of-file reached.
  365. // bail out with what had been received so far.
  366. return [resulting_string, 'TK_STRING'];
  367. }
  368. }
  369. } else {
  370. //
  371. // and handle string also separately
  372. //
  373. while (esc || input.charAt(parser_pos) !== sep) {
  374. resulting_string += input.charAt(parser_pos);
  375. if (!esc) {
  376. esc = input.charAt(parser_pos) === '\\';
  377. } else {
  378. esc = false;
  379. }
  380. parser_pos += 1;
  381. if (parser_pos >= input_length) {
  382. // incomplete string/rexp when end-of-file reached.
  383. // bail out with what had been received so far.
  384. return [resulting_string, 'TK_STRING'];
  385. }
  386. }
  387. }
  388. }
  389. parser_pos += 1;
  390. resulting_string += sep;
  391. if (sep === '/') {
  392. // regexps may have modifiers /regexp/MOD , so fetch those, too
  393. while (parser_pos < input_length && in_array(input.charAt(parser_pos), wordchar)) {
  394. resulting_string += input.charAt(parser_pos);
  395. parser_pos += 1;
  396. }
  397. }
  398. return [resulting_string, 'TK_STRING'];
  399. }
  400. if (c === '#') {
  401. // Spidermonkey-specific sharp variables for circular references
  402. // https://developer.mozilla.org/En/Sharp_variables_in_JavaScript
  403. // http://mxr.mozilla.org/mozilla-central/source/js/src/jsscan.cpp around line 1935
  404. var sharp = '#';
  405. if (parser_pos < input_length && in_array(input.charAt(parser_pos), digits)) {
  406. do {
  407. c = input.charAt(parser_pos);
  408. sharp += c;
  409. parser_pos += 1;
  410. } while (parser_pos < input_length && c !== '#' && c !== '=');
  411. if (c === '#') {
  412. //
  413. } else if (input.charAt(parser_pos) === '[' && input.charAt(parser_pos + 1) === ']') {
  414. sharp += '[]';
  415. parser_pos += 2;
  416. } else if (input.charAt(parser_pos) === '{' && input.charAt(parser_pos + 1) === '}') {
  417. sharp += '{}';
  418. parser_pos += 2;
  419. }
  420. return [sharp, 'TK_WORD'];
  421. }
  422. }
  423. if (c === '<' && input.substring(parser_pos - 1, parser_pos + 3) === '<!--') {
  424. parser_pos += 3;
  425. flags.in_html_comment = true;
  426. return ['<!--', 'TK_COMMENT'];
  427. }
  428. if (c === '-' && flags.in_html_comment && input.substring(parser_pos - 1, parser_pos + 2) === '-->') {
  429. flags.in_html_comment = false;
  430. parser_pos += 2;
  431. if (wanted_newline) {
  432. print_newline();
  433. }
  434. return ['-->', 'TK_COMMENT'];
  435. }
  436. if (in_array(c, punct)) {
  437. while (parser_pos < input_length && in_array(c + input.charAt(parser_pos), punct)) {
  438. c += input.charAt(parser_pos);
  439. parser_pos += 1;
  440. if (parser_pos >= input_length) {
  441. break;
  442. }
  443. }
  444. if (c === '=') {
  445. return [c, 'TK_EQUALS'];
  446. } else {
  447. return [c, 'TK_OPERATOR'];
  448. }
  449. }
  450. return [c, 'TK_UNKNOWN'];
  451. }
  452. //----------------------------------
  453. indent_string = '';
  454. while (opt_indent_size > 0) {
  455. indent_string += opt_indent_char;
  456. opt_indent_size -= 1;
  457. }
  458. input = js_source_text;
  459. last_word = ''; // last 'TK_WORD' passed
  460. last_type = 'TK_START_EXPR'; // last token type
  461. last_text = ''; // last token text
  462. last_last_text = ''; // pre-last token text
  463. output = [];
  464. do_block_just_closed = false;
  465. whitespace = "\n\r\t ".split('');
  466. wordchar = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$'.split('');
  467. digits = '0123456789'.split('');
  468. punct = '+ - * / % & ++ -- = += -= *= /= %= == === != !== > < >= <= >> << >>> >>>= >>= <<= && &= | || ! !! , : ? ^ ^= |= ::'.split(' ');
  469. // words which should always start on new line.
  470. line_starters = 'continue,try,throw,return,var,if,switch,case,default,for,while,break,function'.split(',');
  471. // states showing if we are currently in expression (i.e. "if" case) - 'EXPRESSION', or in usual block (like, procedure), 'BLOCK'.
  472. // some formatting depends on that.
  473. flag_store = [];
  474. set_mode('BLOCK');
  475. parser_pos = 0;
  476. while (true) {
  477. var t = get_next_token(parser_pos);
  478. token_text = t[0];
  479. token_type = t[1];
  480. if (token_type === 'TK_EOF') {
  481. break;
  482. }
  483. switch (token_type) {
  484. case 'TK_START_EXPR':
  485. if (token_text === '[') {
  486. if (last_type === 'TK_WORD' || last_text === ')') {
  487. // this is array index specifier, break immediately
  488. // a[x], fn()[x]
  489. if (in_array(last_text, line_starters)) {
  490. print_single_space();
  491. }
  492. set_mode('(EXPRESSION)');
  493. print_token();
  494. break;
  495. }
  496. if (flags.mode === '[EXPRESSION]' || flags.mode === '[INDENTED-EXPRESSION]') {
  497. if (last_last_text === ']' && last_text === ',') {
  498. // ], [ goes to new line
  499. if (flags.mode === '[EXPRESSION]') {
  500. flags.mode = '[INDENTED-EXPRESSION]';
  501. if (!opt_keep_array_indentation) {
  502. indent();
  503. }
  504. }
  505. set_mode('[EXPRESSION]');
  506. if (!opt_keep_array_indentation) {
  507. print_newline();
  508. }
  509. } else if (last_text === '[') {
  510. if (flags.mode === '[EXPRESSION]') {
  511. flags.mode = '[INDENTED-EXPRESSION]';
  512. if (!opt_keep_array_indentation) {
  513. indent();
  514. }
  515. }
  516. set_mode('[EXPRESSION]');
  517. if (!opt_keep_array_indentation) {
  518. print_newline();
  519. }
  520. } else {
  521. set_mode('[EXPRESSION]');
  522. }
  523. } else {
  524. set_mode('[EXPRESSION]');
  525. }
  526. } else {
  527. set_mode('(EXPRESSION)');
  528. }
  529. if (last_text === ';' || last_type === 'TK_START_BLOCK') {
  530. print_newline();
  531. } else if (last_type === 'TK_END_EXPR' || last_type === 'TK_START_EXPR' || last_type === 'TK_END_BLOCK' || last_text === '.') {
  532. // do nothing on (( and )( and ][ and ]( and .(
  533. } else if (last_type !== 'TK_WORD' && last_type !== 'TK_OPERATOR') {
  534. print_single_space();
  535. } else if (last_word === 'function') {
  536. // function() vs function ()
  537. if (opt_space_after_anon_function) {
  538. print_single_space();
  539. }
  540. } else if (in_array(last_text, line_starters) || last_text === 'catch') {
  541. print_single_space();
  542. }
  543. print_token();
  544. break;
  545. case 'TK_END_EXPR':
  546. if (token_text === ']') {
  547. if (opt_keep_array_indentation) {
  548. if (last_text === '}') {
  549. // trim_output();
  550. // print_newline(true);
  551. remove_indent();
  552. print_token();
  553. restore_mode();
  554. break;
  555. }
  556. } else {
  557. if (flags.mode === '[INDENTED-EXPRESSION]') {
  558. if (last_text === ']') {
  559. restore_mode();
  560. print_newline();
  561. print_token();
  562. break;
  563. }
  564. }
  565. }
  566. }
  567. restore_mode();
  568. print_token();
  569. break;
  570. case 'TK_START_BLOCK':
  571. if (last_word === 'do') {
  572. set_mode('DO_BLOCK');
  573. } else {
  574. set_mode('BLOCK');
  575. }
  576. if (opt_braces_on_own_line) {
  577. if (last_type !== 'TK_OPERATOR') {
  578. if (last_text == 'return') {
  579. print_single_space();
  580. } else {
  581. print_newline(true);
  582. }
  583. }
  584. print_token();
  585. indent();
  586. } else {
  587. if (last_type !== 'TK_OPERATOR' && last_type !== 'TK_START_EXPR') {
  588. if (last_type === 'TK_START_BLOCK') {
  589. print_newline();
  590. } else {
  591. print_single_space();
  592. }
  593. } else {
  594. // if TK_OPERATOR or TK_START_EXPR
  595. if (is_array(flags.previous_mode) && last_text === ',') {
  596. print_newline(); // [a, b, c, {
  597. }
  598. }
  599. indent();
  600. print_token();
  601. }
  602. break;
  603. case 'TK_END_BLOCK':
  604. restore_mode();
  605. if (opt_braces_on_own_line) {
  606. print_newline();
  607. print_token();
  608. } else {
  609. if (last_type === 'TK_START_BLOCK') {
  610. // nothing
  611. if (just_added_newline) {
  612. remove_indent();
  613. } else {
  614. // {}
  615. trim_output();
  616. }
  617. } else {
  618. print_newline();
  619. }
  620. print_token();
  621. }
  622. break;
  623. case 'TK_WORD':
  624. // no, it's not you. even I have problems understanding how this works
  625. // and what does what.
  626. if (do_block_just_closed) {
  627. // do {} ## while ()
  628. print_single_space();
  629. print_token();
  630. print_single_space();
  631. do_block_just_closed = false;
  632. break;
  633. }
  634. if (token_text === 'function') {
  635. if ((just_added_newline || last_text === ';') && last_text !== '{') {
  636. // make sure there is a nice clean space of at least one blank line
  637. // before a new function definition
  638. n_newlines = just_added_newline ? n_newlines : 0;
  639. for (var i = 0; i < 2 - n_newlines; i++) {
  640. print_newline(false);
  641. }
  642. }
  643. }
  644. if (token_text === 'case' || token_text === 'default') {
  645. if (last_text === ':') {
  646. // switch cases following one another
  647. remove_indent();
  648. } else {
  649. // case statement starts in the same line where switch
  650. flags.indentation_level--;
  651. print_newline();
  652. flags.indentation_level++;
  653. }
  654. print_token();
  655. flags.in_case = true;
  656. break;
  657. }
  658. prefix = 'NONE';
  659. if (last_type === 'TK_END_BLOCK') {
  660. if (!in_array(token_text.toLowerCase(), ['else', 'catch', 'finally'])) {
  661. prefix = 'NEWLINE';
  662. } else {
  663. if (opt_braces_on_own_line) {
  664. prefix = 'NEWLINE';
  665. } else {
  666. prefix = 'SPACE';
  667. print_single_space();
  668. }
  669. }
  670. } else if (last_type === 'TK_SEMICOLON' && (flags.mode === 'BLOCK' || flags.mode === 'DO_BLOCK')) {
  671. prefix = 'NEWLINE';
  672. } else if (last_type === 'TK_SEMICOLON' && is_expression(flags.mode)) {
  673. prefix = 'SPACE';
  674. } else if (last_type === 'TK_STRING') {
  675. prefix = 'NEWLINE';
  676. } else if (last_type === 'TK_WORD') {
  677. prefix = 'SPACE';
  678. } else if (last_type === 'TK_START_BLOCK') {
  679. prefix = 'NEWLINE';
  680. } else if (last_type === 'TK_END_EXPR') {
  681. print_single_space();
  682. prefix = 'NEWLINE';
  683. }
  684. if (last_type !== 'TK_END_BLOCK' && in_array(token_text.toLowerCase(), ['else', 'catch', 'finally'])) {
  685. print_newline();
  686. } else if (in_array(token_text, line_starters) || prefix === 'NEWLINE') {
  687. if (last_text === 'else') {
  688. // no need to force newline on else break
  689. print_single_space();
  690. } else if ((last_type === 'TK_START_EXPR' || last_text === '=' || last_text === ',') && token_text === 'function') {
  691. // no need to force newline on 'function': (function
  692. // DONOTHING
  693. } else if (last_text === 'return' || last_text === 'throw') {
  694. // no newline between 'return nnn'
  695. print_single_space();
  696. } else if (last_type !== 'TK_END_EXPR') {
  697. if ((last_type !== 'TK_START_EXPR' || token_text !== 'var') && last_text !== ':') {
  698. // no need to force newline on 'var': for (var x = 0...)
  699. if (token_text === 'if' && last_word === 'else' && last_text !== '{') {
  700. // no newline for } else if {
  701. print_single_space();
  702. } else {
  703. print_newline();
  704. }
  705. }
  706. } else {
  707. if (in_array(token_text, line_starters) && last_text !== ')') {
  708. print_newline();
  709. }
  710. }
  711. } else if (is_array(flags.mode) && last_text === ',' && last_last_text === '}') {
  712. print_newline(); // }, in lists get a newline treatment
  713. } else if (prefix === 'SPACE') {
  714. print_single_space();
  715. }
  716. print_token();
  717. last_word = token_text;
  718. if (token_text === 'var') {
  719. flags.var_line = true;
  720. flags.var_line_reindented = false;
  721. flags.var_line_tainted = false;
  722. }
  723. if (token_text === 'if' || token_text === 'else') {
  724. flags.if_line = true;
  725. }
  726. break;
  727. case 'TK_SEMICOLON':
  728. print_token();
  729. flags.var_line = false;
  730. flags.var_line_reindented = false;
  731. break;
  732. case 'TK_STRING':
  733. if (last_type === 'TK_START_BLOCK' || last_type === 'TK_END_BLOCK' || last_type === 'TK_SEMICOLON') {
  734. print_newline();
  735. } else if (last_type === 'TK_WORD') {
  736. print_single_space();
  737. }
  738. print_token();
  739. break;
  740. case 'TK_EQUALS':
  741. if (flags.var_line) {
  742. // just got an '=' in a var-line, different formatting/line-breaking, etc will now be done
  743. flags.var_line_tainted = true;
  744. }
  745. print_single_space();
  746. print_token();
  747. print_single_space();
  748. break;
  749. case 'TK_OPERATOR':
  750. var space_before = true;
  751. var space_after = true;
  752. if (flags.var_line && token_text === ',' && (is_expression(flags.mode))) {
  753. // do not break on comma, for(var a = 1, b = 2)
  754. flags.var_line_tainted = false;
  755. }
  756. if (flags.var_line) {
  757. if (token_text === ',') {
  758. if (flags.var_line_tainted) {
  759. print_token();
  760. flags.var_line_reindented = true;
  761. flags.var_line_tainted = false;
  762. print_newline();
  763. break;
  764. } else {
  765. flags.var_line_tainted = false;
  766. }
  767. // } else if (token_text === ':') {
  768. // hmm, when does this happen? tests don't catch this
  769. // flags.var_line = false;
  770. }
  771. }
  772. if (last_text === 'return' || last_text === 'throw') {
  773. // "return" had a special handling in TK_WORD. Now we need to return the favor
  774. print_single_space();
  775. print_token();
  776. break;
  777. }
  778. if (token_text === ':' && flags.in_case) {
  779. print_token(); // colon really asks for separate treatment
  780. print_newline();
  781. flags.in_case = false;
  782. break;
  783. }
  784. if (token_text === '::') {
  785. // no spaces around exotic namespacing syntax operator
  786. print_token();
  787. break;
  788. }
  789. if (token_text === ',') {
  790. if (flags.var_line) {
  791. if (flags.var_line_tainted) {
  792. print_token();
  793. print_newline();
  794. flags.var_line_tainted = false;
  795. } else {
  796. print_token();
  797. print_single_space();
  798. }
  799. } else if (last_type === 'TK_END_BLOCK' && flags.mode !== "(EXPRESSION)") {
  800. print_token();
  801. if (flags.mode === 'OBJECT' && last_text === '}') {
  802. print_newline();
  803. } else {
  804. print_single_space();
  805. }
  806. } else {
  807. if (flags.mode === 'OBJECT') {
  808. print_token();
  809. print_newline();
  810. } else {
  811. // EXPR or DO_BLOCK
  812. print_token();
  813. print_single_space();
  814. }
  815. }
  816. break;
  817. // } else if (in_array(token_text, ['--', '++', '!']) || (in_array(token_text, ['-', '+']) && (in_array(last_type, ['TK_START_BLOCK', 'TK_START_EXPR', 'TK_EQUALS']) || in_array(last_text, line_starters) || in_array(last_text, ['==', '!=', '+=', '-=', '*=', '/=', '+', '-'])))) {
  818. } else if (in_array(token_text, ['--', '++', '!']) || (in_array(token_text, ['-', '+']) && (in_array(last_type, ['TK_START_BLOCK', 'TK_START_EXPR', 'TK_EQUALS', 'TK_OPERATOR']) || in_array(last_text, line_starters)))) {
  819. // unary operators (and binary +/- pretending to be unary) special cases
  820. space_before = false;
  821. space_after = false;
  822. if (last_text === ';' && is_expression(flags.mode)) {
  823. // for (;; ++i)
  824. // ^^^
  825. space_before = true;
  826. }
  827. if (last_type === 'TK_WORD' && in_array(last_text, line_starters)) {
  828. space_before = true;
  829. }
  830. if (flags.mode === 'BLOCK' && (last_text === '{' || last_text === ';')) {
  831. // { foo; --i }
  832. // foo(); --bar;
  833. print_newline();
  834. }
  835. } else if (token_text === '.') {
  836. // decimal digits or object.property
  837. space_before = false;
  838. } else if (token_text === ':') {
  839. if (!is_ternary_op()) {
  840. flags.mode = 'OBJECT';
  841. space_before = false;
  842. }
  843. }
  844. if (space_before) {
  845. print_single_space();
  846. }
  847. print_token();
  848. if (space_after) {
  849. print_single_space();
  850. }
  851. /*
  852. if (token_text === '!') {
  853. // flags.eat_next_space = true;
  854. }
  855. */
  856. break;
  857. case 'TK_BLOCK_COMMENT':
  858. var lines = token_text.split(/\x0a|\x0d\x0a/);
  859. if (/^\/\*\*/.test(token_text)) {
  860. // javadoc: reformat and reindent
  861. print_newline();
  862. output.push(lines[0]);
  863. for (i = 1; i < lines.length; i++) {
  864. print_newline();
  865. output.push(' ');
  866. output.push(lines[i].replace(/^\s\s*|\s\s*$/, ''));
  867. }
  868. } else {
  869. // simple block comment: leave intact
  870. if (lines.length > 1) {
  871. // multiline comment block starts with a new line
  872. print_newline();
  873. trim_output();
  874. } else {
  875. // single-line /* comment */ stays where it is
  876. print_single_space();
  877. }
  878. for (i = 0; i < lines.length; i++) {
  879. output.push(lines[i]);
  880. output.push('\n');
  881. }
  882. }
  883. print_newline();
  884. break;
  885. case 'TK_INLINE_COMMENT':
  886. print_single_space();
  887. print_token();
  888. if (is_expression(flags.mode)) {
  889. print_single_space();
  890. } else {
  891. print_newline();
  892. }
  893. break;
  894. case 'TK_COMMENT':
  895. // print_newline();
  896. if (wanted_newline) {
  897. print_newline();
  898. } else {
  899. print_single_space();
  900. }
  901. print_token();
  902. print_newline();
  903. break;
  904. case 'TK_UNKNOWN':
  905. print_token();
  906. break;
  907. }
  908. last_last_text = last_text;
  909. last_type = token_type;
  910. last_text = token_text;
  911. }
  912. return output.join('').replace(/[\n ]+$/, '');
  913. }