var convertTree = function (code, ast, options) { options = getOptions(options); var astGenerator = options.astGenerator; var astErrorGenerator = options.astErrorGenerator; var clone = require("clone"); rocambole.recursive(ast, function (node) { if (astUtil.isExpression(node) || astUtil.isThrowStatement(node)) { var nextElement = power.nextElementSibling(node); if (nextElement && power.isDocComment(nextElement, options.regexpExecuted)) { var commentExpression = power.getCommentAsExpression(nextElement, options.regexpExecuted); var actualCode = code.substring(node.range[0], node.range[1]); actualCode = actualCode.replace(/[;,]+$/, ""); var expectedCode = power.getCodeFromComment(nextElement, options.regexpExecuted); if (options.verbatimActualExpression) { var isContainObjectLiteral = astUtil.containObjectLiteral(node); if (isContainObjectLiteral) { node.expression['x-verbatim-property'] = actualCode; } } if (options.verbatimExpectExpression) { commentExpression['x-verbatim-property'] = expectedCode; } var nodeForReplace = null; if (astUtil.isErrorObject(commentExpression)) { nodeForReplace = astErrorGenerator(clone(node), commentExpression, { actual: actualCode, expected: expectedCode }); } else { // throw doesn't support... if (astUtil.isThrowStatement(node)) { eventEmitter.emit("parse-error", node, "ThrowStatement support only when Error doctest."); } nodeForReplace = astGenerator(node, commentExpression, { actual: actualCode, expected: expectedCode }); } if (nodeForReplace) { power.replaceNode(node, nodeForReplace); } } } }); return ast; };
var format = function (string, userConfig) { userConfig = userConfig || {}; overwriteConfig(_config, userConfig); // overwrite codeStyle with user config // deal with indent (new Array(indent + 1)).join(' ') var INDENT = (function () { var indentStr = ''; var space = _config.useTabIndent ? '\t' : ' '; var indent = _config.indent ? Number(_config.indent) : 4; while (indent--) { indentStr += space; } return indentStr; })(); /** * create a LineBreak token * * @returns {{type: string, value: string, formatter: boolean}} */ var nextLineFactory = function () { return { type: 'LineBreak', value: _config.lineSeparator, formatter: true }; }; /** * create a indent token with indent level * * @returns {Object} */ var indentFactory = function () { var indentStr = ''; for (var i = 0; i < indentLevel; i++) { indentStr += INDENT; } return { type: 'WhiteSpace', value: indentStr }; }; /** * create a indent token with only one indent * * @returns {Object} */ var singleIndentFactory = function () { return { type: 'WhiteSpace', value: INDENT }; }; /** * create a single space token * * @returns {Object} */ var whiteSpaceFactory = function () { return { type: 'WhiteSpace', value: ' ' }; }; /** * check if a token is comment * * @param {Object} token - the token to check * @returns {boolean} */ var isComment = function (token) { return token.type === 'LineComment' || token.type === 'BlockComment'; }; /** * check if a token is comment LineComment * * @param {Object} token - the token to check * @returns {boolean} */ var isLineComment = function (token) { return token.type === 'LineComment'; }; /** * check if a token is white space * * @param {Object} token - the token to check * @returns {boolean} */ var isWhiteSpace = function (token) { return token.type === 'WhiteSpace'; }; /** * check if a token is line break * * @param {Object} token - the token to check * @returns {boolean} */ var isLineBreak = function (token) { return token.type === 'LineBreak'; }; /** * check if a token is comment in one line * * @param {Object} token - the token to check * @returns {boolean} */ var isInlineComment = function (token) { var inline = false; if (token) { if (token.type === 'LineComment') { inline = true; } else if (token.type === 'BlockComment') { inline = (token.value.indexOf('\n') === -1); } } return inline; }; /** * check if only types between startToken and endToken * * @param {Object} startToken - the token to start check * @param {Object} endToken - the token to end check * @param {Array} types - allow types array * @returns {boolean} */ var isTypeBetween = function (startToken, endToken, types) { var is = true; var token = startToken; while (token.next && token.next !== endToken) { token = token.next; if (types.indexOf(token.type) === -1) { is = false; break; } } return is; }; /** * 保证一个语句节点是在新起一行 * * @param {Object} node - target node */ var guaranteeNewLine = function (node) { if (node.startToken.prev && node.startToken.prev.type !== 'LineBreak') { insertBefore(node.startToken, nextLineFactory()); } }; /** * 保证一个token两侧是空白符 * * @param {Object} token - target token */ var guaranteeWhiteSpaceAround = function (token) { if (token.prev.type !== 'WhiteSpace') { insertBefore(token, whiteSpaceFactory()); } if (token.next.type !== 'WhiteSpace' && token.next.type !== 'LineBreak') { insertAfter(token, whiteSpaceFactory()); } }; /** * insert token before a token * * @param {Object} token - target token * @param {Object} insertion - a token to insert */ var insertBefore = function (token, insertion) { if (!token.prev) { // insert at first token.prev = insertion; insertion.next = token; } else { token.prev.next = insertion; insertion.prev = token.prev; insertion.next = token; token.prev = insertion; } }; /** * insert token after a token * * @param {Object} token - target token * @param {Object} insertion - a token to insert */ var insertAfter = function (token, insertion) { if (!token.next) { // insert at last token.next = insertion; insertion.prev = token; } else { token.next.prev = insertion; insertion.next = token.next; insertion.prev = token; token.next = insertion; } }; /** * remove token in tokens * * @param {Object} token - target token */ var removeToken = function (token) { if (token.prev && token.next) { token.prev.next = token.next; token.next.prev = token.prev; } else if (token.prev) { token.prev.next = undefined; } else if (token.next) { token.next.prev = undefined; } }; /** * 这堆操作符前后是要有空白的 * * @type {string[]} */ var SPACE_AROUND_PUNCTUATOR = [ '=', '+=', '-=', '*=', '/=', '%=', '<<=', '>>=', '>>>=', '&=', '^=', '|=', '==', '!=', '===', '!==', '>', '>=', '<', '<=', '+', '-', '*', '/', '%', '&', '|', '^', '~', '<<', '>>', '>>>', '&&', '||' ]; var _rocambole = require('rocambole'); // 先fix再format string = require('./lib/fix').fix(string, _config.fix); var _ast = _rocambole.parse(string); // start clear // 先去除空白 var clearWhiteSpace = function (token) { if (isWhiteSpace(token)) { // 默认都要删除空白 var remove = true; // 空白前面是换行 && 后面是注释的不删除 if (token.prev && token.next && isLineBreak(token.prev) && isComment(token.next)) { remove = false; } if (remove) { removeToken(token); } } }; var token = _ast.startToken; while (token !== _ast.endToken.next) { clearWhiteSpace(token); token = token.next; } if (_config.blankLines.keepMaxBlankLines > 0) { _rocambole.recursive(_ast, function (node) { var keep = 0; var t; if (/Declaration|Statement/.test(node.type) && node.type !== 'BlockStatement' && !(node.type === 'VariableDeclaration' && node.parent.type === 'ForStatement')) { keep = Number(_config.blankLines.keepMaxBlankLines); if (node.endToken.next && isLineBreak(node.endToken.next)) { t = node.endToken.next; t.removeAble = false; while (keep--) { if (t.next && isLineBreak(t.next)) { t.next.removeAble = false; t = t.next; } } } } if (node.type === 'Property') { keep = Number(_config.blankLines.keepMaxBlankLines); if (node.startToken.prev && isLineBreak(node.startToken.prev)) { t = node.startToken.prev; t.removeAble = false; while (keep--) { if (t.prev && isLineBreak(t.prev)) { t.prev.removeAble = false; t = t.prev; } } } } }); } var clearLineBreak = function (token) { // 注释前后的换行一律保留,其他一律删除 if (isLineBreak(token)) { // 默认都要删除换行 var remove = true; // 注释前面的 if (token.next && isComment(token.next)) { remove = false; } // 注释后面的 if (token.prev && isComment(token.prev)) { remove = false; } // 注释前面空白再前面的,这种是有缩进且占整行的注释 if (token.next && token.next.next && isWhiteSpace(token.next) && isComment(token.next.next)) { remove = false; } if (token.prev && !token.prev.prev && Number(_config.blankLines.keepMaxBlankLines) > 0 && isComment(token.prev) && token.prev.value.charAt(0) === '*') { if (token.next && isLineBreak(token.next)) { token.next.removeAble = false; token.next.next.removeAble = false; } } if (token.removeAble === false) { remove = false; } if (remove) { removeToken(token); } } }; token = _ast.startToken; while (token !== _ast.endToken.next) { clearLineBreak(token); token = token.next; } // end clear // start process // 这些关键词之后,必须无脑保证空白,其实return,delete等并不是必须要空白,但是应该没有傻逼这么写吧return(a);忽略这种情况 // 如果这些关键词后面都不加空白,那就傻逼鉴定完毕 shit 所以不提供这种配置 var INSERT_SPACE_AFTER_KEYWORD = ['throw', 'return', 'delete', 'new', 'in', 'typeof', 'instanceof', 'case', 'void']; // 这几个关键词属于同一类型,在它们后边可以加空白也可以不加,都不会出先语法错误 var INSERT_SPACE_AFTER_KEYWORD_WITH_CONFIG = ['if', 'for', 'while', 'switch', 'catch']; var processToken = function (token) { // 必须加空白的地方 if (token.type === 'Keyword' && INSERT_SPACE_AFTER_KEYWORD.indexOf(token.value) !== -1) { insertAfter(token, whiteSpaceFactory()); } // 坑:var后面可以是换行,这时候就不需要空白 if (token.type === 'Keyword' && token.value === 'var' && !isLineBreak(token.next)) { insertAfter(token, whiteSpaceFactory()); } if (_config.spaces.before.parentheses && token.type === 'Keyword' && INSERT_SPACE_AFTER_KEYWORD_WITH_CONFIG.indexOf(token.value) !== -1) { insertAfter(token, whiteSpaceFactory()); } // check around = WhiteSpace if (_config.spaces.around.binaryOperators && token.type === 'Punctuator' && SPACE_AROUND_PUNCTUATOR.indexOf(token.value) !== -1) { guaranteeWhiteSpaceAround(token); } // 特殊处理in/instanceof,这货两边必须保证空白 if (token.type === 'Keyword' && ['in', 'instanceof'].indexOf(token.value) !== -1) { guaranteeWhiteSpaceAround(token); } // 特殊处理finally,这货在ast里不是一个独立type节点 if (token.type === 'Keyword' && token.value === 'finally') { if (_config.spaces.before.keywords && !isWhiteSpace(token.prev)) { insertBefore(token, whiteSpaceFactory()); } } }; token = _ast.startToken; while (token !== _ast.endToken.next) { processToken(token); token = token.next; } // end process // loop node _rocambole.recursive(_ast, function (node) { switch (node.type) { case 'ArrayExpression': node.startToken.indentIncrease = true; node.endToken.indentDecrease = true; node.elements.forEach(function (el) { el && guaranteeNewLine(el); }); if (node.elements.length > 0 && !isLineBreak(node.endToken.prev)) { insertBefore(node.endToken, nextLineFactory()); } break; case 'BreakStatement': guaranteeNewLine(node); break; case 'ConditionalExpression': if (_config.spaces.around.ternaryOperators && node.test) { (function () { var token = node.test.endToken; // TODO 这样做到底安全不? while (!(token.value === '?' && token.type === 'Punctuator')) { token = token.next; } if (!isWhiteSpace(token.prev)) { insertBefore(token, whiteSpaceFactory()); } if (!isWhiteSpace(token.next)) { insertAfter(token, whiteSpaceFactory()); } })(); } if (_config.spaces.around.ternaryOperators && node.consequent) { (function () { var token = node.consequent.endToken; while (!(token.value === ':' && token.type === 'Punctuator')) { token = token.next; } if (!isWhiteSpace(token.prev)) { insertBefore(token, whiteSpaceFactory()); } if (!isWhiteSpace(token.next)) { insertAfter(token, whiteSpaceFactory()); } })(); } break; case 'ContinueStatement': guaranteeNewLine(node); break; case 'DoWhileStatement': guaranteeNewLine(node); if (node.body.type === 'BlockStatement') { if (!isWhiteSpace(node.body.endToken.next)) { _config.spaces.before.keywords && insertAfter(node.body.endToken, whiteSpaceFactory()); } } else { if (isWhiteSpace(node.startToken.next)) { removeToken(node.startToken.next); } node.body.startToken.indentSelf = true; if (!isWhiteSpace(node.test.startToken.prev.prev)) { if (!isLineBreak(node.test.startToken.prev.prev.prev)) { insertBefore(node.test.startToken.prev.prev, nextLineFactory()); } insertBefore(node.test.startToken.prev, whiteSpaceFactory()); } else { if (!isLineBreak(node.test.startToken.prev.prev.prev.prev)) { insertBefore(node.test.startToken.prev.prev.prev, nextLineFactory()); } } } break; case 'ForStatement': guaranteeNewLine(node); if (node.test && !isWhiteSpace(node.test.startToken.prev)) { insertBefore(node.test.startToken, whiteSpaceFactory()); } if (node.update && !isWhiteSpace(node.update.startToken.prev)) { insertBefore(node.update.startToken, whiteSpaceFactory()); } if (_config.spaces.within.parentheses) { if (node.init && !isWhiteSpace(node.init.startToken.prev)) { insertBefore(node.init.startToken, whiteSpaceFactory()); } else {} if (node.update && !isWhiteSpace(node.update.endToken.next)) { insertAfter(node.update.endToken, whiteSpaceFactory()); } } break; case 'ForInStatement': guaranteeNewLine(node); break; case 'VariableDeclaration': if (node.parent.type !== 'ForStatement' && node.parent.type !== 'ForInStatement') { guaranteeNewLine(node); } break; case 'VariableDeclarator': if (node.endToken.next && node.endToken.next.type === 'Punctuator' && node.endToken.next.value === ',' && !isLineBreak(node.endToken.next.next)) { insertAfter(node.endToken.next, whiteSpaceFactory()); } break; case 'ExpressionStatement': guaranteeNewLine(node); break; case 'FunctionDeclaration': guaranteeNewLine(node); insertAfter(node.startToken, whiteSpaceFactory()); if (node.id) { _config.spaces.before.functionDeclarationParentheses && insertAfter(node.id.endToken, whiteSpaceFactory()); } node.params.forEach(function (param, i) { if (i > 0) { insertBefore(param.startToken, whiteSpaceFactory()); } }); if (_config.spaces.within.parentheses && node.params.length > 0) { if (!isWhiteSpace(node.params[0].startToken.prev)) { insertBefore(node.params[0].startToken, whiteSpaceFactory()); } if (!isWhiteSpace(node.params[node.params.length - 1].endToken.next)) { insertAfter(node.params[node.params.length - 1].endToken, whiteSpaceFactory()); } } break; case 'IfStatement': // 坑:if statement 的 consequent 和 alternate 都是有可能不存在的 if (node.parent.type !== 'IfStatement') { guaranteeNewLine(node); } else { insertBefore(node.startToken, whiteSpaceFactory()); } if (node.consequent && node.consequent.type !== 'BlockStatement') { node.consequent.startToken.indentSelf = true; if (node.alternate && node.consequent.endToken.next && !isLineBreak(node.consequent.endToken.next)) { insertAfter(node.consequent.endToken, nextLineFactory()); } } else { if (node.alternate) { _config.spaces.before.keywords && insertAfter(node.consequent.endToken, whiteSpaceFactory()); } } if (node.alternate && node.alternate.type !== 'BlockStatement' && node.alternate.type !== 'IfStatement') { node.alternate.startToken.indentSelf = true; } if (_config.spaces.within.parentheses && node.test) { if (!isWhiteSpace(node.test.startToken.prev)) { insertBefore(node.test.startToken, whiteSpaceFactory()); } if (!isWhiteSpace(node.test.endToken.next)) { insertAfter(node.test.endToken, whiteSpaceFactory()); } } break; case 'ReturnStatement': guaranteeNewLine(node); break; case 'BlockStatement': node.startToken.indentIncrease = true; node.endToken.indentDecrease = true; if (node.startToken.prev && !isWhiteSpace(node.startToken.prev) && !isLineBreak(node.startToken.prev)) { _config.spaces.before.leftBrace && insertBefore(node.startToken, whiteSpaceFactory()); } if (!isLineBreak(node.endToken.prev)) { insertBefore(node.endToken, nextLineFactory()); } break; case 'ObjectExpression': if (!isTypeBetween(node.startToken, node.endToken, ['WhiteSpace', 'LineBreak'])) { node.startToken.indentIncrease = true; node.endToken.indentDecrease = true; if (!isLineBreak(node.endToken.prev)) { insertBefore(node.endToken, nextLineFactory()); } } break; case 'Property': guaranteeNewLine(node); if (_config.spaces.other.beforePropertyNameValueSeparator) { !isWhiteSpace(node.key.endToken.next) && insertAfter(node.key.endToken, whiteSpaceFactory()); } if (_config.spaces.other.afterPropertyNameValueSeparator) { !isWhiteSpace(node.value.startToken.prev) && insertBefore(node.value.startToken, whiteSpaceFactory()); } break; case 'CallExpression': node.arguments.forEach(function (arg, i) { if (i !== 0) { insertBefore(arg.startToken, whiteSpaceFactory()); } }); if (_config.spaces.within.parentheses && node.arguments.length > 0) { if (!isWhiteSpace(node.arguments[0].startToken.prev)) { insertBefore(node.arguments[0].startToken, whiteSpaceFactory()); } if (!isWhiteSpace(node.arguments[node.arguments.length - 1].endToken.next)) { insertAfter(node.arguments[node.arguments.length - 1].endToken, whiteSpaceFactory()); } } break; case 'FunctionExpression': if (_config.spaces.before.functionExpressionParentheses || node.id) { insertAfter(node.startToken, whiteSpaceFactory()); } node.params.forEach(function (param, i, array) { if (param.endToken.next && param.endToken.next.type === 'Punctuator' && param.endToken.next.value === ',') { insertAfter(param.endToken.next, whiteSpaceFactory()); } }); if (_config.spaces.within.parentheses && node.params.length > 0) { if (!isWhiteSpace(node.params[0].startToken.prev)) { insertBefore(node.params[0].startToken, whiteSpaceFactory()); } if (!isWhiteSpace(node.params[node.params.length - 1].endToken.next)) { insertAfter(node.params[node.params.length - 1].endToken, whiteSpaceFactory()); } } break; case 'SequenceExpression': node.expressions.forEach(function (exp) { if (exp.endToken.next && exp.endToken.next.type === 'Punctuator' && exp.endToken.next.value === ',') { insertAfter(exp.endToken.next, whiteSpaceFactory()); } }); break; case 'UnaryExpression': if (['+', '-', '!'].indexOf(node.startToken.value) !== -1) { if (node.startToken.next.type === 'WhiteSpace') { removeToken(node.startToken.next); } } if (node.operator === 'void') { if (node.startToken.next.type !== 'WhiteSpace') { insertAfter(node.startToken, whiteSpaceFactory()); } } break; case 'WhileStatement': guaranteeNewLine(node); if (_config.spaces.within.parentheses && node.test) { if (!isWhiteSpace(node.test.startToken.prev)) { insertBefore(node.test.startToken, whiteSpaceFactory()); } if (!isWhiteSpace(node.test.endToken.next)) { insertAfter(node.test.endToken, whiteSpaceFactory()); } } break; case 'SwitchStatement': guaranteeNewLine(node); node.discriminant.endToken.next.indentIncrease = true; _config.spaces.before.leftBrace && insertAfter(node.discriminant.endToken.next, whiteSpaceFactory()); node.endToken.indentDecrease = true; if (!isLineBreak(node.endToken.prev)) { insertBefore(node.endToken, nextLineFactory()); } if (_config.spaces.within.parentheses && node.discriminant) { if (!isWhiteSpace(node.discriminant.startToken.prev)) { insertBefore(node.discriminant.startToken, whiteSpaceFactory()); } if (!isWhiteSpace(node.discriminant.endToken.next)) { insertAfter(node.discriminant.endToken, whiteSpaceFactory()); } } break; case 'SwitchCase': guaranteeNewLine(node); node.startToken.indentIncrease = true; node.endToken.indentDecrease = true; break; case 'ThrowStatement': guaranteeNewLine(node); break; case 'TryStatement': guaranteeNewLine(node); break; case 'CatchClause': if (_config.spaces.before.keywords && !isWhiteSpace(node.startToken.prev)) { insertBefore(node.startToken, whiteSpaceFactory()); } if (_config.spaces.within.parentheses && node.param) { if (!isWhiteSpace(node.param.startToken.prev)) { insertBefore(node.param.startToken, whiteSpaceFactory()); } if (!isWhiteSpace(node.param.endToken.next)) { insertAfter(node.param.endToken, whiteSpaceFactory()); } } break; case 'EmptyStatement': guaranteeNewLine(node); break; default: break; } }); // process indent start // 缩进这块要单独拿出来处理,不然很容易混乱 var indentLevel = 0; var processIndent = function (token) { if (token.indentIncrease) { indentLevel++; } if (token.type === 'LineBreak') { if (token.next && !isWhiteSpace(token.next) && !isLineBreak(token.next)) { // 如果下一个token是要减小缩进的,那它本身就是要减少缩进的 if (token.next.indentDecrease) { indentLevel--; token.next.indentDecrease = false; } insertAfter(token, indentFactory()); } } if (token.indentDecrease) { indentLevel--; } if (token.indentSelf) { indentLevel++; insertBefore(token, singleIndentFactory()); indentLevel--; } }; token = _ast.startToken; while (token !== _ast.endToken.next) { processIndent(token); token = token.next; } // process indent end // 单独的处理注释的逻辑 var processComment = function (token) { // 行尾注释保持跟前面的代码一个空格的距离 if (isLineComment(token) && token.prev && !isWhiteSpace(token.prev)) { insertBefore(token, whiteSpaceFactory()); } }; token = _ast.startToken; while (token !== _ast.endToken.next) { processComment(token); token = token.next; } var formattedString = _ast.toString(); if (_config.blankLines.atEndOfFile) { if (formattedString.charAt(formattedString.length - 1) !== _config.lineSeparator) { formattedString += _config.lineSeparator; } } else { formattedString = formattedString.trim(); } return formattedString; };
transformAfter: function(ast) { rocambole.recursive(ast, transform); }