Bug 1803360 - [devtools] Put pretty-fast library in tree. r=ochameau.

Differential Revision: https://phabricator.services.mozilla.com/D163560
This commit is contained in:
Nicolas Chevobbe 2022-12-08 15:54:07 +00:00
parent 79dd85e9fe
commit 60a24410b8
12 changed files with 14098 additions and 3388 deletions

View file

@ -63,6 +63,7 @@ devtools/client/debugger/bin/
devtools/client/debugger/configs/ devtools/client/debugger/configs/
devtools/client/debugger/dist/ devtools/client/debugger/dist/
devtools/client/debugger/images/ devtools/client/debugger/images/
devtools/client/debugger/packages/
devtools/client/debugger/test/mochitest/examples/ devtools/client/debugger/test/mochitest/examples/
devtools/client/debugger/index.html devtools/client/debugger/index.html
devtools/client/debugger/webpack.config.js devtools/client/debugger/webpack.config.js

View file

@ -1250,7 +1250,10 @@
"../node_modules/babel-loader/lib/index.js??ref--1!../../shared/vendor/source-map/lib/read-wasm.js": 1098, "../node_modules/babel-loader/lib/index.js??ref--1!../../shared/vendor/source-map/lib/read-wasm.js": 1098,
"../node_modules/babel-loader/lib/index.js??ref--1!../../shared/vendor/source-map/lib/url.js": 1099, "../node_modules/babel-loader/lib/index.js??ref--1!../../shared/vendor/source-map/lib/url.js": 1099,
"../node_modules/babel-loader/lib/index.js??ref--1!../../shared/vendor/whatwg-url.js": 1100, "../node_modules/babel-loader/lib/index.js??ref--1!../../shared/vendor/whatwg-url.js": 1100,
"../node_modules/path-browserify/index.js": 1101 "../node_modules/path-browserify/index.js": 1101,
"../node_modules/babel-loader/lib/index.js??ref--1!../packages/pretty-fast/pretty-fast.js": 1102,
"../node_modules/acorn/dist/acorn.mjs": 1103,
"../packages/pretty-fast/node_modules/acorn/dist/acorn.mjs": 1104
}, },
"usedIds": { "usedIds": {
"0": 0, "0": 0,
@ -2354,7 +2357,10 @@
"1098": 1098, "1098": 1098,
"1099": 1099, "1099": 1099,
"1100": 1100, "1100": 1100,
"1101": 1101 "1101": 1101,
"1102": 1102,
"1103": 1103,
"1104": 1104
} }
}, },
"chunks": { "chunks": {

File diff suppressed because it is too large Load diff

View file

@ -25,7 +25,6 @@
"codemirror": "^5.28.0", "codemirror": "^5.28.0",
"fuzzaldrin-plus": "^0.6.0", "fuzzaldrin-plus": "^0.6.0",
"parse-script-tags": "github:loganfsmyth/parse-script-tags#d771732ca47e1b3554fe67d609fd18cc785c5f26", "parse-script-tags": "github:loganfsmyth/parse-script-tags#d771732ca47e1b3554fe67d609fd18cc785c5f26",
"pretty-fast": "^0.2.7",
"react": "16.8.6", "react": "16.8.6",
"react-aria-components": "^0.0.4", "react-aria-components": "^0.0.4",
"react-dom": "16.8.6", "react-dom": "16.8.6",
@ -91,4 +90,4 @@
"workerjs/@babel/core": "^7.15.5", "workerjs/@babel/core": "^7.15.5",
"workerjs/@babel/register": "^7.15.3" "workerjs/@babel/register": "^7.15.3"
} }
} }

View file

@ -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.

View file

@ -0,0 +1,73 @@
# Pretty Fast
Pretty Fast is a source-map-generating JavaScript pretty printer, that is pretty
fast.
[![Build Status](https://travis-ci.org/mozilla/pretty-fast.png?branch=master)](https://travis-ci.org/mozilla/pretty-fast)
## Install
npm install pretty-fast
## Usage
var prettyFast = require("pretty-fast");
var uglyJS = "function ugly(){code()}";
var pretty = prettyFast(uglyJS, {
url: "test.js",
indent: " "
});
console.log(pretty.code);
// function ugly() {
// code()
// }
console.log(pretty.map);
// [object SourceMapGenerator]
(See the [mozilla/source-map][0] library for information on SourceMapGenerator
instances, and source maps in general.)
[0]: https://github.com/mozilla/source-map
## Options
* `url` - The URL of the JavaScript source being prettified. Used in the
generated source map. If you are prettifying JS that isn't from a file or
doesn't have a URL, you can use a dummy value, such as "(anonymous)".
* `indent` - The string that you want your code indented by. Most people want
one of `" "`, `" "`, or `"\t"`.
* `ecmaVersion` - Indicates the ECMAScript version to parse.
See acorn.parse documentation for more details. Defaults to `"latest"`.
## Issues
[Please use Bugzilla](https://bugzilla.mozilla.org/enter_bug.cgi?product=Firefox&component=Developer%20Tools%3A%20Debugger)
## Goals
* To be pretty fast, while still generating source maps.
* To avoid fully parsing the source text; we should be able to get away with
only a tokenizer and some heuristics.
* Preserve comments.
* Pretty Fast should be able to run inside Web Workers.
## Non-goals
* Extreme configurability of types of indentation, where curly braces go, etc.
* To be the very fastest pretty printer in the universe. This goal is
unattainable given that generating source maps is a requirement. We just need
to be Pretty Fast.
* To perfectly pretty print *exactly* as you would have written the code. This
falls out from both not wanting to support extreme configurability, and
avoiding full on parsing. We just aim to pretty print Pretty Well.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
{
"name": "pretty-fast",
"version": "0.2.7",
"description": "A fast JavaScript pretty printer.",
"author": "Nick Fitzgerald <fitzgen@gmail.com>",
"homepage": "https://github.com/mozilla/pretty-fast",
"repository": {
"type": "git",
"url": "https://github.com/mozilla/pretty-fast.git"
},
"license": "BSD",
"main": "pretty-fast.js",
"scripts": {
"eslint": "eslint -f compact *.js",
"test": "jest"
},
"devDependencies": {
"chalk": "^3.0.0",
"eslint": "6.6.0",
"jest": "^25.1.0"
},
"dependencies": {
"source-map": "^0.5.7",
"acorn": "~8.2.4"
}
}

View file

@ -0,0 +1,999 @@
/* 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/>. */
(function(root, factory) {
"use strict";
if (typeof define === "function" && define.amd) {
define(factory);
} else if (typeof exports === "object") {
module.exports = factory();
} else {
root.prettyFast = factory();
}
})(
this,
function() {
"use strict";
var acorn = this.acorn || require("acorn");
var sourceMap = this.sourceMap || require("source-map");
var SourceNode = sourceMap.SourceNode;
// 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.
var PRE_ARRAY_LITERAL_TOKENS = {
typeof: true,
void: true,
delete: true,
case: true,
do: true,
"=": true,
in: true,
"{": true,
"*": true,
"/": true,
"%": true,
else: true,
";": true,
"++": true,
"--": true,
"+": true,
"-": true,
"~": true,
"!": true,
":": true,
"?": true,
">>": true,
">>>": true,
"<<": true,
"||": true,
"&&": true,
"<": true,
">": true,
"<=": true,
">=": true,
instanceof: true,
"&": true,
"^": true,
"|": true,
"==": true,
"!=": true,
"===": true,
"!==": true,
",": true,
"}": 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 lastToken
* The last token we added to the pretty printed results.
*
* @returns Boolean
* True if we believe it is an array literal, false otherwise.
*/
function isArrayLiteral(token, lastToken) {
if (token.type.label != "[") {
return false;
}
if (!lastToken) {
return true;
}
if (lastToken.type.isAssign) {
return true;
}
return !!PRE_ARRAY_LITERAL_TOKENS[
lastToken.type.keyword || lastToken.type.label
];
}
// If any of these tokens are followed by a token on a new line, we know that
// ASI cannot happen.
var PREVENT_ASI_AFTER_TOKENS = {
// Binary operators
"*": true,
"/": true,
"%": true,
"+": true,
"-": true,
"<<": true,
">>": true,
">>>": true,
"<": true,
">": true,
"<=": true,
">=": true,
instanceof: true,
in: true,
"==": true,
"!=": true,
"===": true,
"!==": true,
"&": true,
"^": true,
"|": true,
"&&": true,
"||": true,
",": true,
".": true,
"=": true,
"*=": true,
"/=": true,
"%=": true,
"+=": true,
"-=": true,
"<<=": true,
">>=": true,
">>>=": true,
"&=": true,
"^=": true,
"|=": true,
// Unary operators
delete: true,
void: true,
typeof: true,
"~": true,
"!": true,
new: true,
// Function calls and grouped expressions
"(": true,
};
// If any of these tokens are on a line after the token before it, we know
// that ASI cannot happen.
var PREVENT_ASI_BEFORE_TOKENS = {
// Binary operators
"*": true,
"/": true,
"%": true,
"<<": true,
">>": true,
">>>": true,
"<": true,
">": true,
"<=": true,
">=": true,
instanceof: true,
in: true,
"==": true,
"!=": true,
"===": true,
"!==": true,
"&": true,
"^": true,
"|": true,
"&&": true,
"||": true,
",": true,
".": true,
"=": true,
"*=": true,
"/=": true,
"%=": true,
"+=": true,
"-=": true,
"<<=": true,
">>=": true,
">>>=": true,
"&=": true,
"^=": true,
"|=": true,
// Function calls
"(": true,
};
/**
* 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) {
var 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 lastToken
* The last token we added to the pretty printed results.
*
* @returns Boolean
* True if we believe ASI occurs.
*/
function isASI(token, lastToken) {
if (!lastToken) {
return false;
}
if (token.loc.start.line === lastToken.loc.start.line) {
return false;
}
if (
lastToken.type.keyword == "return" ||
lastToken.type.keyword == "yield" ||
(lastToken.type.label == "name" && lastToken.value == "yield")
) {
return true;
}
if (
PREVENT_ASI_AFTER_TOKENS[lastToken.type.label || lastToken.type.keyword]
) {
return false;
}
if (PREVENT_ASI_BEFORE_TOKENS[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) {
if (token.isArrayLiteral) {
return true;
}
var ttl = token.type.label;
var top = stack[stack.length - 1];
return (
(ttl == ";" && top != "(") ||
ttl == "{" ||
(ttl == "," && top != "(") ||
(ttl == ":" && (top == "case" || top == "default"))
);
}
/**
* 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.
* @param Function write
* The function to write to the pretty printed results.
* @param Array stack
* The stack of open parens/curlies/brackets/etc.
*
* @returns Boolean
* Returns true if we added a newline to result, false in all other
* cases.
*/
function appendNewline(token, write, stack) {
if (isLineDelimiter(token, stack)) {
write("\n", token.loc.start.line, token.loc.start.column);
return true;
}
return false;
}
/**
* Determines if we need to add a space between the last 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 lastToken
* The last token added to the pretty printed code.
*/
function needsSpaceAfter(token, lastToken) {
if (lastToken) {
if (lastToken.type.isLoop) {
return true;
}
if (lastToken.type.isAssign) {
return true;
}
if (lastToken.type.binop != null) {
return true;
}
var ltt = lastToken.type.label;
if (ltt == "?") {
return true;
}
if (ltt == ":") {
return true;
}
if (ltt == ",") {
return true;
}
if (ltt == ";") {
return true;
}
if (ltt == "${") {
return true;
}
if (ltt == "num" && token.type.label == ".") {
return true;
}
var ltk = lastToken.type.keyword;
var ttl = token.type.label;
if (ltk != null && ttl != ".") {
if (ltk == "break" || ltk == "continue" || ltk == "return") {
return token.type.label != ";";
}
if (
ltk != "debugger" &&
ltk != "null" &&
ltk != "true" &&
ltk != "false" &&
ltk != "this" &&
ltk != "default"
) {
return true;
}
}
if (
ltt == ")" &&
token.type.label != ")" &&
token.type.label != "]" &&
token.type.label != ";" &&
token.type.label != "," &&
token.type.label != "."
) {
return true;
}
if (isIdentifierLike(token) && isIdentifierLike(lastToken)) {
// We must emit a space to avoid merging the tokens.
return true;
}
if (token.type.label == "{" && lastToken.type.label == "name") {
return true;
}
}
if (token.type.isAssign) {
return true;
}
if (token.type.binop != null && lastToken) {
return true;
}
if (token.type.label == "?") {
return true;
}
return false;
}
/**
* 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.
* @param Object lastToken
* The last token we added to the pretty printed code.
* @param Boolean addedNewline
* Whether we added a newline after adding the last token to the pretty
* printed code.
* @param Boolean addedSpace
* Whether we added a space after adding the last token to the pretty
* printed code. This only happens if an inline comment was printed
* since the last token.
* @param Function write
* The function to write pretty printed code to the result SourceNode.
* @param Object options
* The options object.
* @param Number indentLevel
* The number of indents deep we are.
* @param Array stack
* The stack of open curlies, brackets, etc.
*/
function prependWhiteSpace(
token,
lastToken,
addedNewline,
addedSpace,
write,
options,
indentLevel,
stack
) {
var ttk = token.type.keyword;
var ttl = token.type.label;
var newlineAdded = addedNewline;
var spaceAdded = addedSpace;
var ltt = lastToken ? lastToken.type.label : null;
// 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 (lastToken && ltt == "}") {
if (ttk == "while" && stack[stack.length - 1] == "do") {
write(" ", lastToken.loc.start.line, lastToken.loc.start.column);
spaceAdded = true;
} else if (ttk == "else" || ttk == "catch" || ttk == "finally") {
write(" ", lastToken.loc.start.line, lastToken.loc.start.column);
spaceAdded = true;
} else if (
ttl != "(" &&
ttl != ";" &&
ttl != "," &&
ttl != ")" &&
ttl != "." &&
ttl != "template" &&
ttl != "`"
) {
write("\n", lastToken.loc.start.line, lastToken.loc.start.column);
newlineAdded = true;
}
}
if (
(ttl == ":" && stack[stack.length - 1] == "?") ||
(ttl == "}" && stack[stack.length - 1] == "${")
) {
write(" ", lastToken.loc.start.line, lastToken.loc.start.column);
spaceAdded = true;
}
if (lastToken && ltt != "}" && ltt != "." && ttk == "else") {
write(" ", lastToken.loc.start.line, lastToken.loc.start.column);
spaceAdded = true;
}
function ensureNewline() {
if (!newlineAdded) {
write("\n", lastToken.loc.start.line, lastToken.loc.start.column);
newlineAdded = true;
}
}
if (isASI(token, lastToken)) {
ensureNewline();
}
if (decrementsIndent(ttl, stack)) {
ensureNewline();
}
if (newlineAdded) {
if (ttk == "case" || ttk == "default") {
write(
repeat(options.indent, indentLevel - 1),
token.loc.start.line,
token.loc.start.column
);
} else {
write(
repeat(options.indent, indentLevel),
token.loc.start.line,
token.loc.start.column
);
}
} else if (!spaceAdded && needsSpaceAfter(token, lastToken)) {
write(" ", lastToken.loc.start.line, lastToken.loc.start.column);
spaceAdded = true;
}
}
/**
* Repeat the `str` string `n` times.
*
* @param String str
* The string to be repeated.
* @param Number n
* The number of times to repeat the string.
*
* @returns String
* The repeated string.
*/
function repeat(str, n) {
var result = "";
while (n > 0) {
if (n & 1) {
result += str;
}
n >>= 1;
str += str;
}
return result;
}
/**
* Make sure that we output the escaped character combination inside string
* literals instead of various problematic characters.
*/
var sanitize = (function() {
var 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
"'": "\\'",
};
var regExpString =
"(" +
Object.keys(escapeCharacters)
.map(function(c) {
return escapeCharacters[c];
})
.join("|") +
")";
var escapeCharactersRegExp = new RegExp(regExpString, "g");
return function(str) {
return str.replace(escapeCharactersRegExp, function(_, c) {
return escapeCharacters[c];
});
};
})();
/**
* Add the given token to the pretty printed results.
*
* @param Object token
* The token to add.
* @param Function write
* The function to write pretty printed code to the result SourceNode.
*/
function addToken(token, write) {
if (token.type.label == "string") {
write(
"'" + sanitize(token.value) + "'",
token.loc.start.line,
token.loc.start.column
);
} else if (token.type.label == "regexp") {
write(
String(token.value.value),
token.loc.start.line,
token.loc.start.column
);
} else {
let value;
if (token.value != null) {
value = token.value;
if (token.type.label === "privateId") {
value = `#${value}`;
}
} else {
value = token.type.label;
}
write(String(value), token.loc.start.line, token.loc.start.column);
}
}
/**
* Returns true if the given token type belongs on the stack.
*/
function belongsOnStack(token) {
var ttl = token.type.label;
var ttk = token.type.keyword;
return (
ttl == "{" ||
ttl == "(" ||
ttl == "[" ||
ttl == "?" ||
ttl == "${" ||
ttk == "do" ||
ttk == "switch" ||
ttk == "case" ||
ttk == "default"
);
}
/**
* Returns true if the given token should cause us to pop the stack.
*/
function shouldStackPop(token, stack) {
var ttl = token.type.label;
var ttk = token.type.keyword;
var top = stack[stack.length - 1];
return (
ttl == "]" ||
ttl == ")" ||
ttl == "}" ||
(ttl == ":" && (top == "case" || top == "default" || top == "?")) ||
(ttk == "while" && top == "do")
);
}
/**
* Returns true if the given token type should cause us to decrement the
* indent level.
*/
function decrementsIndent(tokenType, stack) {
return (
(tokenType == "}" && stack[stack.length - 1] != "${") ||
(tokenType == "]" && stack[stack.length - 1] == "[\n")
);
}
/**
* Returns true if the given token should cause us to increment the indent
* level.
*/
function incrementsIndent(token) {
return (
token.type.label == "{" ||
token.isArrayLiteral ||
token.type.keyword == "switch"
);
}
/**
* Add a comment to the pretty printed code.
*
* @param Function write
* The function to write pretty printed code to the result SourceNode.
* @param Number indentLevel
* The number of indents deep we are.
* @param Object options
* The options object.
* @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 Number column
* The column number the comment appeared on.
* @param Object nextToken
* The next token if any.
*
* @returns Boolean newlineAdded
* True if a newline was added.
*/
function addComment(
write,
indentLevel,
options,
block,
text,
line,
column,
nextToken
) {
var indentString = repeat(options.indent, indentLevel);
var needNewline = true;
write(indentString, line, column);
if (block) {
write("/*");
// We must pass ignoreNewline in case the comment happens to be "\n".
write(
text
.split(new RegExp("/\n" + indentString + "/", "g"))
.join("\n" + indentString),
null,
null,
true
);
write("*/");
needNewline = !(nextToken && nextToken.loc.start.line == line);
} else {
write("//");
write(text);
}
if (needNewline) {
write("\n");
} else {
write(" ");
}
return needNewline;
}
/**
* The main function.
*
* @param String input
* The ugly JS code we want to pretty print.
* @param Object options
* The options object. Provides configurability of the pretty
* printing. Properties:
* - url: The URL string of the ugly JS code.
* - indent: The string to indent code by.
*
* @returns Object
* An object with the following properties:
* - code: The pretty printed code string.
* - map: A SourceMapGenerator instance.
*/
return function prettyFast(input, options) {
// The level of indents deep we are.
var indentLevel = 0;
// We will accumulate the pretty printed code in this SourceNode.
var result = new SourceNode();
/**
* Write a pretty printed string to the result SourceNode.
*
* We buffer our writes so that we only create one mapping for each line in
* the source map. This enhances performance by avoiding extraneous mapping
* serialization, and flattening the tree that
* `SourceNode#toStringWithSourceMap` will have to recursively walk. When
* timing how long it takes to pretty print jQuery, this optimization
* brought the time down from ~390 ms to ~190ms!
*
* @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 ignoreNewline
* If true, a single "\n" won't result in an additional mapping.
*/
var write = (function() {
var buffer = [];
var bufferLine = -1;
var bufferColumn = -1;
return function write(str, line, column, ignoreNewline) {
if (line != null && bufferLine === -1) {
bufferLine = line;
}
if (column != null && bufferColumn === -1) {
bufferColumn = column;
}
buffer.push(str);
if (str == "\n" && !ignoreNewline) {
var lineStr = "";
for (var i = 0, len = buffer.length; i < len; i++) {
lineStr += buffer[i];
}
result.add(
new SourceNode(bufferLine, bufferColumn, options.url, lineStr)
);
buffer.splice(0, buffer.length);
bufferLine = -1;
bufferColumn = -1;
}
};
})();
// Whether or not we added a newline on after we added the last token.
var addedNewline = false;
// Whether or not we added a space after we added the last token.
var addedSpace = false;
// The current token we will be adding to the pretty printed code.
var token;
// Shorthand for token.type.label, so we don't have to repeatedly access
// properties.
var ttl;
// Shorthand for token.type.keyword, so we don't have to repeatedly access
// properties.
var ttk;
// The last token we added to the pretty printed code.
var lastToken;
// 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"
// - "do"
// - "?"
// - "switch"
// - "case"
// - "default"
//
// The difference between "[" and "[\n" is that "[\n" is used when we are
// treating "[" and "]" tokens as line delimiters and should increment and
// decrement the indent level when we find them.
var stack = [];
// 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]
var tokenQueue = [];
var tokens = acorn.tokenizer(input, {
locations: true,
sourceFile: options.url,
ecmaVersion: options.ecmaVersion || "latest",
onComment(block, text, start, end, startLoc, endLoc) {
tokenQueue.push({
type: {},
comment: true,
block,
text,
loc: { start: startLoc, end: endLoc },
});
},
});
for (;;) {
token = tokens.getToken();
tokenQueue.push(token);
if (token.type.label == "eof") {
break;
}
}
for (var i = 0; i < tokenQueue.length; i++) {
token = tokenQueue[i];
var nextToken = tokenQueue[i + 1];
if (token.comment) {
var commentIndentLevel = indentLevel;
if (lastToken && lastToken.loc.end.line == token.loc.start.line) {
commentIndentLevel = 0;
write(" ");
}
addedNewline = addComment(
write,
commentIndentLevel,
options,
token.block,
token.text,
token.loc.start.line,
token.loc.start.column,
nextToken
);
addedSpace = !addedNewline;
continue;
}
ttk = token.type.keyword;
if (ttk && lastToken && lastToken.type.label == ".") {
token.type = acorn.tokTypes.name;
}
ttl = token.type.label;
if (ttl == "eof") {
if (!addedNewline) {
write("\n");
}
break;
}
token.isArrayLiteral = isArrayLiteral(token, lastToken);
if (belongsOnStack(token)) {
if (token.isArrayLiteral) {
stack.push("[\n");
} else {
stack.push(ttl || ttk);
}
}
if (decrementsIndent(ttl, stack)) {
indentLevel--;
if (
ttl == "}" &&
stack.length > 1 &&
stack[stack.length - 2] == "switch"
) {
indentLevel--;
}
}
prependWhiteSpace(
token,
lastToken,
addedNewline,
addedSpace,
write,
options,
indentLevel,
stack
);
addToken(token, write);
addedSpace = false;
// If the next token is going to be a comment starting on the same line,
// then no need to add one here
if (
!nextToken ||
!nextToken.comment ||
token.loc.end.line != nextToken.loc.start.line
) {
addedNewline = appendNewline(token, write, stack);
}
if (shouldStackPop(token, stack)) {
stack.pop();
if (
ttl == "}" &&
stack.length &&
stack[stack.length - 1] == "switch"
) {
stack.pop();
}
}
if (incrementsIndent(token)) {
indentLevel++;
}
// Acorn's tokenizer re-uses tokens, so we have to copy the last token on
// every iteration. We follow acorn's lead here, and reuse the lastToken
// object the same way that acorn reuses the token object. This allows us
// to avoid allocations and minimize GC pauses.
if (!lastToken) {
lastToken = { loc: { start: {}, end: {} } };
}
lastToken.start = token.start;
lastToken.end = token.end;
lastToken.loc.start.line = token.loc.start.line;
lastToken.loc.start.column = token.loc.start.column;
lastToken.loc.end.line = token.loc.end.line;
lastToken.loc.end.column = token.loc.end.column;
lastToken.type = token.type;
lastToken.value = token.value;
lastToken.isArrayLiteral = token.isArrayLiteral;
}
return result.toStringWithSourceMap({ file: options.url });
};
}.bind(this)
);

View file

@ -0,0 +1,423 @@
/* 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/>. */
"use strict";
/*
* Copyright 2013 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE.md or:
* http://opensource.org/licenses/BSD-2-Clause
*/
const prettyFast = require("./pretty-fast");
const chalk = require("chalk");
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: "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: "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
}
}
`,
},
];
const sourceMap = this.sourceMap || require("source-map");
const includesOnly = cases.find(({ only }) => only);
let output = ``;
for (const { name, input, only, skip } of cases) {
if ((includesOnly && !only) || skip) {
continue;
}
test(name, () => {
const actual = prettyFast(input, {
indent: " ",
url: "test.js",
});
expect(actual.code).toMatchSnapshot();
output += `${chalk.bold(name)}\n${chalk.yellow(input.trim())}\n${chalk.blue(
actual.code
)}\n`;
const smc = new sourceMap.SourceMapConsumer(actual.map.toJSON());
const mappings = [];
smc.eachMapping(
({ generatedColumn, generatedLine, originalColumn, originalLine }) => {
mappings.push(
`(${originalLine}, ${originalColumn}) -> (${generatedLine}, ${generatedColumn})`
);
}
);
expect(mappings).toMatchSnapshot();
});
}
afterAll(() => {
// Enable to view the test run output
if (false) {
console.log(output);
}
});

File diff suppressed because it is too large Load diff