Skip to content

Commit

Permalink
Add Comma-first formatting option
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bitwiseman committed Mar 5, 2015
1 parent b5426a8 commit f8cf725
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 7 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down Expand Up @@ -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
Expand All @@ -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.

[email protected].3
[email protected].5
35 changes: 35 additions & 0 deletions js/lib/beautify.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand All @@ -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();
}
}

}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down
5 changes: 4 additions & 1 deletion js/lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"],
Expand Down Expand Up @@ -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"]');
Expand Down
36 changes: 36 additions & 0 deletions js/test/beautify-javascript-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 37 additions & 3 deletions python/jsbeautifier/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def __init__(self):
self.wrap_line_length = 0
self.break_chained_methods = False
self.end_with_newline = False
self.comma_first = False



Expand Down Expand Up @@ -475,6 +476,13 @@ def print_token_line_indentation(self, current_token):


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

Expand Down Expand Up @@ -950,7 +958,10 @@ def handle_comma(self, current_token):
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)
Expand All @@ -965,6 +976,11 @@ def handle_comma(self, current_token):
# 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):
Expand Down Expand Up @@ -1169,6 +1185,15 @@ def push(self, input):
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
Expand Down Expand Up @@ -1198,6 +1223,7 @@ def __init__(self, indent_string, baseIndentString = ''):
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)
Expand All @@ -1211,6 +1237,7 @@ def add_new_line(self, force_newline):
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
Expand Down Expand Up @@ -1264,6 +1291,11 @@ def trim(self, eat_newlines = False):
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()

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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'):
Expand Down
36 changes: 36 additions & 0 deletions python/jsbeautifier/tests/testjsbeautifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,42 @@ def unicode_char(value):
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
Expand Down
Loading

0 comments on commit f8cf725

Please sign in to comment.