From d83308d87e4055ba1c0f7f6f87bb57e707732ac0 Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 8 Jul 2015 00:16:01 -0500 Subject: [PATCH] Add operator_position functionality - Add sanitization of operator_position option. Throws error if invalid. - Possible operator_position options are: 1) 'before-newline' (default) 2) 'after-newline' 3) 'preserve-newline' - Move some code around in handle_operator in order to separate conflicting logic now that operator newlines are accounted for. - Modify allow_wrap_or_preserved_newline to handle operator preserved newlines as well. - Add -> build tests - Within the tests, add an 'inputlib.js' file holding common input arrays. This was necessary due to the matrices not working cleanly with default behavior. Changes not directly relevant to operator_position - Add Object.values polyfill - Ignore intellijidea project file - Fix a few boolean variable assignments within if statements. Though not always the case, the expression within the if can be assigned directly to the variable --- .gitignore | 1 + js/lib/beautify.js | 185 +++++-- js/lib/cli.js | 3 + .../generated/beautify-javascript-tests.js | 497 +++++++++++++++++- python/jsbeautifier/__init__.py | 147 ++++-- python/jsbeautifier/tests/generated/tests.py | 497 +++++++++++++++++- test/data/javascript/inputlib.js | 88 ++++ test/data/javascript/node.mustache | 8 +- test/data/javascript/python.mustache | 8 +- test/data/javascript/tests.js | 232 +++++++- 10 files changed, 1583 insertions(+), 83 deletions(-) create mode 100644 test/data/javascript/inputlib.js diff --git a/.gitignore b/.gitignore index 599cd506..321a5a9e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ python/dist python/jsbeautifier.egg-info target +.idea/ diff --git a/js/lib/beautify.js b/js/lib/beautify.js index 20ad874b..870aaa50 100644 --- a/js/lib/beautify.js +++ b/js/lib/beautify.js @@ -84,6 +84,23 @@ */ +// Object.values polyfill found here: +// http://tokenposts.blogspot.com.au/2012/04/javascript-objectkeys-browser.html +if (!Object.values) { + Object.values = function(o) { + if (o !== Object(o)) { + throw new TypeError('Object.values called on a non-object'); + } + var k=[],p; + for (p in o) { + if (Object.prototype.hasOwnProperty.call(o,p)) { + k.push(o[p]); + } + } + return k; + }; +} + (function() { var acorn = {}; @@ -179,6 +196,28 @@ return beautifier.beautify(); } + function sanitizeOperatorPosition(opPosition) { + opPosition = opPosition || OPERATOR_POSITION.before_newline; + + var validPositionValues = Object.values(OPERATOR_POSITION); + + if (!in_array(opPosition, validPositionValues)) { + throw new Error("Invalid Option Value: The option 'operator_position' must be one of the following values\n" + + validPositionValues + + "\nYou passed in: '" + opPosition + "'"); + } + + return opPosition; + } + + var OPERATOR_POSITION = { + before_newline: 'before-newline', + after_newline: 'after-newline', + preserve_newline: 'preserve-newline', + }; + + var OPERATOR_POSITION_BEFORE_OR_PRESERVE = [OPERATOR_POSITION.before_newline, OPERATOR_POSITION.preserve_newline]; + var MODE = { BlockStatement: 'BlockStatement', // 'BLOCK' Statement: 'Statement', // 'STATEMENT' @@ -288,6 +327,7 @@ opt.e4x = (options.e4x === undefined) ? false : options.e4x; opt.end_with_newline = (options.end_with_newline === undefined) ? false : options.end_with_newline; opt.comma_first = (options.comma_first === undefined) ? false : options.comma_first; + opt.operator_position = sanitizeOperatorPosition(options.operator_position); // For testing of beautify ignore:start directive opt.test_output_raw = (options.test_output_raw === undefined) ? false : options.test_output_raw; @@ -440,7 +480,19 @@ return } - if ((opt.preserve_newlines && current_token.wanted_newline) || force_linewrap) { + var shouldPreserveOrForce = (opt.preserve_newlines && current_token.wanted_newline) || force_linewrap; + var operatorLogicApplies = in_array(flags.last_text, Tokenizer.positionable_operators) || in_array(current_token.text, Tokenizer.positionable_operators); + + if (operatorLogicApplies) { + var shouldPrintOperatorNewline = ( + in_array(flags.last_text, Tokenizer.positionable_operators) && + in_array(opt.operator_position, OPERATOR_POSITION_BEFORE_OR_PRESERVE) + ) + || in_array(current_token.text, Tokenizer.positionable_operators); + shouldPreserveOrForce = shouldPreserveOrForce && shouldPrintOperatorNewline; + } + + if (shouldPreserveOrForce) { print_newline(false, true); } else if (opt.wrap_line_length) { if (last_type === 'TK_RESERVED' && in_array(flags.last_text, newline_restricted_tokens)) { @@ -1185,6 +1237,18 @@ return; } + if (current_token.text === '::') { + // no spaces around exotic namespacing syntax operator + print_token(); + return; + } + + // Allow line wrapping between operators when operator_position is + // set to before or preserve + if (last_type === 'TK_OPERATOR' && in_array(opt.operator_position, OPERATOR_POSITION_BEFORE_OR_PRESERVE)) { + allow_wrap_or_preserved_newline(); + } + if (current_token.text === ':' && flags.in_case) { flags.case_body = true; indent(); @@ -1194,21 +1258,86 @@ return; } - if (current_token.text === '::') { - // no spaces around exotic namespacing syntax operator - print_token(); - return; - } - - // Allow line wrapping between operators - if (last_type === 'TK_OPERATOR') { - allow_wrap_or_preserved_newline(); - } - var space_before = true; var space_after = true; + var in_ternary = false; + var isGeneratorAsterisk = current_token.text === '*' && last_type === 'TK_RESERVED' && flags.last_text === 'function'; + var isUnary = in_array(current_token.text, ['-', '+']) && ( + in_array(last_type, ['TK_START_BLOCK', 'TK_START_EXPR', 'TK_EQUALS', 'TK_OPERATOR']) || + in_array(flags.last_text, Tokenizer.line_starters) || + flags.last_text === ',' + ); - if (in_array(current_token.text, ['--', '++', '!', '~']) || (in_array(current_token.text, ['-', '+']) && (in_array(last_type, ['TK_START_BLOCK', 'TK_START_EXPR', 'TK_EQUALS', 'TK_OPERATOR']) || in_array(flags.last_text, Tokenizer.line_starters) || flags.last_text === ','))) { + if (current_token.text === ':') { + if (flags.ternary_depth === 0) { + // Colon is invalid javascript outside of ternary and object, but do our best to guess what was meant. + space_before = false; + } else { + flags.ternary_depth -= 1; + in_ternary = true; + } + } else if (current_token.text === '?') { + flags.ternary_depth += 1; + } + + // let's handle the operator_position option prior to any conflicting logic + if (!isUnary && !isGeneratorAsterisk && opt.preserve_newlines && in_array(current_token.text, Tokenizer.positionable_operators)) { + var isColon = current_token.text === ':'; + var isTernaryColon = (isColon && in_ternary); + var isOtherColon = (isColon && !in_ternary); + + switch (opt.operator_position) { + case OPERATOR_POSITION.before_newline: + // if the current token is : and it's not a ternary statement then we set space_before to false + output.space_before_token = !isOtherColon; + + print_token(); + + if (!isColon || isTernaryColon) { + allow_wrap_or_preserved_newline(); + } + + output.space_before_token = true; + return; + + case OPERATOR_POSITION.after_newline: + // if the current token is anything but colon, or (via deduction) it's a colon and in a ternary statement, + // then print a newline. + + output.space_before_token = true; + + if (!isColon || isTernaryColon) { + if (get_token(1).wanted_newline) { + print_newline(false, true); + } else { + allow_wrap_or_preserved_newline(); + } + } else { + output.space_before_token = false; + } + + print_token(); + + output.space_before_token = true; + return; + + case OPERATOR_POSITION.preserve_newline: + if (!isOtherColon) { + allow_wrap_or_preserved_newline(); + } + + // if we just added a newline, or the current token is : and it's not a ternary statement, + // then we set space_before to false + space_before = !(output.just_added_newline() || isOtherColon); + + output.space_before_token = space_before; + print_token(); + output.space_before_token = true; + return; + } + } + + if (in_array(current_token.text, ['--', '++', '!', '~']) || isUnary) { // unary operators (and binary +/- pretending to be unary) special cases space_before = false; @@ -1250,16 +1379,7 @@ // foo(); --bar; print_newline(); } - } else if (current_token.text === ':') { - if (flags.ternary_depth === 0) { - // Colon is invalid javascript outside of ternary and object, but do our best to guess what was meant. - space_before = false; - } else { - flags.ternary_depth -= 1; - } - } else if (current_token.text === '?') { - flags.ternary_depth += 1; - } else if (current_token.text === '*' && last_type === 'TK_RESERVED' && flags.last_text === 'function') { + } else if (isGeneratorAsterisk) { space_before = false; space_after = false; } @@ -1273,9 +1393,7 @@ output.add_raw_token(current_token) if (current_token.directives && current_token.directives['preserve'] === 'end') { // If we're testing the raw output behavior, do not allow a directive to turn it off. - if (!opt.test_output_raw) { - output.raw = false; - } + output.raw = opt.test_output_raw; } return; } @@ -1308,12 +1426,8 @@ // block comment starts with a new line print_newline(false, true); if (lines.length > 1) { - if (all_lines_start_with(lines.slice(1), '*')) { - javadoc = true; - } - else if (each_line_matches_indent(lines.slice(1), lastIndent)) { - starless = true; - } + javadoc = all_lines_start_with(lines.slice(1), '*'); + starless = each_line_matches_indent(lines.slice(1), lastIndent); } // first line always indented @@ -1613,7 +1727,11 @@ var digit_oct = /[01234567]/; var digit_hex = /[0123456789abcdefABCDEF]/; - var punct = ('+ - * / % & ++ -- = += -= *= /= %= == === != !== > < >= <= >> << >>> >>>= >>= <<= && &= | || ! ~ , : ? ^ ^= |= :: => **').split(' '); + this.positionable_operators = '!= !== % & && * ** + - / : < << <= == === > >= >> >>> ? ^ | ||'.split(' ') + var punct = this.positionable_operators.concat( + // non-positionable operators - these do not follow operator position settings + '! %= &= *= ++ += , -- /= :: <<= = => >>= >>>= ^= |= ~'.split(' ')) + // words which should always start on new line. this.line_starters = 'continue,try,throw,return,var,let,const,if,switch,case,default,for,while,break,function,import,export'.split(','); var reserved_words = this.line_starters.concat(['do', 'in', 'else', 'get', 'set', 'new', 'catch', 'finally', 'typeof', 'yield', 'async', 'await', 'from', 'as']); @@ -2177,7 +2295,6 @@ } return out; } - } diff --git a/js/lib/cli.js b/js/lib/cli.js index ce4f3df5..ac9c2b2f 100755 --- a/js/lib/cli.js +++ b/js/lib/cli.js @@ -66,6 +66,7 @@ var fs = require('fs'), "e4x": Boolean, "end_with_newline": Boolean, "comma_first": Boolean, + "operator_position": ["before-newline", "after-newline", "preserve-newline"], // CSS-only "selector_separator_newline": Boolean, "newline_between_rules": Boolean, @@ -108,6 +109,7 @@ var fs = require('fs'), "X": ["--e4x"], "n": ["--end_with_newline"], "C": ["--comma_first"], + "O": ["--operator_position"], // CSS-only "L": ["--selector_separator_newline"], "N": ["--newline_between_rules"], @@ -259,6 +261,7 @@ function usage(err) { msg.push(' -X, --e4x Pass E4X xml literals through untouched'); msg.push(' --good-stuff Warm the cockles of Crockford\'s heart'); msg.push(' -C, --comma-first Put commas at the beginning of new line instead of end'); + msg.push(' -O, --operator-position Set operator position (before-newline|after-newline|preserve-newline) [before-newline]'); break; case "html": msg.push(' -b, --brace-style [collapse|expand|end-expand] ["collapse"]'); diff --git a/js/test/generated/beautify-javascript-tests.js b/js/test/generated/beautify-javascript-tests.js index 270c316a..e9cd7c10 100644 --- a/js/test/generated/beautify-javascript-tests.js +++ b/js/test/generated/beautify-javascript-tests.js @@ -28,6 +28,7 @@ function run_javascript_tests(test_obj, Urlencoded, js_beautify, html_beautify, default_opts.jslint_happy = false; default_opts.keep_array_indentation = false; default_opts.brace_style = 'collapse'; + default_opts.operator_position = 'before-newline'; function reset_options() { @@ -498,6 +499,494 @@ function run_javascript_tests(test_obj, Urlencoded, js_beautify, html_beautify, '}'); + reset_options(); + //============================================================ + // operator_position option - ensure no neswlines if preserve_newlines is false - () + opts.operator_position = 'before-newline'; + opts.preserve_newlines = false; + bt( + 'var res = a + b - c / d * e % f;\n' + + 'var res = g & h | i ^ j;\n' + + 'var res = (k && l || m) ? n : o;\n' + + 'var res = p >> q << r >>> s;\n' + + 'var res = t === u !== v != w == x >= y <= z > aa < ab;\n' + + 'ac + -ad'); + bt( + 'var res = a + b\n' + + '- c /\n' + + 'd * e\n' + + '%\n' + + 'f;\n' + + ' var res = g & h\n' + + '| i ^\n' + + 'j;\n' + + 'var res = (k &&\n' + + 'l\n' + + '|| m) ?\n' + + 'n\n' + + ': o\n' + + ';\n' + + 'var res = p\n' + + '>> q <<\n' + + 'r\n' + + '>>> s;\n' + + 'var res\n' + + ' = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + 'w\n' + + '== x >=\n' + + 'y <= z > aa <\n' + + 'ab;\n' + + 'ac +\n' + + '-ad', + 'var res = a + b - c / d * e % f;\n' + + 'var res = g & h | i ^ j;\n' + + 'var res = (k && l || m) ? n : o;\n' + + 'var res = p >> q << r >>> s;\n' + + 'var res = t === u !== v != w == x >= y <= z > aa < ab;\n' + + 'ac + -ad'); + + // operator_position option - ensure no neswlines if preserve_newlines is false - () + opts.operator_position = 'after-newline'; + opts.preserve_newlines = false; + bt( + 'var res = a + b - c / d * e % f;\n' + + 'var res = g & h | i ^ j;\n' + + 'var res = (k && l || m) ? n : o;\n' + + 'var res = p >> q << r >>> s;\n' + + 'var res = t === u !== v != w == x >= y <= z > aa < ab;\n' + + 'ac + -ad'); + bt( + 'var res = a + b\n' + + '- c /\n' + + 'd * e\n' + + '%\n' + + 'f;\n' + + ' var res = g & h\n' + + '| i ^\n' + + 'j;\n' + + 'var res = (k &&\n' + + 'l\n' + + '|| m) ?\n' + + 'n\n' + + ': o\n' + + ';\n' + + 'var res = p\n' + + '>> q <<\n' + + 'r\n' + + '>>> s;\n' + + 'var res\n' + + ' = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + 'w\n' + + '== x >=\n' + + 'y <= z > aa <\n' + + 'ab;\n' + + 'ac +\n' + + '-ad', + 'var res = a + b - c / d * e % f;\n' + + 'var res = g & h | i ^ j;\n' + + 'var res = (k && l || m) ? n : o;\n' + + 'var res = p >> q << r >>> s;\n' + + 'var res = t === u !== v != w == x >= y <= z > aa < ab;\n' + + 'ac + -ad'); + + // operator_position option - ensure no neswlines if preserve_newlines is false - () + opts.operator_position = 'preserve-newline'; + opts.preserve_newlines = false; + bt( + 'var res = a + b - c / d * e % f;\n' + + 'var res = g & h | i ^ j;\n' + + 'var res = (k && l || m) ? n : o;\n' + + 'var res = p >> q << r >>> s;\n' + + 'var res = t === u !== v != w == x >= y <= z > aa < ab;\n' + + 'ac + -ad'); + bt( + 'var res = a + b\n' + + '- c /\n' + + 'd * e\n' + + '%\n' + + 'f;\n' + + ' var res = g & h\n' + + '| i ^\n' + + 'j;\n' + + 'var res = (k &&\n' + + 'l\n' + + '|| m) ?\n' + + 'n\n' + + ': o\n' + + ';\n' + + 'var res = p\n' + + '>> q <<\n' + + 'r\n' + + '>>> s;\n' + + 'var res\n' + + ' = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + 'w\n' + + '== x >=\n' + + 'y <= z > aa <\n' + + 'ab;\n' + + 'ac +\n' + + '-ad', + 'var res = a + b - c / d * e % f;\n' + + 'var res = g & h | i ^ j;\n' + + 'var res = (k && l || m) ? n : o;\n' + + 'var res = p >> q << r >>> s;\n' + + 'var res = t === u !== v != w == x >= y <= z > aa < ab;\n' + + 'ac + -ad'); + + + reset_options(); + //============================================================ + // operator_position option - set to 'before-newline' (default value) + + // comprehensive, various newlines + bt( + 'var res = a + b\n' + + '- c /\n' + + 'd * e\n' + + '%\n' + + 'f;\n' + + ' var res = g & h\n' + + '| i ^\n' + + 'j;\n' + + 'var res = (k &&\n' + + 'l\n' + + '|| m) ?\n' + + 'n\n' + + ': o\n' + + ';\n' + + 'var res = p\n' + + '>> q <<\n' + + 'r\n' + + '>>> s;\n' + + 'var res\n' + + ' = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + 'w\n' + + '== x >=\n' + + 'y <= z > aa <\n' + + 'ab;\n' + + 'ac +\n' + + '-ad', + 'var res = a + b -\n' + + ' c /\n' + + ' d * e %\n' + + ' f;\n' + + 'var res = g & h |\n' + + ' i ^\n' + + ' j;\n' + + 'var res = (k &&\n' + + ' l ||\n' + + ' m) ?\n' + + ' n :\n' + + ' o;\n' + + 'var res = p >>\n' + + ' q <<\n' + + ' r >>>\n' + + ' s;\n' + + 'var res = t\n' + + '\n' + + ' ===\n' + + ' u !== v !=\n' + + ' w ==\n' + + ' x >=\n' + + ' y <= z > aa <\n' + + ' ab;\n' + + 'ac +\n' + + ' -ad'); + + // colon special case + bt( + 'var a = {\n' + + ' b\n' + + ': bval,\n' + + ' c:\n' + + 'cval\n' + + ' ,d: dval\n' + + '};\n' + + 'var e = f ? g\n' + + ': h;\n' + + 'var i = j ? k :\n' + + 'l;', + 'var a = {\n' + + ' b: bval,\n' + + ' c: cval,\n' + + ' d: dval\n' + + '};\n' + + 'var e = f ? g :\n' + + ' h;\n' + + 'var i = j ? k :\n' + + ' l;'); + + // catch-all, includes brackets and other various code + bt( + 'var d = 1;\n' + + 'if (a === b\n' + + ' && c) {\n' + + ' d = (c * everything\n' + + ' / something_else) %\n' + + ' b;\n' + + ' e\n' + + ' += d;\n' + + '\n' + + '} else if (!(complex && simple) ||\n' + + ' (emotion && emotion.name === "happy")) {\n' + + ' cryTearsOfJoy(many ||\n' + + ' anOcean\n' + + ' || aRiver);\n' + + '}', + 'var d = 1;\n' + + 'if (a === b &&\n' + + ' c) {\n' + + ' d = (c * everything /\n' + + ' something_else) %\n' + + ' b;\n' + + ' e\n' + + ' += d;\n' + + '\n' + + '} else if (!(complex && simple) ||\n' + + ' (emotion && emotion.name === "happy")) {\n' + + ' cryTearsOfJoy(many ||\n' + + ' anOcean ||\n' + + ' aRiver);\n' + + '}'); + + + reset_options(); + //============================================================ + // operator_position option - set to 'after_newline' + opts.operator_position = 'after-newline'; + + // comprehensive, various newlines + bt( + 'var res = a + b\n' + + '- c /\n' + + 'd * e\n' + + '%\n' + + 'f;\n' + + ' var res = g & h\n' + + '| i ^\n' + + 'j;\n' + + 'var res = (k &&\n' + + 'l\n' + + '|| m) ?\n' + + 'n\n' + + ': o\n' + + ';\n' + + 'var res = p\n' + + '>> q <<\n' + + 'r\n' + + '>>> s;\n' + + 'var res\n' + + ' = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + 'w\n' + + '== x >=\n' + + 'y <= z > aa <\n' + + 'ab;\n' + + 'ac +\n' + + '-ad', + 'var res = a + b\n' + + ' - c\n' + + ' / d * e\n' + + ' % f;\n' + + 'var res = g & h\n' + + ' | i\n' + + ' ^ j;\n' + + 'var res = (k\n' + + ' && l\n' + + ' || m)\n' + + ' ? n\n' + + ' : o;\n' + + 'var res = p\n' + + ' >> q\n' + + ' << r\n' + + ' >>> s;\n' + + 'var res = t\n' + + '\n' + + ' === u !== v\n' + + ' != w\n' + + ' == x\n' + + ' >= y <= z > aa\n' + + ' < ab;\n' + + 'ac\n' + + ' + -ad'); + + // colon special case + bt( + 'var a = {\n' + + ' b\n' + + ': bval,\n' + + ' c:\n' + + 'cval\n' + + ' ,d: dval\n' + + '};\n' + + 'var e = f ? g\n' + + ': h;\n' + + 'var i = j ? k :\n' + + 'l;', + 'var a = {\n' + + ' b: bval,\n' + + ' c: cval,\n' + + ' d: dval\n' + + '};\n' + + 'var e = f ? g\n' + + ' : h;\n' + + 'var i = j ? k\n' + + ' : l;'); + + // catch-all, includes brackets and other various code + bt( + 'var d = 1;\n' + + 'if (a === b\n' + + ' && c) {\n' + + ' d = (c * everything\n' + + ' / something_else) %\n' + + ' b;\n' + + ' e\n' + + ' += d;\n' + + '\n' + + '} else if (!(complex && simple) ||\n' + + ' (emotion && emotion.name === "happy")) {\n' + + ' cryTearsOfJoy(many ||\n' + + ' anOcean\n' + + ' || aRiver);\n' + + '}', + 'var d = 1;\n' + + 'if (a === b\n' + + ' && c) {\n' + + ' d = (c * everything\n' + + ' / something_else)\n' + + ' % b;\n' + + ' e\n' + + ' += d;\n' + + '\n' + + '} else if (!(complex && simple)\n' + + ' || (emotion && emotion.name === "happy")) {\n' + + ' cryTearsOfJoy(many\n' + + ' || anOcean\n' + + ' || aRiver);\n' + + '}'); + + + reset_options(); + //============================================================ + // operator_position option - set to 'preserve-newline' + opts.operator_position = 'preserve-newline'; + + // comprehensive, various newlines + bt( + 'var res = a + b\n' + + '- c /\n' + + 'd * e\n' + + '%\n' + + 'f;\n' + + ' var res = g & h\n' + + '| i ^\n' + + 'j;\n' + + 'var res = (k &&\n' + + 'l\n' + + '|| m) ?\n' + + 'n\n' + + ': o\n' + + ';\n' + + 'var res = p\n' + + '>> q <<\n' + + 'r\n' + + '>>> s;\n' + + 'var res\n' + + ' = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + 'w\n' + + '== x >=\n' + + 'y <= z > aa <\n' + + 'ab;\n' + + 'ac +\n' + + '-ad', + 'var res = a + b\n' + + ' - c /\n' + + ' d * e\n' + + ' %\n' + + ' f;\n' + + 'var res = g & h\n' + + ' | i ^\n' + + ' j;\n' + + 'var res = (k &&\n' + + ' l\n' + + ' || m) ?\n' + + ' n\n' + + ' : o;\n' + + 'var res = p\n' + + ' >> q <<\n' + + ' r\n' + + ' >>> s;\n' + + 'var res = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + ' w\n' + + ' == x >=\n' + + ' y <= z > aa <\n' + + ' ab;\n' + + 'ac +\n' + + ' -ad'); + + // colon special case + bt( + 'var a = {\n' + + ' b\n' + + ': bval,\n' + + ' c:\n' + + 'cval\n' + + ' ,d: dval\n' + + '};\n' + + 'var e = f ? g\n' + + ': h;\n' + + 'var i = j ? k :\n' + + 'l;', + 'var a = {\n' + + ' b: bval,\n' + + ' c: cval,\n' + + ' d: dval\n' + + '};\n' + + 'var e = f ? g\n' + + ' : h;\n' + + 'var i = j ? k :\n' + + ' l;'); + + // catch-all, includes brackets and other various code + bt( + 'var d = 1;\n' + + 'if (a === b\n' + + ' && c) {\n' + + ' d = (c * everything\n' + + ' / something_else) %\n' + + ' b;\n' + + ' e\n' + + ' += d;\n' + + '\n' + + '} else if (!(complex && simple) ||\n' + + ' (emotion && emotion.name === "happy")) {\n' + + ' cryTearsOfJoy(many ||\n' + + ' anOcean\n' + + ' || aRiver);\n' + + '}'); + + reset_options(); //============================================================ // New Test Suite @@ -2437,7 +2926,7 @@ function run_javascript_tests(test_obj, Urlencoded, js_beautify, html_beautify, // Line wrap test intputs //.............---------1---------2---------3---------4---------5---------6---------7 //.............1234567890123456789012345678901234567890123456789012345678901234567890 - wrap_input_1=('foo.bar().baz().cucumber((fat && "sassy") || (leans\n&& mean));\n' + + wrap_input_1=('foo.bar().baz().cucumber((fat && "sassy") || (leans && mean));\n' + 'Test_very_long_variable_name_this_should_never_wrap\n.but_this_can\n' + 'return between_return_and_expression_should_never_wrap.but_this_can\n' + 'throw between_throw_and_expression_should_never_wrap.but_this_can\n' + @@ -2452,7 +2941,7 @@ function run_javascript_tests(test_obj, Urlencoded, js_beautify, html_beautify, //.............---------1---------2---------3---------4---------5---------6---------7 //.............1234567890123456789012345678901234567890123456789012345678901234567890 wrap_input_2=('{\n' + - ' foo.bar().baz().cucumber((fat && "sassy") || (leans\n&& mean));\n' + + ' foo.bar().baz().cucumber((fat && "sassy") || (leans && mean));\n' + ' Test_very_long_variable_name_this_should_never_wrap\n.but_this_can\n' + ' return between_return_and_expression_should_never_wrap.but_this_can\n' + ' throw between_throw_and_expression_should_never_wrap.but_this_can\n' + @@ -2867,8 +3356,8 @@ function run_javascript_tests(test_obj, Urlencoded, js_beautify, html_beautify, bt('var a = /*i*/\n"b";', 'var a = /*i*/\n "b";'); bt('var a = /*i*/\nb;', 'var a = /*i*/\n b;'); bt('{\n\n\n"x"\n}', '{\n\n\n "x"\n}'); - bt('if(a &&\nb\n||\nc\n||d\n&&\ne) e = f', 'if (a &&\n b ||\n c || d &&\n e) e = f'); - bt('if(a &&\n(b\n||\nc\n||d)\n&&\ne) e = f', 'if (a &&\n (b ||\n c || d) &&\n e) e = f'); + bt('if(a &&\nb\n||\nc\n||d\n&&\ne) e = f', 'if (a &&\n b ||\n c ||\n d &&\n e) e = f'); + bt('if(a &&\n(b\n||\nc\n||d)\n&&\ne) e = f', 'if (a &&\n (b ||\n c ||\n d) &&\n e) e = f'); test_fragment('\n\n"x"', '"x"'); // this beavior differs between js and python, defaults to unlimited in js, 10 in python diff --git a/python/jsbeautifier/__init__.py b/python/jsbeautifier/__init__.py index 7e214661..4d52898d 100644 --- a/python/jsbeautifier/__init__.py +++ b/python/jsbeautifier/__init__.py @@ -82,6 +82,7 @@ class BeautifierOptions: self.break_chained_methods = False self.end_with_newline = False self.comma_first = False + self.operator_position = 'before-newline' # For testing of beautify ignore:start directive self.test_output_raw = False @@ -319,7 +320,22 @@ Rarely needed options: else: return 0 +OPERATOR_POSITION = { + 'before_newline': 'before-newline', + 'after_newline': 'after-newline', + 'preserve_newline': 'preserve-newline' +} +OPERATOR_POSITION_BEFORE_OR_PRESERVE = [OPERATOR_POSITION['before_newline'], OPERATOR_POSITION['preserve_newline']]; +def sanitizeOperatorPosition(opPosition): + if not opPosition: + return OPERATOR_POSITION['before_newline'] + elif opPosition not in OPERATOR_POSITION.values(): + raise ValueError("Invalid Option Value: The option 'operator_position' must be one of the following values\n" + + str(OPERATOR_POSITION.values()) + + "\nYou passed in: '" + opPosition + "'") + + return opPosition class MODE: BlockStatement, Statement, ObjectLiteral, ArrayLiteral, \ @@ -484,7 +500,15 @@ class Beautifier: if self.output.just_added_newline(): return - if (self.opts.preserve_newlines and current_token.wanted_newline) or force_linewrap: + shouldPreserveOrForce = (self.opts.preserve_newlines and current_token.wanted_newline) or force_linewrap + operatorLogicApplies = self.flags.last_text in Tokenizer.positionable_operators or current_token.text in Tokenizer.positionable_operators + + if operatorLogicApplies: + shouldPrintOperatorNewline = (self.flags.last_text in Tokenizer.positionable_operators and self.opts.operator_position in OPERATOR_POSITION_BEFORE_OR_PRESERVE) \ + or current_token.text in Tokenizer.positionable_operators + shouldPreserveOrForce = shouldPreserveOrForce and shouldPrintOperatorNewline + + if shouldPreserveOrForce: self.print_newline(preserve_statement_flags = True) elif self.opts.wrap_line_length > 0: if self.last_type == 'TK_RESERVED' and self.flags.last_text in self._newline_restricted_tokens: @@ -1106,6 +1130,15 @@ class Beautifier: self.print_token(current_token) return + if current_token.text == '::': + # no spaces around the exotic namespacing syntax operator + self.print_token(current_token) + return + + # Allow line wrapping between operators when operator_position is + # set to before or preserve + if self.last_type == 'TK_OPERATOR' and self.opts.operator_position in OPERATOR_POSITION_BEFORE_OR_PRESERVE: + self.allow_wrap_or_preserved_newline(current_token) if current_token.text == ':' and self.flags.in_case: self.flags.case_body = True @@ -1115,23 +1148,78 @@ class Beautifier: self.flags.in_case = False return - if current_token.text == '::': - # no spaces around the exotic namespacing syntax operator - self.print_token(current_token) - return - - # Allow line wrapping between operators in an expression - if self.last_type == 'TK_OPERATOR': - self.allow_wrap_or_preserved_newline(current_token) - space_before = True space_after = True + in_ternary = False + isGeneratorAsterisk = current_token.text == '*' and self.last_type == 'TK_RESERVED' and self.flags.last_text == 'function' + isUnary = current_token.text in ['+', '-'] \ + and (self.last_type in ['TK_START_BLOCK', 'TK_START_EXPR', 'TK_EQUALS', 'TK_OPERATOR'] \ + or self.flags.last_text in Tokenizer.line_starters or self.flags.last_text == ',') - if current_token.text in ['--', '++', '!', '~'] \ - or (current_token.text in ['+', '-'] \ - and (self.last_type in ['TK_START_BLOCK', 'TK_START_EXPR', 'TK_EQUALS', 'TK_OPERATOR'] \ - or self.flags.last_text in Tokenizer.line_starters or self.flags.last_text == ',')): + if current_token.text == ':': + if self.flags.ternary_depth == 0: + # Colon is invalid javascript outside of ternary and object, but do our best to guess what was meant. + space_before = False + else: + self.flags.ternary_depth -= 1 + in_ternary = True + elif current_token.text == '?': + self.flags.ternary_depth += 1 + + # let's handle the operator_position option prior to any conflicting logic + if (not isUnary) and (not isGeneratorAsterisk) and \ + self.opts.preserve_newlines and current_token.text in Tokenizer.positionable_operators: + + isColon = current_token.text == ':' + isTernaryColon = isColon and in_ternary + isOtherColon = isColon and not in_ternary + + if self.opts.operator_position == OPERATOR_POSITION['before_newline']: + # if the current token is : and it's not a ternary statement then we set space_before to false + self.output.space_before_token = not isOtherColon + + self.print_token(current_token) + + if (not isColon) or isTernaryColon: + self.allow_wrap_or_preserved_newline(current_token) + + self.output.space_before_token = True + + return + + elif self.opts.operator_position == OPERATOR_POSITION['after_newline']: + # if the current token is anything but colon, or (via deduction) it's a colon and in a ternary statement, + # then print a newline. + self.output.space_before_token = True + + if (not isColon) or isTernaryColon: + if self.get_token(1).wanted_newline: + self.print_newline(preserve_statement_flags = True) + else: + self.allow_wrap_or_preserved_newline(current_token) + else: + self.output.space_before_token = False + + self.print_token(current_token) + + self.output.space_before_token = True + return + + elif self.opts.operator_position == OPERATOR_POSITION['preserve_newline']: + if not isOtherColon: + self.allow_wrap_or_preserved_newline(current_token) + + # if we just added a newline, or the current token is : and it's not a ternary statement, + # then we set space_before to false + self.output.space_before_token = not (self.output.just_added_newline() or isOtherColon) + + self.print_token(current_token) + + self.output.space_before_token = True + return + + if current_token.text in ['--', '++', '!', '~'] or isUnary: space_before = False space_after = False @@ -1166,15 +1254,7 @@ class Beautifier: # foo(): --bar self.print_newline() - elif current_token.text == ':': - if self.flags.ternary_depth == 0: - # Colon is invalid javascript outside of ternary and object, but do our best to guess what was meant. - space_before = False - else: - self.flags.ternary_depth -= 1 - elif current_token.text == '?': - self.flags.ternary_depth += 1 - elif current_token.text == '*' and self.last_type == 'TK_RESERVED' and self.flags.last_text == 'function': + elif isGeneratorAsterisk: space_before = False space_after = False @@ -1193,8 +1273,7 @@ class Beautifier: self.output.add_raw_token(current_token) if current_token.directives and current_token.directives.get('preserve') == 'end': # If we're testing the raw output behavior, do not allow a directive to turn it off. - if not self.opts.test_output_raw: - self.output.raw = False + self.output.raw = self.opts.test_output_raw return if current_token.directives: @@ -1222,10 +1301,8 @@ class Beautifier: # block comment starts with a new line self.print_newline(preserve_statement_flags = True) if len(lines) > 1: - if not any(l for l in lines[1:] if ( l.strip() == '' or (l.lstrip())[0] != '*')): - javadoc = True - elif all(l.startswith(last_indent) or l.strip() == '' for l in lines[1:]): - starless = True + javadoc = not any(l for l in lines[1:] if ( l.strip() == '' or (l.lstrip())[0] != '*')) + starless = all(l.startswith(last_indent) or l.strip() == '' for l in lines[1:]) # first line always indented self.print_token(current_token, lines[0]) @@ -1465,7 +1542,11 @@ class Tokenizer: digit_bin = re.compile('[01]') digit_oct = re.compile('[01234567]') digit_hex = re.compile('[0123456789abcdefABCDEF]') - punct = ('+ - * / % & ++ -- = += -= *= /= %= == === != !== > < >= <= >> << >>> >>>= >>= <<= && &= | || ! ~ , : ? ^ ^= |= :: => **').split(' ') + + positionable_operators = '!= !== % & && * ** + - / : < << <= == === > >= >> >>> ? ^ | ||'.split(' ') + punct = (positionable_operators + + # non-positionable operators - these do not follow operator position settings + '! %= &= *= ++ += , -- /= :: <<= = => >>= >>>= ^= |= ~'.split(' ')) # Words which always should start on a new line line_starters = 'continue,try,throw,return,var,let,const,if,switch,case,default,for,while,break,function,import,export'.split(',') @@ -1905,12 +1986,12 @@ def main(): argv = sys.argv[1:] try: - opts, args = getopt.getopt(argv, "s:c:e:o:rdEPjabkil:xhtfvXnCw:", + opts, args = getopt.getopt(argv, "s:c:e:o:rdEPjabkil:xhtfvXnCO:w:", ['indent-size=','indent-char=','eol=''outfile=', 'replace', 'disable-preserve-newlines', 'space-in-paren', 'space-in-empty-paren', 'jslint-happy', 'space-after-anon-function', 'brace-style=', 'keep-array-indentation', 'indent-level=', 'unescape-strings', 'help', 'usage', 'stdin', 'eval-code', 'indent-with-tabs', 'keep-function-indentation', 'version', - 'e4x', 'end-with-newline','comma-first','wrap-line-length']) + 'e4x', 'end-with-newline','comma-first','operator-position=','wrap-line-length']) except getopt.GetoptError as ex: print(ex, file=sys.stderr) return usage(sys.stderr) @@ -1962,6 +2043,8 @@ def main(): js_options.end_with_newline = True elif opt in ('--comma-first', '-C'): js_options.comma_first = True + elif opt in ('--operator-position', '-O'): + js_options.operator_position = sanitizeOperatorPosition(arg) elif opt in ('--wrap-line-length ', '-w'): js_options.wrap_line_length = int(arg) elif opt in ('--stdin', '-i'): diff --git a/python/jsbeautifier/tests/generated/tests.py b/python/jsbeautifier/tests/generated/tests.py index 0d62cc73..edd45dc2 100644 --- a/python/jsbeautifier/tests/generated/tests.py +++ b/python/jsbeautifier/tests/generated/tests.py @@ -39,6 +39,7 @@ class TestJSBeautifier(unittest.TestCase): default_options.jslint_happy = false default_options.keep_array_indentation = false default_options.brace_style = 'collapse' + default_options.operator_position = 'before-newline' cls.default_options = default_options cls.wrapregex = re.compile('^(.+)$', re.MULTILINE) @@ -324,6 +325,494 @@ class TestJSBeautifier(unittest.TestCase): '}') + self.reset_options(); + #============================================================ + # operator_position option - ensure no neswlines if preserve_newlines is false - () + self.options.operator_position = 'before-newline' + self.options.preserve_newlines = false + bt( + 'var res = a + b - c / d * e % f;\n' + + 'var res = g & h | i ^ j;\n' + + 'var res = (k && l || m) ? n : o;\n' + + 'var res = p >> q << r >>> s;\n' + + 'var res = t === u !== v != w == x >= y <= z > aa < ab;\n' + + 'ac + -ad') + bt( + 'var res = a + b\n' + + '- c /\n' + + 'd * e\n' + + '%\n' + + 'f;\n' + + ' var res = g & h\n' + + '| i ^\n' + + 'j;\n' + + 'var res = (k &&\n' + + 'l\n' + + '|| m) ?\n' + + 'n\n' + + ': o\n' + + ';\n' + + 'var res = p\n' + + '>> q <<\n' + + 'r\n' + + '>>> s;\n' + + 'var res\n' + + ' = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + 'w\n' + + '== x >=\n' + + 'y <= z > aa <\n' + + 'ab;\n' + + 'ac +\n' + + '-ad', + 'var res = a + b - c / d * e % f;\n' + + 'var res = g & h | i ^ j;\n' + + 'var res = (k && l || m) ? n : o;\n' + + 'var res = p >> q << r >>> s;\n' + + 'var res = t === u !== v != w == x >= y <= z > aa < ab;\n' + + 'ac + -ad') + + # operator_position option - ensure no neswlines if preserve_newlines is false - () + self.options.operator_position = 'after-newline' + self.options.preserve_newlines = false + bt( + 'var res = a + b - c / d * e % f;\n' + + 'var res = g & h | i ^ j;\n' + + 'var res = (k && l || m) ? n : o;\n' + + 'var res = p >> q << r >>> s;\n' + + 'var res = t === u !== v != w == x >= y <= z > aa < ab;\n' + + 'ac + -ad') + bt( + 'var res = a + b\n' + + '- c /\n' + + 'd * e\n' + + '%\n' + + 'f;\n' + + ' var res = g & h\n' + + '| i ^\n' + + 'j;\n' + + 'var res = (k &&\n' + + 'l\n' + + '|| m) ?\n' + + 'n\n' + + ': o\n' + + ';\n' + + 'var res = p\n' + + '>> q <<\n' + + 'r\n' + + '>>> s;\n' + + 'var res\n' + + ' = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + 'w\n' + + '== x >=\n' + + 'y <= z > aa <\n' + + 'ab;\n' + + 'ac +\n' + + '-ad', + 'var res = a + b - c / d * e % f;\n' + + 'var res = g & h | i ^ j;\n' + + 'var res = (k && l || m) ? n : o;\n' + + 'var res = p >> q << r >>> s;\n' + + 'var res = t === u !== v != w == x >= y <= z > aa < ab;\n' + + 'ac + -ad') + + # operator_position option - ensure no neswlines if preserve_newlines is false - () + self.options.operator_position = 'preserve-newline' + self.options.preserve_newlines = false + bt( + 'var res = a + b - c / d * e % f;\n' + + 'var res = g & h | i ^ j;\n' + + 'var res = (k && l || m) ? n : o;\n' + + 'var res = p >> q << r >>> s;\n' + + 'var res = t === u !== v != w == x >= y <= z > aa < ab;\n' + + 'ac + -ad') + bt( + 'var res = a + b\n' + + '- c /\n' + + 'd * e\n' + + '%\n' + + 'f;\n' + + ' var res = g & h\n' + + '| i ^\n' + + 'j;\n' + + 'var res = (k &&\n' + + 'l\n' + + '|| m) ?\n' + + 'n\n' + + ': o\n' + + ';\n' + + 'var res = p\n' + + '>> q <<\n' + + 'r\n' + + '>>> s;\n' + + 'var res\n' + + ' = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + 'w\n' + + '== x >=\n' + + 'y <= z > aa <\n' + + 'ab;\n' + + 'ac +\n' + + '-ad', + 'var res = a + b - c / d * e % f;\n' + + 'var res = g & h | i ^ j;\n' + + 'var res = (k && l || m) ? n : o;\n' + + 'var res = p >> q << r >>> s;\n' + + 'var res = t === u !== v != w == x >= y <= z > aa < ab;\n' + + 'ac + -ad') + + + self.reset_options(); + #============================================================ + # operator_position option - set to 'before-newline' (default value) + + # comprehensive, various newlines + bt( + 'var res = a + b\n' + + '- c /\n' + + 'd * e\n' + + '%\n' + + 'f;\n' + + ' var res = g & h\n' + + '| i ^\n' + + 'j;\n' + + 'var res = (k &&\n' + + 'l\n' + + '|| m) ?\n' + + 'n\n' + + ': o\n' + + ';\n' + + 'var res = p\n' + + '>> q <<\n' + + 'r\n' + + '>>> s;\n' + + 'var res\n' + + ' = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + 'w\n' + + '== x >=\n' + + 'y <= z > aa <\n' + + 'ab;\n' + + 'ac +\n' + + '-ad', + 'var res = a + b -\n' + + ' c /\n' + + ' d * e %\n' + + ' f;\n' + + 'var res = g & h |\n' + + ' i ^\n' + + ' j;\n' + + 'var res = (k &&\n' + + ' l ||\n' + + ' m) ?\n' + + ' n :\n' + + ' o;\n' + + 'var res = p >>\n' + + ' q <<\n' + + ' r >>>\n' + + ' s;\n' + + 'var res = t\n' + + '\n' + + ' ===\n' + + ' u !== v !=\n' + + ' w ==\n' + + ' x >=\n' + + ' y <= z > aa <\n' + + ' ab;\n' + + 'ac +\n' + + ' -ad') + + # colon special case + bt( + 'var a = {\n' + + ' b\n' + + ': bval,\n' + + ' c:\n' + + 'cval\n' + + ' ,d: dval\n' + + '};\n' + + 'var e = f ? g\n' + + ': h;\n' + + 'var i = j ? k :\n' + + 'l;', + 'var a = {\n' + + ' b: bval,\n' + + ' c: cval,\n' + + ' d: dval\n' + + '};\n' + + 'var e = f ? g :\n' + + ' h;\n' + + 'var i = j ? k :\n' + + ' l;') + + # catch-all, includes brackets and other various code + bt( + 'var d = 1;\n' + + 'if (a === b\n' + + ' && c) {\n' + + ' d = (c * everything\n' + + ' / something_else) %\n' + + ' b;\n' + + ' e\n' + + ' += d;\n' + + '\n' + + '} else if (!(complex && simple) ||\n' + + ' (emotion && emotion.name === "happy")) {\n' + + ' cryTearsOfJoy(many ||\n' + + ' anOcean\n' + + ' || aRiver);\n' + + '}', + 'var d = 1;\n' + + 'if (a === b &&\n' + + ' c) {\n' + + ' d = (c * everything /\n' + + ' something_else) %\n' + + ' b;\n' + + ' e\n' + + ' += d;\n' + + '\n' + + '} else if (!(complex && simple) ||\n' + + ' (emotion && emotion.name === "happy")) {\n' + + ' cryTearsOfJoy(many ||\n' + + ' anOcean ||\n' + + ' aRiver);\n' + + '}') + + + self.reset_options(); + #============================================================ + # operator_position option - set to 'after_newline' + self.options.operator_position = 'after-newline' + + # comprehensive, various newlines + bt( + 'var res = a + b\n' + + '- c /\n' + + 'd * e\n' + + '%\n' + + 'f;\n' + + ' var res = g & h\n' + + '| i ^\n' + + 'j;\n' + + 'var res = (k &&\n' + + 'l\n' + + '|| m) ?\n' + + 'n\n' + + ': o\n' + + ';\n' + + 'var res = p\n' + + '>> q <<\n' + + 'r\n' + + '>>> s;\n' + + 'var res\n' + + ' = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + 'w\n' + + '== x >=\n' + + 'y <= z > aa <\n' + + 'ab;\n' + + 'ac +\n' + + '-ad', + 'var res = a + b\n' + + ' - c\n' + + ' / d * e\n' + + ' % f;\n' + + 'var res = g & h\n' + + ' | i\n' + + ' ^ j;\n' + + 'var res = (k\n' + + ' && l\n' + + ' || m)\n' + + ' ? n\n' + + ' : o;\n' + + 'var res = p\n' + + ' >> q\n' + + ' << r\n' + + ' >>> s;\n' + + 'var res = t\n' + + '\n' + + ' === u !== v\n' + + ' != w\n' + + ' == x\n' + + ' >= y <= z > aa\n' + + ' < ab;\n' + + 'ac\n' + + ' + -ad') + + # colon special case + bt( + 'var a = {\n' + + ' b\n' + + ': bval,\n' + + ' c:\n' + + 'cval\n' + + ' ,d: dval\n' + + '};\n' + + 'var e = f ? g\n' + + ': h;\n' + + 'var i = j ? k :\n' + + 'l;', + 'var a = {\n' + + ' b: bval,\n' + + ' c: cval,\n' + + ' d: dval\n' + + '};\n' + + 'var e = f ? g\n' + + ' : h;\n' + + 'var i = j ? k\n' + + ' : l;') + + # catch-all, includes brackets and other various code + bt( + 'var d = 1;\n' + + 'if (a === b\n' + + ' && c) {\n' + + ' d = (c * everything\n' + + ' / something_else) %\n' + + ' b;\n' + + ' e\n' + + ' += d;\n' + + '\n' + + '} else if (!(complex && simple) ||\n' + + ' (emotion && emotion.name === "happy")) {\n' + + ' cryTearsOfJoy(many ||\n' + + ' anOcean\n' + + ' || aRiver);\n' + + '}', + 'var d = 1;\n' + + 'if (a === b\n' + + ' && c) {\n' + + ' d = (c * everything\n' + + ' / something_else)\n' + + ' % b;\n' + + ' e\n' + + ' += d;\n' + + '\n' + + '} else if (!(complex && simple)\n' + + ' || (emotion && emotion.name === "happy")) {\n' + + ' cryTearsOfJoy(many\n' + + ' || anOcean\n' + + ' || aRiver);\n' + + '}') + + + self.reset_options(); + #============================================================ + # operator_position option - set to 'preserve-newline' + self.options.operator_position = 'preserve-newline' + + # comprehensive, various newlines + bt( + 'var res = a + b\n' + + '- c /\n' + + 'd * e\n' + + '%\n' + + 'f;\n' + + ' var res = g & h\n' + + '| i ^\n' + + 'j;\n' + + 'var res = (k &&\n' + + 'l\n' + + '|| m) ?\n' + + 'n\n' + + ': o\n' + + ';\n' + + 'var res = p\n' + + '>> q <<\n' + + 'r\n' + + '>>> s;\n' + + 'var res\n' + + ' = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + 'w\n' + + '== x >=\n' + + 'y <= z > aa <\n' + + 'ab;\n' + + 'ac +\n' + + '-ad', + 'var res = a + b\n' + + ' - c /\n' + + ' d * e\n' + + ' %\n' + + ' f;\n' + + 'var res = g & h\n' + + ' | i ^\n' + + ' j;\n' + + 'var res = (k &&\n' + + ' l\n' + + ' || m) ?\n' + + ' n\n' + + ' : o;\n' + + 'var res = p\n' + + ' >> q <<\n' + + ' r\n' + + ' >>> s;\n' + + 'var res = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + ' w\n' + + ' == x >=\n' + + ' y <= z > aa <\n' + + ' ab;\n' + + 'ac +\n' + + ' -ad') + + # colon special case + bt( + 'var a = {\n' + + ' b\n' + + ': bval,\n' + + ' c:\n' + + 'cval\n' + + ' ,d: dval\n' + + '};\n' + + 'var e = f ? g\n' + + ': h;\n' + + 'var i = j ? k :\n' + + 'l;', + 'var a = {\n' + + ' b: bval,\n' + + ' c: cval,\n' + + ' d: dval\n' + + '};\n' + + 'var e = f ? g\n' + + ' : h;\n' + + 'var i = j ? k :\n' + + ' l;') + + # catch-all, includes brackets and other various code + bt( + 'var d = 1;\n' + + 'if (a === b\n' + + ' && c) {\n' + + ' d = (c * everything\n' + + ' / something_else) %\n' + + ' b;\n' + + ' e\n' + + ' += d;\n' + + '\n' + + '} else if (!(complex && simple) ||\n' + + ' (emotion && emotion.name === "happy")) {\n' + + ' cryTearsOfJoy(many ||\n' + + ' anOcean\n' + + ' || aRiver);\n' + + '}') + + self.reset_options(); #============================================================ # New Test Suite @@ -2598,7 +3087,7 @@ class TestJSBeautifier(unittest.TestCase): # Line wrap test intputs #..............---------1---------2---------3---------4---------5---------6---------7 #..............1234567890123456789012345678901234567890123456789012345678901234567890 - wrap_input_1=('foo.bar().baz().cucumber((fat && "sassy") || (leans\n&& mean));\n' + + wrap_input_1=('foo.bar().baz().cucumber((fat && "sassy") || (leans && mean));\n' + 'Test_very_long_variable_name_this_should_never_wrap\n.but_this_can\n' + 'return between_return_and_expression_should_never_wrap.but_this_can\n' + 'throw between_throw_and_expression_should_never_wrap.but_this_can\n' + @@ -2613,7 +3102,7 @@ class TestJSBeautifier(unittest.TestCase): #..............---------1---------2---------3---------4---------5---------6---------7 #..............1234567890123456789012345678901234567890123456789012345678901234567890 wrap_input_2=('{\n' + - ' foo.bar().baz().cucumber((fat && "sassy") || (leans\n&& mean));\n' + + ' foo.bar().baz().cucumber((fat && "sassy") || (leans && mean));\n' + ' Test_very_long_variable_name_this_should_never_wrap\n.but_this_can\n' + ' return between_return_and_expression_should_never_wrap.but_this_can\n' + ' throw between_throw_and_expression_should_never_wrap.but_this_can\n' + @@ -3033,8 +3522,8 @@ class TestJSBeautifier(unittest.TestCase): bt('var a = /*i*/\n"b";', 'var a = /*i*/\n "b";') bt('var a = /*i*/\nb;', 'var a = /*i*/\n b;') bt('{\n\n\n"x"\n}', '{\n\n\n "x"\n}') - bt('if(a &&\nb\n||\nc\n||d\n&&\ne) e = f', 'if (a &&\n b ||\n c || d &&\n e) e = f') - bt('if(a &&\n(b\n||\nc\n||d)\n&&\ne) e = f', 'if (a &&\n (b ||\n c || d) &&\n e) e = f') + bt('if(a &&\nb\n||\nc\n||d\n&&\ne) e = f', 'if (a &&\n b ||\n c ||\n d &&\n e) e = f') + bt('if(a &&\n(b\n||\nc\n||d)\n&&\ne) e = f', 'if (a &&\n (b ||\n c ||\n d) &&\n e) e = f') test_fragment('\n\n"x"', '"x"') # this beavior differs between js and python, defaults to unlimited in js, 10 in python bt('a = 1;\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nb = 2;', diff --git a/test/data/javascript/inputlib.js b/test/data/javascript/inputlib.js new file mode 100644 index 00000000..39a08bec --- /dev/null +++ b/test/data/javascript/inputlib.js @@ -0,0 +1,88 @@ +'use strict'; + + +//--------// +// Inputs // +//--------// + +var ops = ['>', '<', '+', '-', '*', '/', '%', '&', '|', '^', '?', ':']; +var operator_position = { + sanity: [ + 'var res = a + b - c / d * e % f;', + 'var res = g & h | i ^ j;', + 'var res = (k && l || m) ? n : o;', + 'var res = p >> q << r >>> s;', + 'var res = t === u !== v != w == x >= y <= z > aa < ab;', + 'ac + -ad' + ], + comprehensive: [ + 'var res = a + b', + '- c /', + 'd * e', + '%', + 'f;', + ' var res = g & h', + '| i ^', + 'j;', + 'var res = (k &&', + 'l', + '|| m) ?', + 'n', + ': o', + ';', + 'var res = p', + '>> q <<', + 'r', + '>>> s;', + 'var res', + ' = t', + '', + ' === u !== v', + ' !=', + 'w', + '== x >=', + 'y <= z > aa <', + 'ab;', + 'ac +', + '-ad' + ], + colon_special_case: [ + 'var a = {', + ' b', + ': bval,', + ' c:', + 'cval', + ' ,d: dval', + '};', + 'var e = f ? g', + ': h;', + 'var i = j ? k :', + 'l;' + ], + catch_all: [ + 'var d = 1;', + 'if (a === b', + ' && c) {', + ' d = (c * everything', + ' / something_else) %', + ' b;', + ' e', + ' += d;', + '', + '} else if (!(complex && simple) ||', + ' (emotion && emotion.name === "happy")) {', + ' cryTearsOfJoy(many ||', + ' anOcean', + ' || aRiver);', + '}' + ] +}; + + +//---------// +// Exports // +//---------// + +module.exports = { + operator_position: operator_position +} diff --git a/test/data/javascript/node.mustache b/test/data/javascript/node.mustache index b6f34474..0062f2d2 100644 --- a/test/data/javascript/node.mustache +++ b/test/data/javascript/node.mustache @@ -611,7 +611,7 @@ function run_javascript_tests(test_obj, Urlencoded, js_beautify, html_beautify, // Line wrap test intputs //.............---------1---------2---------3---------4---------5---------6---------7 //.............1234567890123456789012345678901234567890123456789012345678901234567890 - wrap_input_1=('foo.bar().baz().cucumber((fat && "sassy") || (leans\n&& mean));\n' + + wrap_input_1=('foo.bar().baz().cucumber((fat && "sassy") || (leans && mean));\n' + 'Test_very_long_variable_name_this_should_never_wrap\n.but_this_can\n' + 'return between_return_and_expression_should_never_wrap.but_this_can\n' + 'throw between_throw_and_expression_should_never_wrap.but_this_can\n' + @@ -626,7 +626,7 @@ function run_javascript_tests(test_obj, Urlencoded, js_beautify, html_beautify, //.............---------1---------2---------3---------4---------5---------6---------7 //.............1234567890123456789012345678901234567890123456789012345678901234567890 wrap_input_2=('{\n' + - ' foo.bar().baz().cucumber((fat && "sassy") || (leans\n&& mean));\n' + + ' foo.bar().baz().cucumber((fat && "sassy") || (leans && mean));\n' + ' Test_very_long_variable_name_this_should_never_wrap\n.but_this_can\n' + ' return between_return_and_expression_should_never_wrap.but_this_can\n' + ' throw between_throw_and_expression_should_never_wrap.but_this_can\n' + @@ -1041,8 +1041,8 @@ function run_javascript_tests(test_obj, Urlencoded, js_beautify, html_beautify, bt('var a = /*i*/\n"b";', 'var a = /*i*/\n "b";'); bt('var a = /*i*/\nb;', 'var a = /*i*/\n b;'); bt('{\n\n\n"x"\n}', '{\n\n\n "x"\n}'); - bt('if(a &&\nb\n||\nc\n||d\n&&\ne) e = f', 'if (a &&\n b ||\n c || d &&\n e) e = f'); - bt('if(a &&\n(b\n||\nc\n||d)\n&&\ne) e = f', 'if (a &&\n (b ||\n c || d) &&\n e) e = f'); + bt('if(a &&\nb\n||\nc\n||d\n&&\ne) e = f', 'if (a &&\n b ||\n c ||\n d &&\n e) e = f'); + bt('if(a &&\n(b\n||\nc\n||d)\n&&\ne) e = f', 'if (a &&\n (b ||\n c ||\n d) &&\n e) e = f'); test_fragment('\n\n"x"', '"x"'); // this beavior differs between js and python, defaults to unlimited in js, 10 in python diff --git a/test/data/javascript/python.mustache b/test/data/javascript/python.mustache index 559b6af7..0125a1b4 100644 --- a/test/data/javascript/python.mustache +++ b/test/data/javascript/python.mustache @@ -772,7 +772,7 @@ class TestJSBeautifier(unittest.TestCase): # Line wrap test intputs #..............---------1---------2---------3---------4---------5---------6---------7 #..............1234567890123456789012345678901234567890123456789012345678901234567890 - wrap_input_1=('foo.bar().baz().cucumber((fat && "sassy") || (leans\n&& mean));\n' + + wrap_input_1=('foo.bar().baz().cucumber((fat && "sassy") || (leans && mean));\n' + 'Test_very_long_variable_name_this_should_never_wrap\n.but_this_can\n' + 'return between_return_and_expression_should_never_wrap.but_this_can\n' + 'throw between_throw_and_expression_should_never_wrap.but_this_can\n' + @@ -787,7 +787,7 @@ class TestJSBeautifier(unittest.TestCase): #..............---------1---------2---------3---------4---------5---------6---------7 #..............1234567890123456789012345678901234567890123456789012345678901234567890 wrap_input_2=('{\n' + - ' foo.bar().baz().cucumber((fat && "sassy") || (leans\n&& mean));\n' + + ' foo.bar().baz().cucumber((fat && "sassy") || (leans && mean));\n' + ' Test_very_long_variable_name_this_should_never_wrap\n.but_this_can\n' + ' return between_return_and_expression_should_never_wrap.but_this_can\n' + ' throw between_throw_and_expression_should_never_wrap.but_this_can\n' + @@ -1207,8 +1207,8 @@ class TestJSBeautifier(unittest.TestCase): bt('var a = /*i*/\n"b";', 'var a = /*i*/\n "b";') bt('var a = /*i*/\nb;', 'var a = /*i*/\n b;') bt('{\n\n\n"x"\n}', '{\n\n\n "x"\n}') - bt('if(a &&\nb\n||\nc\n||d\n&&\ne) e = f', 'if (a &&\n b ||\n c || d &&\n e) e = f') - bt('if(a &&\n(b\n||\nc\n||d)\n&&\ne) e = f', 'if (a &&\n (b ||\n c || d) &&\n e) e = f') + bt('if(a &&\nb\n||\nc\n||d\n&&\ne) e = f', 'if (a &&\n b ||\n c ||\n d &&\n e) e = f') + bt('if(a &&\n(b\n||\nc\n||d)\n&&\ne) e = f', 'if (a &&\n (b ||\n c ||\n d) &&\n e) e = f') test_fragment('\n\n"x"', '"x"') # this beavior differs between js and python, defaults to unlimited in js, 10 in python bt('a = 1;\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nb = 2;', diff --git a/test/data/javascript/tests.js b/test/data/javascript/tests.js index e843f493..23f2c0ca 100644 --- a/test/data/javascript/tests.js +++ b/test/data/javascript/tests.js @@ -1,3 +1,5 @@ +var inputlib = require('./inputlib'); + exports.test_data = { default_options: [ { name: "indent_size", value: "4" }, @@ -5,7 +7,8 @@ exports.test_data = { { name: "preserve_newlines", value: "true" }, { name: "jslint_happy", value: "false" }, { name: "keep_array_indentation", value: "false" }, - { name: "brace_style", value: "'collapse'" } + { name: "brace_style", value: "'collapse'" }, + { name: "operator_position", value: "'before-newline'" } ], groups: [{ name: "Unicode Support", @@ -315,6 +318,233 @@ exports.test_data = { ], }, ], + }, { + name: "operator_position option - ensure no neswlines if preserve_newlines is false", + matrix: [ + { + options: [ + { name: "operator_position", value: "'before-newline'" }, + { name: "preserve_newlines", value: "false" } + ] + }, { + options: [ + { name: "operator_position", value: "'after-newline'" }, + { name: "preserve_newlines", value: "false" } + ] + }, { + options: [ + { name: "operator_position", value: "'preserve-newline'" }, + { name: "preserve_newlines", value: "false" } + ] + } + ], + tests: [ + { + unchanged: inputlib.operator_position.sanity + }, { + input: inputlib.operator_position.comprehensive, + output: inputlib.operator_position.sanity, + } + ] + }, { + name: "operator_position option - set to 'before-newline' (default value)", + tests: [ + { + comment: 'comprehensive, various newlines', + input: inputlib.operator_position.comprehensive, + output: [ + 'var res = a + b -', + ' c /', + ' d * e %', + ' f;', + 'var res = g & h |', + ' i ^', + ' j;', + 'var res = (k &&', + ' l ||', + ' m) ?', + ' n :', + ' o;', + 'var res = p >>', + ' q <<', + ' r >>>', + ' s;', + 'var res = t', + '', + ' ===', + ' u !== v !=', + ' w ==', + ' x >=', + ' y <= z > aa <', + ' ab;', + 'ac +', + ' -ad' + ] + }, { + comment: 'colon special case', + input: inputlib.operator_position.colon_special_case, + output: [ + 'var a = {', + ' b: bval,', + ' c: cval,', + ' d: dval', + '};', + 'var e = f ? g :', + ' h;', + 'var i = j ? k :', + ' l;' + ] + }, { + comment: 'catch-all, includes brackets and other various code', + input: inputlib.operator_position.catch_all, + output: [ + 'var d = 1;', + 'if (a === b &&', + ' c) {', + ' d = (c * everything /', + ' something_else) %', + ' b;', + ' e', + ' += d;', + '', + '} else if (!(complex && simple) ||', + ' (emotion && emotion.name === "happy")) {', + ' cryTearsOfJoy(many ||', + ' anOcean ||', + ' aRiver);', + '}' + ] + } + ] + }, { + name: "operator_position option - set to 'after_newline'", + options: [{ + name: "operator_position", value: "'after-newline'" + }], + tests: [ + { + comment: 'comprehensive, various newlines', + input: inputlib.operator_position.comprehensive, + output: [ + 'var res = a + b', + ' - c', + ' / d * e', + ' % f;', + 'var res = g & h', + ' | i', + ' ^ j;', + 'var res = (k', + ' && l', + ' || m)', + ' ? n', + ' : o;', + 'var res = p', + ' >> q', + ' << r', + ' >>> s;', + 'var res = t', + '', + ' === u !== v', + ' != w', + ' == x', + ' >= y <= z > aa', + ' < ab;', + 'ac', + ' + -ad' + ] + }, { + comment: 'colon special case', + input: inputlib.operator_position.colon_special_case, + output: [ + 'var a = {', + ' b: bval,', + ' c: cval,', + ' d: dval', + '};', + 'var e = f ? g', + ' : h;', + 'var i = j ? k', + ' : l;' + ] + }, { + comment: 'catch-all, includes brackets and other various code', + input: inputlib.operator_position.catch_all, + output: [ + 'var d = 1;', + 'if (a === b', + ' && c) {', + ' d = (c * everything', + ' / something_else)', + ' % b;', + ' e', + ' += d;', + '', + '} else if (!(complex && simple)', + ' || (emotion && emotion.name === "happy")) {', + ' cryTearsOfJoy(many', + ' || anOcean', + ' || aRiver);', + '}' + ] + } + ] + }, { + name: "operator_position option - set to 'preserve-newline'", + options: [{ + name: "operator_position", value: "'preserve-newline'" + }], + tests: [ + { + comment: 'comprehensive, various newlines', + input: inputlib.operator_position.comprehensive, + output: [ + 'var res = a + b', + ' - c /', + ' d * e', + ' %', + ' f;', + 'var res = g & h', + ' | i ^', + ' j;', + 'var res = (k &&', + ' l', + ' || m) ?', + ' n', + ' : o;', + 'var res = p', + ' >> q <<', + ' r', + ' >>> s;', + 'var res = t', + '', + ' === u !== v', + ' !=', + ' w', + ' == x >=', + ' y <= z > aa <', + ' ab;', + 'ac +', + ' -ad' + ] + }, { + comment: 'colon special case', + input: inputlib.operator_position.colon_special_case, + output: [ + 'var a = {', + ' b: bval,', + ' c: cval,', + ' d: dval', + '};', + 'var e = f ? g', + ' : h;', + 'var i = j ? k :', + ' l;' + ] + }, { + comment: 'catch-all, includes brackets and other various code', + unchanged: inputlib.operator_position.catch_all + } + ] }, { name: "New Test Suite" },