diff --git a/README.md b/README.md index 5f655a097..1d54e525a 100644 --- a/README.md +++ b/README.md @@ -382,7 +382,8 @@ HTML Beautifier Options: -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 - --inline_custom_elements Inline custom elements [true] + --inline_custom_elements Inline custom elements [false] + --preserve_self_closing_tags Preserve the no-space form of self-closing tags [false] -U, --unformatted List of tags (defaults to inline) that should not be reformatted -T, --content_unformatted List of tags (defaults to pre) whose content should not be reformatted -E, --extra_liners List of tags (defaults to [head,body,/html] that should have an extra newline before them. diff --git a/js/src/cli.js b/js/src/cli.js index d6ec9e93e..71a88ca58 100755 --- a/js/src/cli.js +++ b/js/src/cli.js @@ -120,6 +120,7 @@ var path = require('path'), "max_char": Number, // obsolete since 1.3.5 "inline": [String, Array], "inline_custom_elements": [Boolean], + "preserve_self_closing_tags": [Boolean], "unformatted": [String, Array], "content_unformatted": [String, Array], "indent_inner_html": [Boolean], @@ -173,6 +174,7 @@ var path = require('path'), "W": ["--max_char"], // obsolete since 1.3.5 "d": ["--inline"], // no shorthand for "inline_custom_elements" + // no shorthand for "preserve_self_closing_tags" "U": ["--unformatted"], "T": ["--content_unformatted"], "I": ["--indent_inner_html"], @@ -710,4 +712,4 @@ function logToStdout(str, config) { if (typeof config.quiet === "undefined" || !config.quiet) { console.log(str); } -} \ No newline at end of file +} diff --git a/js/src/css/beautifier.js b/js/src/css/beautifier.js index 5c0e393f5..10234b3fd 100644 --- a/js/src/css/beautifier.js +++ b/js/src/css/beautifier.js @@ -195,6 +195,8 @@ Beautifier.prototype.beautify = function() { var enteringConditionalGroup = false; var insideNonNestedAtRule = false; var insideScssMap = false; + var braceIndentStack = []; + var parenOutdentStack = []; var topCharacter = this._ch; var insideNonSemiColonValues = false; var whitespace; @@ -308,6 +310,7 @@ Beautifier.prototype.beautify = function() { this.preserveSingleSpace(isAfterSpace); this.print_string(this._ch + this.eatString('}')); } else if (this._ch === '{') { + var indentBrace = false; if (insidePropertyValue) { insidePropertyValue = false; this.outdent(); @@ -338,33 +341,43 @@ Beautifier.prototype.beautify = function() { this.print_string(this._ch); this.indent(); this._output.set_indent(this._indentLevel); + indentBrace = true; } else { // inside mixin and first param is object if (previous_ch === '(') { this._output.space_before_token = false; } else if (previous_ch !== ',') { this.indent(); + indentBrace = true; } this.print_string(this._ch); } + if (!indentBrace && parenOutdentStack.length) { + parenOutdentStack[parenOutdentStack.length - 1] = true; + } + braceIndentStack.push(indentBrace); + this.eatWhitespace(true); this._output.add_new_line(); } else if (this._ch === '}') { + var shouldOutdent = braceIndentStack.length ? braceIndentStack.pop() : true; this.outdent(); this._output.add_new_line(); if (previous_ch === '{') { this._output.trim(true); } - if (insidePropertyValue) { + if (insidePropertyValue && shouldOutdent) { this.outdent(); insidePropertyValue = false; } this.print_string(this._ch); - insideRule = false; - if (this._nestedLevel) { - this._nestedLevel--; + if (shouldOutdent) { + insideRule = false; + if (this._nestedLevel) { + this._nestedLevel--; + } } this.eatWhitespace(true); @@ -478,13 +491,17 @@ Beautifier.prototype.beautify = function() { } else { this.eatWhitespace(); parenLevel++; + parenOutdentStack.push(false); this.indent(); } } } else if (this._ch === ')') { + var suppressParenOutdent = parenOutdentStack.length ? parenOutdentStack.pop() : false; if (parenLevel) { parenLevel--; - this.outdent(); + if (!suppressParenOutdent) { + this.outdent(); + } } if (insideScssMap && this._input.peek() === ";" && this._options.selector_separator_newline) { insideScssMap = false; diff --git a/js/src/html/beautifier.js b/js/src/html/beautifier.js index e32940fb7..48ace1ac7 100644 --- a/js/src/html/beautifier.js +++ b/js/src/html/beautifier.js @@ -375,7 +375,7 @@ Beautifier.prototype._handle_tag_close = function(printer, raw_token, last_tag_t printer.add_raw_token(raw_token); } else { if (last_tag_token.tag_start_char === '<') { - printer.set_space_before_token(raw_token.text[0] === '/', true); // space before />, no space before > + printer.set_space_before_token(raw_token.text[0] === '/' && !this._options.preserve_self_closing_tags, true); // space before />, no space before > when preserving self-closing tags if (this._is_wrap_attributes_force_expand_multiline && last_tag_token.has_wrapped_attrs) { printer.print_newline(false); } @@ -818,92 +818,93 @@ Beautifier.prototype._do_optional_end_element = function(parser_token) { // https://www.w3.org/TR/html5/syntax.html#optional-tags if (parser_token.is_empty_element || !parser_token.is_start_tag || !parser_token.parent) { return; - } - if (parser_token.tag_name === 'body') { - // A head element’s end tag may be omitted if the head element is not immediately followed by a space character or a comment. - result = result || this._tag_stack.try_pop('head'); + if (parser_token.tag_start_char === '<') { + if (parser_token.tag_name === 'body') { + // A head element’s end tag may be omitted if the head element is not immediately followed by a space character or a comment. + result = result || this._tag_stack.try_pop('head'); - //} else if (parser_token.tag_name === 'body') { - // DONE: A body element’s end tag may be omitted if the body element is not immediately followed by a comment. + //} else if (parser_token.tag_name === 'body') { + // DONE: A body element’s end tag may be omitted if the body element is not immediately followed by a comment. - } else if (parser_token.tag_name === 'li') { - // An li element’s end tag may be omitted if the li element is immediately followed by another li element or if there is no more content in the parent element. - result = result || this._tag_stack.try_pop('li', ['ol', 'ul', 'menu']); + } else if (parser_token.tag_name === 'li') { + // An li element’s end tag may be omitted if the li element is immediately followed by another li element or if there is no more content in the parent element. + result = result || this._tag_stack.try_pop('li', ['ol', 'ul', 'menu']); - } else if (parser_token.tag_name === 'dd' || parser_token.tag_name === 'dt') { - // A dd element’s end tag may be omitted if the dd element is immediately followed by another dd element or a dt element, or if there is no more content in the parent element. - // A dt element’s end tag may be omitted if the dt element is immediately followed by another dt element or a dd element. - result = result || this._tag_stack.try_pop('dt', ['dl']); - result = result || this._tag_stack.try_pop('dd', ['dl']); + } else if (parser_token.tag_name === 'dd' || parser_token.tag_name === 'dt') { + // A dd element’s end tag may be omitted if the dd element is immediately followed by another dd element or a dt element, or if there is no more content in the parent element. + // A dt element’s end tag may be omitted if the dt element is immediately followed by another dt element or a dd element. + result = result || this._tag_stack.try_pop('dt', ['dl']); + result = result || this._tag_stack.try_pop('dd', ['dl']); - } else if (parser_token.parent.tag_name === 'p' && p_closers.indexOf(parser_token.tag_name) !== -1) { - // IMPORTANT: this else-if works because p_closers has no overlap with any other element we look for in this method - // check for the parent element is an HTML element that is not an ,