/* * 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. */ import {AcornTokenizer} from './AcornTokenizer.js'; import {ESTreeWalker} from './ESTreeWalker.js'; import {FormattedContentBuilder} from './FormattedContentBuilder.js'; // eslint-disable-line no-unused-vars /** * @unrestricted */ export class JavaScriptFormatter { /** * @param {!FormattedContentBuilder} builder */ constructor(builder) { this._builder = builder; } /** * @param {string} text * @param {!Array} 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 AcornTokenizer(this._content); const options = {ranges: false, preserveParens: true, allowImportExportEverywhere: true, ecmaVersion: 2020}; const ast = acorn.parse(this._content, options); const walker = new 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 = 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'; } if (node.type === 'Identifier') { return 't'; } if (node.type === 'ReturnStatement') { if (AT.punctuator(token, ';')) { return 't'; } return node.argument ? 'ts' : 't'; } if (node.type === 'AwaitExpression') { if (AT.punctuator(token, ';')) { return 't'; } return node.argument ? 'ts' : 't'; } if (node.type === 'Property') { if (AT.punctuator(token, ':')) { return 'ts'; } return 't'; } if (node.type === 'ArrayExpression') { if (AT.punctuator(token, ',')) { return 'ts'; } return 't'; } 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'; } } else if (node.type === 'VariableDeclaration') { if (AT.punctuator(token, ',')) { let allVariablesInitialized = true; const declarations = /** @type {!Array.} */ (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'; } if (AT.punctuator(token, '}')) { return 'n'; } if (AT.keyword(token, 'else')) { const preFormat = node.consequent && node.consequent.type === 'BlockStatement' ? 'st' : 'n'; } } 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'; } if (AT.punctuator(token, '}')) { return '