UNPKG

simply-beautiful

Version:

Beautify HTML, JS, CSS, and JSON in the browser or in Node.js!

1,435 lines (1,218 loc) 74.8 kB
(function (root, factory) { // https://github.com/umdjs/umd/blob/master/templates/returnExports.js if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define([], factory); } else if (typeof module === 'object' && module.exports) { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory(); } else { // Browser globals (root is window) root.returnExports = factory(); } }(typeof self !== 'undefined' ? self : this, function () { var environment = (Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]') ? 'node' : 'browser'; /* JS Style HTML --------------- Written by Nochum Sossonko, (nsossonko@hotmail.com) Based on code initially developed by: Einar Lielmanis, <elfz@laacz.lv> http://jsbeautifier.org/ You are free to use this in any way you want, in case you find this useful or working for you. Usage: style_html(html_source); style_html(html_source, options); The options are: indent_size (default 4) — indentation size, indent_char (default space) — character to indent with, max_char (default 70) - maximum amount of characters per line, brace_style (default "collapse") - "collapse" | "expand" | "end-expand" put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line. unformatted (defaults to inline tags) - list of tags, that shouldn't be reformatted indent_scripts (default normal) - "keep"|"separate"|"normal" e.g. style_html(html_source, { 'indent_size': 2, 'indent_char': ' ', 'max_char': 78, 'brace_style': 'expand', 'unformatted': ['a', 'sub', 'sup', 'b', 'i', 'u'] }); */ function style_html(html_source, options) { //Wrapper function to invoke all the necessary constructors and deal with the output. var multi_parser, indent_size, indent_character, max_char, brace_style, unformatted; options = options || {}; indent_size = options.indent_size || 4; indent_character = options.indent_char || ' '; brace_style = options.brace_style || 'collapse'; max_char = options.max_char == 0 ? Infinity : options.max_char || 70; unformatted = options.unformatted || ['a', 'span', 'bdo', 'em', 'strong', 'dfn', 'code', 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'q', 'sub', 'sup', 'tt', 'i', 'b', 'big', 'small', 'u', 's', 'strike', 'font', 'ins', 'del', 'pre', 'address', 'dt', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']; function Parser() { this.pos = 0; //Parser position this.token = ''; this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT this.tags = { //An object to hold tags, their position, and their parent-tags, initiated with default values parent: 'parent1', parentcount: 1, parent1: '' }; this.tag_type = ''; this.token_text = this.last_token = this.last_text = this.token_type = ''; this.Utils = { //Uilities made available to the various functions whitespace: "\n\r\t ".split(''), single_token: 'br,input,link,meta,!doctype,basefont,base,area,hr,wbr,param,img,isindex,?xml,embed,?php,?,?='.split(','), //all the single tags for HTML extra_liners: 'head,body,/html'.split(','), //for tags that need a line of whitespace before them in_array: function (what, arr) { for (var i=0; i<arr.length; i++) { if (what === arr[i]) { return true; } } return false; } } this.get_content = function () { //function to capture regular content between tags var input_char = '', content = [], space = false; //if a space is needed while (this.input.charAt(this.pos) !== '<') { if (this.pos >= this.input.length) { return content.length?content.join(''):['', 'TK_EOF']; } input_char = this.input.charAt(this.pos); this.pos++; this.line_char_count++; if (this.Utils.in_array(input_char, this.Utils.whitespace)) { if (content.length) { space = true; } this.line_char_count--; continue; //don't want to insert unnecessary space } else if (space) { if (this.line_char_count >= this.max_char) { //insert a line when the max_char is reached content.push('\n'); for (var i=0; i<this.indent_level; i++) { content.push(this.indent_string); } this.line_char_count = 0; } else{ content.push(' '); this.line_char_count++; } space = false; } content.push(input_char); //letter at-a-time (or string) inserted to an array } return content.length?content.join(''):''; } this.get_contents_to = function (name) { //get the full content of a script or style to pass to js_beautify if (this.pos == this.input.length) { return ['', 'TK_EOF']; } var input_char = ''; var content = ''; var reg_match = new RegExp('\<\/' + name + '\\s*\>', 'igm'); reg_match.lastIndex = this.pos; var reg_array = reg_match.exec(this.input); var end_script = reg_array?reg_array.index:this.input.length; //absolute end of script if(this.pos < end_script) { //get everything in between the script tags content = this.input.substring(this.pos, end_script); this.pos = end_script; } return content; } this.record_tag = function (tag){ //function to record a tag and its parent in this.tags Object if (this.tags[tag + 'count']) { //check for the existence of this tag type this.tags[tag + 'count']++; this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level } else { //otherwise initialize this tag type this.tags[tag + 'count'] = 1; this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level } this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent) this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1') } this.retrieve_tag = function (tag) { //function to retrieve the opening tag to the corresponding closer if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it var temp_parent = this.tags.parent; //check to see if it's a closable tag. while (temp_parent) { //till we reach '' (the initial value); if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it break; } temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree } if (temp_parent) { //if we caught something this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent } delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference... delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself if (this.tags[tag + 'count'] == 1) { delete this.tags[tag + 'count']; } else { this.tags[tag + 'count']--; } } } this.get_tag = function () { //function to get a full tag and parse its type var input_char = '', content = [], space = false, tag_start, tag_end; do { if (this.pos >= this.input.length) { return content.length?content.join(''):['', 'TK_EOF']; } input_char = this.input.charAt(this.pos); this.pos++; this.line_char_count++; if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space space = true; this.line_char_count--; continue; } if (input_char === "'" || input_char === '"') { if (!content[1] || content[1] !== '!') { //if we're in a comment strings don't get treated specially input_char += this.get_unformatted(input_char); space = true; } } if (input_char === '=') { //no space before = space = false; } if (content.length && content[content.length-1] !== '=' && input_char !== '>' && space) { //no space after = or before > if (this.line_char_count >= this.max_char) { this.print_newline(false, content); this.line_char_count = 0; } else { content.push(' '); this.line_char_count++; } space = false; } if (input_char === '<') { tag_start = this.pos - 1; } content.push(input_char); //inserts character at-a-time (or string) } while (input_char !== '>'); var tag_complete = content.join(''); var tag_index; if (tag_complete.indexOf(' ') != -1) { //if there's whitespace, thats where the tag name ends tag_index = tag_complete.indexOf(' '); } else { //otherwise go with the tag ending tag_index = tag_complete.indexOf('>'); } var tag_check = tag_complete.substring(1, tag_index).toLowerCase(); if (tag_complete.charAt(tag_complete.length-2) === '/' || this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /) this.tag_type = 'SINGLE'; } else if (tag_check === 'script') { //for later script handling this.record_tag(tag_check); this.tag_type = 'SCRIPT'; } else if (tag_check === 'style') { //for future style handling (for now it justs uses get_content) this.record_tag(tag_check); this.tag_type = 'STYLE'; } else if (this.Utils.in_array(tag_check, unformatted)) { // do not reformat the "unformatted" tags var comment = this.get_unformatted('</'+tag_check+'>', tag_complete); //...delegate to get_unformatted function content.push(comment); // Preserve collapsed whitespace either before or after this tag. if (tag_start > 0 && this.Utils.in_array(this.input.charAt(tag_start - 1), this.Utils.whitespace)){ content.splice(0, 0, this.input.charAt(tag_start - 1)); } tag_end = this.pos - 1; if (this.Utils.in_array(this.input.charAt(tag_end + 1), this.Utils.whitespace)){ content.push(this.input.charAt(tag_end + 1)); } this.tag_type = 'SINGLE'; } else if (tag_check.charAt(0) === '!') { //peek for <!-- comment if (tag_check.indexOf('[if') != -1) { //peek for <!--[if conditional comment if (tag_complete.indexOf('!IE') != -1) { //this type needs a closing --> so... var comment = this.get_unformatted('-->', tag_complete); //...delegate to get_unformatted content.push(comment); } this.tag_type = 'START'; } else if (tag_check.indexOf('[endif') != -1) {//peek for <!--[endif end conditional comment this.tag_type = 'END'; this.unindent(); } else if (tag_check.indexOf('[cdata[') != -1) { //if it's a <[cdata[ comment... var comment = this.get_unformatted(']]>', tag_complete); //...delegate to get_unformatted function content.push(comment); this.tag_type = 'SINGLE'; //<![CDATA[ comments are treated like single tags } else { var comment = this.get_unformatted('-->', tag_complete); content.push(comment); this.tag_type = 'SINGLE'; } } else { if (tag_check.charAt(0) === '/') { //this tag is a double tag so check for tag-ending this.retrieve_tag(tag_check.substring(1)); //remove it and all ancestors this.tag_type = 'END'; } else { //otherwise it's a start-tag this.record_tag(tag_check); //push it on the tag stack this.tag_type = 'START'; } if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) { //check if this double needs an extra line this.print_newline(true, this.output); } } return content.join(''); //returns fully formatted tag } this.get_unformatted = function (delimiter, orig_tag) { //function to return unformatted content in its entirety if (orig_tag && orig_tag.indexOf(delimiter) != -1) { return ''; } var input_char = ''; var content = ''; var space = true; do { if (this.pos >= this.input.length) { return content; } input_char = this.input.charAt(this.pos); this.pos++ if (this.Utils.in_array(input_char, this.Utils.whitespace)) { if (!space) { this.line_char_count--; continue; } if (input_char === '\n' || input_char === '\r') { content += '\n'; /* Don't change tab indention for unformatted blocks. If using code for html editing, this will greatly affect <pre> tags if they are specified in the 'unformatted array' for (var i=0; i<this.indent_level; i++) { content += this.indent_string; } space = false; //...and make sure other indentation is erased */ this.line_char_count = 0; continue; } } content += input_char; this.line_char_count++; space = true; } while (content.indexOf(delimiter) == -1); return content; } this.get_token = function () { //initial handler for token-retrieval var token; if (this.last_token === 'TK_TAG_SCRIPT' || this.last_token === 'TK_TAG_STYLE') { //check if we need to format javascript var type = this.last_token.substr(7) token = this.get_contents_to(type); if (typeof token !== 'string') { return token; } return [token, 'TK_' + type]; } if (this.current_mode === 'CONTENT') { token = this.get_content(); if (typeof token !== 'string') { return token; } else { return [token, 'TK_CONTENT']; } } if (this.current_mode === 'TAG') { token = this.get_tag(); if (typeof token !== 'string') { return token; } else { var tag_name_type = 'TK_TAG_' + this.tag_type; return [token, tag_name_type]; } } } this.get_full_indent = function (level) { level = this.indent_level + level || 0; if (level < 1) return ''; return Array(level + 1).join(this.indent_string); } this.printer = function (js_source, indent_character, indent_size, max_char, brace_style) { //handles input/output and some other printing functions this.input = js_source || ''; //gets the input for the Parser this.output = []; this.indent_character = indent_character; this.indent_string = ''; this.indent_size = indent_size; this.brace_style = brace_style; this.indent_level = 0; this.max_char = max_char; this.line_char_count = 0; //count to see if max_char was exceeded for (var i=0; i<this.indent_size; i++) { this.indent_string += this.indent_character; } this.print_newline = function (ignore, arr) { this.line_char_count = 0; if (!arr || !arr.length) { return; } if (!ignore) { //we might want the extra line while (this.Utils.in_array(arr[arr.length-1], this.Utils.whitespace)) { arr.pop(); } } arr.push('\n'); for (var i=0; i<this.indent_level; i++) { arr.push(this.indent_string); } } this.print_token = function (text) { this.output.push(text); } this.indent = function () { this.indent_level++; } this.unindent = function () { if (this.indent_level > 0) { this.indent_level--; } } } return this; } /*_____________________--------------------_____________________*/ multi_parser = new Parser(); //wrapping functions Parser multi_parser.printer(html_source, indent_character, indent_size, max_char, brace_style); //initialize starting values while (true) { var t = multi_parser.get_token(); multi_parser.token_text = t[0]; multi_parser.token_type = t[1]; if (multi_parser.token_type === 'TK_EOF') { break; } switch (multi_parser.token_type) { case 'TK_TAG_START': multi_parser.print_newline(false, multi_parser.output); multi_parser.print_token(multi_parser.token_text); multi_parser.indent(); multi_parser.current_mode = 'CONTENT'; break; case 'TK_TAG_STYLE': case 'TK_TAG_SCRIPT': multi_parser.print_newline(false, multi_parser.output); multi_parser.print_token(multi_parser.token_text); multi_parser.current_mode = 'CONTENT'; break; case 'TK_TAG_END': //Print new line only if the tag has no content and has child if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') { var tag_name = multi_parser.token_text.match(/\w+/)[0]; var tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length -1].match(/<\s*(\w+)/); if (tag_extracted_from_last_output === null || tag_extracted_from_last_output[1] !== tag_name) multi_parser.print_newline(true, multi_parser.output); } multi_parser.print_token(multi_parser.token_text); multi_parser.current_mode = 'CONTENT'; break; case 'TK_TAG_SINGLE': // Don't add a newline before elements that should remain unformatted. var tag_check = multi_parser.token_text.match(/^\s*<([a-z]+)/i); if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)){ multi_parser.print_newline(false, multi_parser.output); } multi_parser.print_token(multi_parser.token_text); multi_parser.current_mode = 'CONTENT'; break; case 'TK_CONTENT': if (multi_parser.token_text !== '') { multi_parser.print_token(multi_parser.token_text); } multi_parser.current_mode = 'TAG'; break; case 'TK_STYLE': case 'TK_SCRIPT': if (multi_parser.token_text !== '') { multi_parser.output.push('\n'); var text = multi_parser.token_text; if (multi_parser.token_type == 'TK_SCRIPT') { var _beautifier = typeof js_beautify == 'function' && js_beautify; } else if (multi_parser.token_type == 'TK_STYLE') { var _beautifier = typeof css_beautify == 'function' && css_beautify; } if (options.indent_scripts == "keep") { var script_indent_level = 0; } else if (options.indent_scripts == "separate") { var script_indent_level = -multi_parser.indent_level; } else { var script_indent_level = 1; } var indentation = multi_parser.get_full_indent(script_indent_level); if (_beautifier) { // call the Beautifier if avaliable text = _beautifier(text.replace(/^\s*/, indentation), options); } else { // simply indent the string otherwise var white = text.match(/^\s*/)[0]; var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1; var reindent = multi_parser.get_full_indent(script_indent_level -_level); text = text.replace(/^\s*/, indentation) .replace(/\r\n|\r|\n/g, '\n' + reindent) .replace(/\s*$/, ''); } if (text) { multi_parser.print_token(text); multi_parser.print_newline(true, multi_parser.output); } } multi_parser.current_mode = 'TAG'; break; } multi_parser.last_token = multi_parser.token_type; multi_parser.last_text = multi_parser.token_text; } return multi_parser.output.join(''); } /* CSS Beautifier --------------- Written by Harutyun Amirjanyan, (amirjanyan@gmail.com) Based on code initially developed by: Einar Lielmanis, <elfz@laacz.lv> http://jsbeautifier.org/ You are free to use this in any way you want, in case you find this useful or working for you. Usage: css_beautify(source_text); css_beautify(source_text, options); The options are: indent_size (default 4) — indentation size, indent_char (default space) — character to indent with, e.g css_beautify(css_source_text, { 'indent_size': 1, 'indent_char': '\t' }); */ // http://www.w3.org/TR/CSS21/syndata.html#tokenization // http://www.w3.org/TR/css3-syntax/ function css_beautify(source_text, options) { options = options || {}; var indentSize = options.indent_size || 4; var indentCharacter = options.indent_char || ' '; // compatibility if (typeof indentSize == "string") indentSize = parseInt(indentSize); // tokenizer var whiteRe = /^\s+$/; var wordRe = /[\w$\-_]/; var pos = -1, ch; function next() { return ch = source_text.charAt(++pos) } function peek() { return source_text.charAt(pos+1) } function eatString(comma) { var start = pos; while(next()){ if (ch=="\\"){ next(); next(); } else if (ch == comma) { break; } else if (ch == "\n") { break; } } return source_text.substring(start, pos + 1); } function eatWhitespace() { var start = pos; while (whiteRe.test(peek())) pos++; return pos != start; } function skipWhitespace() { var start = pos; do{ }while (whiteRe.test(next())) return pos != start + 1; } function eatComment() { var start = pos; next(); while (next()) { if (ch == "*" && peek() == "/") { pos ++; break; } } return source_text.substring(start, pos + 1); } function lookBack(str, index) { return output.slice(-str.length + (index||0), index).join("").toLowerCase() == str; } // printer var indentString = source_text.match(/^[\r\n]*[\t ]*/)[0]; var singleIndent = Array(indentSize + 1).join(indentCharacter); var indentLevel = 0; function indent() { indentLevel++; indentString += singleIndent; } function outdent() { indentLevel--; indentString = indentString.slice(0, -indentSize); } var print = {} print["{"] = function(ch) { print.singleSpace(); output.push(ch); print.newLine(); } print["}"] = function(ch) { print.newLine(); output.push(ch); print.newLine(); } print.newLine = function(keepWhitespace) { if (!keepWhitespace) while (whiteRe.test(output[output.length - 1])) output.pop(); if (output.length) output.push('\n'); if (indentString) output.push(indentString); } print.singleSpace = function() { if (output.length && !whiteRe.test(output[output.length - 1])) output.push(' '); } var output = []; if (indentString) output.push(indentString); /*_____________________--------------------_____________________*/ while(true) { var isAfterSpace = skipWhitespace(); if (!ch) break; if (ch == '{') { indent(); print["{"](ch); } else if (ch == '}') { outdent(); print["}"](ch); } else if (ch == '"' || ch == '\'') { output.push(eatString(ch)) } else if (ch == ';') { output.push(ch, '\n', indentString); } else if (ch == '/' && peek() == '*') { // comment print.newLine(); output.push(eatComment(), "\n", indentString); } else if (ch == '(') { // may be a url if (lookBack("url", -1)) { output.push(ch); eatWhitespace(); if (next()) { if (ch != ')' && ch != '"' && ch != '\'') output.push(eatString(')')); else pos--; } } else { if (isAfterSpace) print.singleSpace(); output.push(ch); eatWhitespace(); } } else if (ch == ')') { output.push(ch); } else if (ch == ',') { eatWhitespace(); output.push(ch); print.singleSpace(); } else if (ch == ']') { output.push(ch); } else if (ch == '[' || ch == '=') { // no whitespace before or after eatWhitespace(); output.push(ch); } else { if (isAfterSpace) print.singleSpace(); output.push(ch); } } var sweetCode = output.join('').replace(/[\n ]+$/, ''); return sweetCode; } if (typeof exports !== "undefined") exports.css_beautify = css_beautify; /*jslint onevar: false, plusplus: false */ /*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */ /* JS Beautifier --------------- Written by Einar Lielmanis, <einar@jsbeautifier.org> http://jsbeautifier.org/ Originally converted to javascript by Vital, <vital76@gmail.com> "End braces on own line" added by Chris J. Shull, <chrisjshull@gmail.com> You are free to use this in any way you want, in case you find this useful or working for you. Usage: js_beautify(js_source_text); js_beautify(js_source_text, options); The options are: indent_size (default 4) - indentation size, indent_char (default space) - character to indent with, preserve_newlines (default true) - whether existing line breaks should be preserved, max_preserve_newlines (default unlimited) - maximum number of line breaks to be preserved in one chunk, jslint_happy (default false) - if true, then jslint-stricter mode is enforced. jslint_happy !jslint_happy --------------------------------- function () function() brace_style (default "collapse") - "collapse" | "expand" | "end-expand" | "expand-strict" put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line. expand-strict: put brace on own line even in such cases: var a = { a: 5, b: 6 } This mode may break your scripts - e.g "return { a: 1 }" will be broken into two lines, so beware. space_before_conditional (default true) - should the space before conditional statement be added, "if(true)" vs "if (true)", unescape_strings (default false) - should printable characters in strings encoded in \xNN notation be unescaped, "example" vs "\x65\x78\x61\x6d\x70\x6c\x65" e.g js_beautify(js_source_text, { 'indent_size': 1, 'indent_char': '\t' }); */ function js_beautify(js_source_text, options) { var input, output, token_text, last_type, last_text, last_last_text, last_word, flags, flag_store, indent_string; var whitespace, wordchar, punct, parser_pos, line_starters, digits; var prefix, token_type, do_block_just_closed; var wanted_newline, just_added_newline, n_newlines; var preindent_string = ''; // Some interpreters have unexpected results with foo = baz || bar; options = options ? options : {}; var opt_brace_style; // compatibility if (options.space_after_anon_function !== undefined && options.jslint_happy === undefined) { options.jslint_happy = options.space_after_anon_function; } if (options.braces_on_own_line !== undefined) { //graceful handling of deprecated option opt_brace_style = options.braces_on_own_line ? "expand" : "collapse"; } opt_brace_style = options.brace_style ? options.brace_style : (opt_brace_style ? opt_brace_style : "collapse"); var opt_indent_size = options.indent_size ? options.indent_size : 4, opt_indent_char = options.indent_char ? options.indent_char : ' ', opt_preserve_newlines = typeof options.preserve_newlines === 'undefined' ? true : options.preserve_newlines, opt_break_chained_methods = typeof options.break_chained_methods === 'undefined' ? false : options.break_chained_methods, opt_max_preserve_newlines = typeof options.max_preserve_newlines === 'undefined' ? false : options.max_preserve_newlines, opt_jslint_happy = options.jslint_happy === 'undefined' ? false : options.jslint_happy, opt_keep_array_indentation = typeof options.keep_array_indentation === 'undefined' ? false : options.keep_array_indentation, opt_space_before_conditional = typeof options.space_before_conditional === 'undefined' ? true : options.space_before_conditional, opt_unescape_strings = typeof options.unescape_strings === 'undefined' ? false : options.unescape_strings; just_added_newline = false; // cache the source's length. var input_length = js_source_text.length; function trim_output(eat_newlines) { eat_newlines = typeof eat_newlines === 'undefined' ? false : eat_newlines; while (output.length && (output[output.length - 1] === ' ' || output[output.length - 1] === indent_string || output[output.length - 1] === preindent_string || (eat_newlines && (output[output.length - 1] === '\n' || output[output.length - 1] === '\r')))) { output.pop(); } } function trim(s) { return s.replace(/^\s\s*|\s\s*$/, ''); } // we could use just string.split, but // IE doesn't like returning empty strings function split_newlines(s) { //return s.split(/\x0d\x0a|\x0a/); s = s.replace(/\x0d/g, ''); var out = [], idx = s.indexOf("\n"); while (idx !== -1) { out.push(s.substring(0, idx)); s = s.substring(idx + 1); idx = s.indexOf("\n"); } if (s.length) { out.push(s); } return out; } function force_newline() { var old_keep_array_indentation = opt_keep_array_indentation; opt_keep_array_indentation = false; print_newline(); opt_keep_array_indentation = old_keep_array_indentation; } function print_newline(ignore_repeated, reset_statement_flags) { flags.eat_next_space = false; if (opt_keep_array_indentation && is_array(flags.mode)) { return; } ignore_repeated = typeof ignore_repeated === 'undefined' ? true : ignore_repeated; reset_statement_flags = typeof reset_statement_flags === 'undefined' ? true : reset_statement_flags; if (reset_statement_flags) { flags.if_line = false; flags.chain_extra_indentation = 0; } trim_output(); if (!output.length) { return; // no newline on start of file } if (output[output.length - 1] !== "\n" || !ignore_repeated) { just_added_newline = true; output.push("\n"); } if (preindent_string) { output.push(preindent_string); } for (var i = 0; i < flags.indentation_level + flags.chain_extra_indentation; i += 1) { output.push(indent_string); } if (flags.var_line && flags.var_line_reindented) { output.push(indent_string); // skip space-stuffing, if indenting with a tab } } function print_single_space() { if (last_type === 'TK_COMMENT') { return print_newline(); } if (flags.eat_next_space) { flags.eat_next_space = false; return; } var last_output = ' '; if (output.length) { last_output = output[output.length - 1]; } if (last_output !== ' ' && last_output !== '\n' && last_output !== indent_string) { // prevent occassional duplicate space output.push(' '); } } function print_token() { just_added_newline = false; flags.eat_next_space = false; output.push(token_text); } function indent() { flags.indentation_level += 1; } function remove_indent() { if (output.length && output[output.length - 1] === indent_string) { output.pop(); } } function set_mode(mode) { if (flags) { flag_store.push(flags); } flags = { previous_mode: flags ? flags.mode : 'BLOCK', mode: mode, var_line: false, var_line_tainted: false, var_line_reindented: false, in_html_comment: false, if_line: false, chain_extra_indentation: 0, in_case_statement: false, // switch(..){ INSIDE HERE } in_case: false, // we're on the exact line with "case 0:" case_body: false, // the indented case-action block eat_next_space: false, indentation_level: (flags ? flags.indentation_level + ((flags.var_line && flags.var_line_reindented) ? 1 : 0) : 0), ternary_depth: 0 }; } function is_array(mode) { return mode === '[EXPRESSION]' || mode === '[INDENTED-EXPRESSION]'; } function is_expression(mode) { return in_array(mode, ['[EXPRESSION]', '(EXPRESSION)', '(FOR-EXPRESSION)', '(COND-EXPRESSION)']); } function restore_mode() { do_block_just_closed = flags.mode === 'DO_BLOCK'; if (flag_store.length > 0) { var mode = flags.mode; flags = flag_store.pop(); flags.previous_mode = mode; } } function all_lines_start_with(lines, c) { for (var i = 0; i < lines.length; i++) { var line = trim(lines[i]); if (line.charAt(0) !== c) { return false; } } return true; } function is_special_word(word) { return in_array(word, ['case', 'return', 'do', 'if', 'throw', 'else']); } function in_array(what, arr) { for (var i = 0; i < arr.length; i += 1) { if (arr[i] === what) { return true; } } return false; } function look_up(exclude) { var local_pos = parser_pos; var c = input.charAt(local_pos); while (in_array(c, whitespace) && c !== exclude) { local_pos++; if (local_pos >= input_length) { return 0; } c = input.charAt(local_pos); } return c; } function get_next_token() { var i; var resulting_string; n_newlines = 0; if (parser_pos >= input_length) { return ['', 'TK_EOF']; } wanted_newline = false; var c = input.charAt(parser_pos); parser_pos += 1; var keep_whitespace = opt_keep_array_indentation && is_array(flags.mode); if (keep_whitespace) { var whitespace_count = 0; while (in_array(c, whitespace)) { if (c === "\n") { trim_output(); output.push("\n"); just_added_newline = true; whitespace_count = 0; } else { if (c === '\t') { whitespace_count += 4; } else if (c === '\r') { // nothing } else { whitespace_count += 1; } } if (parser_pos >= input_length) { return ['', 'TK_EOF']; } c = input.charAt(parser_pos); parser_pos += 1; } if (just_added_newline) { for (i = 0; i < whitespace_count; i++) { output.push(' '); } } } else { while (in_array(c, whitespace)) { if (c === "\n") { n_newlines += ((opt_max_preserve_newlines) ? (n_newlines <= opt_max_preserve_newlines) ? 1 : 0 : 1); } if (parser_pos >= input_length) { return ['', 'TK_EOF']; } c = input.charAt(parser_pos); parser_pos += 1; } if (opt_preserve_newlines) { if (n_newlines > 1) { for (i = 0; i < n_newlines; i += 1) { print_newline(i === 0); just_added_newline = true; } } } wanted_newline = n_newlines > 0; } if (in_array(c, wordchar)) { if (parser_pos < input_length) { while (in_array(input.charAt(parser_pos), wordchar)) { c += input.charAt(parser_pos); parser_pos += 1; if (parser_pos === input_length) { break; } } } // small and surprisingly unugly hack for 1E-10 representation if (parser_pos !== input_length && c.match(/^[0-9]+[Ee]$/) && (input.charAt(parser_pos) === '-' || input.charAt(parser_pos) === '+')) { var sign = input.charAt(parser_pos); parser_pos += 1; var t = get_next_token(); c += sign + t[0]; return [c, 'TK_WORD']; } if (c === 'in') { // hack for 'in' operator return [c, 'TK_OPERATOR']; } if (wanted_newline && last_type !== 'TK_OPERATOR' && last_type !== 'TK_EQUALS' && !flags.if_line && (opt_preserve_newlines || last_text !== 'var')) { print_newline(); } return [c, 'TK_WORD']; } if (c === '(' || c === '[') { return [c, 'TK_START_EXPR']; } if (c === ')' || c === ']') { return [c, 'TK_END_EXPR']; } if (c === '{') { return [c, 'TK_START_BLOCK']; } if (c === '}') { return [c, 'TK_END_BLOCK']; } if (c === ';') { return [c, 'TK_SEMICOLON']; } if (c === '/') { var comment = ''; // peek for comment /* ... */ var inline_comment = true; if (input.charAt(parser_pos) === '*') { parser_pos += 1; if (parser_pos < input_length) { while (parser_pos < input_length && ! (input.charAt(parser_pos) === '*' && input.charAt(parser_pos + 1) && input.charAt(parser_pos + 1) === '/')) { c = input.charAt(parser_pos); comment += c; if (c === "\n" || c === "\r") { inline_comment = false; } parser_pos += 1; if (parser_pos >= input_length) { break; } } } parser_pos += 2; if (inline_comment && n_newlines === 0) { return ['/*' + comment + '*/', 'TK_INLINE_COMMENT']; } else { return ['/*' + comment + '*/', 'TK_BLOCK_COMMENT']; } } // peek for comment // ... if (input.charAt(parser_pos) === '/') { comment = c; while (input.charAt(parser_pos) !== '\r' && input.charAt(parser_pos) !== '\n') { comment += input.charAt(parser_pos); parser_pos += 1; if (parser_pos >= input_length) { break; } } if (wanted_newline) { print_newline(); } return [comment, 'TK_COMMENT']; } } if (c === "'" || // string c === '"' || // string (c === '/' && ((last_type === 'TK_WORD' && is_special_word(last_text)) || (last_text === ')' && in_array(flags.previous_mode, ['(COND-EXPRESSION)', '(FOR-EXPRESSION)'])) || (last_type === 'TK_COMMA' || last_type === 'TK_COMMENT' || last_type === 'TK_START_EXPR' || last_type === 'TK_START_BLOCK' || last_type === 'TK_END_BLOCK' || last_type === 'TK_OPERATOR' || last_type === 'TK_EQUALS' || last_type === 'TK_EOF' || last_type === 'TK_SEMICOLON')))) { // regexp var sep = c; var esc = false; var esc1 = 0; var esc2 = 0; resulting_string = c; if (parser_pos < input_length) { if (sep === '/') { // // handle regexp separately... // var in_char_class = false; while (esc || in_char_class || input.charAt(parser_pos) !== sep) { resulting_string += input.charAt(parser_pos); if (!esc) { esc = input.charAt(parser_pos) === '\\'; if (input.charAt(parser_pos) === '[') { in_char_class = true; } else if (input.charAt(parser_pos) === ']') { in_char_class = false; } } else { esc = false; } parser_pos += 1; if (parser_pos >= input_length) { // incomplete string/rexp when end-of-file reached. // bail out with what had been received so far. return [resulting_string, 'TK_STRING']; } } } else { // // and handle string also separately // while (esc || input.charAt(parser_pos) !== sep) { resulting_string += input.charAt(parser_pos); if (esc1 && esc1 >= esc2) { esc1 = parseInt(resulting_string.substr(-esc2), 16); if (esc1 && esc1 >= 0x20 && esc1 <= 0x7e) { esc1 = String.fromCharCode(esc1); resulting_string = resulting_string.substr(0, resulting_string.length - esc2 - 2) + (((esc1 === sep) || (esc1 === '\\')) ? '\\' : '') + esc1; } esc1 = 0; } if (esc1) { esc1++; } else if (!esc) { esc = input.charAt(parser_pos) === '\\'; } else { esc = false; if (opt_unescape_strings) { if (input.charAt(parser_pos) === 'x') { esc1++; esc2 = 2; } else if (input.charAt(parser_pos) === 'u') { esc1++; esc2 = 4; } } } parser_pos += 1; if (parser_pos >= input_length) { // incomplete string/rexp when end-of-file reached. // bail out with what had been received so far. return [resulting_string, 'TK_STRING']; } } } } parser_pos += 1; resulting_string += sep; if (sep === '/') { // regexps may have modifiers /regexp/MOD , so fetch those, too while (parser_pos < input_length && in_array(input.charAt(parser_pos), wordchar)) { resulting_string += input.charAt(parser_pos); parser_pos += 1; } } return [resulting_string, 'TK_STRING']; } if (c === '#') { if (output.length === 0 && input.charAt(parser_pos) === '!') { // shebang resulting_string = c; while (parser_pos < input_length && c !== '\n') { c = input.charAt(parser_pos); resulting_string += c; parser_pos += 1; } output.push(trim(resulting_string) + '\n'); print_newline(); return get_next_token(); } // Spidermonkey-specific sharp variables for circular references // https://developer.mozilla.org/En/Sharp_variables_in_JavaScript // https://mxr.mozilla.org/mozilla-central/source/js/src/jsscan.cpp around line 1935 var sharp = '#'; if (parser_pos < input_length && in_array(input.charAt(parser_pos), digits)) { do { c = input.charAt(parser_pos); sharp += c; parser_pos += 1; } while (parser_pos < input_length && c !== '#' && c !== '='); if (c === '#') { // } else if (input.charAt(parser_pos) === '[' && input.charAt(parser_pos + 1) === ']') { sharp += '[]'; parser_pos += 2; } else if (input.charAt(parser_pos) === '{' && input.charAt(parser_pos + 1) === '}') { sharp += '{}'; parser_pos += 2; } return [sharp, 'TK_WORD']; } } if (c === '<' && input.substring(parser_pos - 1, parser_pos + 3) === '<!--') { parser_pos += 3; c = '<!--'; while (input.charAt(parser_pos) !== '\n' && parser_pos < input_length) { c += input.charAt(parser_pos); parser_pos++; } flags.in_html_comment = true; return [c, 'TK_COMMENT']; } if (c === '-' && flags.in_html_comment && input.substring(parser_pos - 1, parser_pos + 2) === '-->') { flags.in_html_comment = false; parser_pos += 2; if (wanted_newline) { print_newline(); } return ['-->', 'TK_COMMENT']; } if (c === '.') { return [c, 'TK_DOT']; } if (in_array(c, punct)) { while (parser_pos < input_length && in_array(c + input.charAt(parser_pos), punct)) { c += input.charAt(parser_pos); parser_pos += 1; if (parser_pos >= input_length) { break; } } if (c === ',') { return [c, 'TK_COMMA']; } else if (c === '=') { return [c, 'TK_EQUALS']; } else { return [c, 'TK_OPERATOR']; } } return [c, 'TK_UNKNOWN']; } //---------------------------------- indent_string = ''; while (opt_indent_size > 0) { indent_string += opt_indent_char; opt_indent_size -= 1; } while (js_source_text && (js_source_text.charAt(0) === ' ' || js_source_text.charAt(0) === '\t')) { preindent_string += js_source_text.charAt(0); js_source_text = js_source_text.substring(1); } input = js_source_text; last_word = ''; // last 'TK_WORD' passed last_type = 'TK_START_EXPR'; // last token type last_text = ''; // last token text last_last_text = ''; // pre-last token text output = []; do_block_just_closed = false; whitespace = "\n\r\t ".split(''); wordchar = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$'.split(''); digits = '0123456789'.split(''); punct = '+ - * / % & ++ -- = += -= *= /= %= == === != !== > < >= <= >> << >>> >>