| /* |
| * Copyright (C) 2011 Google 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: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * 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. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT |
| * OWNER OR 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. |
| */ |
| |
| /** |
| * @unrestricted |
| */ |
| export class JavaScriptFormatter { |
| /** |
| * @param {!FormatterWorker.FormattedContentBuilder} builder |
| */ |
| constructor(builder) { |
| this._builder = builder; |
| } |
| |
| /** |
| * @param {string} text |
| * @param {!Array<number>} lineEndings |
| * @param {number} fromOffset |
| * @param {number} toOffset |
| */ |
| format(text, lineEndings, fromOffset, toOffset) { |
| this._fromOffset = fromOffset; |
| this._toOffset = toOffset; |
| this._content = text.substring(this._fromOffset, this._toOffset); |
| this._lastLineNumber = 0; |
| this._tokenizer = new FormatterWorker.AcornTokenizer(this._content); |
| const options = {ranges: false, preserveParens: true, allowImportExportEverywhere: true, ecmaVersion: 2020}; |
| const ast = acorn.parse(this._content, options); |
| const walker = new FormatterWorker.ESTreeWalker(this._beforeVisit.bind(this), this._afterVisit.bind(this)); |
| walker.walk(ast); |
| } |
| |
| /** |
| * @param {?Acorn.TokenOrComment} token |
| * @param {string} format |
| */ |
| _push(token, format) { |
| for (let i = 0; i < format.length; ++i) { |
| if (format[i] === 's') { |
| this._builder.addSoftSpace(); |
| } else if (format[i] === 'S') { |
| this._builder.addHardSpace(); |
| } else if (format[i] === 'n') { |
| this._builder.addNewLine(); |
| } else if (format[i] === '>') { |
| this._builder.increaseNestingLevel(); |
| } else if (format[i] === '<') { |
| this._builder.decreaseNestingLevel(); |
| } else if (format[i] === 't') { |
| if (this._tokenizer.tokenLineStart() - this._lastLineNumber > 1) { |
| this._builder.addNewLine(true); |
| } |
| this._lastLineNumber = this._tokenizer.tokenLineEnd(); |
| this._builder.addToken(this._content.substring(token.start, token.end), this._fromOffset + token.start); |
| } |
| } |
| } |
| |
| /** |
| * @param {!ESTree.Node} node |
| */ |
| _beforeVisit(node) { |
| if (!node.parent) { |
| return; |
| } |
| while (this._tokenizer.peekToken() && this._tokenizer.peekToken().start < node.start) { |
| const token = /** @type {!Acorn.TokenOrComment} */ (this._tokenizer.nextToken()); |
| const format = this._formatToken(node.parent, token); |
| this._push(token, format); |
| } |
| } |
| |
| /** |
| * @param {!ESTree.Node} node |
| */ |
| _afterVisit(node) { |
| while (this._tokenizer.peekToken() && this._tokenizer.peekToken().start < node.end) { |
| const token = /** @type {!Acorn.TokenOrComment} */ (this._tokenizer.nextToken()); |
| const format = this._formatToken(node, token); |
| this._push(token, format); |
| } |
| this._push(null, this._finishNode(node)); |
| } |
| |
| /** |
| * @param {!ESTree.Node} node |
| * @return {boolean} |
| */ |
| _inForLoopHeader(node) { |
| const parent = node.parent; |
| if (!parent) { |
| return false; |
| } |
| if (parent.type === 'ForStatement') { |
| return node === parent.init || node === parent.test || node === parent.update; |
| } |
| if (parent.type === 'ForInStatement' || parent.type === 'ForOfStatement') { |
| return node === parent.left || parent.right; |
| } |
| return false; |
| } |
| |
| /** |
| * @param {!ESTree.Node} node |
| * @param {!Acorn.TokenOrComment} token |
| * @return {string} |
| */ |
| _formatToken(node, token) { |
| const AT = FormatterWorker.AcornTokenizer; |
| if (AT.lineComment(token)) { |
| return 'tn'; |
| } |
| if (AT.blockComment(token)) { |
| return 'tn'; |
| } |
| if (node.type === 'ContinueStatement' || node.type === 'BreakStatement') { |
| return node.label && AT.keyword(token) ? 'ts' : 't'; |
| } else if (node.type === 'Identifier') { |
| return 't'; |
| } else if (node.type === 'ReturnStatement') { |
| if (AT.punctuator(token, ';')) { |
| return 't'; |
| } |
| return node.argument ? 'ts' : 't'; |
| } else if (node.type === 'Property') { |
| if (AT.punctuator(token, ':')) { |
| return 'ts'; |
| } |
| return 't'; |
| } else if (node.type === 'ArrayExpression') { |
| if (AT.punctuator(token, ',')) { |
| return 'ts'; |
| } |
| return 't'; |
| } else if (node.type === 'LabeledStatement') { |
| if (AT.punctuator(token, ':')) { |
| return 'ts'; |
| } |
| } else if ( |
| node.type === 'LogicalExpression' || node.type === 'AssignmentExpression' || node.type === 'BinaryExpression') { |
| if (AT.punctuator(token) && !AT.punctuator(token, '()')) { |
| return 'sts'; |
| } |
| } else if (node.type === 'ConditionalExpression') { |
| if (AT.punctuator(token, '?:')) { |
| return 'sts'; |
| } |
| } else if (node.type === 'VariableDeclarator') { |
| if (AT.punctuator(token, '=')) { |
| return 'sts'; |
| } |
| } else if (node.type === 'ObjectPattern') { |
| if (node.parent && node.parent.type === 'VariableDeclarator' && AT.punctuator(token, '{')) { |
| return 'st'; |
| } |
| if (AT.punctuator(token, ',')) { |
| return 'ts'; |
| } |
| } else if (node.type === 'FunctionDeclaration') { |
| if (AT.punctuator(token, ',)')) { |
| return 'ts'; |
| } |
| } else if (node.type === 'FunctionExpression') { |
| if (AT.punctuator(token, ',)')) { |
| return 'ts'; |
| } |
| if (AT.keyword(token, 'function')) { |
| return node.id ? 'ts' : 't'; |
| } |
| } else if (node.type === 'WithStatement') { |
| if (AT.punctuator(token, ')')) { |
| return node.body && node.body.type === 'BlockStatement' ? 'ts' : 'tn>'; |
| } |
| } else if (node.type === 'SwitchStatement') { |
| if (AT.punctuator(token, '{')) { |
| return 'tn>'; |
| } |
| if (AT.punctuator(token, '}')) { |
| return 'n<tn'; |
| } |
| if (AT.punctuator(token, ')')) { |
| return 'ts'; |
| } |
| } else if (node.type === 'SwitchCase') { |
| if (AT.keyword(token, 'case')) { |
| return 'n<ts'; |
| } |
| if (AT.keyword(token, 'default')) { |
| return 'n<t'; |
| } |
| if (AT.punctuator(token, ':')) { |
| return 'tn>'; |
| } |
| } else if (node.type === 'VariableDeclaration') { |
| if (AT.punctuator(token, ',')) { |
| let allVariablesInitialized = true; |
| const declarations = /** @type {!Array.<!ESTree.Node>} */ (node.declarations); |
| for (let i = 0; i < declarations.length; ++i) { |
| allVariablesInitialized = allVariablesInitialized && !!declarations[i].init; |
| } |
| return !this._inForLoopHeader(node) && allVariablesInitialized ? 'nSSts' : 'ts'; |
| } |
| } else if (node.type === 'BlockStatement') { |
| if (AT.punctuator(token, '{')) { |
| return node.body.length ? 'tn>' : 't'; |
| } |
| if (AT.punctuator(token, '}')) { |
| return node.body.length ? 'n<t' : 't'; |
| } |
| } else if (node.type === 'CatchClause') { |
| if (AT.punctuator(token, ')')) { |
| return 'ts'; |
| } |
| } else if (node.type === 'ObjectExpression') { |
| if (!node.properties.length) { |
| return 't'; |
| } |
| if (AT.punctuator(token, '{')) { |
| return 'tn>'; |
| } |
| if (AT.punctuator(token, '}')) { |
| return 'n<t'; |
| } |
| if (AT.punctuator(token, ',')) { |
| return 'tn'; |
| } |
| } else if (node.type === 'IfStatement') { |
| if (AT.punctuator(token, ')')) { |
| return node.consequent && node.consequent.type === 'BlockStatement' ? 'ts' : 'tn>'; |
| } |
| |
| if (AT.keyword(token, 'else')) { |
| const preFormat = node.consequent && node.consequent.type === 'BlockStatement' ? 'st' : 'n<t'; |
| let postFormat = 'n>'; |
| if (node.alternate && (node.alternate.type === 'BlockStatement' || node.alternate.type === 'IfStatement')) { |
| postFormat = 's'; |
| } |
| return preFormat + postFormat; |
| } |
| } else if (node.type === 'CallExpression') { |
| if (AT.punctuator(token, ',')) { |
| return 'ts'; |
| } |
| } else if (node.type === 'SequenceExpression' && AT.punctuator(token, ',')) { |
| return node.parent && node.parent.type === 'SwitchCase' ? 'ts' : 'tn'; |
| } else if (node.type === 'ForStatement' || node.type === 'ForOfStatement' || node.type === 'ForInStatement') { |
| if (AT.punctuator(token, ';')) { |
| return 'ts'; |
| } |
| if (AT.keyword(token, 'in') || AT.identifier(token, 'of')) { |
| return 'sts'; |
| } |
| |
| if (AT.punctuator(token, ')')) { |
| return node.body && node.body.type === 'BlockStatement' ? 'ts' : 'tn>'; |
| } |
| } else if (node.type === 'WhileStatement') { |
| if (AT.punctuator(token, ')')) { |
| return node.body && node.body.type === 'BlockStatement' ? 'ts' : 'tn>'; |
| } |
| } else if (node.type === 'DoWhileStatement') { |
| const blockBody = node.body && node.body.type === 'BlockStatement'; |
| if (AT.keyword(token, 'do')) { |
| return blockBody ? 'ts' : 'tn>'; |
| } |
| if (AT.keyword(token, 'while')) { |
| return blockBody ? 'sts' : 'n<ts'; |
| } |
| } else if (node.type === 'ClassBody') { |
| if (AT.punctuator(token, '{')) { |
| return 'stn>'; |
| } |
| if (AT.punctuator(token, '}')) { |
| return '<ntn'; |
| } |
| return 't'; |
| } else if (node.type === 'YieldExpression') { |
| return 't'; |
| } else if (node.type === 'Super') { |
| return 't'; |
| } else if (node.type === 'ImportExpression') { |
| return 't'; |
| } else if (node.type === 'ExportAllDeclaration') { |
| if (AT.punctuator(token, '*')) { |
| return 'sts'; |
| } |
| return 't'; |
| } else if (node.type === 'ExportNamedDeclaration' || node.type === 'ImportDeclaration') { |
| if (AT.punctuator(token, '{')) { |
| return 'st'; |
| } |
| if (AT.punctuator(token, ',')) { |
| return 'ts'; |
| } |
| if (AT.punctuator(token, '}')) { |
| return node.source ? 'ts' : 't'; |
| } |
| if (AT.punctuator(token, '*')) { |
| return 'sts'; |
| } |
| return 't'; |
| } |
| return AT.keyword(token) && !AT.keyword(token, 'this') ? 'ts' : 't'; |
| } |
| |
| /** |
| * @param {!ESTree.Node} node |
| * @return {string} |
| */ |
| _finishNode(node) { |
| if (node.type === 'WithStatement') { |
| if (node.body && node.body.type !== 'BlockStatement') { |
| return 'n<'; |
| } |
| } else if (node.type === 'VariableDeclaration') { |
| if (!this._inForLoopHeader(node)) { |
| return 'n'; |
| } |
| } else if (node.type === 'ForStatement' || node.type === 'ForOfStatement' || node.type === 'ForInStatement') { |
| if (node.body && node.body.type !== 'BlockStatement') { |
| return 'n<'; |
| } |
| } else if (node.type === 'BlockStatement') { |
| if (node.parent && node.parent.type === 'IfStatement' && node.parent.alternate && |
| node.parent.consequent === node) { |
| return ''; |
| } |
| if (node.parent && node.parent.type === 'FunctionExpression' && node.parent.parent && |
| node.parent.parent.type === 'Property') { |
| return ''; |
| } |
| if (node.parent && node.parent.type === 'FunctionExpression' && node.parent.parent && |
| node.parent.parent.type === 'VariableDeclarator') { |
| return ''; |
| } |
| if (node.parent && node.parent.type === 'FunctionExpression' && node.parent.parent && |
| node.parent.parent.type === 'CallExpression') { |
| return ''; |
| } |
| if (node.parent && node.parent.type === 'DoWhileStatement') { |
| return ''; |
| } |
| if (node.parent && node.parent.type === 'TryStatement' && node.parent.block === node) { |
| return 's'; |
| } |
| if (node.parent && node.parent.type === 'CatchClause' && node.parent.parent.finalizer) { |
| return 's'; |
| } |
| return 'n'; |
| } else if (node.type === 'WhileStatement') { |
| if (node.body && node.body.type !== 'BlockStatement') { |
| return 'n<'; |
| } |
| } else if (node.type === 'IfStatement') { |
| if (node.alternate) { |
| if (node.alternate.type !== 'BlockStatement' && node.alternate.type !== 'IfStatement') { |
| return '<'; |
| } |
| } else if (node.consequent) { |
| if (node.consequent.type !== 'BlockStatement') { |
| return '<'; |
| } |
| } |
| } else if ( |
| node.type === 'BreakStatement' || node.type === 'ContinueStatement' || node.type === 'ThrowStatement' || |
| node.type === 'ReturnStatement' || node.type === 'ExpressionStatement') { |
| return 'n'; |
| } else if ( |
| node.type === 'ImportDeclaration' || node.type === 'ExportAllDeclaration' || |
| node.type === 'ExportDefaultDeclaration' || node.type === 'ExportNamedDeclaration') { |
| return 'n'; |
| } |
| return ''; |
| } |
| } |
| |
| /* Legacy exported object */ |
| self.FormatterWorker = self.FormatterWorker || {}; |
| |
| /* Legacy exported object */ |
| FormatterWorker = FormatterWorker || {}; |
| |
| /** @constructor */ |
| FormatterWorker.JavaScriptFormatter = JavaScriptFormatter; |