diff --git a/js/src/core/options.js b/js/src/core/options.js index 580388d3..1a214c07 100644 --- a/js/src/core/options.js +++ b/js/src/core/options.js @@ -28,13 +28,110 @@ 'use strict'; +function Options(options, merge_child_field) { + options = _mergeOpts(options, merge_child_field); + this.raw_options = _normalizeOpts(options); + + // Support passing the source text back with no change + this.disabled = this._get_boolean('disabled'); + + this.eol = this._get_characters('eol', 'auto'); + this.end_with_newline = this._get_boolean('end_with_newline'); + this.indent_size = this._get_number('indent_size', 4); + this.indent_char = this._get_characters('indent_char', ' '); + + this.preserve_newlines = this._get_boolean('preserve_newlines', true); + this.max_preserve_newlines = this.max_preserve_newlines = this._get_number('max_preserve_newlines', 32786); + if (!this.preserve_newlines) { + this.max_preserve_newlines = 0; + } + + this.indent_with_tabs = this._get_boolean('indent_with_tabs'); + if (this.indent_with_tabs) { + this.indent_char = '\t'; + this.indent_size = 1; + } + + this.indent_string = this.indent_char; + if (this.indent_size > 1) { + this.indent_string = new Array(this.indent_size + 1).join(this.indent_char); + } + + // Backwards compat with 1.3.x + this.wrap_line_length = this._get_number('wrap_line_length', this._get_number('max_char')); + +} + +Options.prototype._get_array = function(name, default_value) { + var option_value = this.raw_options[name]; + var result = default_value || []; + if (typeof option_value === 'object') { + if (option_value !== null && typeof option_value.concat === 'function') { + result = option_value.concat(); + } + } else if (typeof option_value === 'string') { + result = option_value.split(/[^a-zA-Z0-9_\/\-]+/); + } + return result; +}; + +Options.prototype._get_boolean = function(name, default_value) { + var option_value = this.raw_options[name]; + var result = option_value === undefined ? !!default_value : !!option_value; + return result; +}; + +Options.prototype._get_characters = function(name, default_value) { + var option_value = this.raw_options[name]; + var result = default_value || ''; + if (typeof option_value === 'string') { + result = option_value.replace(/\\r/, '\r').replace(/\\n/, '\n').replace(/\\t/, '\t'); + } + return result; +}; + +Options.prototype._get_number = function(name, default_value) { + var option_value = this.raw_options[name]; + default_value = parseInt(default_value, 10); + if (isNaN(default_value)) { + default_value = 0; + } + var result = parseInt(option_value, 10); + if (isNaN(result)) { + result = default_value; + } + return result; +}; + +Options.prototype._get_selection = function(name, selection_list, default_value) { + default_value = default_value || [selection_list[0]]; + if (!this._is_valid_selection(default_value, selection_list)) { + throw new Error("Invalid Default Value!"); + } + + var result = this._get_array(name, default_value); + if (!this._is_valid_selection(result, selection_list)) { + throw new Error( + "Invalid Option Value: The option '" + name + "' must be one of the following values\n" + selection_list + "\nYou passed in: '" + this.raw_options[name] + "'"); + } + + return result; +}; + +Options.prototype._is_valid_selection = function(result, selection_list) { + return result.length && selection_list.length && + !result.some(function(item) { return selection_list.indexOf(item) === -1; }); +}; + + // merges child options up with the parent options object // Example: obj = {a: 1, b: {a: 2}} // mergeOpts(obj, 'b') // // Returns: {a: 2, b: {a: 2}} -function mergeOpts(allOptions, childFieldName) { +function _mergeOpts(allOptions, childFieldName) { var finalOpts = {}; + allOptions = allOptions || {}; var name; for (name in allOptions) { @@ -44,7 +141,7 @@ function mergeOpts(allOptions, childFieldName) { } //merge in the per type settings for the childFieldName - if (childFieldName in allOptions) { + if (childFieldName && allOptions[childFieldName]) { for (name in allOptions[childFieldName]) { finalOpts[name] = allOptions[childFieldName][name]; } @@ -52,7 +149,7 @@ function mergeOpts(allOptions, childFieldName) { return finalOpts; } -function normalizeOpts(options) { +function _normalizeOpts(options) { var convertedOpts = {}; var key; @@ -63,5 +160,6 @@ function normalizeOpts(options) { return convertedOpts; } -module.exports.mergeOpts = mergeOpts; -module.exports.normalizeOpts = normalizeOpts; \ No newline at end of file +module.exports.Options = Options; +module.exports.normalizeOpts = _normalizeOpts; +module.exports.mergeOpts = _mergeOpts; \ No newline at end of file diff --git a/js/src/css/beautifier.js b/js/src/css/beautifier.js index 7b37654e..df12c57e 100644 --- a/js/src/css/beautifier.js +++ b/js/src/css/beautifier.js @@ -28,8 +28,7 @@ 'use strict'; -var mergeOpts = require('../core/options').mergeOpts; -var normalizeOpts = require('../core/options').normalizeOpts; +var Options = require('./options').Options; var acorn = require('../core/acorn'); var Output = require('../core/output').Output; var InputScanner = require('../core/inputscanner').InputScanner; @@ -39,33 +38,9 @@ var allLineBreaks = acorn.allLineBreaks; function Beautifier(source_text, options) { this._source_text = source_text || ''; - options = options || {}; - // Allow the setting of language/file-type specific options // with inheritance of overall settings - options = mergeOpts(options, 'css'); - options = normalizeOpts(options); - this._options = {}; - - var indentSize = options.indent_size ? parseInt(options.indent_size, 10) : 4; - var indentCharacter = options.indent_char || ' '; - var preserve_newlines = (options.preserve_newlines === undefined) ? false : options.preserve_newlines; - var selectorSeparatorNewline = (options.selector_separator_newline === undefined) ? true : options.selector_separator_newline; - var end_with_newline = (options.end_with_newline === undefined) ? false : options.end_with_newline; - var newline_between_rules = (options.newline_between_rules === undefined) ? true : options.newline_between_rules; - var space_around_combinator = (options.space_around_combinator === undefined) ? false : options.space_around_combinator; - space_around_combinator = space_around_combinator || ((options.space_around_selector_separator === undefined) ? false : options.space_around_selector_separator); - var eol = options.eol ? options.eol : 'auto'; - - // Support passing the source text back with no change - this._options.disabled = (options.disabled === undefined) ? false : options.disabled; - - if (options.indent_with_tabs) { - indentCharacter = '\t'; - indentSize = 1; - } - - eol = eol.replace(/\\r/, '\r').replace(/\\n/, '\n'); + this._options = new Options(options); // tokenizer var whitespaceChar = /\s/; @@ -96,21 +71,21 @@ function Beautifier(source_text, options) { // When allowAtLeastOneNewLine is true, will output new lines for each // newline character found; if the user has preserve_newlines off, only // the first newline will be output - function eatWhitespace(allowAtLeastOneNewLine) { + this.eatWhitespace = function(allowAtLeastOneNewLine) { var result = whitespaceChar.test(input.peek()); var isFirstNewLine = true; while (whitespaceChar.test(input.peek())) { ch = input.next(); if (allowAtLeastOneNewLine && ch === '\n') { - if (preserve_newlines || isFirstNewLine) { + if (this._options.preserve_newlines || isFirstNewLine) { isFirstNewLine = false; output.add_new_line(true); } } } return result; - } + }; // Nested pseudo-class if we are insideRule // and the next special character found opens @@ -174,6 +149,7 @@ function Beautifier(source_text, options) { } var source_text = this._source_text; + var eol = this._options.eol; if (eol === 'auto') { eol = '\n'; if (source_text && lineBreak.test(source_text || '')) { @@ -186,19 +162,17 @@ function Beautifier(source_text, options) { source_text = source_text.replace(allLineBreaks, '\n'); // reset - var singleIndent = new Array(indentSize + 1).join(indentCharacter); var baseIndentString = ''; var preindent_index = 0; if (source_text && source_text.length) { - while ((source_text.charAt(preindent_index) === ' ' || - source_text.charAt(preindent_index) === '\t')) { + while ((source_text.charAt(preindent_index) === ' ' || source_text.charAt(preindent_index) === '\t')) { preindent_index += 1; } baseIndentString = source_text.substring(0, preindent_index); source_text = source_text.substring(preindent_index); } - output = new Output(singleIndent, baseIndentString); + output = new Output(this._options.indent_string, baseIndentString); input = new InputScanner(source_text); indentLevel = 0; nestedLevel = 0; @@ -235,7 +209,7 @@ function Beautifier(source_text, options) { print_string(input.read(block_comment_pattern)); // Ensures any new lines following the comment are preserved - eatWhitespace(true); + this.eatWhitespace(true); // Block comments are followed by a new line so they don't // share a line with other properties @@ -249,7 +223,7 @@ function Beautifier(source_text, options) { print_string(input.read(comment_pattern)); // Ensures any new lines following the comment are preserved - eatWhitespace(true); + this.eatWhitespace(true); } else if (ch === '@') { preserveSingleSpace(isAfterSpace); @@ -309,12 +283,12 @@ function Beautifier(source_text, options) { // otherwise, declarations are also allowed insideRule = (indentLevel >= nestedLevel); } - if (newline_between_rules && insideRule) { + if (this._options.newline_between_rules && insideRule) { if (output.previous_line && output.previous_line.item(-1) !== '{') { output.ensure_empty_line_above('/', ','); } } - eatWhitespace(true); + this.eatWhitespace(true); output.add_new_line(); } else if (ch === '}') { outdent(); @@ -334,25 +308,23 @@ function Beautifier(source_text, options) { nestedLevel--; } - eatWhitespace(true); + this.eatWhitespace(true); output.add_new_line(); - if (newline_between_rules && !output.just_added_blankline()) { + if (this._options.newline_between_rules && !output.just_added_blankline()) { if (input.peek() !== '}') { output.add_new_line(true); } } } else if (ch === ":") { - if ((insideRule || enteringConditionalGroup) && - !(input.lookBack("&") || foundNestedPseudoClass()) && - !input.lookBack("(") && !insideAtExtend) { + if ((insideRule || enteringConditionalGroup) && !(input.lookBack("&") || foundNestedPseudoClass()) && !input.lookBack("(") && !insideAtExtend) { // 'property: value' delimiter // which could be in a conditional group query print_string(':'); if (!insidePropertyValue) { insidePropertyValue = true; output.space_before_token = true; - eatWhitespace(true); + this.eatWhitespace(true); indent(); } } else { @@ -375,7 +347,7 @@ function Beautifier(source_text, options) { } else if (ch === '"' || ch === '\'') { preserveSingleSpace(isAfterSpace); print_string(ch + eatString(ch)); - eatWhitespace(true); + this.eatWhitespace(true); } else if (ch === ';') { if (insidePropertyValue) { outdent(); @@ -384,7 +356,7 @@ function Beautifier(source_text, options) { insideAtExtend = false; insideAtImport = false; print_string(ch); - eatWhitespace(true); + this.eatWhitespace(true); // This maintains single line comments on the same // line. Block comments are also affected, but @@ -396,7 +368,7 @@ function Beautifier(source_text, options) { } else if (ch === '(') { // may be a url if (input.lookBack("url")) { print_string(ch); - eatWhitespace(); + this.eatWhitespace(); ch = input.next(); if (ch === ')' || ch === '"' || ch !== '\'') { input.back(); @@ -408,29 +380,28 @@ function Beautifier(source_text, options) { parenLevel++; preserveSingleSpace(isAfterSpace); print_string(ch); - eatWhitespace(); + this.eatWhitespace(); } } else if (ch === ')') { print_string(ch); parenLevel--; } else if (ch === ',') { print_string(ch); - eatWhitespace(true); - if (selectorSeparatorNewline && !insidePropertyValue && parenLevel < 1 && !insideAtImport) { + this.eatWhitespace(true); + if (this._options.selector_separator_newline && !insidePropertyValue && parenLevel < 1 && !insideAtImport) { output.add_new_line(); } else { output.space_before_token = true; } - } else if ((ch === '>' || ch === '+' || ch === '~') && - !insidePropertyValue && parenLevel < 1) { + } else if ((ch === '>' || ch === '+' || ch === '~') && !insidePropertyValue && parenLevel < 1) { //handle combinator spacing - if (space_around_combinator) { + if (this._options.space_around_combinator) { output.space_before_token = true; print_string(ch); output.space_before_token = true; } else { print_string(ch); - eatWhitespace(); + this.eatWhitespace(); // squash extra whitespace if (ch && whitespaceChar.test(ch)) { ch = ''; @@ -442,7 +413,7 @@ function Beautifier(source_text, options) { preserveSingleSpace(isAfterSpace); print_string(ch); } else if (ch === '=') { // no whitespace before or after - eatWhitespace(); + this.eatWhitespace(); print_string('='); if (whitespaceChar.test(ch)) { ch = ''; @@ -456,7 +427,7 @@ function Beautifier(source_text, options) { } } - var sweetCode = output.get_code(end_with_newline, eol); + var sweetCode = output.get_code(this._options.end_with_newline, eol); return sweetCode; }; diff --git a/js/src/css/options.js b/js/src/css/options.js new file mode 100644 index 00000000..d69a9d9b --- /dev/null +++ b/js/src/css/options.js @@ -0,0 +1,46 @@ +/*jshint node:true */ +/* + + The MIT License (MIT) + + Copyright (c) 2007-2018 Einar Lielmanis, Liam Newman, and contributors. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +'use strict'; + +var BaseOptions = require('../core/options').Options; + +function Options(options) { + BaseOptions.call(this, options, 'css'); + + this.selector_separator_newline = this._get_boolean('selector_separator_newline', true); + this.newline_between_rules = this._get_boolean('newline_between_rules', true); + var space_around_selector_separator = this._get_boolean('space_around_selector_separator'); + this.space_around_combinator = this._get_boolean('space_around_combinator') || space_around_selector_separator; + +} +Options.prototype = new BaseOptions(); + + + +module.exports.Options = Options; \ No newline at end of file diff --git a/js/src/html/beautifier.js b/js/src/html/beautifier.js index 2b13a724..da0ad67d 100644 --- a/js/src/html/beautifier.js +++ b/js/src/html/beautifier.js @@ -28,8 +28,7 @@ 'use strict'; -var mergeOpts = require('../core/options').mergeOpts; -var normalizeOpts = require('../core/options').normalizeOpts; +var Options = require('../html/options').Options; var acorn = require('../core/acorn'); var Output = require('../core/output').Output; var Tokenizer = require('../html/tokenizer').Tokenizer; @@ -38,22 +37,15 @@ var TOKEN = require('../html/tokenizer').TOKEN; var lineBreak = acorn.lineBreak; var allLineBreaks = acorn.allLineBreaks; -var Printer = function(indent_character, indent_size, wrap_line_length, max_preserve_newlines, preserve_newlines) { //handles input/output and some other printing functions +var Printer = function(indent_string, wrap_line_length, max_preserve_newlines, preserve_newlines) { //handles input/output and some other printing functions - this.indent_character = indent_character; - this.indent_string = indent_character; - this.indent_size = indent_size; this.indent_level = 0; this.alignment_size = 0; this.wrap_line_length = wrap_line_length; this.max_preserve_newlines = max_preserve_newlines; this.preserve_newlines = preserve_newlines; - if (this.indent_size > 1) { - this.indent_string = new Array(this.indent_size + 1).join(this.indent_character); - } - - this._output = new Output(this.indent_string, ''); + this._output = new Output(indent_string, ''); }; @@ -98,9 +90,9 @@ Printer.prototype.traverse_whitespace = function(raw_token) { // at the wrap_line_length, append a newline/indentation. // return true if a newline was added, false if a space was added Printer.prototype.print_space_or_wrap = function(text) { - if (this._output.current_line.get_character_count() + text.length + 1 >= this.wrap_line_length) { //insert a line when the wrap_line_length is reached - if (this._output.add_new_line()) { - return true; + if (this.wrap_line_length) { + if (this._output.current_line.get_character_count() + text.length + 1 >= this.wrap_line_length) { //insert a line when the wrap_line_length is reached + return this._output.add_new_line(); } } return false; @@ -232,18 +224,6 @@ TagStack.prototype.indent_to_tag = function(tag_list) { } }; -function get_array(input, default_list) { - var result = default_list || []; - if (typeof input === 'object') { - if (input !== null && typeof input.concat === 'function') { - result = input.concat(); - } - } else if (typeof input === 'string') { - result = input.trim().replace(/\s*,\s*/g, ',').split(','); - } - return result; -} - function Beautifier(source_text, options, js_beautify, css_beautify) { //Wrapper function to invoke all the necessary constructors and deal with the output. this._source_text = source_text || ''; @@ -254,75 +234,9 @@ function Beautifier(source_text, options, js_beautify, css_beautify) { // Allow the setting of language/file-type specific options // with inheritance of overall settings - options = mergeOpts(options, 'html'); - options = normalizeOpts(options); - - // backwards compatibility to 1.3.4 - if ((options.wrap_line_length === undefined || parseInt(options.wrap_line_length, 10) === 0) && - (options.max_char !== undefined && parseInt(options.max_char, 10) !== 0)) { - options.wrap_line_length = options.max_char; - } - - this._options = Object.assign({}, options); - - this._options.indent_inner_html = (options.indent_inner_html === undefined) ? false : options.indent_inner_html; - this._options.indent_body_inner_html = (options.indent_body_inner_html === undefined) ? true : options.indent_body_inner_html; - this._options.indent_head_inner_html = (options.indent_head_inner_html === undefined) ? true : options.indent_head_inner_html; - this._options.indent_size = (options.indent_size === undefined) ? 4 : parseInt(options.indent_size, 10); - this._options.indent_character = (options.indent_char === undefined) ? ' ' : options.indent_char; - this._options.wrap_line_length = parseInt(options.wrap_line_length, 10) === 0 ? 32786 : parseInt(options.wrap_line_length || 250, 10); - this._options.preserve_newlines = (options.preserve_newlines === undefined) ? true : options.preserve_newlines; - this._options.max_preserve_newlines = this._options.preserve_newlines ? - (isNaN(parseInt(options.max_preserve_newlines, 10)) ? 32786 : parseInt(options.max_preserve_newlines, 10)) : - 0; - this._options.indent_handlebars = (options.indent_handlebars === undefined) ? false : options.indent_handlebars; - this._options.wrap_attributes = (options.wrap_attributes === undefined) ? 'auto' : options.wrap_attributes; - this._options.wrap_attributes_indent_size = (isNaN(parseInt(options.wrap_attributes_indent_size, 10))) ? this._options.indent_size : parseInt(options.wrap_attributes_indent_size, 10); - this._options.end_with_newline = (options.end_with_newline === undefined) ? false : options.end_with_newline; - this._options.extra_liners = get_array(options.extra_liners, ['head', 'body', '/html']); - this._options.eol = options.eol ? options.eol : 'auto'; - - if (options.indent_with_tabs) { - this._options.indent_character = '\t'; - this._options.indent_size = 1; - } - - // Support passing the source text back with no change - this._options.disabled = (options.disabled === undefined) ? false : options.disabled; - - this._options.eol = this._options.eol.replace(/\\r/, '\r').replace(/\\n/, '\n'); - - this._options.inline = get_array(options.inline, [ - // https://www.w3.org/TR/html5/dom.html#phrasing-content - 'a', 'abbr', 'area', 'audio', 'b', 'bdi', 'bdo', 'br', 'button', 'canvas', 'cite', - 'code', 'data', 'datalist', 'del', 'dfn', 'em', 'embed', 'i', 'iframe', 'img', - 'input', 'ins', 'kbd', 'keygen', 'label', 'map', 'mark', 'math', 'meter', 'noscript', - 'object', 'output', 'progress', 'q', 'ruby', 's', 'samp', /* 'script', */ 'select', 'small', - 'span', 'strong', 'sub', 'sup', 'svg', 'template', 'textarea', 'time', 'u', 'var', - 'video', 'wbr', 'text', - // prexisting - not sure of full effect of removing, leaving in - 'acronym', 'address', 'big', 'dt', 'ins', 'strike', 'tt' - ]); - this._options.void_elements = get_array(options.void_elements, [ - // HTLM void elements - aka self-closing tags - aka singletons - // https://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements - 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', - 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr', - // NOTE: Optional tags are too complex for a simple list - // they are hard coded in _do_optional_end_element - - // Doctype and xml elements - '!doctype', '?xml', - // ?php and ?= tags - '?php', '?=', - // other tags that were in this list, keeping just in case - 'basefont', 'isindex' - ]); - this._options.unformatted = get_array(options.unformatted, []); - this._options.content_unformatted = get_array(options.content_unformatted, [ - 'pre', 'textarea' - ]); + var optionHtml = new Options(options, 'html'); + this._options = optionHtml; this._is_wrap_attributes_force = this._options.wrap_attributes.substr(0, 'force'.length) === 'force'; this._is_wrap_attributes_force_expand_multiline = (this._options.wrap_attributes === 'force-expand-multiline'); @@ -356,7 +270,7 @@ Beautifier.prototype.beautify = function() { var last_tag_token = new TagOpenParserToken(); - var printer = new Printer(this._options.indent_character, this._options.indent_size, + var printer = new Printer(this._options.indent_string, this._options.wrap_line_length, this._options.max_preserve_newlines, this._options.preserve_newlines); var tokens = new Tokenizer(source_text, this._options).tokenize(); @@ -410,7 +324,6 @@ Beautifier.prototype._handle_tag_close = function(printer, raw_token, last_tag_t if (last_tag_token.indent_content && !(last_tag_token.is_unformatted || last_tag_token.is_content_unformatted)) { - printer.indent(); // only indent once per opened tag @@ -517,13 +430,13 @@ Beautifier.prototype._print_custom_beatifier_text = function(printer, raw_token, var Child_options = function() { this.eol = '\n'; }; - Child_options.prototype = this._options; + Child_options.prototype = this._options.raw_options; var child_options = new Child_options(); text = _beautifier(indentation + text, child_options); } else { // simply indent the string otherwise var white = text.match(/^\s*/)[0]; - var _level = white.match(/[^\n\r]*$/)[0].split(this._indent_string).length - 1; + var _level = white.match(/[^\n\r]*$/)[0].split(this._options.indent_string).length - 1; var reindent = this._get_full_indent(script_indent_level - _level); text = (indentation + text.trim()) .replace(/\r\n|\r|\n/g, '\n' + reindent); diff --git a/js/src/html/options.js b/js/src/html/options.js new file mode 100644 index 00000000..37c54f04 --- /dev/null +++ b/js/src/html/options.js @@ -0,0 +1,81 @@ +/*jshint node:true */ +/* + + The MIT License (MIT) + + Copyright (c) 2007-2018 Einar Lielmanis, Liam Newman, and contributors. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +'use strict'; + +var BaseOptions = require('../core/options').Options; + +function Options(options) { + BaseOptions.call(this, options, 'html'); + + this.indent_inner_html = this._get_boolean('indent_inner_html', true); + this.indent_body_inner_html = this._get_boolean('indent_body_inner_html', true); + this.indent_head_inner_html = this._get_boolean('indent_head_inner_html', true); + + 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'])[0]; + 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']); + + this.inline = this._get_array('inline', [ + // https://www.w3.org/TR/html5/dom.html#phrasing-content + 'a', 'abbr', 'area', 'audio', 'b', 'bdi', 'bdo', 'br', 'button', 'canvas', 'cite', + 'code', 'data', 'datalist', 'del', 'dfn', 'em', 'embed', 'i', 'iframe', 'img', + 'input', 'ins', 'kbd', 'keygen', 'label', 'map', 'mark', 'math', 'meter', 'noscript', + 'object', 'output', 'progress', 'q', 'ruby', 's', 'samp', /* 'script', */ 'select', 'small', + 'span', 'strong', 'sub', 'sup', 'svg', 'template', 'textarea', 'time', 'u', 'var', + 'video', 'wbr', 'text', + // prexisting - not sure of full effect of removing, leaving in + 'acronym', 'address', 'big', 'dt', 'ins', 'strike', 'tt' + ]); + this.void_elements = this._get_array('void_elements', [ + // HTLM void elements - aka self-closing tags - aka singletons + // https://www.w3.org/html/wg/drafts/html/master/syntax.html#void-elements + 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', + 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr', + // NOTE: Optional tags are too complex for a simple list + // they are hard coded in _do_optional_end_element + + // Doctype and xml elements + '!doctype', '?xml', + // ?php and ?= tags + '?php', '?=', + // other tags that were in this list, keeping just in case + 'basefont', 'isindex' + ]); + this.unformatted = this._get_array('unformatted', []); + this.content_unformatted = this._get_array('content_unformatted', [ + 'pre', 'textarea' + ]); +} +Options.prototype = new BaseOptions(); + + + +module.exports.Options = Options; \ No newline at end of file diff --git a/js/src/javascript/beautifier.js b/js/src/javascript/beautifier.js index f3cae630..909d336b 100644 --- a/js/src/javascript/beautifier.js +++ b/js/src/javascript/beautifier.js @@ -28,10 +28,9 @@ 'use strict'; -var mergeOpts = require('../core/options').mergeOpts; -var normalizeOpts = require('../core/options').normalizeOpts; var acorn = require('../core/acorn'); var Output = require('../core/output').Output; +var Options = require('./options').Options; var Tokenizer = require('./tokenizer').Tokenizer; var line_starters = require('./tokenizer').line_starters; var positionable_operators = require('./tokenizer').positionable_operators; @@ -70,18 +69,6 @@ function generateMapFromStrings(list) { return result; } -function sanitizeOperatorPosition(opPosition) { - opPosition = opPosition || OPERATOR_POSITION.before_newline; - - if (!in_array(opPosition, validPositionValues)) { - throw new Error("Invalid Option Value: The option 'operator_position' must be one of the following values\n" + - validPositionValues + - "\nYou passed in: '" + opPosition + "'"); - } - - return opPosition; -} - var validPositionValues = ['before-newline', 'after-newline', 'preserve-newline']; // Generate map from array @@ -166,74 +153,7 @@ function Beautifier(source_text, options) { this._previous_flags = null; this._flag_store = null; - this._options = {}; - - // Allow the setting of language/file-type specific options - // with inheritance of overall settings - options = mergeOpts(options, 'js'); - options = normalizeOpts(options); - - // compatibility, re - if (options.brace_style === "expand-strict") { //graceful handling of deprecated option - options.brace_style = "expand"; - } else if (options.brace_style === "collapse-preserve-inline") { //graceful handling of deprecated option - options.brace_style = "collapse,preserve-inline"; - } else if (options.braces_on_own_line !== undefined) { //graceful handling of deprecated option - options.brace_style = options.braces_on_own_line ? "expand" : "collapse"; - } else if (!options.brace_style) { //Nothing exists to set it - options.brace_style = "collapse"; - } - - //preserve-inline in delimited string will trigger brace_preserve_inline, everything - //else is considered a brace_style and the last one only will have an effect - var brace_style_split = options.brace_style.split(/[^a-zA-Z0-9_\-]+/); - this._options.brace_preserve_inline = false; //Defaults in case one or other was not specified in meta-option - this._options.brace_style = "collapse"; - for (var bs = 0; bs < brace_style_split.length; bs++) { - if (brace_style_split[bs] === "preserve-inline") { - this._options.brace_preserve_inline = true; - } else { - this._options.brace_style = brace_style_split[bs]; - } - } - - this._options.indent_size = options.indent_size ? parseInt(options.indent_size, 10) : 4; - this._options.indent_char = options.indent_char ? options.indent_char : ' '; - this._options.eol = options.eol ? options.eol : 'auto'; - this._options.preserve_newlines = (options.preserve_newlines === undefined) ? true : options.preserve_newlines; - this._options.unindent_chained_methods = (options.unindent_chained_methods === undefined) ? false : options.unindent_chained_methods; - this._options.break_chained_methods = (options.break_chained_methods === undefined) ? false : options.break_chained_methods; - this._options.max_preserve_newlines = (options.max_preserve_newlines === undefined) ? 0 : parseInt(options.max_preserve_newlines, 10); - this._options.space_in_paren = (options.space_in_paren === undefined) ? false : options.space_in_paren; - this._options.space_in_empty_paren = (options.space_in_empty_paren === undefined) ? false : options.space_in_empty_paren; - this._options.jslint_happy = (options.jslint_happy === undefined) ? false : options.jslint_happy; - this._options.space_after_anon_function = (options.space_after_anon_function === undefined) ? false : options.space_after_anon_function; - this._options.keep_array_indentation = (options.keep_array_indentation === undefined) ? false : options.keep_array_indentation; - this._options.space_before_conditional = (options.space_before_conditional === undefined) ? true : options.space_before_conditional; - this._options.unescape_strings = (options.unescape_strings === undefined) ? false : options.unescape_strings; - this._options.wrap_line_length = (options.wrap_line_length === undefined) ? 0 : parseInt(options.wrap_line_length, 10); - this._options.e4x = (options.e4x === undefined) ? false : options.e4x; - this._options.end_with_newline = (options.end_with_newline === undefined) ? false : options.end_with_newline; - this._options.comma_first = (options.comma_first === undefined) ? false : options.comma_first; - this._options.operator_position = sanitizeOperatorPosition(options.operator_position); - - // Support passing the source text back with no change - this._options.disabled = (options.disabled === undefined) ? false : options.disabled; - - // For testing of beautify preserve:start directive - this._options.test_output_raw = (options.test_output_raw === undefined) ? false : options.test_output_raw; - - // force this._options.space_after_anon_function to true if this._options.jslint_happy - if (this._options.jslint_happy) { - this._options.space_after_anon_function = true; - } - - if (options.indent_with_tabs) { - this._options.indent_char = '\t'; - this._options.indent_size = 1; - } - - this._options.eol = this._options.eol.replace(/\\r/, '\r').replace(/\\n/, '\n'); + this._options = new Options(options); } Beautifier.prototype.create_flags = function(flags_base, mode) { @@ -274,8 +194,6 @@ Beautifier.prototype.create_flags = function(flags_base, mode) { Beautifier.prototype._reset = function(source_text) { var baseIndentString = ''; - var indent_string = new Array(this._options.indent_size + 1).join(this._options.indent_char); - var preindent_index = 0; if (source_text && source_text.length) { while ((source_text.charAt(preindent_index) === ' ' || @@ -288,7 +206,7 @@ Beautifier.prototype._reset = function(source_text) { this._last_type = TOKEN.START_BLOCK; // last token type this._last_last_text = ''; // pre-last token text - this._output = new Output(indent_string, baseIndentString); + this._output = new Output(this._options.indent_string, baseIndentString); // If testing the ignore directive, start with output disable set to true this._output.raw = this._options.test_output_raw; @@ -306,7 +224,7 @@ Beautifier.prototype._reset = function(source_text) { // MODE.BlockStatement and continues on. this._flag_store = []; this.set_mode(MODE.BlockStatement); - var tokenizer = new Tokenizer(source_text, this._options, indent_string); + var tokenizer = new Tokenizer(source_text, this._options); this._tokens = tokenizer.tokenize(); return source_text; }; diff --git a/js/src/javascript/options.js b/js/src/javascript/options.js new file mode 100644 index 00000000..4081a0fd --- /dev/null +++ b/js/src/javascript/options.js @@ -0,0 +1,91 @@ +/*jshint node:true */ +/* + + The MIT License (MIT) + + Copyright (c) 2007-2018 Einar Lielmanis, Liam Newman, and contributors. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +'use strict'; + +var BaseOptions = require('../core/options').Options; + +var validPositionValues = ['before-newline', 'after-newline', 'preserve-newline']; + +function Options(options) { + BaseOptions.call(this, options, 'js'); + + // compatibility, re + var raw_brace_style = this.raw_options.brace_style || null; + if (raw_brace_style === "expand-strict") { //graceful handling of deprecated option + this.raw_options.brace_style = "expand"; + } else if (raw_brace_style === "collapse-preserve-inline") { //graceful handling of deprecated option + this.raw_options.brace_style = "collapse,preserve-inline"; + } else if (this.raw_options.braces_on_own_line !== undefined) { //graceful handling of deprecated option + this.raw_options.brace_style = this.raw_options.braces_on_own_line ? "expand" : "collapse"; + // } else if (!raw_brace_style) { //Nothing exists to set it + // raw_brace_style = "collapse"; + } + + //preserve-inline in delimited string will trigger brace_preserve_inline, everything + //else is considered a brace_style and the last one only will have an effect + + var brace_style_split = this._get_selection('brace_style', ['collapse', 'expand', 'end-expand', 'none', 'preserve-inline']); + + this.brace_preserve_inline = false; //Defaults in case one or other was not specified in meta-option + this.brace_style = "collapse"; + + for (var bs = 0; bs < brace_style_split.length; bs++) { + if (brace_style_split[bs] === "preserve-inline") { + this.brace_preserve_inline = true; + } else { + this.brace_style = brace_style_split[bs]; + } + } + + this.unindent_chained_methods = this._get_boolean('unindent_chained_methods'); + this.break_chained_methods = this._get_boolean('break_chained_methods'); + this.space_in_paren = this._get_boolean('space_in_paren'); + this.space_in_empty_paren = this._get_boolean('space_in_empty_paren'); + this.jslint_happy = this._get_boolean('jslint_happy'); + this.space_after_anon_function = this._get_boolean('space_after_anon_function'); + this.keep_array_indentation = this._get_boolean('keep_array_indentation'); + this.space_before_conditional = this._get_boolean('space_before_conditional', true); + this.unescape_strings = this._get_boolean('unescape_strings'); + this.e4x = this._get_boolean('e4x'); + this.comma_first = this._get_boolean('comma_first'); + this.operator_position = this._get_selection('operator_position', validPositionValues)[0]; + + // For testing of beautify preserve:start directive + this.test_output_raw = this._get_boolean('test_output_raw'); + + // force this._options.space_after_anon_function to true if this._options.jslint_happy + if (this.jslint_happy) { + this.space_after_anon_function = true; + } +} +Options.prototype = new BaseOptions(); + + + +module.exports.Options = Options; \ No newline at end of file diff --git a/js/test/generated/beautify-javascript-tests.js b/js/test/generated/beautify-javascript-tests.js index 67dcc668..41821e71 100644 --- a/js/test/generated/beautify-javascript-tests.js +++ b/js/test/generated/beautify-javascript-tests.js @@ -39,8 +39,6 @@ function run_javascript_tests(test_obj, Urlencoded, js_beautify, html_beautify, indent_char: ' ', preserve_newlines: true, jslint_happy: false, - keep_array_indentation: false, - brace_style: 'collapse', space_before_conditional: true, break_chained_methods: false, selector_separator: '\n', @@ -52,9 +50,6 @@ function run_javascript_tests(test_obj, Urlencoded, js_beautify, html_beautify, default_opts.indent_char = ' '; default_opts.preserve_newlines = true; default_opts.jslint_happy = false; - default_opts.keep_array_indentation = false; - default_opts.brace_style = 'collapse'; - default_opts.operator_position = 'before-newline'; function reset_options() { @@ -620,6 +615,48 @@ function run_javascript_tests(test_obj, Urlencoded, js_beautify, html_beautify, ' e();\n' + '}'); + // Brace style permutations - () + reset_options(); + set_name('Brace style permutations - ()'); + bt( + 'var a ={a: 2};\n' + + 'var a ={a: 2};', + // -- output -- + 'var a = {\n' + + ' a: 2\n' + + '};\n' + + 'var a = {\n' + + ' a: 2\n' + + '};'); + bt( + '//case 1\n' + + 'if (a == 1){}\n' + + '//case 2\n' + + 'else if (a == 2){}', + // -- output -- + '//case 1\n' + + 'if (a == 1) {}\n' + + '//case 2\n' + + 'else if (a == 2) {}'); + bt( + 'if(1){2}else{3}', + // -- output -- + 'if (1) {\n' + + ' 2\n' + + '} else {\n' + + ' 3\n' + + '}'); + bt( + 'try{a();}catch(b){c();}catch(d){}finally{e();}', + // -- output -- + 'try {\n' + + ' a();\n' + + '} catch (b) {\n' + + ' c();\n' + + '} catch (d) {} finally {\n' + + ' e();\n' + + '}'); + // Brace style permutations - (brace_style = ""collapse"") reset_options(); set_name('Brace style permutations - (brace_style = ""collapse"")'); @@ -1272,6 +1309,55 @@ function run_javascript_tests(test_obj, Urlencoded, js_beautify, html_beautify, //============================================================ + // operator_position option - ensure no neswlines if preserve_newlines is false - (preserve_newlines = "false") + reset_options(); + set_name('operator_position option - ensure no neswlines if preserve_newlines is false - (preserve_newlines = "false")'); + opts.preserve_newlines = false; + bt( + 'var res = a + b - c / d * e % f;\n' + + 'var res = g & h | i ^ j;\n' + + 'var res = (k && l || m) ? n : o;\n' + + 'var res = p >> q << r >>> s;\n' + + 'var res = t === u !== v != w == x >= y <= z > aa < ab;\n' + + 'ac + -ad'); + bt( + 'var res = a + b\n' + + '- c /\n' + + 'd * e\n' + + '%\n' + + 'f;\n' + + ' var res = g & h\n' + + '| i ^\n' + + 'j;\n' + + 'var res = (k &&\n' + + 'l\n' + + '|| m) ?\n' + + 'n\n' + + ': o\n' + + ';\n' + + 'var res = p\n' + + '>> q <<\n' + + 'r\n' + + '>>> s;\n' + + 'var res\n' + + ' = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + 'w\n' + + '== x >=\n' + + 'y <= z > aa <\n' + + 'ab;\n' + + 'ac +\n' + + '-ad', + // -- output -- + 'var res = a + b - c / d * e % f;\n' + + 'var res = g & h | i ^ j;\n' + + 'var res = (k && l || m) ? n : o;\n' + + 'var res = p >> q << r >>> s;\n' + + 'var res = t === u !== v != w == x >= y <= z > aa < ab;\n' + + 'ac + -ad'); + // operator_position option - ensure no neswlines if preserve_newlines is false - (operator_position = ""before-newline"", preserve_newlines = "false") reset_options(); set_name('operator_position option - ensure no neswlines if preserve_newlines is false - (operator_position = ""before-newline"", preserve_newlines = "false")'); @@ -1424,9 +1510,131 @@ function run_javascript_tests(test_obj, Urlencoded, js_beautify, html_beautify, //============================================================ - // operator_position option - set to "before-newline" (default value) + // operator_position option - set to "before-newline" (default value) - () reset_options(); - set_name('operator_position option - set to "before-newline" (default value)'); + set_name('operator_position option - set to "before-newline" (default value) - ()'); + + // comprehensive, various newlines + bt( + 'var res = a + b\n' + + '- c /\n' + + 'd * e\n' + + '%\n' + + 'f;\n' + + ' var res = g & h\n' + + '| i ^\n' + + 'j;\n' + + 'var res = (k &&\n' + + 'l\n' + + '|| m) ?\n' + + 'n\n' + + ': o\n' + + ';\n' + + 'var res = p\n' + + '>> q <<\n' + + 'r\n' + + '>>> s;\n' + + 'var res\n' + + ' = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + 'w\n' + + '== x >=\n' + + 'y <= z > aa <\n' + + 'ab;\n' + + 'ac +\n' + + '-ad', + // -- output -- + 'var res = a + b -\n' + + ' c /\n' + + ' d * e %\n' + + ' f;\n' + + 'var res = g & h |\n' + + ' i ^\n' + + ' j;\n' + + 'var res = (k &&\n' + + ' l ||\n' + + ' m) ?\n' + + ' n :\n' + + ' o;\n' + + 'var res = p >>\n' + + ' q <<\n' + + ' r >>>\n' + + ' s;\n' + + 'var res = t\n' + + '\n' + + ' ===\n' + + ' u !== v !=\n' + + ' w ==\n' + + ' x >=\n' + + ' y <= z > aa <\n' + + ' ab;\n' + + 'ac +\n' + + ' -ad'); + + // colon special case + bt( + 'var a = {\n' + + ' b\n' + + ': bval,\n' + + ' c:\n' + + 'cval\n' + + ' ,d: dval\n' + + '};\n' + + 'var e = f ? g\n' + + ': h;\n' + + 'var i = j ? k :\n' + + 'l;', + // -- output -- + 'var a = {\n' + + ' b: bval,\n' + + ' c: cval,\n' + + ' d: dval\n' + + '};\n' + + 'var e = f ? g :\n' + + ' h;\n' + + 'var i = j ? k :\n' + + ' l;'); + + // catch-all, includes brackets and other various code + bt( + 'var d = 1;\n' + + 'if (a === b\n' + + ' && c) {\n' + + ' d = (c * everything\n' + + ' / something_else) %\n' + + ' b;\n' + + ' e\n' + + ' += d;\n' + + '\n' + + '} else if (!(complex && simple) ||\n' + + ' (emotion && emotion.name === "happy")) {\n' + + ' cryTearsOfJoy(many ||\n' + + ' anOcean\n' + + ' || aRiver);\n' + + '}', + // -- output -- + 'var d = 1;\n' + + 'if (a === b &&\n' + + ' c) {\n' + + ' d = (c * everything /\n' + + ' something_else) %\n' + + ' b;\n' + + ' e\n' + + ' += d;\n' + + '\n' + + '} else if (!(complex && simple) ||\n' + + ' (emotion && emotion.name === "happy")) {\n' + + ' cryTearsOfJoy(many ||\n' + + ' anOcean ||\n' + + ' aRiver);\n' + + '}'); + + // operator_position option - set to "before-newline" (default value) - (operator_position = ""before-newline"") + reset_options(); + set_name('operator_position option - set to "before-newline" (default value) - (operator_position = ""before-newline"")'); + opts.operator_position = 'before-newline'; // comprehensive, various newlines bt( diff --git a/python/cssbeautifier/__init__.py b/python/cssbeautifier/__init__.py index 6d3d2143..f636e1b4 100644 --- a/python/cssbeautifier/__init__.py +++ b/python/cssbeautifier/__init__.py @@ -52,7 +52,7 @@ def beautify_file(file_name, opts=default_options()): raise Exception() stream = sys.stdin - except Exception as ex: + except Exception: print("Must pipe input or define input file.\n", file=sys.stderr) usage(sys.stderr) raise Exception() diff --git a/python/cssbeautifier/css/beautifier.py b/python/cssbeautifier/css/beautifier.py index 812c494d..5e040553 100644 --- a/python/cssbeautifier/css/beautifier.py +++ b/python/cssbeautifier/css/beautifier.py @@ -3,8 +3,6 @@ import sys import re import copy from .options import BeautifierOptions -from jsbeautifier.core.options import mergeOpts -from jsbeautifier.core.options import normalizeOpts from jsbeautifier.core.output import Output from jsbeautifier.core.inputscanner import InputScanner from jsbeautifier.__version__ import __version__ @@ -117,12 +115,7 @@ class Beautifier: self.__source_text = source_text - opts = mergeOpts(opts, 'css') - opts = normalizeOpts(opts) - - # Continue to accept deprecated option - opts.space_around_combinator = opts.space_around_combinator or \ - opts.space_around_selector_separator + opts = BeautifierOptions(opts) self.opts = opts self.indentSize = opts.indent_size @@ -130,12 +123,6 @@ class Beautifier: self.input = None self.ch = None - if self.opts.indent_with_tabs: - self.indentChar = "\t" - self.indentSize = 1 - - self.opts.eol = self.opts.eol.replace('\\r', '\r').replace('\\n', '\n') - # https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule # also in CONDITIONAL_GROUP_RULE below diff --git a/python/cssbeautifier/css/options.py b/python/cssbeautifier/css/options.py index 3d235893..2cdadfef 100644 --- a/python/cssbeautifier/css/options.py +++ b/python/cssbeautifier/css/options.py @@ -23,41 +23,18 @@ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from jsbeautifier.core.options import Options as BaseOptions -class BeautifierOptions: - def __init__(self): - self.indent_size = 4 - self.indent_char = ' ' - self.indent_with_tabs = False - self.preserve_newlines = False - self.selector_separator_newline = True - self.end_with_newline = False - self.newline_between_rules = True - self.space_around_combinator = False - self.eol = 'auto' - self.disabled = False +class BeautifierOptions(BaseOptions): + def __init__(self, options=None): + super(BeautifierOptions, self).__init__(options, 'css') - self.css = None - self.js = None - self.html = None + self.selector_separator_newline = self._get_boolean('selector_separator_newline', True) + self.newline_between_rules = self._get_boolean('newline_between_rules', True) # deprecated - self.space_around_selector_separator = False + space_around_selector_separator = self._get_boolean('space_around_selector_separator') - def __repr__(self): - return """indent_size = %d -indent_char = [%s] -indent_with_tabs = [%s] -preserve_newlines = [%s] -separate_selectors_newline = [%s] -end_with_newline = [%s] -newline_between_rules = [%s] -space_around_combinator = [%s] -""" % (self.indent_size, - self.indent_char, - self.indent_with_tabs, - self.preserve_newlines, - self.selector_separator_newline, - self.end_with_newline, - self.newline_between_rules, - self.space_around_combinator) + # Continue to accept deprecated option + self.space_around_combinator = self._get_boolean('space_around_combinator') or \ + space_around_selector_separator diff --git a/python/jsbeautifier/__init__.py b/python/jsbeautifier/__init__.py index c7b3e54e..690952ea 100644 --- a/python/jsbeautifier/__init__.py +++ b/python/jsbeautifier/__init__.py @@ -9,7 +9,7 @@ import errno import copy from jsbeautifier.__version__ import __version__ from jsbeautifier.javascript.options import BeautifierOptions -from jsbeautifier.javascript.beautifier import Beautifier, sanitizeOperatorPosition +from jsbeautifier.javascript.beautifier import Beautifier # # The MIT License (MIT) @@ -275,7 +275,7 @@ def main(): elif opt in ('--comma-first', '-C'): js_options.comma_first = True elif opt in ('--operator-position', '-O'): - js_options.operator_position = sanitizeOperatorPosition(arg) + js_options.operator_position = arg elif opt in ('--wrap-line-length ', '-w'): js_options.wrap_line_length = int(arg) elif opt in ('--stdin', '-i'): diff --git a/python/jsbeautifier/core/options.py b/python/jsbeautifier/core/options.py index d87b57f3..4ccb20fd 100644 --- a/python/jsbeautifier/core/options.py +++ b/python/jsbeautifier/core/options.py @@ -23,6 +23,106 @@ # SOFTWARE. import copy +import re + +class Options: + def __init__(self, options=None, merge_child_field=None): + self.css = None + self.js = None + self.html = None + + options = _mergeOpts(options, merge_child_field) + self.raw_options = _normalizeOpts(options) + + # Support passing the source text back with no change + self.disabled = self._get_boolean('disabled') + + self.eol = self._get_characters('eol', 'auto') + self.end_with_newline = self._get_boolean('end_with_newline') + self.indent_size = self._get_number('indent_size', 4) + self.indent_char = self._get_characters('indent_char', ' ') + + self.preserve_newlines = self._get_boolean('preserve_newlines', True) + # TODO: fix difference in js and python + self.max_preserve_newlines = self.max_preserve_newlines = self._get_number('max_preserve_newlines', 10) + if not self.preserve_newlines: + self.max_preserve_newlines = 0 + + self.indent_with_tabs = self._get_boolean('indent_with_tabs') + if self.indent_with_tabs: + self.indent_char = '\t' + self.indent_size = 1 + + self.indent_string = self.indent_char * self.indent_size + + # Backwards compat with 1.3.x + self.wrap_line_length = self._get_number('wrap_line_length', self._get_number('max_char')) + + def _get_array(self, name, default_value=[]): + option_value = getattr(self.raw_options, name, default_value) + result = [] + if isinstance(option_value, list): + result = copy.copy(option_value) + elif isinstance(option_value, str): + result = re.compile(r"[^a-zA-Z0-9_/\-]+").split(option_value) + + return result + + def _get_boolean(self, name, default_value=False): + option_value = getattr(self.raw_options, name, default_value) + result = False + try: + result = bool(option_value) + except ValueError: + pass + + return result + + def _get_characters(self, name, default_value=''): + option_value = getattr(self.raw_options, name, default_value) + result = '' + if isinstance(option_value, str): + result = option_value.replace('\\r', '\r').replace('\\n', '\n').replace('\\t', '\t') + + return result + + def _get_number(self, name, default_value=0): + option_value = getattr(self.raw_options, name, default_value) + result = 0 + try: + result = int(option_value) + except ValueError: + pass + + return result + + def _get_selection(self, name, selection_list, default_value=None): + default_value = default_value or [selection_list[0]] + if not self._is_valid_selection(default_value, selection_list): + raise ValueError("Invalid Default Value!") + + result = self._get_array(name, default_value) + self._is_valid_selection(result, selection_list) + if not self._is_valid_selection(result, selection_list): + raise ValueError( + "Invalid Option Value: The option 'operator_position' must be one of the following values\n" + + str(selection_list) + + "\nYou passed in: '" + + str(getattr(self.raw_options, name, None)) + + "'") + + return result + + def _is_valid_selection(self, result, selection_list): + if len(result) == 0 or len(selection_list) == 0: + return False + + for item in result: + if item not in selection_list: + return False + + return True + # merges child options up with the parent options object # Example: obj = {a: 1, b: {a: 2}} @@ -31,8 +131,8 @@ import copy # Returns: {a: 2, b: {a: 2}} -def mergeOpts(options, childFieldName): - finalOpts = copy.copy(options) +def _mergeOpts(options, childFieldName): + finalOpts = copy.copy(options) or object() local = getattr(finalOpts, childFieldName, None) if local: @@ -42,10 +142,10 @@ def mergeOpts(options, childFieldName): return finalOpts -def normalizeOpts(options): - convertedOpts = copy.copy(options) - - for key in options.__dict__: +def _normalizeOpts(options): + convertedOpts = copy.copy(options) or object() + option_keys = copy.copy(getattr(convertedOpts, '__dict__', {})) + for key in option_keys: if '-' in key: delattr(convertedOpts, key) setattr(convertedOpts, key.replace('-', '_'), getattr(options, key, None)) diff --git a/python/jsbeautifier/javascript/beautifier.py b/python/jsbeautifier/javascript/beautifier.py index 7c80e7bb..7cfa07aa 100644 --- a/python/jsbeautifier/javascript/beautifier.py +++ b/python/jsbeautifier/javascript/beautifier.py @@ -28,8 +28,6 @@ import copy from .tokenizer import Tokenizer from .tokenizer import TOKEN from .options import BeautifierOptions -from ..core.options import mergeOpts -from ..core.options import normalizeOpts from ..core.output import Output @@ -82,21 +80,6 @@ OPERATOR_POSITION_BEFORE_OR_PRESERVE = [ OPERATOR_POSITION['preserve_newline']] -def sanitizeOperatorPosition(opPosition): - if not opPosition: - return OPERATOR_POSITION['before_newline'] - elif opPosition not in OPERATOR_POSITION.values(): - raise ValueError( - "Invalid Option Value: The option 'operator_position' must be one of the following values\n" + - str( - OPERATOR_POSITION.values()) + - "\nYou passed in: '" + - opPosition + - "'") - - return opPosition - - class MODE: BlockStatement, Statement, ObjectLiteral, ArrayLiteral, \ ForInitializer, Conditional, Expression = range(7) @@ -119,10 +102,10 @@ def remove_redundant_indentation(output, frame): class Beautifier: - def __init__(self, opts=default_options()): + def __init__(self, opts=None): import jsbeautifier.core.acorn as acorn self.acorn = acorn - self._options = copy.copy(opts) + self._options = BeautifierOptions(opts) self._blank_state() @@ -136,23 +119,12 @@ class Beautifier: self._flag_store = [] self._tokens = None - # force opts.space_after_anon_function to true if opts.jslint_happy - if self._options.jslint_happy: - self._options.space_after_anon_function = True - - if self._options.indent_with_tabs: - self._options.indent_char = "\t" - self._options.indent_size = 1 - if self._options.eol == 'auto': self._options.eol = '\n' if self.acorn.lineBreak.search(js_source_text or ''): self._options.eol = self.acorn.lineBreak.search( js_source_text).group() - self._options.eol = self._options.eol.replace('\\r', '\r').replace('\\n', '\n') - - indent_string = self._options.indent_char * self._options.indent_size baseIndentString = '' self._last_type = TOKEN.START_BLOCK # last token type @@ -166,7 +138,7 @@ class Beautifier: preindent_index += 1 js_source_text = js_source_text[preindent_index:] - self._output = Output(indent_string, baseIndentString) + self._output = Output(self._options.indent_string, baseIndentString) # If testing the ignore directive, start with output disable set to # true self._output.raw = self._options.test_output_raw @@ -176,32 +148,8 @@ class Beautifier: def beautify(self, source_text='', opts=None): if opts is not None: - opts = mergeOpts(opts, 'js') - opts = normalizeOpts(opts) - self._options = copy.copy(opts) + self._options = BeautifierOptions(opts) - # Compat with old form - if self._options.brace_style == 'collapse-preserve-inline': - self._options.brace_style = 'collapse,preserve-inline' - - # split always returns at least one value - split = re.compile(r"[^a-zA-Z0-9_\-]+").split(self._options.brace_style) - # preserve-inline in delimited string will trigger brace_preserve_inline - # Everything else is considered a brace_style and the last one only will - # have an effect - # specify defaults in case one half of meta-option is missing - self._options.brace_style = "collapse" - self._options.brace_preserve_inline = False - for bs in split: - if bs == "preserve-inline": - self._options.brace_preserve_inline = True - else: - # validate each brace_style that's not a preserve-inline - # (results in very similar validation as js version) - if bs not in ['expand', 'collapse', 'end-expand', 'none']: - raise(Exception( - 'opts.brace_style must be "expand", "collapse", "end-expand", or "none".')) - self._options.brace_style = bs source_text = source_text or '' if self._options.disabled: diff --git a/python/jsbeautifier/javascript/options.py b/python/jsbeautifier/javascript/options.py index 725b2c3a..e4c7f320 100644 --- a/python/jsbeautifier/javascript/options.py +++ b/python/jsbeautifier/javascript/options.py @@ -23,67 +23,71 @@ # SOFTWARE. -class BeautifierOptions: - def __init__(self): - self.indent_size = 4 - self.indent_char = ' ' - self.indent_with_tabs = False - self.eol = 'auto' - self.preserve_newlines = True - self.max_preserve_newlines = 10 - self.space_in_paren = False - self.space_in_empty_paren = False - self.e4x = False - self.jslint_happy = False - self.space_after_anon_function = False - self.brace_style = 'collapse' - self.keep_array_indentation = False - self.space_before_conditional = True - self.keep_function_indentation = False - self.eval_code = False - self.unescape_strings = False - self.wrap_line_length = 0 - self.unindent_chained_methods = False - self.break_chained_methods = False - self.end_with_newline = False - self.comma_first = False - self.operator_position = 'before-newline' - self.disabled = False +from ..core.options import Options as BaseOptions + +OPERATOR_POSITION = [ + 'before-newline', + 'after-newline', + 'preserve-newline' +] + +class BeautifierOptions(BaseOptions): + def __init__(self, options=None): + super(BeautifierOptions, self).__init__(options, 'js') self.css = None self.js = None self.html = None + # compatibility, re + + raw_brace_style = getattr(self.raw_options, 'brace_style', None) + if raw_brace_style == "expand-strict": # graceful handling of deprecated option + setattr(self.raw_options, 'brace_style', "expand") + elif raw_brace_style == "collapse-preserve-inline": # graceful handling of deprecated option + setattr(self.raw_options, 'brace_style', "collapse,preserve-inline") + # elif bool(self.raw_options.braces_on_own_line): # graceful handling of deprecated option + # raw_brace_style = "expand": "collapse" + # elif raw_brace_style is None: # Nothing exists to set it + # setattr(self.raw_options, 'brace_style', "collapse") + + # preserve-inline in delimited string will trigger brace_preserve_inline, everything + # else is considered a brace_style and the last one only will have an effect + + brace_style_split = self._get_selection('brace_style', ['collapse', 'expand', 'end-expand', 'none', 'preserve-inline']) + + # preserve-inline in delimited string will trigger brace_preserve_inline + # Everything else is considered a brace_style and the last one only will + # have an effect + # specify defaults in case one half of meta-option is missing + self.brace_preserve_inline = False + self.brace_style = "collapse" + + for bs in brace_style_split: + if bs == "preserve-inline": + self.brace_preserve_inline = True + else: + self.brace_style = bs + + self.unindent_chained_methods = self._get_boolean('unindent_chained_methods') + self.break_chained_methods = self._get_boolean('break_chained_methods') + self.space_in_paren = self._get_boolean('space_in_paren') + self.space_in_empty_paren = self._get_boolean('space_in_empty_paren') + self.jslint_happy = self._get_boolean('jslint_happy') + self.space_after_anon_function = self._get_boolean('space_after_anon_function') + self.keep_array_indentation = self._get_boolean('keep_array_indentation') + self.space_before_conditional = self._get_boolean('space_before_conditional', True) + self.unescape_strings = self._get_boolean('unescape_strings') + self.e4x = self._get_boolean('e4x') + self.comma_first = self._get_boolean('comma_first') + self.operator_position = self._get_selection('operator_position', OPERATOR_POSITION)[0] + # For testing of beautify preserve:start directive self.test_output_raw = False self.editorconfig = False - def __repr__(self): - return \ - """indent_size = %d -indent_char = [%s] -preserve_newlines = %s -max_preserve_newlines = %d -space_in_paren = %s -jslint_happy = %s -space_after_anon_function = %s -indent_with_tabs = %s -brace_style = %s -keep_array_indentation = %s -eval_code = %s -wrap_line_length = %s -unescape_strings = %s -""" % (self.indent_size, - self.indent_char, - self.preserve_newlines, - self.max_preserve_newlines, - self.space_in_paren, - self.jslint_happy, - self.space_after_anon_function, - self.indent_with_tabs, - self.brace_style, - self.keep_array_indentation, - self.eval_code, - self.wrap_line_length, - self.unescape_strings, - ) + # force opts.space_after_anon_function to true if opts.jslint_happy + if self.jslint_happy: + self.space_after_anon_function = True + + self.eval_code = False \ No newline at end of file diff --git a/python/jsbeautifier/javascript/tokenizer.py b/python/jsbeautifier/javascript/tokenizer.py index 37c4338b..64bf35ba 100644 --- a/python/jsbeautifier/javascript/tokenizer.py +++ b/python/jsbeautifier/javascript/tokenizer.py @@ -115,7 +115,7 @@ class Tokenizer(BaseTokenizer): line_starters = line_starters def __init__(self, input_string, opts): - BaseTokenizer.__init__(self, input_string, opts) + super(Tokenizer, self).__init__(input_string, opts) self.in_html_comment = False self.has_char_escapes = False diff --git a/python/jsbeautifier/tests/generated/tests.py b/python/jsbeautifier/tests/generated/tests.py index c0590ccf..45f6f8a6 100644 --- a/python/jsbeautifier/tests/generated/tests.py +++ b/python/jsbeautifier/tests/generated/tests.py @@ -55,8 +55,6 @@ class TestJSBeautifier(unittest.TestCase): default_options.indent_char = ' ' default_options.preserve_newlines = true default_options.jslint_happy = false - default_options.keep_array_indentation = true - default_options.brace_style = 'collapse' default_options.indent_level = 0 default_options.break_chained_methods = false default_options.eol = '\n' @@ -65,9 +63,6 @@ class TestJSBeautifier(unittest.TestCase): default_options.indent_char = ' ' default_options.preserve_newlines = true default_options.jslint_happy = false - default_options.keep_array_indentation = false - default_options.brace_style = 'collapse' - default_options.operator_position = 'before-newline' self.options = copy.copy(default_options) @@ -416,6 +411,47 @@ class TestJSBeautifier(unittest.TestCase): ' e();\n' + '}') + # Brace style permutations - () + self.reset_options() + bt( + 'var a ={a: 2};\n' + + 'var a ={a: 2};', + # -- output -- + 'var a = {\n' + + ' a: 2\n' + + '};\n' + + 'var a = {\n' + + ' a: 2\n' + + '};') + bt( + '//case 1\n' + + 'if (a == 1){}\n' + + '//case 2\n' + + 'else if (a == 2){}', + # -- output -- + '//case 1\n' + + 'if (a == 1) {}\n' + + '//case 2\n' + + 'else if (a == 2) {}') + bt( + 'if(1){2}else{3}', + # -- output -- + 'if (1) {\n' + + ' 2\n' + + '} else {\n' + + ' 3\n' + + '}') + bt( + 'try{a();}catch(b){c();}catch(d){}finally{e();}', + # -- output -- + 'try {\n' + + ' a();\n' + + '} catch (b) {\n' + + ' c();\n' + + '} catch (d) {} finally {\n' + + ' e();\n' + + '}') + # Brace style permutations - (brace_style = ""collapse"") self.reset_options() self.options.brace_style = 'collapse' @@ -1059,6 +1095,54 @@ class TestJSBeautifier(unittest.TestCase): #============================================================ + # operator_position option - ensure no neswlines if preserve_newlines is false - (preserve_newlines = "false") + self.reset_options() + self.options.preserve_newlines = false + bt( + 'var res = a + b - c / d * e % f;\n' + + 'var res = g & h | i ^ j;\n' + + 'var res = (k && l || m) ? n : o;\n' + + 'var res = p >> q << r >>> s;\n' + + 'var res = t === u !== v != w == x >= y <= z > aa < ab;\n' + + 'ac + -ad') + bt( + 'var res = a + b\n' + + '- c /\n' + + 'd * e\n' + + '%\n' + + 'f;\n' + + ' var res = g & h\n' + + '| i ^\n' + + 'j;\n' + + 'var res = (k &&\n' + + 'l\n' + + '|| m) ?\n' + + 'n\n' + + ': o\n' + + ';\n' + + 'var res = p\n' + + '>> q <<\n' + + 'r\n' + + '>>> s;\n' + + 'var res\n' + + ' = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + 'w\n' + + '== x >=\n' + + 'y <= z > aa <\n' + + 'ab;\n' + + 'ac +\n' + + '-ad', + # -- output -- + 'var res = a + b - c / d * e % f;\n' + + 'var res = g & h | i ^ j;\n' + + 'var res = (k && l || m) ? n : o;\n' + + 'var res = p >> q << r >>> s;\n' + + 'var res = t === u !== v != w == x >= y <= z > aa < ab;\n' + + 'ac + -ad') + # operator_position option - ensure no neswlines if preserve_newlines is false - (operator_position = ""before-newline"", preserve_newlines = "false") self.reset_options() self.options.operator_position = 'before-newline' @@ -1208,7 +1292,7 @@ class TestJSBeautifier(unittest.TestCase): #============================================================ - # operator_position option - set to "before-newline" (default value) + # operator_position option - set to "before-newline" (default value) - () self.reset_options() # comprehensive, various newlines @@ -1328,6 +1412,127 @@ class TestJSBeautifier(unittest.TestCase): ' aRiver);\n' + '}') + # operator_position option - set to "before-newline" (default value) - (operator_position = ""before-newline"") + self.reset_options() + self.options.operator_position = 'before-newline' + + # comprehensive, various newlines + bt( + 'var res = a + b\n' + + '- c /\n' + + 'd * e\n' + + '%\n' + + 'f;\n' + + ' var res = g & h\n' + + '| i ^\n' + + 'j;\n' + + 'var res = (k &&\n' + + 'l\n' + + '|| m) ?\n' + + 'n\n' + + ': o\n' + + ';\n' + + 'var res = p\n' + + '>> q <<\n' + + 'r\n' + + '>>> s;\n' + + 'var res\n' + + ' = t\n' + + '\n' + + ' === u !== v\n' + + ' !=\n' + + 'w\n' + + '== x >=\n' + + 'y <= z > aa <\n' + + 'ab;\n' + + 'ac +\n' + + '-ad', + # -- output -- + 'var res = a + b -\n' + + ' c /\n' + + ' d * e %\n' + + ' f;\n' + + 'var res = g & h |\n' + + ' i ^\n' + + ' j;\n' + + 'var res = (k &&\n' + + ' l ||\n' + + ' m) ?\n' + + ' n :\n' + + ' o;\n' + + 'var res = p >>\n' + + ' q <<\n' + + ' r >>>\n' + + ' s;\n' + + 'var res = t\n' + + '\n' + + ' ===\n' + + ' u !== v !=\n' + + ' w ==\n' + + ' x >=\n' + + ' y <= z > aa <\n' + + ' ab;\n' + + 'ac +\n' + + ' -ad') + + # colon special case + bt( + 'var a = {\n' + + ' b\n' + + ': bval,\n' + + ' c:\n' + + 'cval\n' + + ' ,d: dval\n' + + '};\n' + + 'var e = f ? g\n' + + ': h;\n' + + 'var i = j ? k :\n' + + 'l;', + # -- output -- + 'var a = {\n' + + ' b: bval,\n' + + ' c: cval,\n' + + ' d: dval\n' + + '};\n' + + 'var e = f ? g :\n' + + ' h;\n' + + 'var i = j ? k :\n' + + ' l;') + + # catch-all, includes brackets and other various code + bt( + 'var d = 1;\n' + + 'if (a === b\n' + + ' && c) {\n' + + ' d = (c * everything\n' + + ' / something_else) %\n' + + ' b;\n' + + ' e\n' + + ' += d;\n' + + '\n' + + '} else if (!(complex && simple) ||\n' + + ' (emotion && emotion.name === "happy")) {\n' + + ' cryTearsOfJoy(many ||\n' + + ' anOcean\n' + + ' || aRiver);\n' + + '}', + # -- output -- + 'var d = 1;\n' + + 'if (a === b &&\n' + + ' c) {\n' + + ' d = (c * everything /\n' + + ' something_else) %\n' + + ' b;\n' + + ' e\n' + + ' += d;\n' + + '\n' + + '} else if (!(complex && simple) ||\n' + + ' (emotion && emotion.name === "happy")) {\n' + + ' cryTearsOfJoy(many ||\n' + + ' anOcean ||\n' + + ' aRiver);\n' + + '}') + #============================================================ # operator_position option - set to "after_newline" diff --git a/test/data/html/tests.js b/test/data/html/tests.js index 7d0009fd..13cf7b10 100644 --- a/test/data/html/tests.js +++ b/test/data/html/tests.js @@ -685,26 +685,12 @@ exports.test_data = { ], tests: [{ fragment: true, - input_: '{{#if 0}}\n' + - '