Skip to content

Commit

Permalink
Add minimum attributes option for html attribute wrap
Browse files Browse the repository at this point in the history
  • Loading branch information
ang-zeyu committed Apr 20, 2021
1 parent 8603142 commit 6edbfb5
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 29 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions js/src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"],
Expand Down Expand Up @@ -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]');
Expand Down
56 changes: 27 additions & 29 deletions js/src/html/beautifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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('</') === 0) {
raw_token.type === TOKEN.TAG_OPEN && !parser_token.is_start_tag) {
// End element tags for unformatted or content_unformatted elements
// are printed raw to keep any newlines inside them exactly the same.
printer.add_raw_token(raw_token);
Expand All @@ -561,6 +546,19 @@ Beautifier.prototype._handle_tag_open = function(printer, raw_token, last_tag_to
printer.print_token(raw_token);
}

// count the number of attributes
if (parser_token.is_start_tag && this._is_wrap_attributes_force) {
var peek_index = 0;
var peek_token;
do {
peek_token = tokens.peek(peek_index);
if (peek_token.type === TOKEN.ATTRIBUTE) {
parser_token.attr_count += 1;
}
peek_index += 1;
} while (peek_token.type !== TOKEN.EOF && peek_token.type !== TOKEN.TAG_CLOSE);
}

//indent attributes an auto, forced, aligned or forced-align line-wrap
if (this._is_wrap_attributes_force_aligned || this._is_wrap_attributes_aligned_multiple || this._is_wrap_attributes_preserve_aligned) {
parser_token.alignment_size = raw_token.text.length + 1;
Expand Down
1 change: 1 addition & 0 deletions js/src/html/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ function Options(options) {
this.indent_handlebars = this._get_boolean('indent_handlebars', true);
this.wrap_attributes = this._get_selection('wrap_attributes',
['auto', 'force', 'force-aligned', 'force-expand-multiline', 'aligned-multiple', 'preserve', 'preserve-aligned']);
this.wrap_attributes_min_attrs = this._get_number('wrap_attributes_min_attrs', 2);
this.wrap_attributes_indent_size = this._get_number('wrap_attributes_indent_size', this.indent_size);
this.extra_liners = this._get_array('extra_liners', ['head', 'body', '/html']);

Expand Down
65 changes: 65 additions & 0 deletions test/data/html/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: [
'<input type="text" class="form-control" autocomplete="off"',
'[(ngModel)]="myValue" />'
],
output: [
'<input{{indent_attr_first}}type="text"',
'{{indent_attr}}class="form-control"',
'{{indent_attr}}autocomplete="off"',
'{{indent_attr}}[(ngModel)]="myValue"{{indent_end}}/>'
]
}, {
input: [
'<input type="text" autocomplete="off"',
'[(ngModel)]="myValue" />'
],
output: '<input type="text" autocomplete="off" [(ngModel)]="myValue" />'
}, {
input: [
'<cmpnt v-bind:xx="xx" ' +
'@someevent="dosomething" someprop',
'class="xx-button">',
'<div class="alert alert-info" style="margin-left: 1px;" role="alert">lorem ipsum</div>',
'</cmpnt>'
],
output: [
'<cmpnt{{indent_attr_first}}v-bind:xx="xx"',
'{{indent_attr}}@someevent="dosomething"',
'{{indent_attr}}someprop',
'{{indent_attr}}class="xx-button"{{newline_end}}>',
' <div class="alert alert-info" style="margin-left: 1px;" role="alert">lorem ipsum</div>',
'</cmpnt>'
]
}]
}, {
name: "Handlebars Indenting Off",
description: "Test handlebar behavior when indenting is off",
Expand Down

0 comments on commit 6edbfb5

Please sign in to comment.