summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Workers/Formatter/EsprimaFormatter.js
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Workers/Formatter/EsprimaFormatter.js
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Workers/Formatter/EsprimaFormatter.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/Workers/Formatter/EsprimaFormatter.js934
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",
+};