From a7030f32d3d1563ff5f3f58797233045112ee787 Mon Sep 17 00:00:00 2001 From: Ze Yu Date: Sun, 19 Jul 2020 01:31:46 +0800 Subject: [PATCH] Add minimum attributes option for attribute wrap --- README.md | 1 + js/src/cli.js | 3 + js/src/html/beautifier.js | 56 +++++++------- js/src/html/options.js | 1 + js/test/generated/beautify-html-tests.js | 99 ++++++++++++++++++++++++ test/data/html/tests.js | 65 ++++++++++++++++ 6 files changed, 196 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 4f38f536d..a37e56add 100644 --- a/README.md +++ b/README.md @@ -340,6 +340,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 wrapping attributes -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 8ea1562c9..d761cdf07 100755 --- a/js/src/cli.js +++ b/js/src/cli.js @@ -87,6 +87,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, @@ -151,6 +152,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"], @@ -383,6 +385,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 wrapping attributes [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 82361b139..714cfc930 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 used and there are more than 1 attributes + 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 && last_tag_token.attr_count > 1))) { + 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(''); + //============================================================ + // Test wrap_attributes_min_attrs with force/force-xx options - (wrap_attributes = ""force"", wrap_attributes_min_attrs = "4") + reset_options(); + set_name('Test wrap_attributes_min_attrs with force/force-xx options - (wrap_attributes = ""force"", wrap_attributes_min_attrs = "4")'); + opts.wrap_attributes = 'force'; + opts.wrap_attributes_min_attrs = 4; + bth( + '', + // -- output -- + ''); + bth( + '', + // -- output -- + ''); + bth( + '\n' + + '\n' + + '', + // -- output -- + '\n' + + ' \n' + + ''); + + // Test wrap_attributes_min_attrs with force/force-xx options - (wrap_attributes = ""force-aligned"", wrap_attributes_min_attrs = "4") + reset_options(); + set_name('Test wrap_attributes_min_attrs with force/force-xx options - (wrap_attributes = ""force-aligned"", wrap_attributes_min_attrs = "4")'); + opts.wrap_attributes = 'force-aligned'; + opts.wrap_attributes_min_attrs = 4; + bth( + '', + // -- output -- + ''); + bth( + '', + // -- output -- + ''); + bth( + '\n' + + '\n' + + '', + // -- output -- + '\n' + + ' \n' + + ''); + + // Test wrap_attributes_min_attrs with force/force-xx options - (wrap_attributes = ""force-expand-multiline"", wrap_attributes_min_attrs = "4") + reset_options(); + set_name('Test wrap_attributes_min_attrs with force/force-xx options - (wrap_attributes = ""force-expand-multiline"", wrap_attributes_min_attrs = "4")'); + opts.wrap_attributes = 'force-expand-multiline'; + opts.wrap_attributes_min_attrs = 4; + bth( + '', + // -- output -- + ''); + bth( + '', + // -- output -- + ''); + bth( + '\n' + + '\n' + + '', + // -- output -- + '\n' + + ' \n' + + ''); + + //============================================================ // Handlebars Indenting Off reset_options(); diff --git a/test/data/html/tests.js b/test/data/html/tests.js index ee62acde6..2517541ee 100644 --- a/test/data/html/tests.js +++ b/test/data/html/tests.js @@ -1193,6 +1193,71 @@ exports.test_data = { '^^^indent_attr$$$(typeaheadOnSelect)="onSuggestionSelected($event)" />' ] }] + }, { + 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: "Handlebars Indenting Off", description: "Test handlebar behavior when indenting is off",