Add Comma-first formatting option

This doesn't try to fight the user but given the opportunity it will put commas at the start of lines instead of the ends.

Fixes #245
Fixes #80
Closes #369
Closes #350
This commit is contained in:
Liam Newman 2014-09-30 17:45:10 -07:00
parent b5426a8558
commit f8cf725b09
7 changed files with 199 additions and 7 deletions

View File

@ -99,7 +99,8 @@ Beautifier Options:
-x, --unescape-strings Decode printable characters encoded in xNN notation
-w, --wrap-line-length Wrap lines at next opportunity after N characters [0]
-X, --e4x Pass E4X xml literals through untouched
-n, --end_with_newline End output with newline
-n, --end-with-newline End output with newline
-C, --comma-first Put commas at the beginning of new line instead of end
--good-stuff Warm the cockles of Crockford's heart
```
@ -172,7 +173,7 @@ HTML Beautifier Options:
-p, --preserve-newlines Preserve existing line-breaks (--no-preserve-newlines disables)
-m, --max-preserve-newlines Maximum number of line-breaks to be preserved in one chunk [10]
-U, --unformatted List of tags (defaults to inline) that should not be reformatted
-n, --end_with_newline End output with newline
-n, --end-with-newline End output with newline
```
# License
@ -191,4 +192,4 @@ Thanks also to Jason Diamond, Patrick Hof, Nochum Sossonko, Andreas Schneider, D
Vasilevsky, Vital Batmanov, Ron Baldwin, Gabriel Harrison, Chris J. Shull,
Mathias Bynens, Vittorio Gambaletta and others.
js-beautify@1.5.3
js-beautify@1.5.5

View File

@ -278,6 +278,7 @@
opt.wrap_line_length = (options.wrap_line_length === undefined) ? 0 : parseInt(options.wrap_line_length, 10);
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;
// force opt.space_after_anon_function to true if opt.jslint_happy
@ -448,6 +449,16 @@
}
function print_token(printable_token) {
if (opt.comma_first && last_type === 'TK_COMMA'
&& output.just_added_newline()) {
if(output.previous_line.last() === ',') {
output.previous_line.pop();
print_token_line_indentation();
output.add_token(',');
output.space_before_token = true;
}
}
printable_token = printable_token || current_token.text;
print_token_line_indentation();
output.add_token(printable_token);
@ -1028,6 +1039,11 @@
print_newline(false, true);
} else {
output.space_before_token = true;
// for comma-first, we want to allow a newline before the comma
// to turn into a newline after the comma, which we will fixup later
if (opt.comma_first) {
allow_wrap_or_preserved_newline();
}
}
return;
}
@ -1042,6 +1058,11 @@
} else {
// EXPR or DO_BLOCK
output.space_before_token = true;
// for comma-first, we want to allow a newline before the comma
// to turn into a newline after the comma, which we will fixup later
if (opt.comma_first) {
allow_wrap_or_preserved_newline();
}
}
}
@ -1261,6 +1282,16 @@
_empty = false;
}
this.pop = function() {
var item = null;
if (!_empty) {
item = _items.pop();
_character_count -= item.length;
_empty = _items.length === 0;
}
return item;
}
this.remove_indent = function() {
if (_indent_count > 0) {
_indent_count -= 1;
@ -1297,6 +1328,7 @@
var lines =[];
this.baseIndentString = baseIndentString;
this.indent_string = indent_string;
this.previous_line = null;
this.current_line = null;
this.space_before_token = false;
@ -1311,6 +1343,7 @@
}
if (force_newline || !this.just_added_newline()) {
this.previous_line = this.current_line;
this.current_line = new OutputLine(this);
lines.push(this.current_line);
return true;
@ -1387,6 +1420,8 @@
this.current_line = lines[lines.length - 1]
this.current_line.trim();
}
this.previous_line = lines.length > 1 ? lines[lines.length - 2] : null;
}
this.just_added_newline = function() {

View File

@ -64,6 +64,7 @@ var fs = require('fs'),
"wrap_attributes_indent_size": Number,
"e4x": Boolean,
"end_with_newline": Boolean,
"comma_first": Boolean,
// CSS-only
"selector_separator_newline": Boolean,
"newline_between_rules": Boolean,
@ -103,6 +104,7 @@ var fs = require('fs'),
"w": ["--wrap_line_length"],
"X": ["--e4x"],
"n": ["--end_with_newline"],
"C": ["--comma_first"],
// CSS-only
"L": ["--selector_separator_newline"],
"N": ["--newline_between_rules"],
@ -234,7 +236,8 @@ function usage(err) {
msg.push(' -w, --wrap-line-length Wrap lines at next opportunity after N characters [0]');
msg.push(' -X, --e4x Pass E4X xml literals through untouched');
msg.push(' --good-stuff Warm the cockles of Crockford\'s heart');
msg.push(' -n, --end_with_newline End output with newline');
msg.push(' -n, --end-with-newline End output with newline');
msg.push(' -C, --comma-first Put commas at the beginning of new line instead of end');
break;
case "html":
msg.push(' -b, --brace-style [collapse|expand|end-expand] ["collapse"]');

View File

@ -254,6 +254,42 @@ function run_javascript_tests(test_obj, Urlencoded, js_beautify, html_beautify,
test_fragment(' \n\nreturn .5\n\n\n\n', ' return .5');
test_fragment('\n', '');
// Comma-first option - (c0 = "\n, ", c1 = "\n , ", c2 = "\n , ", c3 = "\n , ")
opts.comma_first = true;
bt('{a:1, b:2}', '{\n a: 1\n , b: 2\n}');
bt('var a=1, b=c[d], e=6;', 'var a = 1\n , b = c[d]\n , e = 6;');
bt('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');
bt('for(var a=1,b=2,\nc=3;d<3;d++)\ne', 'for (var a = 1, b = 2\n , c = 3; d < 3; d++)\n e');
bt('function foo() {\n return [\n "one"\n , "two"\n ];\n}');
bt('a=[[1,2],[4,5],[7,8]]', 'a = [\n [1, 2]\n , [4, 5]\n , [7, 8]\n]');
bt('a=[[1,2],[4,5],[7,8],]', 'a = [\n [1, 2]\n , [4, 5]\n , [7, 8]\n, ]');
bt('a=[[1,2],[4,5],function(){},[7,8]]', 'a = [\n [1, 2]\n , [4, 5]\n , function() {}\n , [7, 8]\n]');
bt('a=[[1,2],[4,5],function(){},function(){},[7,8]]', 'a = [\n [1, 2]\n , [4, 5]\n , function() {}\n , function() {}\n , [7, 8]\n]');
bt('a=[[1,2],[4,5],function(){},[7,8]]', 'a = [\n [1, 2]\n , [4, 5]\n , function() {}\n , [7, 8]\n]');
bt('a=[b,c,function(){},function(){},d]', 'a = [b, c, function() {}, function() {}, d]');
bt('a=[b,c,\nfunction(){},function(){},d]', 'a = [b, c\n , function() {}\n , function() {}\n , d\n]');
bt('a=[a[1],b[4],c[d[7]]]', 'a = [a[1], b[4], c[d[7]]]');
bt('[1,2,[3,4,[5,6],7],8]', '[1, 2, [3, 4, [5, 6], 7], 8]');
bt('[[["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]');
// Comma-first option - (c0 = ",\n", c1 = ",\n ", c2 = ",\n ", c3 = ",\n ")
opts.comma_first = false;
bt('{a:1, b:2}', '{\n a: 1,\n b: 2\n}');
bt('var a=1, b=c[d], e=6;', 'var a = 1,\n b = c[d],\n e = 6;');
bt('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');
bt('for(var a=1,b=2,\nc=3;d<3;d++)\ne', 'for (var a = 1, b = 2,\n c = 3; d < 3; d++)\n e');
bt('function foo() {\n return [\n "one",\n "two"\n ];\n}');
bt('a=[[1,2],[4,5],[7,8]]', 'a = [\n [1, 2],\n [4, 5],\n [7, 8]\n]');
bt('a=[[1,2],[4,5],[7,8],]', 'a = [\n [1, 2],\n [4, 5],\n [7, 8],\n]');
bt('a=[[1,2],[4,5],function(){},[7,8]]', 'a = [\n [1, 2],\n [4, 5],\n function() {},\n [7, 8]\n]');
bt('a=[[1,2],[4,5],function(){},function(){},[7,8]]', 'a = [\n [1, 2],\n [4, 5],\n function() {},\n function() {},\n [7, 8]\n]');
bt('a=[[1,2],[4,5],function(){},[7,8]]', 'a = [\n [1, 2],\n [4, 5],\n function() {},\n [7, 8]\n]');
bt('a=[b,c,function(){},function(){},d]', 'a = [b, c, function() {}, function() {}, d]');
bt('a=[b,c,\nfunction(){},function(){},d]', 'a = [b, c,\n function() {},\n function() {},\n d\n]');
bt('a=[a[1],b[4],c[d[7]]]', 'a = [a[1], b[4], c[d[7]]]');
bt('[1,2,[3,4,[5,6],7],8]', '[1, 2, [3, 4, [5, 6], 7], 8]');
bt('[[["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]');
// New Test Suite
// Old tests

View File

@ -79,6 +79,7 @@ class BeautifierOptions:
self.wrap_line_length = 0
self.break_chained_methods = False
self.end_with_newline = False
self.comma_first = False
@ -475,6 +476,13 @@ class Beautifier:
def print_token(self, current_token, s=None):
if self.opts.comma_first and self.last_type == 'TK_COMMA' and self.output.just_added_newline():
if self.output.previous_line.last() == ',':
self.output.previous_line.pop()
self.print_token_line_indentation(current_token)
self.output.add_token(',')
self.output.space_before_token = True
if s == None:
s = current_token.text
@ -950,7 +958,10 @@ class Beautifier:
self.print_newline(preserve_statement_flags = True)
else:
self.output.space_before_token = True
# for comma-first, we want to allow a newline before the comma
# to turn into a newline after the comma, which we will fixup later
if self.opts.comma_first:
self.allow_wrap_or_preserved_newline(current_token)
return
self.print_token(current_token)
@ -965,6 +976,11 @@ class Beautifier:
# EXPR or DO_BLOCK
self.output.space_before_token = True
# for comma-first, we want to allow a newline before the comma
# to turn into a newline after the comma, which we will fixup later
if self.opts.comma_first:
self.allow_wrap_or_preserved_newline(current_token)
def handle_operator(self, current_token):
if self.start_of_statement(current_token):
@ -1169,6 +1185,15 @@ class OutputLine:
self.__character_count += len(input)
self.__empty = False
def pop(self):
item = None
if not self.is_empty():
item = self.__items.pop()
self.__character_count -= len(item)
self.__empty = len(self.__items) == 0
return item
def remove_indent(self):
if self.__indent_count > 0:
self.__indent_count -= 1
@ -1198,6 +1223,7 @@ class Output:
self.baseIndentLength = len(baseIndentString)
self.indent_length = len(indent_string)
self.lines = []
self.previous_line = None
self.current_line = None
self.space_before_token = False
self.add_new_line(True)
@ -1211,6 +1237,7 @@ class Output:
return False
if force_newline or not self.just_added_newline():
self.previous_line = self.current_line
self.current_line = OutputLine(self)
self.lines.append(self.current_line)
return True
@ -1264,6 +1291,11 @@ class Output:
self.current_line = self.lines[-1]
self.current_line.trim()
if len(self.lines) > 1:
self.previous_line = self.lines[-2]
else:
self.previous_line = None
def just_added_newline(self):
return self.current_line.is_empty()
@ -1669,12 +1701,12 @@ def main():
argv = sys.argv[1:]
try:
opts, args = getopt.getopt(argv, "s:c:o:rdEPjabkil:xhtfvXnw:",
opts, args = getopt.getopt(argv, "s:c:o:rdEPjabkil:xhtfvXnCw:",
['indent-size=','indent-char=','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','wrap-line-length'])
'e4x', 'end-with-newline','comma-first','wrap-line-length'])
except getopt.GetoptError as ex:
print(ex, file=sys.stderr)
return usage(sys.stderr)
@ -1722,6 +1754,8 @@ def main():
js_options.e4x = True
elif opt in ('--end-with-newline', '-n'):
js_options.end_with_newline = True
elif opt in ('--comma-first', '-C'):
js_options.comma_first = True
elif opt in ('--wrap-line-length ', '-w'):
js_options.wrap_line_length = int(arg)
elif opt in ('--stdin', '-i'):

View File

@ -74,6 +74,42 @@ class TestJSBeautifier(unittest.TestCase):
test_fragment(' \n\nreturn .5\n\n\n\n', ' return .5')
test_fragment('\n', '')
# Comma-first option - (c0 = "\n, ", c1 = "\n , ", c2 = "\n , ", c3 = "\n , ")
self.options.comma_first = true
bt('{a:1, b:2}', '{\n a: 1\n , b: 2\n}')
bt('var a=1, b=c[d], e=6;', 'var a = 1\n , b = c[d]\n , e = 6;')
bt('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')
bt('for(var a=1,b=2,\nc=3;d<3;d++)\ne', 'for (var a = 1, b = 2\n , c = 3; d < 3; d++)\n e')
bt('function foo() {\n return [\n "one"\n , "two"\n ];\n}')
bt('a=[[1,2],[4,5],[7,8]]', 'a = [\n [1, 2]\n , [4, 5]\n , [7, 8]\n]')
bt('a=[[1,2],[4,5],[7,8],]', 'a = [\n [1, 2]\n , [4, 5]\n , [7, 8]\n, ]')
bt('a=[[1,2],[4,5],function(){},[7,8]]', 'a = [\n [1, 2]\n , [4, 5]\n , function() {}\n , [7, 8]\n]')
bt('a=[[1,2],[4,5],function(){},function(){},[7,8]]', 'a = [\n [1, 2]\n , [4, 5]\n , function() {}\n , function() {}\n , [7, 8]\n]')
bt('a=[[1,2],[4,5],function(){},[7,8]]', 'a = [\n [1, 2]\n , [4, 5]\n , function() {}\n , [7, 8]\n]')
bt('a=[b,c,function(){},function(){},d]', 'a = [b, c, function() {}, function() {}, d]')
bt('a=[b,c,\nfunction(){},function(){},d]', 'a = [b, c\n , function() {}\n , function() {}\n , d\n]')
bt('a=[a[1],b[4],c[d[7]]]', 'a = [a[1], b[4], c[d[7]]]')
bt('[1,2,[3,4,[5,6],7],8]', '[1, 2, [3, 4, [5, 6], 7], 8]')
bt('[[["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]')
# Comma-first option - (c0 = ",\n", c1 = ",\n ", c2 = ",\n ", c3 = ",\n ")
self.options.comma_first = false
bt('{a:1, b:2}', '{\n a: 1,\n b: 2\n}')
bt('var a=1, b=c[d], e=6;', 'var a = 1,\n b = c[d],\n e = 6;')
bt('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')
bt('for(var a=1,b=2,\nc=3;d<3;d++)\ne', 'for (var a = 1, b = 2,\n c = 3; d < 3; d++)\n e')
bt('function foo() {\n return [\n "one",\n "two"\n ];\n}')
bt('a=[[1,2],[4,5],[7,8]]', 'a = [\n [1, 2],\n [4, 5],\n [7, 8]\n]')
bt('a=[[1,2],[4,5],[7,8],]', 'a = [\n [1, 2],\n [4, 5],\n [7, 8],\n]')
bt('a=[[1,2],[4,5],function(){},[7,8]]', 'a = [\n [1, 2],\n [4, 5],\n function() {},\n [7, 8]\n]')
bt('a=[[1,2],[4,5],function(){},function(){},[7,8]]', 'a = [\n [1, 2],\n [4, 5],\n function() {},\n function() {},\n [7, 8]\n]')
bt('a=[[1,2],[4,5],function(){},[7,8]]', 'a = [\n [1, 2],\n [4, 5],\n function() {},\n [7, 8]\n]')
bt('a=[b,c,function(){},function(){},d]', 'a = [b, c, function() {}, function() {}, d]')
bt('a=[b,c,\nfunction(){},function(){},d]', 'a = [b, c,\n function() {},\n function() {},\n d\n]')
bt('a=[a[1],b[4],c[d[7]]]', 'a = [a[1], b[4], c[d[7]]]')
bt('[1,2,[3,4,[5,6],7],8]', '[1, 2, [3, 4, [5, 6], 7], 8]')
bt('[[["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]')
# New Test Suite
# Old tests

View File

@ -44,6 +44,53 @@ exports.test_data = {
{ fragment: ' \n\nreturn .5\n\n\n\n', output: ' return .5{{eof}}' },
{ fragment: '\n', output: '{{eof}}' }
],
}, {
name: "Comma-first option",
description: "Put commas at the start of lines instead of the end",
matrix: [
{
options: [
{ name: "comma_first", value: "true" }
],
c0: '\\n, ',
c1: '\\n , ',
c2: '\\n , ',
c3: '\\n , '
}, {
options: [
{ name: "comma_first", value: "false" }
],
c0: ',\\n',
c1: ',\\n ',
c2: ',\\n ',
c3: ',\\n '
}
],
tests: [
{ input: '{a:1, b:2}', output: "{\n a: 1{{c1}}b: 2\n}" },
{ input: 'var a=1, b=c[d], e=6;', output: 'var a = 1{{c1}}b = c[d]{{c1}}e = 6;' },
{ input: "for(var a=1,b=2,c=3;d<3;d++)\ne", output: "for (var a = 1, b = 2, c = 3; d < 3; d++)\n e" },
{ input: "for(var a=1,b=2,\nc=3;d<3;d++)\ne", output: "for (var a = 1, b = 2{{c2}}c = 3; d < 3; d++)\n e" },
{ input: 'function foo() {\n return [\n "one"{{c2}}"two"\n ];\n}' },
{ input: 'a=[[1,2],[4,5],[7,8]]', output: "a = [\n [1, 2]{{c1}}[4, 5]{{c1}}[7, 8]\n]" },
{ input: 'a=[[1,2],[4,5],[7,8],]', output: "a = [\n [1, 2]{{c1}}[4, 5]{{c1}}[7, 8]{{c0}}]" },
{ input: 'a=[[1,2],[4,5],function(){},[7,8]]',
output: "a = [\n [1, 2]{{c1}}[4, 5]{{c1}}function() {}{{c1}}[7, 8]\n]" },
{ input: 'a=[[1,2],[4,5],function(){},function(){},[7,8]]',
output: "a = [\n [1, 2]{{c1}}[4, 5]{{c1}}function() {}{{c1}}function() {}{{c1}}[7, 8]\n]" },
{ input: 'a=[[1,2],[4,5],function(){},[7,8]]',
output: "a = [\n [1, 2]{{c1}}[4, 5]{{c1}}function() {}{{c1}}[7, 8]\n]" },
{ input: 'a=[b,c,function(){},function(){},d]',
output: "a = [b, c, function() {}, function() {}, d]" },
{ input: 'a=[b,c,\nfunction(){},function(){},d]',
output: "a = [b, c{{c1}}function() {}{{c1}}function() {}{{c1}}d\n]" },
{ input: 'a=[a[1],b[4],c[d[7]]]', output: "a = [a[1], b[4], c[d[7]]]" },
{ input: '[1,2,[3,4,[5,6],7],8]', output: "[1, 2, [3, 4, [5, 6], 7], 8]" },
{ input: '[[["1","2"],["3","4"]],[["5","6","7"],["8","9","0"]],[["1","2","3"],["4","5","6","7"],["8","9","0"]]]',
output: '[\n [\n ["1", "2"]{{c2}}["3", "4"]\n ]{{c1}}[\n ["5", "6", "7"]{{c2}}["8", "9", "0"]\n ]{{c1}}[\n ["1", "2", "3"]{{c2}}["4", "5", "6", "7"]{{c2}}["8", "9", "0"]\n ]\n]' },
],
}, {
name: "New Test Suite"
},