UNPKG

pretty-js

Version:
1,561 lines (1,317 loc) 44.6 kB
/** * JavaScript beautifier * * The code will call on Complexion to first tokenize the JavaScript and * then run through these rules to insert appropriate whitespace. * * In procedure will be to run through each token. Whitespace tokens are * removed and others will add whitespace again just after each token. All * whitespace is managed by this beautifier. */ "use strict"; // fid-umd {"name":"prettyJs","depends":[{"name":"Complexion","commonjs":"complexion","nodejs":"complexion"},{"name":"complexionJs","commonjs":"complexion-js","nodejs":"complexion-js"}]} (function (name, root, factory) { /** * Tests if something is an object. * * @param {*} x * @return {boolean} */ function isObject(x) { return typeof x === "object"; } if (isObject(module) && isObject(module.exports)) { module.exports = factory(require("complexion"), require("complexion-js")); } else if (isObject(exports)) { exports[name] = factory(require("complexion"), require("complexion-js")); } else if (isObject(root.define) && root.define.amd) { root.define(name, ["Complexion", "complexionJs"], factory); } else if (isObject(root.modulejs)) { root.modulejs.define(name, ["Complexion", "complexionJs"], factory); } else if (isObject(root.YUI)) { root.YUI.add(name, function (Y) { Y[name] = factory(Y.Complexion, Y.complexionJs); }, "", { requires: ["Complexion", "complexionJs"] }); } else { root[name] = factory(root.Complexion, root.complexionJs); } }("prettyJs", this, function (Complexion, complexionJs) { // eslint-disable-line no-invalid-this // fid-umd end var keywordContentProcessors, processors, punctuatorContentProcessors, tokenizer; /** * Allowed options * * @typedef {Object} prettyJs~options * @property {?boolean} bom Always add, remove, or just preserve BOM * @property {string} commentSpace Spaces to the left of single comments * @property {?string} convertStrings Set to "double", "single" or falsy * @property {boolean} elseNewline When enabled, else and catch on new line * @property {string} indent What to use for a single indent level * @property {boolean} jslint Use jslint-compatible rules * @property {string} newline What string to use for newlines * @property {boolean} noSpaceAfterIf Remove space in "if (" * @property {boolean} noSpaceAfterFor Remove space in "for (" * @property {boolean} noSpaceAfterFunction Remove space in "function (" * @property {boolean} noSpaceAfterSwitch Remove space in "switch (" * @property {?boolean} quoteProperties Wrap object properties in quotes */ /** * @typedef {Object} prettyJs~resultBit * @property {string} code * @property {string} content */ /** * This is where the result of all of the hard work will end up * * @class Result * @property {Array.<prettyJs~resultBit>} contexts Context and indentation * @property {Array.<prettyJs~resultBit>} fragments Formatted output * @property {prettyJs~options} options * @param {prettyJs~options} options */ function Result(options) { this.contexts = []; this.fragments = []; this.options = options; } /** * Adds a blank line if the previous non-whitespace token is a } or ; */ Result.prototype.addConditionalNewline = function () { var prev; prev = this.getPreviousNonWhitespace(); if (!prev) { return; } if (prev.content === ";" || prev.content === "}") { this.removeWhitespace(); this.addNewline(); this.addNewline(); } }; /** * Sets a context and adds the indentation to the output * * @param {(string|prettyJs~resultBit)} code * @param {string} [indent] defaults to this.options.indent */ Result.prototype.addContext = function (code, indent) { if (typeof code === "object") { this.contexts.push(code); } else { if (typeof indent === "undefined") { indent = this.options.indent; } this.contexts.push({ code: code, content: indent }); } }; /** * Adds a chunk of text to the list * * @param {string} code * @param {string} content */ Result.prototype.addFragment = function (code, content) { this.fragments.push({ code: code, content: content }); }; /** * Adds a newline to the list * * Also can strip spaces and indentation so we don't have extra whitespace * at the end of lines. */ Result.prototype.addNewline = function () { var type; type = this.getType(); while (type === "INDENT" || type === "SPACE") { this.removeFragment(); type = this.getType(); } this.addFragment("NEWLINE", this.options.newline); this.addFragment("INDENT", this.getIndentation()); }; /** * Adds a space to the list */ Result.prototype.addSpace = function () { this.addFragment("SPACE", " "); }; /** * Adds a chunk of text to the list based on a token * * @param {complexionJs~ComplexionJsToken} token */ Result.prototype.addToken = function (token) { this.fragments.push({ code: token.type, content: token.content }); }; /** * Returns true if a blank line should be added at the current position * before adding a comment * * @return {boolean} */ Result.prototype.commentShouldHaveNewline = function () { var check, last; last = this.getPreviousNonWhitespace(); // No extra newline at the beginning of a file if (!last) { return false; } // No extra newline when following an open symbol check = last.content; if (check === "{" || check === "(" || check === "[") { return false; } // No extra newline after some token types check = last.code; if (check === "LINE_COMMENT" || check === "BOM") { return false; } return true; }; /** * Gets the current context, if there is one * * @return {?string} */ Result.prototype.getContextCode = function () { if (!this.contexts.length) { return null; } return this.contexts[this.contexts.length - 1].code; }; /** * Returns the last fragment object * * @return {?prettyJs~resultBit} */ Result.prototype.getFragment = function () { if (!this.fragments.length) { return null; } return this.fragments[this.fragments.length - 1]; }; /** * Returns the current indentation string * * @return {string} */ Result.prototype.getIndentation = function () { var i, str; str = ""; for (i = 0; i < this.contexts.length; i += 1) { str += this.contexts[i].content; } return str; }; /** * Returns the last fragment which is not whitespace. * * @return {?prettyJs~resultBit} */ Result.prototype.getPreviousNonWhitespace = function () { var code, i; for (i = this.fragments.length - 1; i >= 0; i -= 1) { code = this.fragments[i].code; if (code !== "SPACE" && code !== "INDENT" && code !== "NEWLINE") { return this.fragments[i]; } } return null; }; /** * Returns the text from the last fragment added * * Does not call getFragment for speed (saves a function call) * * @return {?string} */ Result.prototype.getText = function () { if (!this.fragments.length) { return null; } return this.fragments[this.fragments.length - 1].content; }; /** * Returns the code from the last fragment added * * Does not call getFragment for speed (saves a function call) * * @return {?string} */ Result.prototype.getType = function () { if (!this.fragments.length) { return null; } return this.fragments[this.fragments.length - 1].code; }; /** * Return true if we could be making an array literal * * Does not call getFragment for speed (saves a function call) * * @return {boolean} */ Result.prototype.isArrayLiteral = function () { var prev; prev = this.getPreviousNonWhitespace(); if (!prev) { return true; } if (prev.code === "KEYWORD") { // Things that allow properties if (prev.content === "this" || prev.content === "super") { // this['some property'] return false; } return true; } if (prev.code === "IDENTIFIER_NAME") { // thing[1] return false; } if (prev.content === ")" || prev.content === "]") { // test()[1] // multiArray[0][1] return false; } return true; }; /** * Return true if we could be doing type conversion at this point * * @return {boolean} */ Result.prototype.isTypeConversion = function () { var prev; if (!this.fragments.length) { return true; } prev = this.getPreviousNonWhitespace(); if (!prev) { return true; } if (prev.code === "KEYWORD") { return true; } // Most punctuators imply that the next thing done will likely be // a type conversion. The rest seem to imply math. if (prev.code !== "PUNCTUATOR") { return false; } // These are all flags for math if (prev.content === ")" || prev.content === "}" || prev.content === "]") { return false; } return true; }; /** * Returns true if the last token was a newline. Skips spaces and * indentation. * * @return {boolean} */ Result.prototype.lastWasNewline = function () { var code, i; i = this.fragments.length - 1; while (i >= 0) { code = this.fragments[i].code; if (code === "NEWLINE") { return true; } if (code !== "SPACE" && code !== "INDENT") { return false; } i -= 1; } // Slightly odd. No content's the same as a newline. return true; }; /** * Removes a level from the context * * @return {?prettyJs~resultBit} */ Result.prototype.removeContext = function () { var self; self = this; if (!self.contexts.length) { // Force indents to go up when there were no contexts self.fragments.forEach(function (fragment) { if (fragment.code === "INDENT") { fragment.content = self.options.indent + fragment.content; } }); return null; } return self.contexts.pop(); }; /** * Removes a level from the context when the context should end at * the end of a statement. This method will get called when hitting * a semicolon, closing brace, and in other situations that would * indicate that a statement is complete. The contexts associated * with statement-level constructs would be removed. */ Result.prototype.removeContextForStatement = function () { var context; context = this.getContextCode(); while (context === "IF" || context === "ELSE" || context === "FOR" || context === "TERNARY" || context === "VAR") { this.removeContext(); context = this.getContextCode(); } }; /** * Removes a fragment from the stack * * @return {?prettyJs~resultBit} */ Result.prototype.removeFragment = function () { if (!this.fragments.length) { return null; } return this.fragments.pop(); }; /** * Removes whitespace from the end of the fragments */ Result.prototype.removeWhitespace = function () { var type; type = this.getType(); while (type === "NEWLINE" || type === "SPACE" || type === "INDENT") { this.removeFragment(); type = this.getType(); } }; /** * Returns the result as a string * * @return {string} */ Result.prototype.toString = function () { var i, str; str = ""; for (i = 0; i < this.fragments.length; i += 1) { str += this.fragments[i].content; } return str; }; /** * Convert a quoted string to a different quoting method * * @param {prettyJs~Result} result * @param {string} content String content WITH QUOTES * @return {string} Converted string */ function convertString(result, content) { var converted, quote; if (!result.options.convertStrings) { return content; } if (result.options.convertStrings === "single") { quote = "'"; } else { quote = "\""; } if (content.charAt(0) === quote) { return content; } // Remove quotes converted = content.substr(1, content.length - 2); /* Unescape all quotes and be careful with properly escaped * backslashes, like "\\'" */ /* jslint regexp:true*/ converted = converted.replace(/\\./g, function (match) { /* jslint regexp:false*/ var c; c = match.charAt(1); if (c === "\"" || c === "'") { return c; } return match; }); // Escape our quotes again converted = converted.replace(new RegExp(quote, "g"), "\\" + quote); return quote + converted + quote; } /** * Initialize options with their default values and guarantee that the * options variable is an object. * * @param {*} options * @return {prettyJs~options} options */ function initializeOptions(options) { var defaults; defaults = { bom: false, // Causes problems and unnecessary with UTF-8 commentSpace: " ", // Looks nice before single line comments convertStrings: "double", // Mimics JSON elseNewline: false, // Matches jslint rules indent: " ", // The eternal spaces vs. tabs debate jslint: false, // Some jslint-specific rules newline: "\n", // Unix-style newlines quoteProperties: false, // Prefer to unquote properties trailingNewline: false // Prefer to remove trailing newline }; if (!options) { options = {}; } Object.keys(defaults).forEach(function (key) { if (typeof options[key] === "undefined") { options[key] = defaults[key]; } }); if (options.convertStrings !== "single" && options.convertStrings !== "double") { options.convertStrings = false; } if (options.jslint) { options.quoteProperties = false; } return options; } /** * Token processing function * * @typedef {Function} prettyJs~processor * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ /** * Hand off the token processing to another function based on * the token's content. * * @param {Object} rules Map of exact string to processor * @param {prettyJs~processor} defaultProcessor * @return {prettyJs~processor} */ function processByContent(rules, defaultProcessor) { return function (result, token) { var fn; if (Object.prototype.hasOwnProperty.call(rules, token.content)) { fn = rules[token.content]; } else { fn = defaultProcessor; } return fn(result, token); }; } /** * Passes off an individual token to a processing function. * * @param {prettyJs~result} result * @param {complexionJs~ComplexionJsToken} token * @param {number} index * @param {Array.<complexionJs~ComplexionJsToken>} tokenList */ function processToken(result, token, index, tokenList) { var fn; fn = processors[token.type]; if (!fn) { throw new Error("Unhandled token type " + token.type + " at line " + token.line + " col " + token.col + ", offset " + token.offset); } fn(result, token, index, tokenList); } /** * Byte order mark * * If the `bom` option is set to `true` or `false` we always remove it. * When it's true, the BOM is added immediately by the prettyJs * function itself. * * @param {prettyJs~Result} result */ function tokenBom(result) { if (result.options.bom === null || typeof result.options.bom === "undefined") { result.addFragment("BOM", "\ufeff"); } } /** * Copy a token to the result. * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenCopy(result, token) { result.addToken(token); } /** * Copy a token to the result and add a newline. * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenCopyAndNewline(result, token) { result.addToken(token); result.addNewline(); } /** * Copy a token to the result and add a space. * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenCopyAndSpace(result, token) { result.addToken(token); result.addSpace(); } /** * Switches may have "case" and "default" outdented. * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenKeywordCase(result, token) { var context; context = result.getContextCode(); if (context === "BRACE") { // Treat this as an identifier tokenCopyAndSpace(result, token); return; } result.removeWhitespace(); /* Add a blank line between this keyword and the previous * content unless it's the first "case" in a switch or * there's multiple "case"/"default" rules together. */ if (result.getText() !== "{" && result.getText() !== ":") { result.addNewline(); } if (result.options.jslint) { context = result.removeContext(); result.addNewline(); result.addContext(context); } else { if (result.getContextCode() !== "SWITCH_BLOCK") { result.removeContext(); } result.addNewline(); result.addContext(token.content.toUpperCase()); } tokenCopyAndSpace(result, token); } /** * The start of a control flow block * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenKeywordControlFlow(result, token) { result.addConditionalNewline(); tokenCopyAndSpace(result, token); result.addContext(token.content.toUpperCase(), ""); } /** * "else" and "catch" should be on the same line as a closing } * but "else" should be on a new, unindented line when there was no } * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenKeywordElse(result, token) { var prev; // Remove newlines and indentation result.removeWhitespace(); if (result.options.elseNewline) { // Reducing to just one newline result.addNewline(); } else { prev = result.getFragment(); if (prev) { if (prev.content === "}") { result.addSpace(); } else { result.addNewline(); } } } tokenCopyAndSpace(result, token); result.addContext(token.content.toUpperCase(), ""); } /** * These statements should have a newline in front of them if * they are the first content on the line * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenKeywordOffsetLine(result, token) { result.addConditionalNewline(); tokenCopyAndSpace(result, token); } /** * The variables declared in `var` should be indented. * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenKeywordVar(result, token) { tokenCopyAndSpace(result, token); result.addContext("VAR"); } /** * Lots of minor changes: * - Standardizes newlines * - Removes trailing whitespace * - Starts all lines with an aligned "*" * - Reindents * - 2 blank lines before multi-line comments * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenMultiLineComment(result, token) { var addNewline, addSpace, str; // Standardize newlines into ones I prefer str = token.content.replace(/\r?\n|\r/g, "\n"); // Removing closing comment tag str = str.replace(/\*\/$/, ""); // Remember if there was a newline right before this tag addNewline = false; if (str.match(/\n[ \t\f]*$/)) { addSpace = false; addNewline = true; str = str.replace(/\n[ \t\f]*$/, ""); } else { // Remember if there was a space right before the end tag /* jslint regexp:true*/ addSpace = str.match(/[^ \t\f\n]([ \t\f]*)$/); /* jslint regexp:false*/ if (addSpace && addSpace[1]) { addSpace = addSpace[1]; str = str.substr(0, str.length - addSpace.length); } else { addSpace = false; } } // Remove trailing whitespace and whitespace after newlines str = str.replace(/[ \t\f]*\n/g, "\n").replace(/\n[ \t\f]*/g, "\n"); // Force all lines to start with indentation + space + star + space str = str.replace(/\n\*/g, "\n"); // Remove stars /* jslint regexp:true*/ str = str.replace(/\n([^ \n])/g, "\n $1"); // Adds a space /* jslint regexp:false*/ str = str.replace(/\n/g, "\n" + result.getIndentation() + " *"); // Add star /* jslint regexp:true*/ str = str.replace(/([^* ])(\*\/)$/, "$1 $2"); /* jslint regexp:false*/ // Convert newlines into ones you prefer if (result.options.newline !== "\n") { str = str.replace(/\n/g, result.options.newline); } // Add closing tag back if (addNewline) { str += "\n" + result.getIndentation() + " "; } else if (addSpace) { // Add the spacing back str += addSpace; } str += "*/"; if (result.commentShouldHaveNewline()) { result.removeWhitespace(); result.addNewline(); result.addNewline(); result.addNewline(); } result.addFragment("BLOCK_COMMENT", str); result.addNewline(); } /** * Handles what happens with close braces * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenPunctuatorBraceClose(result, token) { var context, extraNewline; // Terminate a statement if one was started result.removeContextForStatement(); // Decrease indentation result.removeContext(); result.removeWhitespace(); context = result.getContextCode(); extraNewline = false; if (context === "SWITCH_BLOCK") { // The last context could have been from "case" or "default" result.removeContext(); context = result.getContextCode(); } if (context === "CATCH" || context === "ELSE" || context === "FOR" || context === "FINALLY" || context === "FUNCTION" || context === "IF" || context === "SWITCH") { // Done with the function declaration or block of code result.removeContext(); if (result.getContextCode() === "ELSE") { result.removeContext(); } extraNewline = true; } // If not empty, add a newline if (result.getText() !== "{") { result.addNewline(); } // Add content, newline result.addFragment("PUNCTUATOR", token.content); result.addNewline(); // If this was a function or similar, add another newline if (extraNewline) { result.addNewline(); } } /** * Handles what happens after open braces * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenPunctuatorBraceOpen(result, token) { var context; context = result.getContextCode(); // Remember some contexts so we can key off them later if (context === "FOR" || context === "FUNCTION" || context === "SWITCH" || context === "IF" || context === "ELSE") { result.addContext(context + "_BLOCK"); } else { result.addContext("BRACE"); } result.addToken(token); result.addNewline(); } /** * Handles what happens after closing brackets * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenPunctuatorBracketClose(result, token) { var lastText, prevContext; // Decrease indentation prevContext = result.removeContext(); result.removeWhitespace(); lastText = result.getText(); if (prevContext.code !== "ARRAY_INDEX" && (lastText !== "(" && lastText !== "{" && lastText !== "[")) { result.addNewline(); } tokenCopyAndSpace(result, token); } /** * Handles what happens after open brackets * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenPunctuatorBracketOpen(result, token) { if (result.isArrayLiteral()) { result.addContext("BRACKET"); result.addToken(token); result.addNewline(); } else { result.removeWhitespace(); result.addContext("ARRAY_INDEX"); result.addToken(token); } } /** * Handles colons * * Switches, ternary, object literals * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenPunctuatorColon(result, token) { var context, prev; /** * Returns true if the string passed in can be an identifier without * additional quoting * * @param {string} content * @return {boolean} */ function canBeIdentifier(content) { var tokenList; // Reuse the tokenizer to see if the content is an identifier tokenList = tokenizer.tokenize(content); if (tokenList.length !== 1 || tokenList[0].type !== "IDENTIFIER_NAME") { return false; } if (!result.options.jslint) { return true; } if (content.charAt(0) === "_" || content.charAt(-1) === "_") { return false; } return true; } context = result.getContextCode(); if (context === "CATCH") { /* This should be a keyword instead * * { catch: false } * * Remove the CATCH context */ result.removeContext(); context = result.getContextCode(); } if (context === "SWITCH_BLOCK" || context === "CASE" || context === "DEFAULT") { result.removeWhitespace(); result.addToken(token); result.addNewline(); return; } if (context !== "TERNARY") { // Property name as a string or identifier result.removeWhitespace(); if (result.options.quoteProperties === true) { // Force quotes if (result.getType() === "IDENTIFIER_NAME" || result.getType() === "KEYWORD") { prev = result.removeFragment(); result.addFragment("STRING", convertString(result, JSON.stringify(prev.content))); } } else if (result.options.quoteProperties === false) { // Remove quotes when possible if (result.getType() === "STRING") { prev = result.getFragment().content; prev = prev.substr(1, prev.length - 2); if (canBeIdentifier(prev)) { result.removeFragment(); result.addFragment("IDENTIFIER_NAME", prev); } } } } tokenCopyAndSpace(result, token); } /** * Pre/Post Increment/Decrement (++ and --) * * No space between the identifier and the punctuator when the * specific option is enabled. * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenPunctuatorIncDec(result, token) { /** * Checks if this is pre-increment or post-increment. * * @return {boolean} */ function isPre() { var prev; prev = result.getPreviousNonWhitespace(); if (!prev || prev.code !== "IDENTIFIER_NAME") { return true; } return false; } if (!result.options.noSpaceWithIncDec) { tokenCopyAndSpace(result, token); return; } if (isPre()) { tokenCopy(result, token); return; } result.removeWhitespace(); tokenCopyAndSpace(result, token); } /** * Handles commas * * jslint only increases indentation when there is a comma * inside a function's argument list. * test(function () { * oneLevelIndent(); * }); * test2(true, * function () { * twoLevelIndent(); * }); * * In order to attach to the previous token, no whitespace before this one. * * Add newline if we are not in a var / function's context. * * { * a: a, * b: b * } * * return a(), * b; * * No newline sometimes (var / function call) * * var a, b; * * a(b, c); * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenPunctuatorComma(result, token) { var context; result.removeWhitespace(); context = result.getContextCode(); if (context === "TERNARY") { result.removeContext(); context = result.getContextCode(); } result.addToken(token); if (context === "FUNCTION_ARGS") { /* This is the first comma seen in a function call. It sets a * new context that can be used for indentation on other things * for jslint-compatible formatting. */ result.addContext("FUNCTION_ARGS_COMMA", ""); result.addSpace(); } else if (context === "FUNCTION_ARGS_COMMA" || context === "VAR" || context === "FOR_CONDITION") { /* Only add a space for "var" and "for" * * var a, b, c; * * for (a = 1, b = 2; ... * * x(1, 2) */ result.addSpace(); } else if (context === "BRACE" || context === "BRACKET") { /* Add newlines inside arrays and objects * * x = [ * 1, * 2 * ]; * * x = { * a: 1, * b: 2 * }; */ result.addNewline(); } else { /* Merging multiple lines onto one, typically from a minifier * * return a = f(), * a[1] = 123, * a; */ result.addContext("COMMA_OPERATOR"); result.addNewline(); result.removeContext("COMMA_OPERATOR"); } } /** * Handles what happens after closing parenthesis * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenPunctuatorParenthesisClose(result, token) { result.removeContextForStatement(); if (result.getContextCode() === "FUNCTION_ARGS_COMMA") { result.removeContext(); } result.removeContext(); result.removeWhitespace(); tokenCopyAndSpace(result, token); } /** * Handles what happens after open parenthesis * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenPunctuatorParenthesisOpen(result, token) { var context, prev; prev = result.getPreviousNonWhitespace(); if (!prev) { result.addContext("PAREN", ""); } else if (prev.code === "IDENTIFIER_NAME" || prev.content === "}" || prev.content === ")" || prev.content === "]") { // someFunction( // function something() {}( // (function () {})( // methods[x]( result.removeWhitespace(); result.addContext("FUNCTION_ARGS", ""); } else if (prev.content === "function") { // function ( if (result.options.noSpaceAfterFunction) { result.removeWhitespace(); } result.addContext("FUNCTION_ARGS", ""); } else { context = result.getContextCode(); if (context === "IF") { // if ( if (result.options.noSpaceAfterIf) { result.removeWhitespace(); } result.addContext("IF_CONDITION"); } else if (context === "FOR") { // for ( if (result.options.noSpaceAfterFor) { result.removeWhitespace(); } result.addContext("FOR_CONDITION", ""); } else { // This function can only be called for "function", // "for", "if" and "switch". This needs to be "switch". if (result.options.noSpaceAfterSwitch) { result.removeWhitespace(); } result.addContext("PAREN", ""); } } // No space after open parenthesis result.addToken(token); } /** * Handles periods * * Attach to the content before and after. * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenPunctuatorPeriod(result, token) { result.removeWhitespace(); result.addToken(token); } /** * Plus and Minus * * No space following the plus or minus if we are doing type conversion. * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenPunctuatorPlusMinus(result, token) { var isTypeConversion; // Check if this is type conversion before adding the symbol isTypeConversion = result.isTypeConversion(); result.addToken(token); if (!isTypeConversion) { result.addSpace(); } } /** * Handles question marks, a ternary operation * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenPunctuatorQuestion(result, token) { result.addContext("TERNARY", ""); result.addToken(token); result.addSpace(); } /** * Adds a semicolon and newline * * Do not use the token content here because it may be an implicit * semicolon, which does not have content. * * Extra newlines after a "var" statement and after a "use strict". * * @param {prettyJs~Result} result */ function tokenSemicolon(result) { var oldContext, previousText; /** * Does this need two newlines after? * * @return {boolean} */ function needsTwoNewlines() { if (oldContext === "VAR") { // var a, b, c; return true; } if (previousText === "\"use strict\"" || previousText === "'use strict'") { // "use strict"; // 'use strict'; return true; } return false; } oldContext = result.getContextCode(); result.removeContextForStatement(); result.removeWhitespace(); previousText = result.getText(); result.addFragment("SEMICOLON", ";"); // Do not use token.content here if (result.getContextCode() === "FOR_CONDITION") { // for (a = 1; // for (a = 1; a < b; result.addSpace(); } else { if (needsTwoNewlines()) { result.addNewline(); } result.addNewline(); } } /** * Skips the addition of a token to the result */ function tokenSkip() { return; } /** * If the comment had no prior content on the line, then check if the * the comment should have a newline. If so, wipe out the whitespace * and add a couple of newlines. If there was content already on the * line, remove whitespace and add the commentSpace before the comment. * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token * @param {number} index * @param {Array.<complexionJs~ComplexionJsToken>} tokenList */ function tokenSingleLineComment(result, token, index, tokenList) { var str; /** * Detect if the comment was originally on the same line as * some code. * * @return {boolean} */ function originallyOnSameLine() { var check; while (index) { index -= 1; check = tokenList[index].type; if (check === "LINE_TERMINATOR") { return false; } if (check !== "WHITESPACE") { /** * Check the content to see if a newline would make sense * in this situation. */ check = tokenList[index].content; if (check === "{" || check === "[") { return false; } return true; } } return false; } // Remove trailing whitespace str = token.content.replace(/ [\t\f]*\n/g, result.options.newline); if (originallyOnSameLine() || !result.lastWasNewline()) { result.removeWhitespace(); result.addFragment("COMMENT_WHITESPACE", result.options.commentSpace); result.addFragment("STATEMENT_COMMENT", str); } else { if (result.commentShouldHaveNewline(index, tokenList)) { result.removeWhitespace(); result.addNewline(); result.addNewline(); } result.addFragment("LINE_COMMENT", str); } result.addNewline(); } /** * Processes a string and may convert the string to single or double * quotes. * * @param {prettyJs~Result} result * @param {complexionJs~ComplexionJsToken} token */ function tokenString(result, token) { var str; str = convertString(result, token.content); result.addFragment("STRING", str); result.addSpace(); } // Initialize a new tokenizer with the default options tokenizer = new Complexion(); complexionJs(tokenizer); keywordContentProcessors = { case: tokenKeywordCase, catch: tokenKeywordElse, default: tokenKeywordCase, else: tokenKeywordElse, finally: tokenKeywordElse, for: tokenKeywordControlFlow, function: tokenKeywordControlFlow, if: tokenKeywordControlFlow, return: tokenKeywordOffsetLine, switch: tokenKeywordControlFlow, throw: tokenKeywordOffsetLine, try: tokenKeywordOffsetLine, var: tokenKeywordVar, while: tokenKeywordOffsetLine }; punctuatorContentProcessors = { "{": tokenPunctuatorBraceOpen, "}": tokenPunctuatorBraceClose, "[": tokenPunctuatorBracketOpen, "]": tokenPunctuatorBracketClose, "(": tokenPunctuatorParenthesisOpen, ")": tokenPunctuatorParenthesisClose, ".": tokenPunctuatorPeriod, ";": tokenSemicolon, ",": tokenPunctuatorComma, ":": tokenPunctuatorColon, "+": tokenPunctuatorPlusMinus, "-": tokenPunctuatorPlusMinus, "++": tokenPunctuatorIncDec, "--": tokenPunctuatorIncDec, "!": tokenCopy, "?": tokenPunctuatorQuestion }; processors = { BOM: tokenBom, BOOLEAN_LITERAL: tokenCopyAndSpace, IDENTIFIER_NAME: tokenCopyAndSpace, IMPLICIT_SEMICOLON: tokenSemicolon, KEYWORD: processByContent(keywordContentProcessors, tokenCopyAndSpace), LINE_TERMINATOR: tokenSkip, // Other rules manage all spaces MULTI_LINE_COMMENT: tokenMultiLineComment, NULL_LITERAL: tokenCopyAndSpace, NUMERIC_LITERAL: tokenCopyAndSpace, PUNCTUATOR: processByContent(punctuatorContentProcessors, tokenCopyAndSpace), REGULAR_EXPRESSION_LITERAL: tokenCopyAndSpace, SHEBANG: tokenCopyAndNewline, SINGLE_LINE_COMMENT: tokenSingleLineComment, STRING_LITERAL: tokenString, WHITESPACE: tokenSkip // Other rules manage all whitespace }; return function (str, options) { var result, tokenList; options = initializeOptions(options); tokenList = tokenizer.tokenize(str); result = new Result(options); if (options.bom === true) { // See tokenBom for why this always adds a new token result.addFragment("BOM", "\ufeff"); } // Set a placeholder for a zero-length indentation result.addFragment("INDENT", result.getIndentation()); tokenList.forEach(function (token, index) { processToken(result, token, index, tokenList); }); result.removeWhitespace(); if (options.trailingNewline === true) { result.addNewline(); } return result.toString(); }; // fid-umd post })); // fid-umd post-end