diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Workers/Formatter/EsprimaFormatter.js | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Workers/Formatter/EsprimaFormatter.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Workers/Formatter/EsprimaFormatter.js | 934 |
1 files changed, 934 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Workers/Formatter/EsprimaFormatter.js b/Source/WebInspectorUI/UserInterface/Workers/Formatter/EsprimaFormatter.js new file mode 100644 index 000000000..7feae46b8 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Workers/Formatter/EsprimaFormatter.js @@ -0,0 +1,934 @@ +/* + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +// There is no standard for tokenizer types in JavaScript ASTs. +// This currently assumes Esprima tokens, ranges, and comments. +// <http://esprima.org/demo/parse.html> + +EsprimaFormatter = class EsprimaFormatter +{ + constructor(sourceText, sourceType, indentString = " ") + { + this._success = false; + + let tree = (function() { + try { + return esprima.parse(sourceText, {attachComment: true, range: true, tokens: true, sourceType}); + } catch (error) { + return null; + } + })(); + + if (!tree) + return; + + this._sourceText = sourceText; + this._builder = null; + + let walker = new ESTreeWalker(this._before.bind(this), this._after.bind(this)); + + this._tokens = tree.tokens; + this._tokensLength = this._tokens.length; + this._tokenIndex = 0; + + this._lineEndings = sourceText.lineEndings(); + this._lineEndingsIndex = 0; + + this._builder = new FormatterContentBuilder(indentString); + this._builder.setOriginalLineEndings(this._lineEndings.slice()); + + walker.walk(tree); + this._afterProgram(tree); + this._builder.appendNewline(); + + this._success = true; + } + + // Static + + static isWhitespace(ch) + { + return isECMAScriptWhitespace(ch) || isECMAScriptLineTerminator(ch); + } + + // Public + + get success() + { + return this._success; + } + + get formattedText() + { + if (!this._success) + return null; + return this._builder.formattedContent; + } + + get sourceMapData() + { + if (!this._success) + return null; + return this._builder.sourceMapData; + } + + // Private + + _insertNewlinesBeforeToken(token) + { + let force = false; + while (token.range[0] > this._lineEndings[this._lineEndingsIndex]) { + this._builder.appendNewline(force); + this._lineEndingsIndex++; + force = true; + } + } + + _insertComment(comment) + { + if (comment.type === "Line") + this._builder.appendToken("//" + comment.value, comment.range[0]); + else if (comment.type === "Block") + this._builder.appendToken("/*" + comment.value + "*/", comment.range[0]); + this._builder.appendNewline(); + comment.__handled = true; + } + + _insertSameLineTrailingComments(node) + { + let endOfLine = this._lineEndings[this._lineEndingsIndex]; + for (let comment of node.trailingComments) { + if (comment.range[0] > endOfLine) + break; + this._builder.removeLastNewline(); + this._builder.appendSpace(); + this._insertComment(comment); + } + } + + _insertCommentsAndNewlines(comments) + { + for (let comment of comments) { + // A previous node may have handled this as a trailing comment. + if (comment.__handled) + continue; + + // We expect the comment to be ahead of the last line. + // But if it is ahead of the next line ending, then it + // was preceded by an empty line. So include that. + if (comment.range[0] > this._lineEndings[this._lineEndingsIndex + 1]) + this._builder.appendNewline(true); + + this._insertComment(comment); + + // Remove line endings for this comment. + while (comment.range[1] > this._lineEndings[this._lineEndingsIndex]) + this._lineEndingsIndex++; + } + } + + _before(node) + { + if (!node.parent) + return; + + // Handle the tokens before this node, so in the context of our parent node. + while (this._tokenIndex < this._tokensLength && this._tokens[this._tokenIndex].range[0] < node.range[0]) { + let token = this._tokens[this._tokenIndex++]; + this._insertNewlinesBeforeToken(token); + this._handleTokenAtNode(token, node.parent); + } + + if (node.leadingComments) + this._insertCommentsAndNewlines(node.leadingComments); + } + + _after(node) + { + // Handle any other tokens inside of this node before exiting. + while (this._tokenIndex < this._tokensLength && this._tokens[this._tokenIndex].range[0] < node.range[1]) { + let token = this._tokens[this._tokenIndex++]; + this._insertNewlinesBeforeToken(token); + this._handleTokenAtNode(token, node); + } + + this._exitNode(node); + + if (node.trailingComments) + this._insertSameLineTrailingComments(node); + } + + _isInForHeader(node) + { + let parent = node.parent; + if (!parent) + return false; + + return (parent.type === "ForStatement" || parent.type === "ForInStatement" || parent.type === "ForOfStatement") && node !== parent.body; + } + + _isRangeWhitespace(from, to) + { + let substring = this._sourceText.substring(from, to); + for (let i = 0; i < substring.length; ++i) { + if (!EsprimaFormatter.isWhitespace(substring.charCodeAt(i))) + return false; + } + + return true; + } + + _handleTokenAtNode(token, node) + { + let builder = this._builder; + let nodeType = node.type; + let tokenType = token.type; + let tokenValue = token.value; + let tokenOffset = token.range[0]; + + // Very common types that just pass through. + if (nodeType === "MemberExpression" || nodeType === "Literal" || nodeType === "ThisExpression" || nodeType === "UpdateExpression") { + builder.appendToken(tokenValue, tokenOffset); + return; + } + + // Most identifiers just pass through, but a few are special. + if (nodeType === "Identifier") { + builder.appendToken(tokenValue, tokenOffset); + if (tokenValue === "async" && node.parent.type === "Property" && node.parent.value.async && token.range[1] !== node.range[1]) + builder.appendSpace(); + return; + } + + // Most newline handling is done with semicolons. However, we preserve + // newlines so code relying on automatic semicolon insertion should + // continue to work. + if (tokenValue === ";") { + // Avoid newlines for for loop header semicolons. + if (nodeType === "ForStatement") { + builder.appendToken(tokenValue, tokenOffset); + // Do not include spaces in empty for loop header sections: for(;;) + if (node.test || node.update) { + if (node.test && this._isRangeWhitespace(token.range[1], node.test.range[0])) + builder.appendSpace(); + else if (node.update && this._isRangeWhitespace(token.range[1], node.update.range[0])) + builder.appendSpace(); + } + return; + } + + // Sometimes more specific nodes gets the semicolon inside a for loop header. + // Avoid newlines. Example is a VariableDeclaration in: for (var a, b; ...; ...). + if (this._isInForHeader(node)) { + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + + // Avoid newline for single statement arrow functions with semicolons. + if (node.parent.type === "BlockStatement" && node.parent.body.length === 1 && node.parent.parent && node.parent.parent.type === "ArrowFunctionExpression") { + builder.appendToken(tokenValue, tokenOffset); + return; + } + + builder.appendToken(tokenValue, tokenOffset); + builder.appendNewline(); + return; + } + + if (nodeType === "CallExpression" || nodeType === "ArrayExpression" || nodeType === "ArrayPattern" || nodeType === "ObjectPattern" || nodeType === "SequenceExpression") { + if (tokenValue === ",") { + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "LogicalExpression" || nodeType === "BinaryExpression") { + if (tokenValue !== "(" && tokenValue !== ")") { + builder.appendSpace(); + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + if (tokenType === "Keyword") { + // in, instanceof, ... + builder.appendSpace(); + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "BlockStatement") { + let isSingleStatementArrowFunction = node.parent.type === "ArrowFunctionExpression" && node.body.length === 1; + if (tokenValue === "{") { + // Class methods we put the opening brace on its own line. + if (node.parent && node.parent.parent && node.parent.parent.type === "MethodDefinition" && node.body.length) { + builder.appendNewline(); + builder.appendToken(tokenValue, tokenOffset); + builder.appendNewline(); + builder.indent(); + return; + } + builder.appendToken(tokenValue, tokenOffset); + if (node.body.length && !isSingleStatementArrowFunction) + builder.appendNewline(); + builder.indent(); + return; + } + if (tokenValue === "}") { + if (node.body.length && !isSingleStatementArrowFunction) + builder.appendNewline(); + builder.dedent(); + builder.appendToken(tokenValue, tokenOffset); + return; + } + console.warn("Unexpected BlockStatement token", token); + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "VariableDeclaration") { + if (tokenValue === ",") { + if (this._isInForHeader(node)) { + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + builder.appendToken(tokenValue, tokenOffset); + builder.appendNewline(); + return; + } + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + // If this is a multiple variable declaration, indent. + if (node.declarations.length > 1 && !node.__autoDedent) { + builder.indent(); + node.__autoDedent = true; + } + return; + } + + if (nodeType === "VariableDeclarator" || nodeType === "AssignmentExpression") { + if (tokenType === "Punctuator") { + let surroundWithSpaces = tokenValue !== "(" && tokenValue !== ")"; + if (surroundWithSpaces) + builder.appendSpace(); + builder.appendToken(tokenValue, tokenOffset); + if (surroundWithSpaces) + builder.appendSpace(); + return; + } + console.warn("Unexpected " + nodeType + " token", token); + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "IfStatement") { + if (tokenType === "Keyword") { + if (tokenValue === "else") { + if (node.__autoDedent) { + builder.dedent(); + node.__autoDedent = false; + } + builder.appendSpace(); + builder.appendToken(tokenValue, tokenOffset); + + if (node.alternate && (node.alternate.type !== "BlockStatement" && node.alternate.type !== "IfStatement")) { + builder.appendNewline(); + builder.indent(); + node.__autoDedent = true; + } else + builder.appendSpace(); + return; + } + + console.assert(tokenValue === "if", token); + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + + // The last ')' in if(){}. + if (tokenValue === ")" && this._isRangeWhitespace(token.range[1], node.consequent.range[0])) { + if (node.consequent.type === "BlockStatement") { + // The block will handle indenting. + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + + builder.appendToken(tokenValue, tokenOffset); + builder.appendNewline(); + builder.indent(); + node.__autoDedent = true; + return; + } + + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "ReturnStatement") { + if (tokenValue === ";") { + builder.appendToken(tokenValue, tokenOffset); + return; + } + builder.appendToken(tokenValue, tokenOffset); + if (node.argument) { + // Multi-line LogicalExpressions (&& and ||) are a common style of return + // statement that benefits from indentation. + if (node.argument.type === "LogicalExpression" && !node.__autoDedent) { + builder.indent(); + node.__autoDedent = true; + } + builder.appendSpace(); + } + return; + } + + if (nodeType === "FunctionDeclaration" || nodeType === "FunctionExpression") { + if (tokenType === "Keyword") { + console.assert(tokenValue === "function", token); + builder.appendToken(tokenValue, tokenOffset); + if (node.id) + builder.appendSpace(); + return; + } + if (tokenType === "Punctuator") { + if (tokenValue === "*") { + builder.removeLastWhitespace(); + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + builder.appendToken(tokenValue, tokenOffset); + if (tokenValue === ")" || tokenValue === ",") + builder.appendSpace(); + return; + } + if (tokenType === "Identifier" && tokenValue === "async") { + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "WhileStatement" || nodeType === "WithStatement") { + if (tokenValue === "while" || tokenValue === "with") { + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + + // The last ')' in while(){} or with(){}. + if (tokenValue === ")" && this._isRangeWhitespace(token.range[1], node.body.range[0])) { + if (node.body.type === "BlockStatement") { + // The block will handle indenting. + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + + builder.appendToken(tokenValue, tokenOffset); + builder.appendNewline(); + builder.indent(); + node.__autoDedent = true; + return; + } + + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "ForStatement" || nodeType === "ForOfStatement" || nodeType === "ForInStatement") { + if (tokenValue === "for") { + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + if (tokenValue === "in" || tokenValue === "of") { + builder.appendSpace(); + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + + // The last ')' in for(){}. + if (tokenValue === ")" && this._isRangeWhitespace(token.range[1], node.body.range[0])) { + if (node.body.type === "BlockStatement") { + // The block will handle indenting. + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + + builder.appendToken(tokenValue, tokenOffset); + builder.appendNewline(); + builder.indent(); + node.__autoDedent = true; + return; + } + + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "SwitchStatement") { + if (tokenValue === "switch") { + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + if (tokenType === "Punctuator") { + if (tokenValue === ")") { + // FIXME: Would be nice to only add a space if this the ')' closing the discriminant: switch((1)){} + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + if (tokenValue === "{") { + builder.appendToken(tokenValue, tokenOffset); + builder.appendNewline(); + return; + } + if (tokenValue === "}") { + builder.appendNewline(); + builder.appendToken(tokenValue, tokenOffset); + builder.appendNewline(); + return; + } + } + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "SwitchCase") { + if (tokenType === "Keyword") { + if (tokenValue === "case") { + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + if (tokenValue === "default") { + builder.appendToken(tokenValue, tokenOffset); + return; + } + console.warn("Unexpected SwitchCase Keyword token", token); + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (tokenValue === ":") { + builder.appendToken(tokenValue, tokenOffset); + builder.appendNewline(); + if (node.consequent.length) { + builder.indent(); + node.__autoDedent = true; + } + return; + } + + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "NewExpression") { + if (tokenValue === "new") { + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + if (tokenValue === ",") { + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "DoWhileStatement") { + if (tokenValue === "do") { + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + if (tokenValue === "while") { + builder.appendSpace(); + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "ThrowStatement") { + if (tokenValue === "throw") { + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "UnaryExpression") { + if (tokenType === "Keyword") { + // typeof, instanceof, void + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "ConditionalExpression") { + if (tokenValue === "?" || tokenValue === ":") { + builder.appendSpace(); + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "ObjectExpression") { + // FIXME: It would be nice to detect if node.properties is very short + // and the node.properties themselves are very small then inline them + // instead of always adding newlines. Objects like: {a}, {a:1} but + // not objects like {a:function(){1;}}. + if (tokenValue === "{") { + builder.appendToken(tokenValue, tokenOffset); + if (node.properties.length) { + builder.appendNewline(); + builder.indent(); + } + return; + } + if (tokenValue === "}") { + if (node.properties.length) { + builder.appendNewline(); + builder.dedent(); + } + builder.appendToken(tokenValue, tokenOffset); + return; + } + if (tokenValue === ",") { + builder.appendToken(tokenValue, tokenOffset); + if (node.properties.length) + builder.appendNewline(); + else + builder.appendSpace(); + return; + } + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "ArrowFunctionExpression") { + if (tokenType === "Punctuator") { + if (tokenValue === "=>") { + builder.appendSpace(); + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + if (tokenValue === ",") { + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + } + builder.appendToken(tokenValue, tokenOffset); + if (tokenType === "Identifier" && tokenValue === "async") + builder.appendSpace(); + return; + } + + if (nodeType === "AwaitExpression") { + builder.appendToken(tokenValue, tokenOffset); + if (tokenType === "Identifier" && tokenValue === "await") + builder.appendSpace(); + return; + } + + if (nodeType === "Property") { + console.assert(tokenValue === ":" || tokenValue === "get" || tokenValue === "set" || tokenValue === "async" || tokenValue === "*" || tokenValue === "[" || tokenValue === "]" || tokenValue === "(" || tokenValue === ")", token); + builder.appendToken(tokenValue, tokenOffset); + if (tokenValue === ":" || tokenValue === "get" || tokenValue === "set" || tokenValue === "async") + builder.appendSpace(); + return; + } + + if (nodeType === "MethodDefinition") { + console.assert(tokenValue === "static" || tokenValue === "get" || tokenValue === "set" || tokenValue === "async" || tokenValue === "*" || tokenValue === "[" || tokenValue === "]" || tokenValue === "(" || tokenValue === ")", token); + builder.appendToken(tokenValue, tokenOffset); + if (tokenValue === "static" || tokenValue === "get" || tokenValue === "set" || tokenValue === "async") + builder.appendSpace(); + return; + } + + if (nodeType === "BreakStatement" || nodeType === "ContinueStatement") { + builder.appendToken(tokenValue, tokenOffset); + if (tokenType === "Keyword" && node.label) + builder.appendSpace(); + return; + } + + if (nodeType === "LabeledStatement") { + console.assert(tokenValue === ":", token); + builder.appendToken(tokenValue, tokenOffset); + builder.appendNewline(); + return; + } + + if (nodeType === "TryStatement") { + if (tokenValue === "try") { + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + if (tokenValue === "finally") { + builder.appendSpace(); + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + console.warn("Unexpected TryStatement token", token); + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "CatchClause") { + if (tokenValue === "catch") { + builder.appendSpace(); + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + if (tokenValue === ")") { + // The block will handle indenting. + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "ClassExpression" || nodeType === "ClassDeclaration") { + if (tokenValue === "class") { + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + if (tokenValue === "extends") { + builder.appendSpace(); + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "ClassBody") { + if (tokenValue === "{") { + if (node.parent.id) + builder.appendSpace(); + builder.appendToken(tokenValue, tokenOffset); + if (node.body.length) + builder.appendNewline(); + builder.indent(); + return; + } + if (tokenValue === "}") { + if (node.body.length) + builder.appendNewline(); + builder.dedent(); + builder.appendToken(tokenValue, tokenOffset); + builder.appendNewline(); + return; + } + console.warn("Unexpected ClassBody token", token); + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "YieldExpression") { + if (tokenType === "Keyword") { + console.assert(tokenValue === "yield", token); + builder.appendToken(tokenValue, tokenOffset); + if (node.argument) + builder.appendSpace(); + return; + } + builder.appendToken(tokenValue, tokenOffset); + return; + } + + if (nodeType === "ImportDeclaration" || nodeType === "ExportNamedDeclaration") { + if (tokenValue === "}" || (tokenType === "Identifier" && tokenValue === "from")) + builder.appendSpace(); + builder.appendToken(tokenValue, tokenOffset); + if (tokenValue !== "}") + builder.appendSpace(); + return; + } + + if (nodeType === "ExportSpecifier" || nodeType === "ImportSpecifier") { + if (tokenType === "Identifier" && tokenValue === "as") + builder.appendSpace(); + builder.appendToken(tokenValue, tokenOffset); + builder.appendSpace(); + return; + } + + if (nodeType === "ExportAllDeclaration" || nodeType === "ExportDefaultDeclaration" || nodeType === "ImportDefaultSpecifier" || nodeType === "ImportNamespaceSpecifier") { + builder.appendToken(tokenValue, tokenOffset); + if (tokenValue !== "(" && tokenValue !== ")") + builder.appendSpace(); + return; + } + + // Include these here so we get only get warnings about unhandled nodes. + if (nodeType === "ExpressionStatement" + || nodeType === "SpreadElement" + || nodeType === "SpreadProperty" + || nodeType === "Super" + || nodeType === "Import" + || nodeType === "MetaProperty" + || nodeType === "RestElement" + || nodeType === "RestProperty" + || nodeType === "TemplateElement" + || nodeType === "TemplateLiteral" + || nodeType === "DebuggerStatement" + || nodeType === "AssignmentPattern") { + builder.appendToken(tokenValue, tokenOffset); + return; + } + + // Warn about possible unhandled types. + console.warn(nodeType, tokenValue); + + // Fallback behavior in case there are unhandled types. + builder.appendToken(tokenValue, tokenOffset); + + if (tokenType === "Keyword") + builder.appendSpace(); + } + + _exitNode(node) + { + if (node.__autoDedent) + this._builder.dedent(); + + if (node.type === "BlockStatement") { + if (node.parent) { + // Newline after if(){} + if (node.parent.type === "IfStatement" && (!node.parent.alternate || node.parent.consequent !== node)) { + this._builder.appendNewline(); + return; + } + // Newline after for(){} + if (node.parent.type === "ForStatement" || node.parent.type === "ForOfStatement" || node.parent.type === "ForInStatement") { + this._builder.appendNewline(); + return; + } + // Newline after while(){} + if (node.parent.type === "WhileStatement") { + this._builder.appendNewline(); + return; + } + // Newline after function(){} + if (node.parent.type === "FunctionDeclaration") { + this._builder.appendNewline(); + return; + } + // Newline after catch block in try{}catch(e){} + if (node.parent.type === "CatchClause" && !node.parent.parent.finalizer) { + this._builder.appendNewline(); + return; + } + // Newline after finally block in try{}catch(e){}finally{} + if (node.parent.type === "TryStatement" && node.parent.finalizer && node.parent.finalizer === node) { + this._builder.appendNewline(); + return; + } + // Newline after class body methods in class {method(){}} + if (node.parent.parent && node.parent.parent.type === "MethodDefinition") { + this._builder.appendNewline(); + return; + } + // Newline after anonymous block inside a block or program. + if (node.parent.type === "BlockStatement" || node.parent.type === "Program") { + this._builder.appendNewline(); + return; + } + } + return; + } + } + + _afterProgram(programNode) + { + if (!programNode) + return; + + console.assert(programNode.type === "Program"); + + // If a program ends with comments, Esprima puts those + // comments on the last node of the body. However, if + // a program is entirely comments, then they are + // leadingComments on the program node. + + if (programNode.body.length) { + let lastNode = programNode.body[programNode.body.length - 1]; + if (lastNode.trailingComments) + this._insertCommentsAndNewlines(lastNode.trailingComments); + } else { + if (programNode.leadingComments) + this._insertCommentsAndNewlines(programNode.leadingComments); + console.assert(!programNode.trailingComments); + } + } +}; + +EsprimaFormatter.SourceType = { + Script: "script", + Module: "module", +}; |