diff --git a/README.md b/README.md index 80d31d361..bf9365ee6 100644 --- a/README.md +++ b/README.md @@ -368,6 +368,7 @@ HTML Beautifier Options: -S, --indent-scripts [keep|separate|normal] ["normal"] -w, --wrap-line-length Maximum characters per line (0 disables) [250] -A, --wrap-attributes Wrap attributes to new lines [auto|force|force-aligned|force-expand-multiline|aligned-multiple|preserve|preserve-aligned] ["auto"] + -M, --wrap-attributes-min-attrs Minimum number of html tag attributes for force wrap attribute options [2] -i, --wrap-attributes-indent-size Indent wrapped attributes to after N characters [indent-size] (ignored if wrap-attributes is "aligned") -d, --inline List of tags to be considered inline tags -U, --unformatted List of tags (defaults to inline) that should not be reformatted diff --git a/js/src/cli.js b/js/src/cli.js index 40b456893..71d478eca 100755 --- a/js/src/cli.js +++ b/js/src/cli.js @@ -99,6 +99,7 @@ var path = require('path'), "unescape_strings": Boolean, "wrap_line_length": Number, "wrap_attributes": ["auto", "force", "force-aligned", "force-expand-multiline", "aligned-multiple", "preserve", "preserve-aligned"], + "wrap_attributes_min_attrs": Number, "wrap_attributes_indent_size": Number, "e4x": Boolean, "end_with_newline": Boolean, @@ -163,6 +164,7 @@ var path = require('path'), "N": ["--newline_between_rules"], // HTML-only "A": ["--wrap_attributes"], + "M": ["--wrap_attributes_min_attrs"], "i": ["--wrap_attributes_indent_size"], "W": ["--max_char"], // obsolete since 1.3.5 "d": ["--inline"], @@ -395,6 +397,7 @@ function usage(err) { msg.push(' -S, --indent-scripts [keep|separate|normal] ["normal"]'); msg.push(' -w, --wrap-line-length Wrap lines that exceed N characters [0]'); msg.push(' -A, --wrap-attributes Wrap html tag attributes to new lines [auto|force|force-aligned|force-expand-multiline|aligned-multiple|preserve|preserve-aligned] ["auto"]'); + msg.push(' -M, --wrap-attributes-min-attrs Minimum number of html tag attributes for force wrap attribute options [2]'); msg.push(' -i, --wrap-attributes-indent-size Indent wrapped tags to after N characters [indent-level]'); msg.push(' -p, --preserve-newlines Preserve line-breaks (--no-preserve-newlines disables)'); msg.push(' -m, --max-preserve-newlines Number of line-breaks to be preserved in one chunk [10]'); diff --git a/js/src/html/beautifier.js b/js/src/html/beautifier.js index f6decd381..f678b5062 100644 --- a/js/src/html/beautifier.js +++ b/js/src/html/beautifier.js @@ -296,11 +296,11 @@ Beautifier.prototype.beautify = function() { while (raw_token.type !== TOKEN.EOF) { if (raw_token.type === TOKEN.TAG_OPEN || raw_token.type === TOKEN.COMMENT) { - parser_token = this._handle_tag_open(printer, raw_token, last_tag_token, last_token); + parser_token = this._handle_tag_open(printer, raw_token, last_tag_token, last_token, tokens); last_tag_token = parser_token; } else if ((raw_token.type === TOKEN.ATTRIBUTE || raw_token.type === TOKEN.EQUALS || raw_token.type === TOKEN.VALUE) || (raw_token.type === TOKEN.TEXT && !last_tag_token.tag_complete)) { - parser_token = this._handle_inside_tag(printer, raw_token, last_tag_token, tokens); + parser_token = this._handle_inside_tag(printer, raw_token, last_tag_token, last_token); } else if (raw_token.type === TOKEN.TAG_CLOSE) { parser_token = this._handle_tag_close(printer, raw_token, last_tag_token); } else if (raw_token.type === TOKEN.TEXT) { @@ -357,7 +357,7 @@ Beautifier.prototype._handle_tag_close = function(printer, raw_token, last_tag_t return parser_token; }; -Beautifier.prototype._handle_inside_tag = function(printer, raw_token, last_tag_token, tokens) { +Beautifier.prototype._handle_inside_tag = function(printer, raw_token, last_tag_token, last_token) { var wrapped = last_tag_token.has_wrapped_attrs; var parser_token = { text: raw_token.text, @@ -378,7 +378,6 @@ Beautifier.prototype._handle_inside_tag = function(printer, raw_token, last_tag_ } else { if (raw_token.type === TOKEN.ATTRIBUTE) { printer.set_space_before_token(true); - last_tag_token.attr_count += 1; } else if (raw_token.type === TOKEN.EQUALS) { //no space before = printer.set_space_before_token(false); } else if (raw_token.type === TOKEN.VALUE && raw_token.previous.type === TOKEN.EQUALS) { //no space before value @@ -391,29 +390,15 @@ Beautifier.prototype._handle_inside_tag = function(printer, raw_token, last_tag_ wrapped = wrapped || raw_token.newlines !== 0; } - - if (this._is_wrap_attributes_force) { - var force_attr_wrap = last_tag_token.attr_count > 1; - if (this._is_wrap_attributes_force_expand_multiline && last_tag_token.attr_count === 1) { - var is_only_attribute = true; - var peek_index = 0; - var peek_token; - do { - peek_token = tokens.peek(peek_index); - if (peek_token.type === TOKEN.ATTRIBUTE) { - is_only_attribute = false; - break; - } - peek_index += 1; - } while (peek_index < 4 && peek_token.type !== TOKEN.EOF && peek_token.type !== TOKEN.TAG_CLOSE); - - force_attr_wrap = !is_only_attribute; - } - - if (force_attr_wrap) { - printer.print_newline(false); - wrapped = true; - } + // Wrap for 'force' options, and if the number of attributes is at least that specified in 'wrap_attributes_min_attrs': + // 1. always wrap the second and beyond attributes + // 2. wrap the first attribute only if 'force-expand-multiline' is specified + if (this._is_wrap_attributes_force && + last_tag_token.attr_count >= this._options.wrap_attributes_min_attrs && + (last_token.type !== TOKEN.TAG_OPEN || // ie. second attribute and beyond + this._is_wrap_attributes_force_expand_multiline)) { + printer.print_newline(false); + wrapped = true; } } printer.print_token(raw_token); @@ -542,12 +527,12 @@ Beautifier.prototype._print_custom_beatifier_text = function(printer, raw_token, } }; -Beautifier.prototype._handle_tag_open = function(printer, raw_token, last_tag_token, last_token) { +Beautifier.prototype._handle_tag_open = function(printer, raw_token, last_tag_token, last_token, tokens) { var parser_token = this._get_tag_open_token(raw_token); if ((last_tag_token.is_unformatted || last_tag_token.is_content_unformatted) && !last_tag_token.is_empty_element && - raw_token.type === TOKEN.TAG_OPEN && raw_token.text.indexOf('' ] }] + }, { + name: "Test wrap_attributes_min_attrs with force/force-xx options", + description: "", + matrix: [{ + options: [ + { name: "wrap_attributes", value: "'force'" }, + { name: "wrap_attributes_min_attrs", value: "4" } + ], + indent_attr: ' ', + indent_attr_first: ' ', + indent_end: ' ', + newline_end: '' + }, { + options: [ + { name: "wrap_attributes", value: "'force-aligned'" }, + { name: "wrap_attributes_min_attrs", value: "4" } + ], + indent_attr: ' ', + indent_attr_first: ' ', + indent_end: ' ', + newline_end: '' + }, { + options: [ + { name: "wrap_attributes", value: "'force-expand-multiline'" }, + { name: "wrap_attributes_min_attrs", value: "4" } + ], + indent_attr: ' ', + indent_attr_first: '\n ', + indent_end: '\n', + newline_end: '\n' + }], + tests: [{ + input: [ + '' + ], + output: [ + '' + ] + }, { + input: [ + '' + ], + output: '' + }, { + input: [ + '', + '', + '' + ], + output: [ + '', + ' ', + '' + ] + }] + }, { + name: "Test wrap_attributes_min_attrs = 1 with force/force-xx options", + description: "", + matrix: [{ + // Should not wrap, by design + options: [ + { name: "wrap_attributes", value: "'force'" }, + { name: "wrap_attributes_min_attrs", value: "1" } + ], + indent_attr: ' ', + newline_end: ' ' + }, { + // Should not wrap, by design + options: [ + { name: "wrap_attributes", value: "'force-aligned'" }, + { name: "wrap_attributes_min_attrs", value: "1" } + ], + indent_attr: ' ', + newline_end: ' ' + }, { + // Should wrap + options: [ + { name: "wrap_attributes", value: "'force-expand-multiline'" }, + { name: "wrap_attributes_min_attrs", value: "1" } + ], + indent_attr: '\n ', + newline_end: '\n' + }], + tests: [{ + input: [ + '' + ], + output: '' + }] }, { name: "Handlebars Indenting Off", description: "Test handlebar behavior when indenting is off",