diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /devtools/client/debugger/src/workers/pretty-print | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/debugger/src/workers/pretty-print')
7 files changed, 3747 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/workers/pretty-print/LICENSE.md b/devtools/client/debugger/src/workers/pretty-print/LICENSE.md new file mode 100644 index 0000000000..cc8e9a752c --- /dev/null +++ b/devtools/client/debugger/src/workers/pretty-print/LICENSE.md @@ -0,0 +1,23 @@ +Copyright (c) 2013, Nick Fitzgerald +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. + +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 HOLDER 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. diff --git a/devtools/client/debugger/src/workers/pretty-print/index.js b/devtools/client/debugger/src/workers/pretty-print/index.js new file mode 100644 index 0000000000..4b151b39e4 --- /dev/null +++ b/devtools/client/debugger/src/workers/pretty-print/index.js @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +import { WorkerDispatcher } from "devtools/client/shared/worker-utils"; + +const WORKER_URL = + "resource://devtools/client/debugger/dist/pretty-print-worker.js"; + +export class PrettyPrintDispatcher extends WorkerDispatcher { + constructor(jestUrl) { + super(jestUrl || WORKER_URL); + } + + #prettyPrintTask = this.task("prettyPrint"); + #prettyPrintInlineScriptTask = this.task("prettyPrintInlineScript"); + #getSourceMapForTask = this.task("getSourceMapForTask"); + + prettyPrint(options) { + return this.#prettyPrintTask(options); + } + + prettyPrintInlineScript(options) { + return this.#prettyPrintInlineScriptTask(options); + } + + getSourceMap(taskId) { + return this.#getSourceMapForTask(taskId); + } +} diff --git a/devtools/client/debugger/src/workers/pretty-print/moz.build b/devtools/client/debugger/src/workers/pretty-print/moz.build new file mode 100644 index 0000000000..b7223ac81a --- /dev/null +++ b/devtools/client/debugger/src/workers/pretty-print/moz.build @@ -0,0 +1,10 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DIRS += [] + +CompiledModules( + "index.js", +) diff --git a/devtools/client/debugger/src/workers/pretty-print/pretty-fast.js b/devtools/client/debugger/src/workers/pretty-print/pretty-fast.js new file mode 100644 index 0000000000..44b07f4eda --- /dev/null +++ b/devtools/client/debugger/src/workers/pretty-print/pretty-fast.js @@ -0,0 +1,1178 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +/* eslint-disable complexity */ + +var acorn = require("acorn"); +var sourceMap = require("source-map"); +const NEWLINE_CODE = 10; + +export function prettyFast(input, options) { + return new PrettyFast(options).getPrettifiedCodeAndSourceMap(input); +} + +// If any of these tokens are seen before a "[" token, we know that "[" token +// is the start of an array literal, rather than a property access. +// +// The only exception is "}", which would need to be disambiguated by +// parsing. The majority of the time, an open bracket following a closing +// curly is going to be an array literal, so we brush the complication under +// the rug, and handle the ambiguity by always assuming that it will be an +// array literal. +const PRE_ARRAY_LITERAL_TOKENS = new Set([ + "typeof", + "void", + "delete", + "case", + "do", + "=", + "in", + "of", + "...", + "{", + "*", + "/", + "%", + "else", + ";", + "++", + "--", + "+", + "-", + "~", + "!", + ":", + "?", + ">>", + ">>>", + "<<", + "||", + "&&", + "<", + ">", + "<=", + ">=", + "instanceof", + "&", + "^", + "|", + "==", + "!=", + "===", + "!==", + ",", + "}", +]); + +// If any of these tokens are seen before a "{" token, we know that "{" token +// is the start of an object literal, rather than the start of a block. +const PRE_OBJECT_LITERAL_TOKENS = new Set([ + "typeof", + "void", + "delete", + "=", + "in", + "of", + "...", + "*", + "/", + "%", + "++", + "--", + "+", + "-", + "~", + "!", + ">>", + ">>>", + "<<", + "<", + ">", + "<=", + ">=", + "instanceof", + "&", + "^", + "|", + "==", + "!=", + "===", + "!==", +]); + +class PrettyFast { + /** + * @param {Object} options: Provides configurability of the pretty printing. + * @param {String} options.url: The URL string of the ugly JS code. + * @param {String} options.indent: The string to indent code by. + * @param {SourceMapGenerator} options.sourceMapGenerator: An optional sourceMapGenerator + * the mappings will be added to. + * @param {Boolean} options.prefixWithNewLine: When true, the pretty printed code will start + * with a line break + * @param {Integer} options.originalStartLine: The line the passed script starts at (1-based). + * This is used for inline scripts where we need to account for the lines + * before the script tag + * @param {Integer} options.originalStartColumn: The column the passed script starts at (1-based). + * This is used for inline scripts where we need to account for the position + * of the script tag within the line. + * @param {Integer} options.generatedStartLine: The line where the pretty printed script + * will start at (1-based). This is used for pretty printing HTML file, + * where we might have handle previous inline scripts that impact the + * position of this script. + */ + constructor(options = {}) { + // The level of indents deep we are. + this.#indentLevel = 0; + this.#indentChar = options.indent; + + // We will handle mappings between ugly and pretty printed code in this SourceMapGenerator. + this.#sourceMapGenerator = + options.sourceMapGenerator || + new sourceMap.SourceMapGenerator({ + file: options.url, + }); + + this.#file = options.url; + this.#hasOriginalStartLine = "originalStartLine" in options; + this.#hasOriginalStartColumn = "originalStartColumn" in options; + this.#hasGeneratedStartLine = "generatedStartLine" in options; + this.#originalStartLine = options.originalStartLine; + this.#originalStartColumn = options.originalStartColumn; + this.#generatedStartLine = options.generatedStartLine; + this.#prefixWithNewLine = options.prefixWithNewLine; + } + + /* options */ + #indentChar; + #indentLevel; + #file; + #hasOriginalStartLine; + #hasOriginalStartColumn; + #hasGeneratedStartLine; + #originalStartLine; + #originalStartColumn; + #prefixWithNewLine; + #generatedStartLine; + #sourceMapGenerator; + + /* internals */ + + // Whether or not we added a newline on after we added the previous token. + #addedNewline = false; + // Whether or not we added a space after we added the previous token. + #addedSpace = false; + #currentCode = ""; + #currentLine = 1; + #currentColumn = 0; + // The tokens parsed by acorn. + #tokenQueue; + // The index of the current token in this.#tokenQueue. + #currentTokenIndex; + // The previous token we added to the pretty printed code. + #previousToken; + // Stack of token types/keywords that can affect whether we want to add a + // newline or a space. We can make that decision based on what token type is + // on the top of the stack. For example, a comma in a parameter list should + // be followed by a space, while a comma in an object literal should be + // followed by a newline. + // + // Strings that go on the stack: + // + // - "{" + // - "{\n" + // - "(" + // - "(\n" + // - "[" + // - "[\n" + // - "do" + // - "?" + // - "switch" + // - "case" + // - "default" + // + // The difference between "[" and "[\n" (as well as "{" and "{\n", and "(" and "(\n") + // is that "\n" is used when we are treating (curly) brackets/parens as line delimiters + // and should increment and decrement the indent level when we find them. + // "[" can represent either a property access (e.g. `x["hi"]`), or an empty array literal + // "{" only represents an empty object literals + // "(" can represent lots of different things (wrapping expression, if/loop condition, function call, …) + #stack = []; + + /** + * @param {String} input: The ugly JS code we want to pretty print. + * @returns {Object} + * An object with the following properties: + * - code: The pretty printed code string. + * - map: A SourceMapGenerator instance. + */ + getPrettifiedCodeAndSourceMap(input) { + // Add the initial new line if needed + if (this.#prefixWithNewLine) { + this.#write("\n"); + } + + // Pass through acorn's tokenizer and append tokens and comments into a + // single queue to process. For example, the source file: + // + // foo + // // a + // // b + // bar + // + // After this process, tokenQueue has the following token stream: + // + // [ foo, '// a', '// b', bar] + this.#tokenQueue = this.#getTokens(input); + + for (let i = 0, len = this.#tokenQueue.length; i < len; i++) { + this.#currentTokenIndex = i; + const token = this.#tokenQueue[i]; + const nextToken = this.#tokenQueue[i + 1]; + this.#handleToken(token, nextToken); + + // Acorn's tokenizer re-uses tokens, so we have to copy the previous token on + // every iteration. We follow acorn's lead here, and reuse the previousToken + // object the same way that acorn reuses the token object. This allows us + // to avoid allocations and minimize GC pauses. + if (!this.#previousToken) { + this.#previousToken = { loc: { start: {}, end: {} } }; + } + this.#previousToken.start = token.start; + this.#previousToken.end = token.end; + this.#previousToken.loc.start.line = token.loc.start.line; + this.#previousToken.loc.start.column = token.loc.start.column; + this.#previousToken.loc.end.line = token.loc.end.line; + this.#previousToken.loc.end.column = token.loc.end.column; + this.#previousToken.type = token.type; + this.#previousToken.value = token.value; + } + + return { code: this.#currentCode, map: this.#sourceMapGenerator }; + } + + /** + * Write a pretty printed string to the prettified string and for tokens, add their + * mapping to the SourceMapGenerator. + * + * @param String str + * The string to be added to the result. + * @param Number line + * The line number the string came from in the ugly source. + * @param Number column + * The column number the string came from in the ugly source. + * @param Boolean isToken + * Set to true when writing tokens, so we can differentiate them from the + * whitespace we add. + */ + #write(str, line, column, isToken) { + this.#currentCode += str; + if (isToken) { + this.#sourceMapGenerator.addMapping({ + source: this.#file, + // We need to swap original and generated locations, as the prettified text should + // be seen by the sourcemap service as the "original" one. + generated: { + // originalStartLine is 1-based, and here we just want to offset by a number of + // lines, so we need to decrement it + line: this.#hasOriginalStartLine + ? line + (this.#originalStartLine - 1) + : line, + // We only need to adjust the column number if we're looking at the first line, to + // account for the html text before the opening <script> tag. + column: + line == 1 && this.#hasOriginalStartColumn + ? column + this.#originalStartColumn + : column, + }, + original: { + // generatedStartLine is 1-based, and here we just want to offset by a number of + // lines, so we need to decrement it. + line: this.#hasGeneratedStartLine + ? this.#currentLine + (this.#generatedStartLine - 1) + : this.#currentLine, + column: this.#currentColumn, + }, + name: null, + }); + } + + for (let idx = 0, length = str.length; idx < length; idx++) { + if (str.charCodeAt(idx) === NEWLINE_CODE) { + this.#currentLine++; + this.#currentColumn = 0; + } else { + this.#currentColumn++; + } + } + } + + /** + * Add the given token to the pretty printed results. + * + * @param Object token + * The token to add. + */ + #writeToken(token) { + if (token.type.label == "string") { + this.#write( + `'${sanitize(token.value)}'`, + token.loc.start.line, + token.loc.start.column, + true + ); + } else if (token.type.label == "regexp") { + this.#write( + String(token.value.value), + token.loc.start.line, + token.loc.start.column, + true + ); + } else { + let value; + if (token.value != null) { + value = token.value; + if (token.type.label === "privateId") { + value = `#${value}`; + } + } else { + value = token.type.label; + } + this.#write( + String(value), + token.loc.start.line, + token.loc.start.column, + true + ); + } + } + + /** + * Returns the tokens computed with acorn. + * + * @param String input + * The JS code we want the tokens of. + * @returns Array<Object> + */ + #getTokens(input) { + const tokens = []; + + const res = acorn.tokenizer(input, { + locations: true, + ecmaVersion: "latest", + onComment(block, text, start, end, startLoc, endLoc) { + tokens.push({ + type: {}, + comment: true, + block, + text, + loc: { start: startLoc, end: endLoc }, + }); + }, + }); + + for (;;) { + const token = res.getToken(); + tokens.push(token); + if (token.type.label == "eof") { + break; + } + } + + return tokens; + } + + /** + * Add the required whitespace before this token, whether that is a single + * space, newline, and/or the indent on fresh lines. + * + * @param Object token + * The token we are currently handling. + * @param {Object|undefined} nextToken + * The next token, might not exist if we're on the last token + */ + #handleToken(token, nextToken) { + if (token.comment) { + let commentIndentLevel = this.#indentLevel; + if (this.#previousToken?.loc?.end?.line == token.loc.start.line) { + commentIndentLevel = 0; + this.#write(" "); + } + this.#addComment( + commentIndentLevel, + token.block, + token.text, + token.loc.start.line, + nextToken + ); + return; + } + + // Shorthand for token.type.keyword, so we don't have to repeatedly access + // properties. + const ttk = token.type.keyword; + + if (ttk && this.#previousToken?.type?.label == ".") { + token.type = acorn.tokTypes.name; + } + + // Shorthand for token.type.label, so we don't have to repeatedly access + // properties. + const ttl = token.type.label; + + if (ttl == "eof") { + if (!this.#addedNewline) { + this.#write("\n"); + } + return; + } + + if (belongsOnStack(token)) { + let stackEntry; + + if (isArrayLiteral(token, this.#previousToken)) { + // Don't add new lines for empty array literals + stackEntry = nextToken?.type?.label === "]" ? "[" : "[\n"; + } else if (isObjectLiteral(token, this.#previousToken)) { + // Don't add new lines for empty object literals + stackEntry = nextToken?.type?.label === "}" ? "{" : "{\n"; + } else if ( + isRoundBracketStartingLongParenthesis( + token, + this.#tokenQueue, + this.#currentTokenIndex + ) + ) { + stackEntry = "(\n"; + } else if (ttl == "{") { + // We need to add a line break for "{" which are not empty object literals + stackEntry = "{\n"; + } else { + stackEntry = ttl || ttk; + } + + this.#stack.push(stackEntry); + } + + this.#maybeDecrementIndent(token); + this.#prependWhiteSpace(token); + this.#writeToken(token); + this.#addedSpace = false; + + // If the next token is going to be a comment starting on the same line, + // then no need to add a new line here + if ( + !nextToken || + !nextToken.comment || + token.loc.end.line != nextToken.loc.start.line + ) { + this.#maybeAppendNewline(token); + } + + this.#maybePopStack(token); + this.#maybeIncrementIndent(token); + } + + /** + * Returns true if the given token should cause us to pop the stack. + */ + #maybePopStack(token) { + const ttl = token.type.label; + const ttk = token.type.keyword; + const top = this.#stack.at(-1); + + if ( + ttl == "]" || + ttl == ")" || + ttl == "}" || + (ttl == ":" && (top == "case" || top == "default" || top == "?")) || + (ttk == "while" && top == "do") + ) { + this.#stack.pop(); + if (ttl == "}" && this.#stack.at(-1) == "switch") { + this.#stack.pop(); + } + } + } + + #maybeIncrementIndent(token) { + if ( + // Don't increment indent for empty object literals + (token.type.label == "{" && this.#stack.at(-1) === "{\n") || + // Don't increment indent for empty array literals + (token.type.label == "[" && this.#stack.at(-1) === "[\n") || + token.type.keyword == "switch" || + (token.type.label == "(" && this.#stack.at(-1) === "(\n") + ) { + this.#indentLevel++; + } + } + + #shouldDecrementIndent(token) { + const top = this.#stack.at(-1); + const ttl = token.type.label; + return ( + (ttl == "}" && top == "{\n") || + (ttl == "]" && top == "[\n") || + (ttl == ")" && top == "(\n") + ); + } + + #maybeDecrementIndent(token) { + if (!this.#shouldDecrementIndent(token)) { + return; + } + + const ttl = token.type.label; + this.#indentLevel--; + if (ttl == "}" && this.#stack.at(-2) == "switch") { + this.#indentLevel--; + } + } + + /** + * Add a comment to the pretty printed code. + * + * @param Number indentLevel + * The number of indents deep we are (might be different from this.#indentLevel). + * @param Boolean block + * True if the comment is a multiline block style comment. + * @param String text + * The text of the comment. + * @param Number line + * The line number to comment appeared on. + * @param Object nextToken + * The next token if any. + */ + #addComment(indentLevel, block, text, line, nextToken) { + const indentString = this.#indentChar.repeat(indentLevel); + const needNewLineAfter = + !block || !(nextToken && nextToken.loc.start.line == line); + + if (block) { + const commentLinesText = text + .split(new RegExp(`/\n${indentString}/`, "g")) + .join(`\n${indentString}`); + + this.#write( + `${indentString}/*${commentLinesText}*/${needNewLineAfter ? "\n" : " "}` + ); + } else { + this.#write(`${indentString}//${text}\n`); + } + + this.#addedNewline = needNewLineAfter; + this.#addedSpace = !needNewLineAfter; + } + + /** + * Add the required whitespace before this token, whether that is a single + * space, newline, and/or the indent on fresh lines. + * + * @param Object token + * The token we are about to add to the pretty printed code. + */ + #prependWhiteSpace(token) { + const ttk = token.type.keyword; + const ttl = token.type.label; + let newlineAdded = this.#addedNewline; + let spaceAdded = this.#addedSpace; + const ltt = this.#previousToken?.type?.label; + + // Handle whitespace and newlines after "}" here instead of in + // `isLineDelimiter` because it is only a line delimiter some of the + // time. For example, we don't want to put "else if" on a new line after + // the first if's block. + if (this.#previousToken && ltt == "}") { + if ( + (ttk == "while" && this.#stack.at(-1) == "do") || + needsSpaceBeforeClosingCurlyBracket(ttk) + ) { + this.#write(" "); + spaceAdded = true; + } else if (needsLineBreakBeforeClosingCurlyBracket(ttl)) { + this.#write("\n"); + newlineAdded = true; + } + } + + if ( + (ttl == ":" && this.#stack.at(-1) == "?") || + (ttl == "}" && this.#stack.at(-1) == "${") + ) { + this.#write(" "); + spaceAdded = true; + } + + if (this.#previousToken && ltt != "}" && ltt != "." && ttk == "else") { + this.#write(" "); + spaceAdded = true; + } + + const ensureNewline = () => { + if (!newlineAdded) { + this.#write("\n"); + newlineAdded = true; + } + }; + + if (isASI(token, this.#previousToken)) { + ensureNewline(); + } + + if (this.#shouldDecrementIndent(token)) { + ensureNewline(); + } + + if (newlineAdded) { + let indentLevel = this.#indentLevel; + if (ttk == "case" || ttk == "default") { + indentLevel--; + } + this.#write(this.#indentChar.repeat(indentLevel)); + } else if (!spaceAdded && needsSpaceAfter(token, this.#previousToken)) { + this.#write(" "); + spaceAdded = true; + } + } + + /** + * Append the necessary whitespace to the result after we have added the given + * token. + * + * @param Object token + * The token that was just added to the result. + */ + #maybeAppendNewline(token) { + if (!isLineDelimiter(token, this.#stack)) { + this.#addedNewline = false; + return; + } + + this.#write("\n"); + this.#addedNewline = true; + } +} + +/** + * Determines if we think that the given token starts an array literal. + * + * @param Object token + * The token we want to determine if it is an array literal. + * @param Object previousToken + * The previous token we added to the pretty printed results. + * + * @returns Boolean + * True if we believe it is an array literal, false otherwise. + */ +function isArrayLiteral(token, previousToken) { + if (token.type.label != "[") { + return false; + } + if (!previousToken) { + return true; + } + if (previousToken.type.isAssign) { + return true; + } + + return PRE_ARRAY_LITERAL_TOKENS.has( + previousToken.type.keyword || + // Some tokens ('of', 'yield', …) have a `token.type.keyword` of 'name' and their + // actual value in `token.value` + (previousToken.type.label == "name" + ? previousToken.value + : previousToken.type.label) + ); +} + +/** + * Determines if we think that the given token starts an object literal. + * + * @param Object token + * The token we want to determine if it is an object literal. + * @param Object previousToken + * The previous token we added to the pretty printed results. + * + * @returns Boolean + * True if we believe it is an object literal, false otherwise. + */ +function isObjectLiteral(token, previousToken) { + if (token.type.label != "{") { + return false; + } + if (!previousToken) { + return false; + } + if (previousToken.type.isAssign) { + return true; + } + return PRE_OBJECT_LITERAL_TOKENS.has( + previousToken.type.keyword || previousToken.type.label + ); +} + +/** + * Determines if we think that the given token starts a long parenthesis + * + * @param {Object} token + * The token we want to determine if it is the beginning of a long paren. + * @param {Array<Object>} tokenQueue + * The whole list of tokens parsed by acorn + * @param {Integer} currentTokenIndex + * The index of `token` in `tokenQueue` + * @returns + */ +function isRoundBracketStartingLongParenthesis( + token, + tokenQueue, + currentTokenIndex +) { + if (token.type.label !== "(") { + return false; + } + + // If we're just wrapping an object, we'll have a new line right after + if (tokenQueue[currentTokenIndex + 1].type.label == "{") { + return false; + } + + // We're going to iterate through the following tokens until : + // - we find the closing parent + // - or we reached the maximum character we think should be in parenthesis + const longParentContentLength = 60; + + // Keep track of other parens so we know when we get the closing one for `token` + let parenCount = 0; + let parenContentLength = 0; + for (let i = currentTokenIndex + 1, len = tokenQueue.length; i < len; i++) { + const currToken = tokenQueue[i]; + const ttl = currToken.type.label; + + if (ttl == "(") { + parenCount++; + } else if (ttl == ")") { + if (parenCount == 0) { + // Matching closing paren, if we got here, we didn't reach the length limit, + // as we return when parenContentLength is greater than the limit. + return false; + } + parenCount--; + } + + // Aside block comments, all tokens start and end location are on the same line, so + // we can use `start` and `end` to deduce the token length. + const tokenLength = currToken.comment + ? currToken.text.length + : currToken.end - currToken.start; + parenContentLength += tokenLength; + + // If we didn't find the matching closing paren yet and the characters from the + // tokens we evaluated so far are longer than the limit, so consider the token + // a long paren. + if (parenContentLength > longParentContentLength) { + return true; + } + } + + // if we get to here, we didn't found a closing paren, which shouldn't happen + // (scripts with syntax error are not displayed in the debugger), but just to + // be safe, return false. + return false; +} + +// If any of these tokens are followed by a token on a new line, we know that +// ASI cannot happen. +const PREVENT_ASI_AFTER_TOKENS = new Set([ + // Binary operators + "*", + "/", + "%", + "+", + "-", + "<<", + ">>", + ">>>", + "<", + ">", + "<=", + ">=", + "instanceof", + "in", + "==", + "!=", + "===", + "!==", + "&", + "^", + "|", + "&&", + "||", + ",", + ".", + "=", + "*=", + "/=", + "%=", + "+=", + "-=", + "<<=", + ">>=", + ">>>=", + "&=", + "^=", + "|=", + // Unary operators + "delete", + "void", + "typeof", + "~", + "!", + "new", + // Function calls and grouped expressions + "(", +]); + +// If any of these tokens are on a line after the token before it, we know +// that ASI cannot happen. +const PREVENT_ASI_BEFORE_TOKENS = new Set([ + // Binary operators + "*", + "/", + "%", + "<<", + ">>", + ">>>", + "<", + ">", + "<=", + ">=", + "instanceof", + "in", + "==", + "!=", + "===", + "!==", + "&", + "^", + "|", + "&&", + "||", + ",", + ".", + "=", + "*=", + "/=", + "%=", + "+=", + "-=", + "<<=", + ">>=", + ">>>=", + "&=", + "^=", + "|=", + // Function calls + "(", +]); + +/** + * Determine if a token can look like an identifier. More precisely, + * this determines if the token may end or start with a character from + * [A-Za-z0-9_]. + * + * @param Object token + * The token we are looking at. + * + * @returns Boolean + * True if identifier-like. + */ +function isIdentifierLike(token) { + const ttl = token.type.label; + return ( + ttl == "name" || ttl == "num" || ttl == "privateId" || !!token.type.keyword + ); +} + +/** + * Determines if Automatic Semicolon Insertion (ASI) occurs between these + * tokens. + * + * @param Object token + * The current token. + * @param Object previousToken + * The previous token we added to the pretty printed results. + * + * @returns Boolean + * True if we believe ASI occurs. + */ +function isASI(token, previousToken) { + if (!previousToken) { + return false; + } + if (token.loc.start.line === previousToken.loc.start.line) { + return false; + } + if ( + previousToken.type.keyword == "return" || + previousToken.type.keyword == "yield" || + (previousToken.type.label == "name" && previousToken.value == "yield") + ) { + return true; + } + if ( + PREVENT_ASI_AFTER_TOKENS.has( + previousToken.type.label || previousToken.type.keyword + ) + ) { + return false; + } + if (PREVENT_ASI_BEFORE_TOKENS.has(token.type.label || token.type.keyword)) { + return false; + } + return true; +} + +/** + * Determine if we should add a newline after the given token. + * + * @param Object token + * The token we are looking at. + * @param Array stack + * The stack of open parens/curlies/brackets/etc. + * + * @returns Boolean + * True if we should add a newline. + */ +function isLineDelimiter(token, stack) { + const ttl = token.type.label; + const top = stack.at(-1); + return ( + (ttl == ";" && top != "(") || + // Don't add a new line for empty object literals + (ttl == "{" && top == "{\n") || + // Don't add a new line for empty array literals + (ttl == "[" && top == "[\n") || + ((ttl == "," || ttl == "||" || ttl == "&&") && top != "(") || + (ttl == ":" && (top == "case" || top == "default")) || + (ttl == "(" && top == "(\n") + ); +} + +/** + * Determines if we need to add a space after the token we are about to add. + * + * @param Object token + * The token we are about to add to the pretty printed code. + * @param Object [previousToken] + * Optional previous token added to the pretty printed code. + */ +function needsSpaceAfter(token, previousToken) { + if (previousToken && needsSpaceBetweenTokens(token, previousToken)) { + return true; + } + + if (token.type.isAssign) { + return true; + } + if (token.type.binop != null && previousToken) { + return true; + } + if (token.type.label == "?") { + return true; + } + if (token.type.label == "=>") { + return true; + } + + return false; +} + +function needsSpaceBeforePreviousToken(previousToken) { + if (previousToken.type.isLoop) { + return true; + } + if (previousToken.type.isAssign) { + return true; + } + if (previousToken.type.binop != null) { + return true; + } + if (previousToken.value == "of") { + return true; + } + + const previousTokenTypeLabel = previousToken.type.label; + if (previousTokenTypeLabel == "?") { + return true; + } + if (previousTokenTypeLabel == ":") { + return true; + } + if (previousTokenTypeLabel == ",") { + return true; + } + if (previousTokenTypeLabel == ";") { + return true; + } + if (previousTokenTypeLabel == "${") { + return true; + } + if (previousTokenTypeLabel == "=>") { + return true; + } + return false; +} + +function isBreakContinueOrReturnStatement(previousTokenKeyword) { + return ( + previousTokenKeyword == "break" || + previousTokenKeyword == "continue" || + previousTokenKeyword == "return" + ); +} + +function needsSpaceBeforePreviousTokenKeywordAfterNotDot(previousTokenKeyword) { + return ( + previousTokenKeyword != "debugger" && + previousTokenKeyword != "null" && + previousTokenKeyword != "true" && + previousTokenKeyword != "false" && + previousTokenKeyword != "this" && + previousTokenKeyword != "default" + ); +} + +function needsSpaceBeforeClosingParen(tokenTypeLabel) { + return ( + tokenTypeLabel != ")" && + tokenTypeLabel != "]" && + tokenTypeLabel != ";" && + tokenTypeLabel != "," && + tokenTypeLabel != "." + ); +} + +/** + * Determines if we need to add a space between the previous token we added and + * the token we are about to add. + * + * @param Object token + * The token we are about to add to the pretty printed code. + * @param Object previousToken + * The previous token added to the pretty printed code. + */ +function needsSpaceBetweenTokens(token, previousToken) { + if (needsSpaceBeforePreviousToken(previousToken)) { + return true; + } + + const ltt = previousToken.type.label; + if (ltt == "num" && token.type.label == ".") { + return true; + } + + const ltk = previousToken.type.keyword; + const ttl = token.type.label; + if (ltk != null && ttl != ".") { + if (isBreakContinueOrReturnStatement(ltk)) { + return ttl != ";"; + } + if (needsSpaceBeforePreviousTokenKeywordAfterNotDot(ltk)) { + return true; + } + } + + if (ltt == ")" && needsSpaceBeforeClosingParen(ttl)) { + return true; + } + + if (isIdentifierLike(token) && isIdentifierLike(previousToken)) { + // We must emit a space to avoid merging the tokens. + return true; + } + + if (token.type.label == "{" && previousToken.type.label == "name") { + return true; + } + + return false; +} + +function needsSpaceBeforeClosingCurlyBracket(tokenTypeKeyword) { + return ( + tokenTypeKeyword == "else" || + tokenTypeKeyword == "catch" || + tokenTypeKeyword == "finally" + ); +} + +function needsLineBreakBeforeClosingCurlyBracket(tokenTypeLabel) { + return ( + tokenTypeLabel != "(" && + tokenTypeLabel != ";" && + tokenTypeLabel != "," && + tokenTypeLabel != ")" && + tokenTypeLabel != "." && + tokenTypeLabel != "template" && + tokenTypeLabel != "`" + ); +} + +const escapeCharacters = { + // Backslash + "\\": "\\\\", + // Newlines + "\n": "\\n", + // Carriage return + "\r": "\\r", + // Tab + "\t": "\\t", + // Vertical tab + "\v": "\\v", + // Form feed + "\f": "\\f", + // Null character + "\0": "\\x00", + // Line separator + "\u2028": "\\u2028", + // Paragraph separator + "\u2029": "\\u2029", + // Single quotes + "'": "\\'", +}; + +// eslint-disable-next-line prefer-template +const regExpString = "(" + Object.values(escapeCharacters).join("|") + ")"; +const escapeCharactersRegExp = new RegExp(regExpString, "g"); + +function sanitizerReplaceFunc(_, c) { + return escapeCharacters[c]; +} + +/** + * Make sure that we output the escaped character combination inside string + * literals instead of various problematic characters. + */ +function sanitize(str) { + return str.replace(escapeCharactersRegExp, sanitizerReplaceFunc); +} + +/** + * Returns true if the given token type belongs on the stack. + */ +function belongsOnStack(token) { + const ttl = token.type.label; + const ttk = token.type.keyword; + return ( + ttl == "{" || + ttl == "(" || + ttl == "[" || + ttl == "?" || + ttl == "${" || + ttk == "do" || + ttk == "switch" || + ttk == "case" || + ttk == "default" + ); +} diff --git a/devtools/client/debugger/src/workers/pretty-print/tests/__snapshots__/prettyFast.spec.js.snap b/devtools/client/debugger/src/workers/pretty-print/tests/__snapshots__/prettyFast.spec.js.snap new file mode 100644 index 0000000000..498bee267e --- /dev/null +++ b/devtools/client/debugger/src/workers/pretty-print/tests/__snapshots__/prettyFast.spec.js.snap @@ -0,0 +1,1974 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ASI return 1`] = ` +"function f() { + return + { + } +} +" +`; + +exports[`ASI return 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 9) -> (1, 9)", + "(1, 10) -> (1, 10)", + "(1, 11) -> (1, 11)", + "(1, 13) -> (1, 13)", + "(2, 2) -> (2, 13)", + "(3, 2) -> (3, 13)", + "(4, 2) -> (3, 14)", + "(5, 0) -> (4, 11)", +] +`; + +exports[`Arrays 1`] = ` +"var a = [ + 1, + 2, + 3 +]; +" +`; + +exports[`Arrays 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 6) -> (1, 5)", + "(1, 8) -> (1, 6)", + "(2, 2) -> (1, 7)", + "(2, 3) -> (1, 8)", + "(3, 2) -> (1, 9)", + "(3, 3) -> (1, 10)", + "(4, 2) -> (1, 11)", + "(5, 0) -> (1, 12)", + "(5, 1) -> (1, 13)", +] +`; + +exports[`Arrays and spread operator 1`] = ` +"var a = [ + 1, + ...[ + 2, + 3 + ], + ...[], + 4 +]; +" +`; + +exports[`Arrays and spread operator 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 6) -> (1, 5)", + "(1, 8) -> (1, 6)", + "(2, 2) -> (1, 7)", + "(2, 3) -> (1, 8)", + "(3, 2) -> (1, 9)", + "(3, 5) -> (1, 12)", + "(4, 4) -> (1, 13)", + "(4, 5) -> (1, 14)", + "(5, 4) -> (1, 15)", + "(6, 2) -> (1, 16)", + "(6, 3) -> (1, 17)", + "(7, 2) -> (1, 18)", + "(7, 5) -> (1, 21)", + "(7, 6) -> (1, 22)", + "(7, 7) -> (1, 23)", + "(8, 2) -> (1, 25)", + "(9, 0) -> (1, 26)", + "(9, 1) -> (1, 27)", +] +`; + +exports[`Binary operators 1`] = ` +"var a = 5 * 30; +var b = 5 >> 3; +" +`; + +exports[`Binary operators 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 6) -> (1, 5)", + "(1, 8) -> (1, 6)", + "(1, 10) -> (1, 7)", + "(1, 12) -> (1, 8)", + "(1, 14) -> (1, 10)", + "(2, 0) -> (1, 11)", + "(2, 4) -> (1, 15)", + "(2, 6) -> (1, 16)", + "(2, 8) -> (1, 17)", + "(2, 10) -> (1, 18)", + "(2, 13) -> (1, 20)", + "(2, 14) -> (1, 21)", +] +`; + +exports[`Bug 975477 don't move end of line comments to next line 1`] = ` +"switch (request.action) { + case 'show': //$NON-NLS-0$ + if (localStorage.hideicon !== 'true') { //$NON-NLS-0$ + chrome.pageAction.show(sender.tab.id); + } + break; + case 'hide': /*Multiline + Comment */ + break; + default: + console.warn('unknown request'); //$NON-NLS-0$ + // don't respond if you don't understand the message. + return; +} +" +`; + +exports[`Bug 975477 don't move end of line comments to next line 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 7) -> (1, 7)", + "(1, 8) -> (1, 8)", + "(1, 15) -> (1, 15)", + "(1, 16) -> (1, 16)", + "(1, 22) -> (1, 22)", + "(1, 24) -> (1, 24)", + "(2, 2) -> (2, 13)", + "(2, 7) -> (2, 18)", + "(2, 13) -> (2, 24)", + "(3, 4) -> (3, 15)", + "(3, 7) -> (3, 18)", + "(3, 8) -> (3, 19)", + "(3, 20) -> (3, 31)", + "(3, 21) -> (3, 32)", + "(3, 30) -> (3, 41)", + "(3, 34) -> (3, 45)", + "(3, 40) -> (3, 51)", + "(3, 42) -> (3, 53)", + "(4, 6) -> (4, 17)", + "(4, 12) -> (4, 23)", + "(4, 13) -> (4, 24)", + "(4, 23) -> (4, 34)", + "(4, 24) -> (4, 35)", + "(4, 28) -> (4, 39)", + "(4, 29) -> (4, 40)", + "(4, 35) -> (4, 46)", + "(4, 36) -> (4, 47)", + "(4, 39) -> (4, 50)", + "(4, 40) -> (4, 51)", + "(4, 42) -> (4, 53)", + "(4, 43) -> (4, 54)", + "(5, 4) -> (5, 15)", + "(6, 4) -> (6, 15)", + "(6, 9) -> (6, 20)", + "(7, 2) -> (7, 13)", + "(7, 7) -> (7, 18)", + "(7, 13) -> (7, 24)", + "(9, 4) -> (9, 15)", + "(9, 9) -> (9, 20)", + "(10, 2) -> (10, 13)", + "(10, 9) -> (10, 20)", + "(11, 4) -> (11, 15)", + "(11, 11) -> (11, 22)", + "(11, 12) -> (11, 23)", + "(11, 16) -> (11, 27)", + "(11, 17) -> (11, 28)", + "(11, 34) -> (11, 45)", + "(11, 35) -> (11, 46)", + "(13, 4) -> (13, 15)", + "(13, 10) -> (13, 21)", + "(14, 0) -> (14, 11)", +] +`; + +exports[`Bug 977082 - space between grouping operator and dot notation 1`] = ` +"JSON.stringify(3).length; +([1, +2, +3]).length; +(new Date()).toLocaleString(); +" +`; + +exports[`Bug 977082 - space between grouping operator and dot notation 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 5) -> (1, 5)", + "(1, 14) -> (1, 14)", + "(1, 15) -> (1, 15)", + "(1, 16) -> (1, 16)", + "(1, 17) -> (1, 17)", + "(1, 18) -> (1, 18)", + "(1, 24) -> (1, 24)", + "(2, 0) -> (2, 11)", + "(2, 1) -> (2, 12)", + "(2, 2) -> (2, 13)", + "(2, 3) -> (2, 14)", + "(3, 0) -> (2, 15)", + "(3, 1) -> (2, 16)", + "(4, 0) -> (2, 17)", + "(4, 1) -> (2, 18)", + "(4, 2) -> (2, 19)", + "(4, 3) -> (2, 20)", + "(4, 4) -> (2, 21)", + "(4, 10) -> (2, 27)", + "(5, 0) -> (3, 11)", + "(5, 1) -> (3, 12)", + "(5, 5) -> (3, 16)", + "(5, 9) -> (3, 20)", + "(5, 10) -> (3, 21)", + "(5, 11) -> (3, 22)", + "(5, 12) -> (3, 23)", + "(5, 13) -> (3, 24)", + "(5, 27) -> (3, 38)", + "(5, 28) -> (3, 39)", + "(5, 29) -> (3, 40)", +] +`; + +exports[`Bug 1206633 - spaces in for of 1`] = ` +"for (let tab of tabs) { +} +" +`; + +exports[`Bug 1206633 - spaces in for of 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 5) -> (1, 5)", + "(1, 9) -> (1, 9)", + "(1, 13) -> (1, 13)", + "(1, 16) -> (1, 16)", + "(1, 20) -> (1, 20)", + "(1, 22) -> (1, 22)", + "(2, 0) -> (1, 23)", +] +`; + +exports[`Bug 1261971 - indentation after switch statement 1`] = ` +"{ + switch (x) { + } + if (y) { + } + done(); +} +" +`; + +exports[`Bug 1261971 - indentation after switch statement 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(2, 2) -> (1, 1)", + "(2, 9) -> (1, 7)", + "(2, 10) -> (1, 8)", + "(2, 11) -> (1, 9)", + "(2, 13) -> (1, 10)", + "(3, 2) -> (1, 11)", + "(4, 2) -> (1, 12)", + "(4, 5) -> (1, 14)", + "(4, 6) -> (1, 15)", + "(4, 7) -> (1, 16)", + "(4, 9) -> (1, 17)", + "(5, 2) -> (1, 18)", + "(6, 2) -> (1, 19)", + "(6, 6) -> (1, 23)", + "(6, 7) -> (1, 24)", + "(6, 8) -> (1, 25)", + "(7, 0) -> (1, 26)", +] +`; + +exports[`Bug pretty-sure-3 - escaping line and paragraph separators 1`] = ` +"x = '\\\\u2029\\\\u2028'; +" +`; + +exports[`Bug pretty-sure-3 - escaping line and paragraph separators 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 2) -> (1, 2)", + "(1, 4) -> (1, 4)", + "(1, 18) -> (1, 18)", +] +`; + +exports[`Bug pretty-sure-4 - escaping null character before digit 1`] = ` +"x = '\\\\x001'; +" +`; + +exports[`Bug pretty-sure-4 - escaping null character before digit 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 2) -> (1, 2)", + "(1, 4) -> (1, 4)", + "(1, 11) -> (1, 13)", +] +`; + +exports[`Bug pretty-sure-5 - empty multiline comment shouldn't throw exception 1`] = ` +"{ + /* + */ + return; +} +" +`; + +exports[`Bug pretty-sure-5 - empty multiline comment shouldn't throw exception 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(4, 2) -> (4, 13)", + "(4, 8) -> (4, 19)", + "(5, 0) -> (5, 11)", +] +`; + +exports[`Bug pretty-sure-6 - inline comment shouldn't move parenthesis to next line 1`] = ` +"return /* inline comment */ (1 + 1); +" +`; + +exports[`Bug pretty-sure-6 - inline comment shouldn't move parenthesis to next line 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 28) -> (1, 28)", + "(1, 29) -> (2, 13)", + "(1, 31) -> (2, 14)", + "(1, 33) -> (2, 15)", + "(1, 34) -> (2, 16)", + "(1, 35) -> (2, 17)", +] +`; + +exports[`Bug pretty-sure-7 - accessing a literal number property requires a space 1`] = ` +"0 .toString() + x.toString(); +" +`; + +exports[`Bug pretty-sure-7 - accessing a literal number property requires a space 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 2) -> (1, 2)", + "(1, 3) -> (1, 3)", + "(1, 11) -> (1, 11)", + "(1, 12) -> (1, 12)", + "(1, 14) -> (1, 13)", + "(1, 16) -> (1, 14)", + "(1, 17) -> (1, 15)", + "(1, 18) -> (1, 16)", + "(1, 26) -> (1, 24)", + "(1, 27) -> (1, 25)", + "(1, 28) -> (1, 26)", +] +`; + +exports[`Bug pretty-sure-8 - return and yield only accept arguments when on the same line 1`] = ` +"{ + return + (x) + yield + (x) + yield + * x +} +" +`; + +exports[`Bug pretty-sure-8 - return and yield only accept arguments when on the same line 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(2, 2) -> (2, 13)", + "(3, 2) -> (3, 13)", + "(3, 3) -> (3, 14)", + "(3, 4) -> (3, 15)", + "(4, 2) -> (4, 13)", + "(5, 2) -> (5, 13)", + "(5, 3) -> (5, 14)", + "(5, 4) -> (5, 15)", + "(6, 2) -> (6, 13)", + "(7, 2) -> (7, 13)", + "(7, 4) -> (7, 14)", + "(8, 0) -> (8, 11)", +] +`; + +exports[`Bug pretty-sure-9 - accept unary operator at start of file 1`] = ` +"+ 0 +" +`; + +exports[`Bug pretty-sure-9 - accept unary operator at start of file 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 2) -> (1, 2)", +] +`; + +exports[`Class extension within a function 1`] = ` +"(function () { + class X extends Y { + constructor() { + } + } +}) () +" +`; + +exports[`Class extension within a function 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 1) -> (1, 1)", + "(1, 10) -> (1, 9)", + "(1, 11) -> (1, 10)", + "(1, 13) -> (1, 12)", + "(2, 2) -> (1, 15)", + "(2, 8) -> (1, 21)", + "(2, 10) -> (1, 23)", + "(2, 18) -> (1, 31)", + "(2, 20) -> (1, 33)", + "(3, 4) -> (1, 35)", + "(3, 15) -> (1, 46)", + "(3, 16) -> (1, 47)", + "(3, 18) -> (1, 49)", + "(4, 4) -> (1, 50)", + "(5, 2) -> (1, 52)", + "(6, 0) -> (1, 55)", + "(6, 1) -> (1, 56)", + "(6, 3) -> (1, 57)", + "(6, 4) -> (1, 58)", +] +`; + +exports[`Class handling 1`] = ` +"class Class { + constructor() { + } +} +" +`; + +exports[`Class handling 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 6) -> (1, 7)", + "(1, 12) -> (1, 12)", + "(2, 2) -> (1, 13)", + "(2, 13) -> (1, 24)", + "(2, 14) -> (1, 25)", + "(2, 16) -> (1, 26)", + "(3, 2) -> (1, 27)", + "(4, 0) -> (1, 28)", +] +`; + +exports[`Code that relies on ASI 1`] = ` +"var foo = 10 +var bar = 20 +function g() { + a() + b() +} +" +`; + +exports[`Code that relies on ASI 2`] = ` +Array [ + "(1, 0) -> (2, 11)", + "(1, 4) -> (2, 15)", + "(1, 8) -> (2, 19)", + "(1, 10) -> (2, 21)", + "(2, 0) -> (3, 11)", + "(2, 4) -> (3, 15)", + "(2, 8) -> (3, 19)", + "(2, 10) -> (3, 21)", + "(3, 0) -> (4, 11)", + "(3, 9) -> (4, 20)", + "(3, 10) -> (4, 21)", + "(3, 11) -> (4, 22)", + "(3, 13) -> (4, 24)", + "(4, 2) -> (5, 13)", + "(4, 3) -> (5, 14)", + "(4, 4) -> (5, 15)", + "(5, 2) -> (6, 13)", + "(5, 3) -> (6, 14)", + "(5, 4) -> (6, 15)", + "(6, 0) -> (7, 11)", +] +`; + +exports[`Const handling 1`] = ` +"const d = 'yes'; +" +`; + +exports[`Const handling 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 6) -> (1, 6)", + "(1, 8) -> (1, 8)", + "(1, 10) -> (1, 10)", + "(1, 15) -> (1, 15)", +] +`; + +exports[`Continue/break statements 1`] = ` +"while (1) { + if (x) { + continue + } + if (y) { + break + } + if (z) { + break foo + } +} +" +`; + +exports[`Continue/break statements 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 6) -> (1, 5)", + "(1, 7) -> (1, 6)", + "(1, 8) -> (1, 7)", + "(1, 10) -> (1, 8)", + "(2, 2) -> (1, 9)", + "(2, 5) -> (1, 11)", + "(2, 6) -> (1, 12)", + "(2, 7) -> (1, 13)", + "(2, 9) -> (1, 14)", + "(3, 4) -> (1, 15)", + "(4, 2) -> (1, 23)", + "(5, 2) -> (1, 24)", + "(5, 5) -> (1, 26)", + "(5, 6) -> (1, 27)", + "(5, 7) -> (1, 28)", + "(5, 9) -> (1, 29)", + "(6, 4) -> (1, 30)", + "(7, 2) -> (1, 35)", + "(8, 2) -> (1, 36)", + "(8, 5) -> (1, 38)", + "(8, 6) -> (1, 39)", + "(8, 7) -> (1, 40)", + "(8, 9) -> (1, 41)", + "(9, 4) -> (1, 42)", + "(9, 10) -> (1, 48)", + "(10, 2) -> (1, 51)", + "(11, 0) -> (1, 52)", +] +`; + +exports[`Delete 1`] = ` +"delete obj.prop; +" +`; + +exports[`Delete 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 7) -> (1, 7)", + "(1, 10) -> (1, 10)", + "(1, 11) -> (1, 11)", + "(1, 15) -> (1, 15)", +] +`; + +exports[`Do/while loop 1`] = ` +"do { + x +} while (y) +" +`; + +exports[`Do/while loop 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 3) -> (1, 2)", + "(2, 2) -> (1, 3)", + "(3, 0) -> (1, 4)", + "(3, 2) -> (1, 5)", + "(3, 8) -> (1, 10)", + "(3, 9) -> (1, 11)", + "(3, 10) -> (1, 12)", +] +`; + +exports[`Dot handling with keywords which are identifier name 1`] = ` +"y.await.break.const.delete.else.return.new.yield = 1.23; +" +`; + +exports[`Dot handling with keywords which are identifier name 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 1) -> (1, 1)", + "(1, 2) -> (1, 2)", + "(1, 7) -> (1, 7)", + "(1, 8) -> (1, 8)", + "(1, 13) -> (1, 13)", + "(1, 14) -> (1, 14)", + "(1, 19) -> (1, 19)", + "(1, 20) -> (1, 20)", + "(1, 26) -> (1, 26)", + "(1, 27) -> (1, 27)", + "(1, 31) -> (1, 31)", + "(1, 32) -> (1, 32)", + "(1, 38) -> (1, 38)", + "(1, 39) -> (1, 39)", + "(1, 42) -> (1, 42)", + "(1, 43) -> (1, 43)", + "(1, 49) -> (1, 49)", + "(1, 51) -> (1, 51)", + "(1, 55) -> (1, 55)", +] +`; + +exports[`Dot handling with let which is identifier name 1`] = ` +"y.let.let = 1.23; +" +`; + +exports[`Dot handling with let which is identifier name 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 1) -> (1, 1)", + "(1, 2) -> (1, 2)", + "(1, 5) -> (1, 5)", + "(1, 6) -> (1, 6)", + "(1, 10) -> (1, 10)", + "(1, 12) -> (1, 12)", + "(1, 16) -> (1, 16)", +] +`; + +exports[`Empty object/array literals 1`] = ` +"let a = []; +const b = {}; +c = { + ...{}, + d: 42 +}; +for (let x of []) { + for (let y in {}) { + } +} +" +`; + +exports[`Empty object/array literals 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 6) -> (1, 5)", + "(1, 8) -> (1, 6)", + "(1, 9) -> (1, 7)", + "(1, 10) -> (1, 8)", + "(2, 0) -> (1, 9)", + "(2, 6) -> (1, 15)", + "(2, 8) -> (1, 16)", + "(2, 10) -> (1, 17)", + "(2, 11) -> (1, 18)", + "(2, 12) -> (1, 19)", + "(3, 0) -> (1, 20)", + "(3, 2) -> (1, 21)", + "(3, 4) -> (1, 22)", + "(4, 2) -> (1, 23)", + "(4, 5) -> (1, 26)", + "(4, 6) -> (1, 27)", + "(4, 7) -> (1, 28)", + "(5, 2) -> (1, 29)", + "(5, 3) -> (1, 30)", + "(5, 5) -> (1, 32)", + "(6, 0) -> (1, 34)", + "(6, 1) -> (1, 35)", + "(7, 0) -> (1, 36)", + "(7, 4) -> (1, 39)", + "(7, 5) -> (1, 40)", + "(7, 9) -> (1, 44)", + "(7, 11) -> (1, 46)", + "(7, 14) -> (1, 49)", + "(7, 15) -> (1, 50)", + "(7, 16) -> (1, 51)", + "(7, 18) -> (1, 52)", + "(8, 2) -> (1, 53)", + "(8, 6) -> (1, 56)", + "(8, 7) -> (1, 57)", + "(8, 11) -> (1, 61)", + "(8, 13) -> (1, 63)", + "(8, 16) -> (1, 66)", + "(8, 17) -> (1, 67)", + "(8, 18) -> (1, 68)", + "(8, 20) -> (1, 69)", + "(9, 2) -> (1, 70)", + "(10, 0) -> (1, 71)", +] +`; + +exports[`Escaping backslashes in strings 1`] = ` +"'\\\\\\\\' +" +`; + +exports[`Escaping backslashes in strings 2`] = ` +Array [ + "(1, 0) -> (1, 0)", +] +`; + +exports[`Escaping carriage return in strings 1`] = ` +"'\\\\r' +" +`; + +exports[`Escaping carriage return in strings 2`] = ` +Array [ + "(1, 0) -> (1, 0)", +] +`; + +exports[`Escaping form feed in strings 1`] = ` +"'\\\\f' +" +`; + +exports[`Escaping form feed in strings 2`] = ` +Array [ + "(1, 0) -> (1, 0)", +] +`; + +exports[`Escaping null character in strings 1`] = ` +"'\\\\x00' +" +`; + +exports[`Escaping null character in strings 2`] = ` +Array [ + "(1, 0) -> (1, 0)", +] +`; + +exports[`Escaping tab in strings 1`] = ` +"'\\\\t' +" +`; + +exports[`Escaping tab in strings 2`] = ` +Array [ + "(1, 0) -> (1, 0)", +] +`; + +exports[`Escaping vertical tab in strings 1`] = ` +"'\\\\v' +" +`; + +exports[`Escaping vertical tab in strings 2`] = ` +Array [ + "(1, 0) -> (1, 0)", +] +`; + +exports[`False assignment 1`] = ` +"var foo = false; +" +`; + +exports[`False assignment 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 8) -> (1, 7)", + "(1, 10) -> (1, 8)", + "(1, 15) -> (1, 13)", +] +`; + +exports[`Fat arrow function 1`] = ` +"const a = () => 42 +addEventListener('click', e => { + return false +}); +const sum = (c, d) => c + d +" +`; + +exports[`Fat arrow function 2`] = ` +Array [ + "(1, 0) -> (2, 6)", + "(1, 6) -> (2, 12)", + "(1, 8) -> (2, 14)", + "(1, 10) -> (2, 16)", + "(1, 11) -> (2, 17)", + "(1, 13) -> (2, 19)", + "(1, 16) -> (2, 22)", + "(2, 0) -> (3, 6)", + "(2, 16) -> (3, 22)", + "(2, 17) -> (3, 23)", + "(2, 24) -> (3, 30)", + "(2, 26) -> (3, 32)", + "(2, 28) -> (3, 34)", + "(2, 31) -> (3, 37)", + "(3, 2) -> (3, 39)", + "(3, 9) -> (3, 46)", + "(4, 0) -> (3, 52)", + "(4, 1) -> (3, 53)", + "(4, 2) -> (3, 54)", + "(5, 0) -> (4, 6)", + "(5, 6) -> (4, 12)", + "(5, 10) -> (4, 16)", + "(5, 12) -> (4, 18)", + "(5, 13) -> (4, 19)", + "(5, 14) -> (4, 20)", + "(5, 16) -> (4, 21)", + "(5, 17) -> (4, 22)", + "(5, 19) -> (4, 24)", + "(5, 22) -> (4, 27)", + "(5, 24) -> (4, 28)", + "(5, 26) -> (4, 29)", +] +`; + +exports[`For loop 1`] = ` +"for (var i = 0; i < n; i++) { + console.log(i); +} +" +`; + +exports[`For loop 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 5) -> (1, 5)", + "(1, 9) -> (1, 9)", + "(1, 11) -> (1, 11)", + "(1, 13) -> (1, 13)", + "(1, 14) -> (1, 14)", + "(1, 16) -> (1, 16)", + "(1, 18) -> (1, 18)", + "(1, 20) -> (1, 20)", + "(1, 21) -> (1, 21)", + "(1, 23) -> (1, 23)", + "(1, 24) -> (1, 24)", + "(1, 26) -> (1, 26)", + "(1, 28) -> (1, 28)", + "(2, 2) -> (1, 30)", + "(2, 9) -> (1, 37)", + "(2, 10) -> (1, 38)", + "(2, 13) -> (1, 41)", + "(2, 14) -> (1, 42)", + "(2, 15) -> (1, 43)", + "(2, 16) -> (1, 44)", + "(3, 0) -> (1, 46)", +] +`; + +exports[`Function calls 1`] = ` +"var result = func(a, b, c, d); +" +`; + +exports[`Function calls 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 11) -> (1, 10)", + "(1, 13) -> (1, 11)", + "(1, 17) -> (1, 15)", + "(1, 18) -> (1, 16)", + "(1, 19) -> (1, 17)", + "(1, 21) -> (1, 18)", + "(1, 22) -> (1, 19)", + "(1, 24) -> (1, 20)", + "(1, 25) -> (1, 21)", + "(1, 27) -> (1, 22)", + "(1, 28) -> (1, 23)", + "(1, 29) -> (1, 24)", +] +`; + +exports[`Getter and setter literals 1`] = ` +"var obj = { + get foo() { + return this._foo + }, + set foo(v) { + this._foo = v + } +} +" +`; + +exports[`Getter and setter literals 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 8) -> (1, 7)", + "(1, 10) -> (1, 8)", + "(2, 2) -> (1, 9)", + "(2, 6) -> (1, 13)", + "(2, 9) -> (1, 16)", + "(2, 10) -> (1, 17)", + "(2, 12) -> (1, 18)", + "(3, 4) -> (1, 19)", + "(3, 11) -> (1, 26)", + "(3, 15) -> (1, 30)", + "(3, 16) -> (1, 31)", + "(4, 2) -> (1, 35)", + "(4, 3) -> (1, 36)", + "(5, 2) -> (1, 37)", + "(5, 6) -> (1, 41)", + "(5, 9) -> (1, 44)", + "(5, 10) -> (1, 45)", + "(5, 11) -> (1, 46)", + "(5, 13) -> (1, 47)", + "(6, 4) -> (1, 48)", + "(6, 8) -> (1, 52)", + "(6, 9) -> (1, 53)", + "(6, 14) -> (1, 57)", + "(6, 16) -> (1, 58)", + "(7, 2) -> (1, 59)", + "(8, 0) -> (1, 60)", +] +`; + +exports[`If/else statement 1`] = ` +"if (c) { + then() +} else { + other() +} +" +`; + +exports[`If/else statement 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 3) -> (1, 2)", + "(1, 4) -> (1, 3)", + "(1, 5) -> (1, 4)", + "(1, 7) -> (1, 5)", + "(2, 2) -> (1, 6)", + "(2, 6) -> (1, 10)", + "(2, 7) -> (1, 11)", + "(3, 0) -> (1, 12)", + "(3, 2) -> (1, 13)", + "(3, 7) -> (1, 17)", + "(4, 2) -> (1, 18)", + "(4, 7) -> (1, 23)", + "(4, 8) -> (1, 24)", + "(5, 0) -> (1, 25)", +] +`; + +exports[`If/else without curlies 1`] = ` +"if (c) a else b +" +`; + +exports[`If/else without curlies 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 3) -> (1, 2)", + "(1, 4) -> (1, 3)", + "(1, 5) -> (1, 4)", + "(1, 7) -> (1, 6)", + "(1, 9) -> (1, 8)", + "(1, 14) -> (1, 13)", +] +`; + +exports[`Immediately invoked function expression 1`] = ` +"(function () { + thingy() +}()) +" +`; + +exports[`Immediately invoked function expression 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 1) -> (1, 1)", + "(1, 10) -> (1, 9)", + "(1, 11) -> (1, 10)", + "(1, 13) -> (1, 11)", + "(2, 2) -> (1, 12)", + "(2, 8) -> (1, 18)", + "(2, 9) -> (1, 19)", + "(3, 0) -> (1, 20)", + "(3, 1) -> (1, 21)", + "(3, 2) -> (1, 22)", + "(3, 3) -> (1, 23)", +] +`; + +exports[`In operator 1`] = ` +"if (foo in bar) { + doThing() +} +" +`; + +exports[`In operator 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 3) -> (1, 2)", + "(1, 4) -> (1, 3)", + "(1, 8) -> (1, 7)", + "(1, 11) -> (1, 10)", + "(1, 14) -> (1, 13)", + "(1, 16) -> (1, 14)", + "(2, 2) -> (1, 15)", + "(2, 9) -> (1, 22)", + "(2, 10) -> (1, 23)", + "(3, 0) -> (1, 24)", +] +`; + +exports[`Indented multiline comment 1`] = ` +"function foo() { + /** + * java doc style comment + * more comment + */ + bar(); +} +" +`; + +exports[`Indented multiline comment 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 9) -> (1, 9)", + "(1, 12) -> (1, 12)", + "(1, 13) -> (1, 13)", + "(1, 15) -> (1, 15)", + "(6, 2) -> (6, 13)", + "(6, 5) -> (6, 16)", + "(6, 6) -> (6, 17)", + "(6, 7) -> (6, 18)", + "(7, 0) -> (7, 11)", +] +`; + +exports[`Instanceof 1`] = ` +"var a = x instanceof y; +" +`; + +exports[`Instanceof 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 6) -> (1, 5)", + "(1, 8) -> (1, 6)", + "(1, 10) -> (1, 8)", + "(1, 21) -> (1, 19)", + "(1, 22) -> (1, 20)", +] +`; + +exports[`Let handling with value 1`] = ` +"let d = 'yes'; +" +`; + +exports[`Let handling with value 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 6) -> (1, 6)", + "(1, 8) -> (1, 8)", + "(1, 13) -> (1, 13)", +] +`; + +exports[`Let handling without value 1`] = ` +"let d; +" +`; + +exports[`Let handling without value 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 5) -> (1, 5)", +] +`; + +exports[`Long parenthesis 1`] = ` +"if ( + thisIsAVeryLongVariable && + thisIsAnotherOne || + yetAnotherVeryLongVariable +) { + ( + thisIsAnotherOne = thisMayReturnNull() || + 'hi', + thisIsAVeryLongVariable = 42, + yetAnotherVeryLongVariable && + doSomething( + true /* do it well */ , + thisIsAVeryLongVariable, + thisIsAnotherOne, + yetAnotherVeryLongVariable + ) + ) +} +for ( + let thisIsAnotherVeryLongVariable = 0; + i < thisIsAnotherVeryLongVariable.length; + thisIsAnotherVeryLongVariable++ +) { +} +const x = ({ + thisIsAnotherVeryLongPropertyName: 'but should not cause the paren to be a line delimiter' +}) +" +`; + +exports[`Long parenthesis 2`] = ` +Array [ + "(1, 0) -> (2, 6)", + "(1, 3) -> (2, 9)", + "(2, 2) -> (2, 10)", + "(2, 26) -> (2, 34)", + "(3, 2) -> (2, 37)", + "(3, 19) -> (2, 54)", + "(4, 2) -> (2, 57)", + "(5, 0) -> (2, 83)", + "(5, 2) -> (2, 85)", + "(6, 2) -> (3, 8)", + "(7, 4) -> (3, 9)", + "(7, 21) -> (3, 26)", + "(7, 23) -> (3, 28)", + "(7, 40) -> (3, 45)", + "(7, 41) -> (3, 46)", + "(7, 43) -> (3, 48)", + "(8, 4) -> (3, 51)", + "(8, 8) -> (3, 55)", + "(9, 4) -> (3, 57)", + "(9, 28) -> (3, 81)", + "(9, 30) -> (3, 83)", + "(9, 32) -> (3, 85)", + "(10, 4) -> (3, 87)", + "(10, 31) -> (3, 114)", + "(11, 4) -> (3, 117)", + "(11, 15) -> (3, 128)", + "(12, 6) -> (3, 129)", + "(12, 28) -> (3, 150)", + "(13, 6) -> (3, 151)", + "(13, 29) -> (3, 174)", + "(14, 6) -> (3, 176)", + "(14, 22) -> (3, 192)", + "(15, 6) -> (3, 194)", + "(16, 4) -> (3, 220)", + "(17, 2) -> (3, 221)", + "(18, 0) -> (4, 6)", + "(19, 0) -> (5, 6)", + "(19, 4) -> (5, 10)", + "(20, 2) -> (5, 11)", + "(20, 6) -> (5, 15)", + "(20, 36) -> (5, 45)", + "(20, 38) -> (5, 47)", + "(20, 39) -> (5, 48)", + "(21, 2) -> (5, 50)", + "(21, 4) -> (5, 52)", + "(21, 6) -> (5, 54)", + "(21, 35) -> (5, 83)", + "(21, 36) -> (5, 84)", + "(21, 42) -> (5, 90)", + "(22, 2) -> (5, 92)", + "(22, 31) -> (5, 121)", + "(23, 0) -> (5, 123)", + "(23, 2) -> (5, 125)", + "(24, 0) -> (5, 126)", + "(25, 0) -> (6, 6)", + "(25, 6) -> (6, 12)", + "(25, 8) -> (6, 14)", + "(25, 10) -> (6, 16)", + "(25, 11) -> (6, 17)", + "(26, 2) -> (6, 18)", + "(26, 35) -> (6, 51)", + "(26, 37) -> (6, 53)", + "(27, 0) -> (6, 108)", + "(27, 1) -> (6, 109)", +] +`; + +exports[`Multi line comment 1`] = ` +"/* Comment + more comment */ +function foo() { + bar(); +} +" +`; + +exports[`Multi line comment 2`] = ` +Array [ + "(3, 0) -> (4, 4)", + "(3, 9) -> (4, 13)", + "(3, 12) -> (4, 16)", + "(3, 13) -> (4, 17)", + "(3, 15) -> (4, 19)", + "(4, 2) -> (4, 21)", + "(4, 5) -> (4, 24)", + "(4, 6) -> (4, 25)", + "(4, 7) -> (4, 26)", + "(5, 0) -> (4, 28)", +] +`; + +exports[`Multiple single line comments 1`] = ` +"function f() { + // a + // b + // c +} +" +`; + +exports[`Multiple single line comments 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 9) -> (1, 9)", + "(1, 10) -> (1, 10)", + "(1, 11) -> (1, 11)", + "(1, 13) -> (1, 13)", + "(5, 0) -> (5, 11)", +] +`; + +exports[`Named class handling 1`] = ` +"let unnamed = class Class { + constructor() { + } +} +" +`; + +exports[`Named class handling 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 12) -> (1, 11)", + "(1, 14) -> (1, 12)", + "(1, 20) -> (1, 18)", + "(1, 26) -> (1, 23)", + "(2, 2) -> (1, 24)", + "(2, 13) -> (1, 35)", + "(2, 14) -> (1, 36)", + "(2, 16) -> (1, 37)", + "(3, 2) -> (1, 38)", + "(4, 0) -> (1, 39)", +] +`; + +exports[`Nested function 1`] = ` +"function foo() { + function bar() { + debugger; + } + bar(); +} +" +`; + +exports[`Nested function 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 9) -> (1, 9)", + "(1, 12) -> (1, 12)", + "(1, 13) -> (1, 13)", + "(1, 15) -> (1, 15)", + "(2, 2) -> (1, 17)", + "(2, 11) -> (1, 26)", + "(2, 14) -> (1, 29)", + "(2, 15) -> (1, 30)", + "(2, 17) -> (1, 32)", + "(3, 4) -> (1, 34)", + "(3, 12) -> (1, 42)", + "(4, 2) -> (1, 44)", + "(5, 2) -> (1, 46)", + "(5, 5) -> (1, 49)", + "(5, 6) -> (1, 50)", + "(5, 7) -> (1, 51)", + "(6, 0) -> (1, 53)", +] +`; + +exports[`New expression 1`] = ` +"var foo = new Foo(); +" +`; + +exports[`New expression 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 8) -> (1, 7)", + "(1, 10) -> (1, 8)", + "(1, 14) -> (1, 12)", + "(1, 17) -> (1, 15)", + "(1, 18) -> (1, 16)", + "(1, 19) -> (1, 17)", +] +`; + +exports[`Non-ASI function call 1`] = ` +"f() +" +`; + +exports[`Non-ASI function call 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 1) -> (2, 11)", + "(1, 2) -> (2, 12)", +] +`; + +exports[`Non-ASI in 1`] = ` +"'x' in foo +" +`; + +exports[`Non-ASI in 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (2, 11)", + "(1, 7) -> (2, 14)", +] +`; + +exports[`Non-ASI new 1`] = ` +"new F() +" +`; + +exports[`Non-ASI new 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (2, 11)", + "(1, 5) -> (2, 12)", + "(1, 6) -> (2, 13)", +] +`; + +exports[`Non-ASI property access 1`] = ` +"[ + 1, + 2, + 3 +] +[0] +" +`; + +exports[`Non-ASI property access 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(2, 2) -> (1, 1)", + "(2, 3) -> (1, 2)", + "(3, 2) -> (1, 3)", + "(3, 3) -> (1, 4)", + "(4, 2) -> (1, 5)", + "(5, 0) -> (1, 6)", + "(6, 0) -> (2, 11)", + "(6, 1) -> (2, 12)", + "(6, 2) -> (2, 13)", +] +`; + +exports[`Null assignment 1`] = ` +"var i = null; +" +`; + +exports[`Null assignment 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 6) -> (1, 5)", + "(1, 8) -> (1, 6)", + "(1, 12) -> (1, 10)", +] +`; + +exports[`Objects 1`] = ` +"var o = { + a: 1, + b: 2 +}; +" +`; + +exports[`Objects 2`] = ` +Array [ + "(1, 0) -> (2, 6)", + "(1, 4) -> (2, 10)", + "(1, 6) -> (2, 11)", + "(1, 8) -> (2, 12)", + "(2, 2) -> (2, 13)", + "(2, 3) -> (2, 14)", + "(2, 5) -> (2, 15)", + "(2, 6) -> (2, 16)", + "(3, 2) -> (3, 6)", + "(3, 3) -> (3, 7)", + "(3, 5) -> (3, 8)", + "(4, 0) -> (3, 9)", + "(4, 1) -> (3, 10)", +] +`; + +exports[`Optional chaining parsing support 1`] = ` +"x?.y?.z?.['a']?.check(); +" +`; + +exports[`Optional chaining parsing support 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 1) -> (1, 1)", + "(1, 3) -> (1, 3)", + "(1, 4) -> (1, 4)", + "(1, 6) -> (1, 6)", + "(1, 7) -> (1, 7)", + "(1, 9) -> (1, 9)", + "(1, 10) -> (1, 10)", + "(1, 13) -> (1, 13)", + "(1, 14) -> (1, 14)", + "(1, 16) -> (1, 16)", + "(1, 21) -> (1, 21)", + "(1, 22) -> (1, 22)", + "(1, 23) -> (1, 23)", +] +`; + +exports[`Private fields parsing support 1`] = ` +"class MyClass { + constructor(a) { + this.#a = a; + this.#b = Math.random(); + this.ab = this.#getAB(); + } + #a + #b = 'default value' + static #someStaticPrivate + #getA() { + return this.#a; + } + #getAB() { + return this.#getA() + this.#b + } +} +" +`; + +exports[`Private fields parsing support 2`] = ` +Array [ + "(1, 0) -> (2, 6)", + "(1, 6) -> (2, 12)", + "(1, 14) -> (2, 20)", + "(2, 2) -> (3, 8)", + "(2, 13) -> (3, 19)", + "(2, 14) -> (3, 20)", + "(2, 15) -> (3, 21)", + "(2, 17) -> (3, 23)", + "(3, 4) -> (4, 10)", + "(3, 8) -> (4, 14)", + "(3, 9) -> (4, 15)", + "(3, 12) -> (4, 18)", + "(3, 14) -> (4, 20)", + "(3, 15) -> (4, 21)", + "(4, 4) -> (4, 22)", + "(4, 8) -> (4, 26)", + "(4, 9) -> (4, 27)", + "(4, 12) -> (4, 30)", + "(4, 14) -> (4, 32)", + "(4, 18) -> (4, 36)", + "(4, 19) -> (4, 37)", + "(4, 25) -> (4, 43)", + "(4, 26) -> (4, 44)", + "(4, 27) -> (4, 45)", + "(5, 4) -> (4, 46)", + "(5, 8) -> (4, 50)", + "(5, 9) -> (4, 51)", + "(5, 12) -> (4, 54)", + "(5, 14) -> (4, 56)", + "(5, 18) -> (4, 60)", + "(5, 19) -> (4, 61)", + "(5, 25) -> (4, 67)", + "(5, 26) -> (4, 68)", + "(5, 27) -> (4, 69)", + "(6, 2) -> (5, 8)", + "(7, 2) -> (6, 8)", + "(8, 2) -> (7, 8)", + "(8, 5) -> (7, 11)", + "(8, 7) -> (7, 13)", + "(9, 2) -> (8, 8)", + "(9, 9) -> (8, 15)", + "(10, 2) -> (9, 8)", + "(10, 7) -> (9, 13)", + "(10, 8) -> (9, 14)", + "(10, 10) -> (9, 16)", + "(11, 4) -> (10, 10)", + "(11, 11) -> (10, 17)", + "(11, 15) -> (10, 21)", + "(11, 16) -> (10, 22)", + "(11, 18) -> (10, 24)", + "(12, 2) -> (11, 8)", + "(13, 2) -> (12, 8)", + "(13, 8) -> (12, 14)", + "(13, 9) -> (12, 15)", + "(13, 11) -> (12, 17)", + "(14, 4) -> (13, 10)", + "(14, 11) -> (13, 17)", + "(14, 15) -> (13, 21)", + "(14, 16) -> (13, 22)", + "(14, 21) -> (13, 27)", + "(14, 22) -> (13, 28)", + "(14, 24) -> (13, 29)", + "(14, 26) -> (13, 30)", + "(14, 30) -> (13, 34)", + "(14, 31) -> (14, 12)", + "(15, 2) -> (15, 8)", + "(16, 0) -> (16, 6)", +] +`; + +exports[`Regexp 1`] = ` +"var r = /foobar/g; +" +`; + +exports[`Regexp 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 6) -> (1, 5)", + "(1, 8) -> (1, 6)", + "(1, 17) -> (1, 15)", +] +`; + +exports[`Simple function 1`] = ` +"function foo() { + bar(); +} +" +`; + +exports[`Simple function 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 9) -> (1, 9)", + "(1, 12) -> (1, 12)", + "(1, 13) -> (1, 13)", + "(1, 15) -> (1, 15)", + "(2, 2) -> (1, 17)", + "(2, 5) -> (1, 20)", + "(2, 6) -> (1, 21)", + "(2, 7) -> (1, 22)", + "(3, 0) -> (1, 24)", +] +`; + +exports[`Single line comment 1`] = ` +"// Comment +function foo() { + bar(); +} +" +`; + +exports[`Single line comment 2`] = ` +Array [ + "(2, 0) -> (3, 4)", + "(2, 9) -> (3, 13)", + "(2, 12) -> (3, 16)", + "(2, 13) -> (3, 17)", + "(2, 15) -> (3, 19)", + "(3, 2) -> (3, 21)", + "(3, 5) -> (3, 24)", + "(3, 6) -> (3, 25)", + "(3, 7) -> (3, 26)", + "(4, 0) -> (3, 28)", +] +`; + +exports[`Stack-keyword property access 1`] = ` +"foo.a = 1.1; +foo.do.switch.case.default = 2.2; +foo.b = 3.3; +" +`; + +exports[`Stack-keyword property access 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 3) -> (1, 3)", + "(1, 4) -> (1, 4)", + "(1, 6) -> (1, 5)", + "(1, 8) -> (1, 6)", + "(1, 11) -> (1, 9)", + "(2, 0) -> (1, 10)", + "(2, 3) -> (1, 13)", + "(2, 4) -> (1, 14)", + "(2, 6) -> (1, 16)", + "(2, 7) -> (1, 17)", + "(2, 13) -> (1, 23)", + "(2, 14) -> (1, 24)", + "(2, 18) -> (1, 28)", + "(2, 19) -> (1, 29)", + "(2, 27) -> (1, 36)", + "(2, 29) -> (1, 37)", + "(2, 32) -> (1, 40)", + "(3, 0) -> (1, 41)", + "(3, 3) -> (1, 44)", + "(3, 4) -> (1, 45)", + "(3, 6) -> (1, 46)", + "(3, 8) -> (1, 47)", + "(3, 11) -> (1, 50)", +] +`; + +exports[`String with quote 1`] = ` +"var foo = '\\\\''; +" +`; + +exports[`String with quote 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 8) -> (1, 8)", + "(1, 10) -> (1, 10)", + "(1, 14) -> (1, 13)", +] +`; + +exports[`String with semicolon 1`] = ` +"var foo = ';'; +" +`; + +exports[`String with semicolon 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 8) -> (1, 8)", + "(1, 10) -> (1, 10)", + "(1, 13) -> (1, 13)", +] +`; + +exports[`Subclass handling 1`] = ` +"class Class extends Base { + constructor() { + } +} +" +`; + +exports[`Subclass handling 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 6) -> (1, 7)", + "(1, 12) -> (1, 14)", + "(1, 20) -> (1, 23)", + "(1, 25) -> (1, 27)", + "(2, 2) -> (1, 28)", + "(2, 13) -> (1, 39)", + "(2, 14) -> (1, 40)", + "(2, 16) -> (1, 41)", + "(3, 2) -> (1, 42)", + "(4, 0) -> (1, 43)", +] +`; + +exports[`Switch statements 1`] = ` +"switch (x) { + case a: + foo(); + break; + default: + bar() +} +" +`; + +exports[`Switch statements 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 7) -> (1, 6)", + "(1, 8) -> (1, 7)", + "(1, 9) -> (1, 8)", + "(1, 11) -> (1, 9)", + "(2, 2) -> (1, 10)", + "(2, 7) -> (1, 15)", + "(2, 8) -> (1, 16)", + "(3, 4) -> (1, 17)", + "(3, 7) -> (1, 20)", + "(3, 8) -> (1, 21)", + "(3, 9) -> (1, 22)", + "(4, 4) -> (1, 23)", + "(4, 9) -> (1, 28)", + "(5, 2) -> (1, 29)", + "(5, 9) -> (1, 36)", + "(6, 4) -> (1, 37)", + "(6, 7) -> (1, 40)", + "(6, 8) -> (1, 41)", + "(7, 0) -> (1, 42)", +] +`; + +exports[`Template literals 1`] = ` +"\`abc\${ JSON.stringify({ + clas: 'testing' +}) }def\`; +{ + a(); +} +" +`; + +exports[`Template literals 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 1) -> (1, 1)", + "(1, 4) -> (1, 4)", + "(1, 7) -> (1, 6)", + "(1, 11) -> (1, 10)", + "(1, 12) -> (1, 11)", + "(1, 21) -> (1, 20)", + "(1, 22) -> (1, 21)", + "(2, 2) -> (1, 22)", + "(2, 6) -> (1, 26)", + "(2, 8) -> (1, 28)", + "(3, 0) -> (1, 37)", + "(3, 1) -> (1, 38)", + "(3, 3) -> (1, 39)", + "(3, 4) -> (1, 40)", + "(3, 7) -> (1, 43)", + "(3, 8) -> (1, 44)", + "(4, 0) -> (1, 45)", + "(5, 2) -> (1, 46)", + "(5, 3) -> (1, 47)", + "(5, 4) -> (1, 48)", + "(5, 5) -> (1, 49)", + "(6, 0) -> (1, 50)", +] +`; + +exports[`Ternary operator 1`] = ` +"bar ? baz : bang; +" +`; + +exports[`Ternary operator 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 3)", + "(1, 6) -> (1, 4)", + "(1, 10) -> (1, 7)", + "(1, 12) -> (1, 8)", + "(1, 16) -> (1, 12)", +] +`; + +exports[`This property access 1`] = ` +"var foo = this.foo; +" +`; + +exports[`This property access 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 8) -> (1, 7)", + "(1, 10) -> (1, 8)", + "(1, 14) -> (1, 12)", + "(1, 15) -> (1, 13)", + "(1, 18) -> (1, 16)", +] +`; + +exports[`True assignment 1`] = ` +"var foo = true; +" +`; + +exports[`True assignment 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 8) -> (1, 7)", + "(1, 10) -> (1, 8)", + "(1, 14) -> (1, 12)", +] +`; + +exports[`Try/catch/finally statement 1`] = ` +"try { + dangerous() +} catch (e) { + handle(e) +} finally { + cleanup() +} +" +`; + +exports[`Try/catch/finally statement 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 3)", + "(2, 2) -> (1, 4)", + "(2, 11) -> (1, 13)", + "(2, 12) -> (1, 14)", + "(3, 0) -> (1, 15)", + "(3, 2) -> (1, 16)", + "(3, 8) -> (1, 21)", + "(3, 9) -> (1, 22)", + "(3, 10) -> (1, 23)", + "(3, 12) -> (1, 24)", + "(4, 2) -> (1, 25)", + "(4, 8) -> (1, 31)", + "(4, 9) -> (1, 32)", + "(4, 10) -> (1, 33)", + "(5, 0) -> (1, 34)", + "(5, 2) -> (1, 35)", + "(5, 10) -> (1, 42)", + "(6, 2) -> (1, 43)", + "(6, 9) -> (1, 50)", + "(6, 10) -> (1, 51)", + "(7, 0) -> (1, 52)", +] +`; + +exports[`Undefined assignment 1`] = ` +"var i = undefined; +" +`; + +exports[`Undefined assignment 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 6) -> (1, 5)", + "(1, 8) -> (1, 6)", + "(1, 17) -> (1, 15)", +] +`; + +exports[`Unnamed class handling 1`] = ` +"let unnamed = class { + constructor() { + } +} +" +`; + +exports[`Unnamed class handling 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 12) -> (1, 11)", + "(1, 14) -> (1, 12)", + "(1, 20) -> (1, 17)", + "(2, 2) -> (1, 18)", + "(2, 13) -> (1, 29)", + "(2, 14) -> (1, 30)", + "(2, 16) -> (1, 31)", + "(3, 2) -> (1, 32)", + "(4, 0) -> (1, 33)", +] +`; + +exports[`Void 0 assignment 1`] = ` +"var i = void 0; +" +`; + +exports[`Void 0 assignment 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 6) -> (1, 5)", + "(1, 8) -> (1, 6)", + "(1, 13) -> (1, 11)", + "(1, 14) -> (1, 12)", +] +`; + +exports[`With statement 1`] = ` +"with (obj) { + crock() +} +" +`; + +exports[`With statement 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 5) -> (1, 4)", + "(1, 6) -> (1, 5)", + "(1, 9) -> (1, 8)", + "(1, 11) -> (1, 9)", + "(2, 2) -> (1, 10)", + "(2, 7) -> (1, 15)", + "(2, 8) -> (1, 16)", + "(3, 0) -> (1, 17)", +] +`; + +exports[`for..of loop 1`] = ` +"for (const x of [ + 1, + 2, + 3 +]) { + console.log(x) +} +" +`; + +exports[`for..of loop 2`] = ` +Array [ + "(1, 0) -> (1, 0)", + "(1, 4) -> (1, 4)", + "(1, 5) -> (1, 5)", + "(1, 11) -> (1, 11)", + "(1, 13) -> (1, 13)", + "(1, 16) -> (1, 16)", + "(2, 2) -> (1, 17)", + "(2, 3) -> (1, 18)", + "(3, 2) -> (1, 19)", + "(3, 3) -> (1, 20)", + "(4, 2) -> (1, 21)", + "(5, 0) -> (1, 22)", + "(5, 1) -> (1, 23)", + "(5, 3) -> (1, 25)", + "(6, 2) -> (1, 27)", + "(6, 9) -> (1, 34)", + "(6, 10) -> (1, 35)", + "(6, 13) -> (1, 38)", + "(6, 14) -> (1, 39)", + "(6, 15) -> (1, 40)", + "(7, 0) -> (1, 42)", +] +`; diff --git a/devtools/client/debugger/src/workers/pretty-print/tests/prettyFast.spec.js b/devtools/client/debugger/src/workers/pretty-print/tests/prettyFast.spec.js new file mode 100644 index 0000000000..f1f7a1c635 --- /dev/null +++ b/devtools/client/debugger/src/workers/pretty-print/tests/prettyFast.spec.js @@ -0,0 +1,434 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +/* + * Copyright 2013 Mozilla Foundation and contributors + * Licensed under the New BSD license. See LICENSE.md or: + * http://opensource.org/licenses/BSD-2-Clause + */ +import { prettyFast } from "../pretty-fast"; +import { SourceMapConsumer } from "devtools/client/shared/vendor/source-map/source-map"; + +const cases = [ + { + name: "Simple function", + input: "function foo() { bar(); }", + }, + { + name: "Nested function", + input: "function foo() { function bar() { debugger; } bar(); }", + }, + { + name: "Immediately invoked function expression", + input: "(function(){thingy()}())", + }, + { + name: "Single line comment", + input: ` + // Comment + function foo() { bar(); }`, + }, + { + name: "Multi line comment", + input: ` + /* Comment + more comment */ + function foo() { bar(); } + `, + }, + { name: "Null assignment", input: "var i=null;" }, + { name: "Undefined assignment", input: "var i=undefined;" }, + { name: "Void 0 assignment", input: "var i=void 0;" }, + { + name: "This property access", + input: "var foo=this.foo;\n", + }, + + { + name: "True assignment", + input: "var foo=true;\n", + }, + + { + name: "False assignment", + input: "var foo=false;\n", + }, + + { + name: "For loop", + input: "for (var i = 0; i < n; i++) { console.log(i); }", + }, + + { + name: "for..of loop", + input: "for (const x of [1,2,3]) { console.log(x) }", + }, + + { + name: "String with semicolon", + input: "var foo = ';';\n", + }, + + { + name: "String with quote", + input: 'var foo = "\'";\n', + }, + + { + name: "Function calls", + input: "var result=func(a,b,c,d);", + }, + + { + name: "Regexp", + input: "var r=/foobar/g;", + }, + + { + name: "In operator", + input: "if(foo in bar){doThing()}", + output: "if (foo in bar) {\n" + " doThing()\n" + "}\n", + }, + { + name: "With statement", + input: "with(obj){crock()}", + }, + { + name: "New expression", + input: "var foo=new Foo();", + }, + { + name: "Continue/break statements", + input: "while(1){if(x){continue}if(y){break}if(z){break foo}}", + }, + { + name: "Instanceof", + input: "var a=x instanceof y;", + }, + { + name: "Binary operators", + input: "var a=5*30;var b=5>>3;", + }, + { + name: "Delete", + input: "delete obj.prop;", + }, + + { + name: "Try/catch/finally statement", + input: "try{dangerous()}catch(e){handle(e)}finally{cleanup()}", + }, + { + name: "If/else statement", + input: "if(c){then()}else{other()}", + }, + { + name: "If/else without curlies", + input: "if(c) a else b", + }, + { + name: "Objects", + input: ` + var o={a:1, + b:2};`, + }, + { + name: "Do/while loop", + input: "do{x}while(y)", + }, + { + name: "Arrays", + input: "var a=[1,2,3];", + }, + { + name: "Arrays and spread operator", + input: "var a=[1,...[2,3],...[], 4];", + }, + { + name: "Empty object/array literals", + input: `let a=[];const b={};c={...{},d: 42};for(let x of []){for(let y in {}){}}`, + }, + { + name: "Code that relies on ASI", + input: ` + var foo = 10 + var bar = 20 + function g() { + a() + b() + }`, + }, + { + name: "Ternary operator", + input: "bar?baz:bang;", + }, + { + name: "Switch statements", + input: "switch(x){case a:foo();break;default:bar()}", + }, + + { + name: "Multiple single line comments", + input: `function f() { + // a + // b + // c + }`, + }, + { + name: "Indented multiline comment", + input: `function foo() { + /** + * java doc style comment + * more comment + */ + bar(); + }`, + }, + { + name: "ASI return", + input: `function f() { + return + {} + }`, + }, + { + name: "Non-ASI property access", + input: `[1,2,3] + [0]`, + }, + { + name: "Non-ASI in", + input: `'x' + in foo`, + }, + + { + name: "Non-ASI function call", + input: `f + ()`, + }, + { + name: "Non-ASI new", + input: `new + F()`, + }, + { + name: "Getter and setter literals", + input: "var obj={get foo(){return this._foo},set foo(v){this._foo=v}}", + }, + { + name: "Escaping backslashes in strings", + input: "'\\\\'\n", + }, + { + name: "Escaping carriage return in strings", + input: "'\\r'\n", + }, + { + name: "Escaping tab in strings", + input: "'\\t'\n", + }, + { + name: "Escaping vertical tab in strings", + input: "'\\v'\n", + }, + { + name: "Escaping form feed in strings", + input: "'\\f'\n", + }, + { + name: "Escaping null character in strings", + input: "'\\0'\n", + }, + { + name: "Bug 977082 - space between grouping operator and dot notation", + input: `JSON.stringify(3).length; + ([1,2,3]).length; + (new Date()).toLocaleString();`, + }, + { + name: "Bug 975477 don't move end of line comments to next line", + input: `switch (request.action) { + case 'show': //$NON-NLS-0$ + if (localStorage.hideicon !== 'true') { //$NON-NLS-0$ + chrome.pageAction.show(sender.tab.id); + } + break; + case 'hide': /*Multiline + Comment */ + break; + default: + console.warn('unknown request'); //$NON-NLS-0$ + // don't respond if you don't understand the message. + return; + }`, + }, + { + name: "Const handling", + input: "const d = 'yes';\n", + }, + { + name: "Let handling without value", + input: "let d;\n", + }, + { + name: "Let handling with value", + input: "let d = 'yes';\n", + }, + { + name: "Template literals", + // issue in acorn + input: "`abc${JSON.stringify({clas: 'testing'})}def`;{a();}", + }, + { + name: "Class handling", + input: "class Class{constructor(){}}", + }, + { + name: "Subclass handling", + input: "class Class extends Base{constructor(){}}", + }, + { + name: "Unnamed class handling", + input: "let unnamed=class{constructor(){}}", + }, + { + name: "Named class handling", + input: "let unnamed=class Class{constructor(){}}", + }, + { + name: "Class extension within a function", + input: "(function() { class X extends Y { constructor() {} } })()", + }, + { + name: "Bug 1261971 - indentation after switch statement", + input: "{switch(x){}if(y){}done();}", + }, + { + name: "Bug 1206633 - spaces in for of", + input: "for (let tab of tabs) {}", + }, + { + name: "Bug pretty-sure-3 - escaping line and paragraph separators", + input: "x = '\\u2029\\u2028';", + }, + { + name: "Bug pretty-sure-4 - escaping null character before digit", + input: "x = '\\u00001';", + }, + { + name: "Bug pretty-sure-5 - empty multiline comment shouldn't throw exception", + input: `{ + /* + */ + return; + }`, + }, + { + name: "Bug pretty-sure-6 - inline comment shouldn't move parenthesis to next line", + input: `return /* inline comment */ ( + 1+1);`, + }, + { + name: "Bug pretty-sure-7 - accessing a literal number property requires a space", + input: "0..toString()+x.toString();", + }, + { + name: "Bug pretty-sure-8 - return and yield only accept arguments when on the same line", + input: `{ + return + (x) + yield + (x) + yield + *x + }`, + }, + { + name: "Bug pretty-sure-9 - accept unary operator at start of file", + input: "+ 0", + }, + { + name: "Stack-keyword property access", + input: "foo.a=1.1;foo.do.switch.case.default=2.2;foo.b=3.3;\n", + }, + { + name: "Dot handling with let which is identifier name", + input: "y.let.let = 1.23;\n", + }, + { + name: "Dot handling with keywords which are identifier name", + input: "y.await.break.const.delete.else.return.new.yield = 1.23;\n", + }, + { + name: "Optional chaining parsing support", + input: "x?.y?.z?.['a']?.check();\n", + }, + { + name: "Private fields parsing support", + input: ` + class MyClass { + constructor(a) { + this.#a = a;this.#b = Math.random();this.ab = this.#getAB(); + } + #a + #b = "default value" + static #someStaticPrivate + #getA() { + return this.#a; + } + #getAB() { + return this.#getA()+this. + #b + } + } + `, + }, + { + name: "Long parenthesis", + input: ` + if (thisIsAVeryLongVariable && thisIsAnotherOne || yetAnotherVeryLongVariable) { + (thisIsAnotherOne = thisMayReturnNull() || "hi", thisIsAVeryLongVariable = 42, yetAnotherVeryLongVariable && doSomething(true /* do it well */,thisIsAVeryLongVariable, thisIsAnotherOne, yetAnotherVeryLongVariable)) + } + for (let thisIsAnotherVeryLongVariable = 0; i < thisIsAnotherVeryLongVariable.length; thisIsAnotherVeryLongVariable++) {} + const x = ({thisIsAnotherVeryLongPropertyName: "but should not cause the paren to be a line delimiter"}) + `, + }, + { + name: "Fat arrow function", + input: ` + const a = () => 42 + addEventListener("click", e => { return false }); + const sum = (c,d) => c+d + `, + }, +]; + +const includesOnly = cases.find(({ only }) => only); + +for (const { name, input, only, skip } of cases) { + if ((includesOnly && !only) || skip) { + continue; + } + test(name, async () => { + const actual = prettyFast(input, { + indent: " ", + url: "test.js", + }); + + expect(actual.code).toMatchSnapshot(); + + const smc = await new SourceMapConsumer(actual.map.toJSON()); + const mappings = []; + smc.eachMapping( + ({ generatedColumn, generatedLine, originalColumn, originalLine }) => { + mappings.push( + `(${originalLine}, ${originalColumn}) -> (${generatedLine}, ${generatedColumn})` + ); + } + ); + expect(mappings).toMatchSnapshot(); + }); +} diff --git a/devtools/client/debugger/src/workers/pretty-print/worker.js b/devtools/client/debugger/src/workers/pretty-print/worker.js new file mode 100644 index 0000000000..1c5587db9f --- /dev/null +++ b/devtools/client/debugger/src/workers/pretty-print/worker.js @@ -0,0 +1,98 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ + +import { workerHandler } from "../../../../shared/worker-utils"; +import { prettyFast } from "./pretty-fast"; + +var { SourceMapGenerator } = require("source-map"); + +const sourceMapGeneratorByTaskId = new Map(); + +function prettyPrint({ url, indent, sourceText }) { + const { code, map: sourceMapGenerator } = prettyFast(sourceText, { + url, + indent, + }); + + return { + code, + sourceMap: sourceMapGenerator.toJSON(), + }; +} + +function prettyPrintInlineScript({ + taskId, + url, + indent, + sourceText, + originalStartLine, + originalStartColumn, + generatedStartLine, +}) { + let taskSourceMapGenerator; + if (!sourceMapGeneratorByTaskId.has(taskId)) { + taskSourceMapGenerator = new SourceMapGenerator({ file: url }); + sourceMapGeneratorByTaskId.set(taskId, taskSourceMapGenerator); + } else { + taskSourceMapGenerator = sourceMapGeneratorByTaskId.get(taskId); + } + + const { code } = prettyFast(sourceText, { + url, + indent, + sourceMapGenerator: taskSourceMapGenerator, + /* + * By default prettyPrint will trim the text, and we'd have the pretty text displayed + * just after the script tag, e.g.: + * + * ``` + * <script>if (true) { + * something() + * } + * </script> + * ``` + * + * We want the text to start on a new line, so prepend a line break, so we get + * something like: + * + * ``` + * <script> + * if (true) { + * something() + * } + * </script> + * ``` + */ + prefixWithNewLine: true, + originalStartLine, + originalStartColumn, + generatedStartLine, + }); + + // When a taskId was passed, we only return the pretty printed text. + // The source map should be retrieved with getSourceMapForTask. + return code; +} + +/** + * Get the source map for a pretty-print task + * + * @param {Integer} taskId: The taskId that was used to call prettyPrint + * @returns {Object} A source map object + */ +function getSourceMapForTask(taskId) { + if (!sourceMapGeneratorByTaskId.has(taskId)) { + return null; + } + + const taskSourceMapGenerator = sourceMapGeneratorByTaskId.get(taskId); + sourceMapGeneratorByTaskId.delete(taskId); + return taskSourceMapGenerator.toJSON(); +} + +self.onmessage = workerHandler({ + prettyPrint, + prettyPrintInlineScript, + getSourceMapForTask, +}); |