Generator performance (#13593)
* bench: setup benchmarks * add charcodes * perf: use string as buffer backend baseline 256 empty statements: 3_718 ops/sec ±18.65% (0.269ms) baseline 512 empty statements: 2_070 ops/sec ±0.96% (0.483ms) baseline 1024 empty statements: 1_012 ops/sec ±1.76% (0.988ms) baseline 2048 empty statements: 510 ops/sec ±1.53% (1.96ms) current 256 empty statements: 3_965 ops/sec ±21.14% (0.252ms) current 512 empty statements: 2_219 ops/sec ±1.29% (0.451ms) current 1024 empty statements: 1_089 ops/sec ±1.53% (0.918ms) current 2048 empty statements: 548 ops/sec ±1.87% (1.824ms) * perf: add endsWithCharAndNewline baseline 256 1-length identifiers: 1_947 ops/sec ±25.11% (0.514ms) baseline 512 1-length identifiers: 1_115 ops/sec ±0.89% (0.897ms) baseline 1024 1-length identifiers: 537 ops/sec ±1.71% (1.862ms) baseline 2048 1-length identifiers: 273 ops/sec ±0.57% (3.669ms) current 256 1-length identifiers: 2_178 ops/sec ±27.17% (0.459ms) current 512 1-length identifiers: 1_250 ops/sec ±1.19% (0.8ms) current 1024 1-length identifiers: 622 ops/sec ±0.71% (1.608ms) current 2048 1-length identifiers: 308 ops/sec ±1.35% (3.251ms) * perf: avoid one byte string compare * perf: avoid scaning word for / * perf: hoist babel type methods baseline 256 25-length identifiers: 1_869 ops/sec ±29.4% (0.535ms) baseline 512 25-length identifiers: 1_092 ops/sec ±1.62% (0.916ms) baseline 1024 25-length identifiers: 537 ops/sec ±1.29% (1.862ms) baseline 2048 25-length identifiers: 264 ops/sec ±2% (3.793ms) current 256 25-length identifiers: 2_462 ops/sec ±23.38% (0.406ms) current 512 25-length identifiers: 1_401 ops/sec ±0.73% (0.714ms) current 1024 25-length identifiers: 671 ops/sec ±1.55% (1.491ms) current 2048 25-length identifiers: 332 ops/sec ±1.44% (3.014ms) * perf: hoist parens methods baseline 256 1-length identifiers: 2_678 ops/sec ±24.96% (0.373ms) baseline 512 1-length identifiers: 1_472 ops/sec ±2.33% (0.68ms) baseline 1024 1-length identifiers: 737 ops/sec ±1.74% (1.357ms) baseline 2048 1-length identifiers: 371 ops/sec ±0.79% (2.695ms) current 256 1-length identifiers: 2_633 ops/sec ±32.44% (0.38ms) current 512 1-length identifiers: 1_676 ops/sec ±1.49% (0.597ms) current 1024 1-length identifiers: 803 ops/sec ±1.95% (1.246ms) current 2048 1-length identifiers: 385 ops/sec ±2.22% (2.596ms) * cleanup unused benchcase * Update packages/babel-generator/src/buffer.ts Co-authored-by: Nicolò Ribaudo <nicolo.ribaudo@gmail.com> * cleanup benchmarks Co-authored-by: Nicolò Ribaudo <nicolo.ribaudo@gmail.com>
This commit is contained in:
parent
2b6acada3c
commit
03e8476b27
@ -179,6 +179,10 @@ module.exports = function (api) {
|
||||
plugins: ["babel-plugin-transform-charcodes"],
|
||||
assumptions: parserAssumptions,
|
||||
},
|
||||
{
|
||||
test: ["packages/babel-generator"].map(normalize),
|
||||
plugins: ["babel-plugin-transform-charcodes"],
|
||||
},
|
||||
convertESM && {
|
||||
test: [
|
||||
"./packages/babel-cli",
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
import Benchmark from "benchmark";
|
||||
import baseline from "@babel-baseline/generator";
|
||||
import current from "../../lib/index.js";
|
||||
import parser from "@babel/parser";
|
||||
import { report } from "../util.mjs";
|
||||
|
||||
const suite = new Benchmark.Suite();
|
||||
|
||||
function createInput(length) {
|
||||
return parser.parse(";".repeat(length));
|
||||
}
|
||||
|
||||
function benchCases(name, implementation, options) {
|
||||
for (const length of [256, 512, 1024, 2048]) {
|
||||
const input = createInput(length);
|
||||
suite.add(`${name} ${length} empty statements`, () => {
|
||||
implementation(input, options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
benchCases("baseline", baseline.default);
|
||||
benchCases("current", current.default);
|
||||
|
||||
suite.on("cycle", report).run();
|
||||
@ -0,0 +1,25 @@
|
||||
import Benchmark from "benchmark";
|
||||
import baseline from "@babel-baseline/generator";
|
||||
import current from "../../lib/index.js";
|
||||
import parser from "@babel/parser";
|
||||
import { report } from "../util.mjs";
|
||||
|
||||
const suite = new Benchmark.Suite();
|
||||
|
||||
function createInput(length) {
|
||||
return parser.parse("a;".repeat(length));
|
||||
}
|
||||
|
||||
function benchCases(name, implementation, options) {
|
||||
for (const length of [256, 512, 1024, 2048]) {
|
||||
const input = createInput(length);
|
||||
suite.add(`${name} ${length} 1-length identifiers`, () => {
|
||||
implementation(input, options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
benchCases("baseline", baseline.default);
|
||||
benchCases("current", current.default);
|
||||
|
||||
suite.on("cycle", report).run();
|
||||
@ -0,0 +1,25 @@
|
||||
import Benchmark from "benchmark";
|
||||
import baseline from "@babel-baseline/generator";
|
||||
import current from "../../lib/index.js";
|
||||
import parser from "@babel/parser";
|
||||
import { report } from "../util.mjs";
|
||||
|
||||
const suite = new Benchmark.Suite();
|
||||
|
||||
function createInput(length) {
|
||||
return parser.parse("parseMaybeImportAssertion;".repeat(length));
|
||||
}
|
||||
|
||||
function benchCases(name, implementation, options) {
|
||||
for (const length of [256, 512, 1024, 2048]) {
|
||||
const input = createInput(length);
|
||||
suite.add(`${name} ${length} 25-length identifiers`, () => {
|
||||
implementation(input, options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
benchCases("baseline", baseline.default);
|
||||
benchCases("current", current.default);
|
||||
|
||||
suite.on("cycle", report).run();
|
||||
11193
packages/babel-generator/benchmark/real-case/jquery-3.6.txt
Normal file
11193
packages/babel-generator/benchmark/real-case/jquery-3.6.txt
Normal file
File diff suppressed because it is too large
Load Diff
30
packages/babel-generator/benchmark/real-case/jquery.mjs
Normal file
30
packages/babel-generator/benchmark/real-case/jquery.mjs
Normal file
@ -0,0 +1,30 @@
|
||||
import Benchmark from "benchmark";
|
||||
import baseline from "@babel-baseline/generator";
|
||||
import current from "../../lib/index.js";
|
||||
import parser from "@babel/parser";
|
||||
import { report } from "../util.mjs";
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
const suite = new Benchmark.Suite();
|
||||
|
||||
function createInput(length) {
|
||||
return parser.parse(
|
||||
readFileSync(new URL("./jquery-3.6.txt", import.meta.url), {
|
||||
encoding: "utf-8",
|
||||
}).repeat(length)
|
||||
);
|
||||
}
|
||||
|
||||
function benchCases(name, implementation, options) {
|
||||
for (const length of [1, 2]) {
|
||||
const input = createInput(length);
|
||||
suite.add(`${name} ${length} jquery 3.6`, () => {
|
||||
implementation(input, options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
benchCases("baseline", baseline.default);
|
||||
benchCases("current", current.default);
|
||||
|
||||
suite.on("cycle", report).run();
|
||||
17
packages/babel-generator/benchmark/util.mjs
Normal file
17
packages/babel-generator/benchmark/util.mjs
Normal file
@ -0,0 +1,17 @@
|
||||
export function report(event) {
|
||||
const bench = event.target;
|
||||
const timeMs = bench.stats.mean * 1000;
|
||||
const time =
|
||||
timeMs < 10
|
||||
? `${Math.round(timeMs * 1000) / 1000}ms`
|
||||
: `${Math.round(timeMs)}ms`;
|
||||
const msg = `${bench.name}: ${formatNumber(bench.hz)} ops/sec ±${
|
||||
Math.round(bench.stats.rme * 100) / 100
|
||||
}% (${time})`;
|
||||
console.log(msg);
|
||||
}
|
||||
|
||||
function formatNumber(x) {
|
||||
if (x < 100) return `${Math.round(x * 100) / 100}`;
|
||||
return `${Math.round(x)}`.replace(/\d(?=(?:\d{3})+$)/g, "$&_");
|
||||
}
|
||||
@ -24,10 +24,13 @@
|
||||
"source-map": "^0.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel-baseline/generator": "npm:@babel/generator@7.14.5",
|
||||
"@babel/helper-fixtures": "workspace:*",
|
||||
"@babel/parser": "workspace:*",
|
||||
"@types/jsesc": "^2.5.0",
|
||||
"@types/source-map": "^0.5.0"
|
||||
"@types/source-map": "^0.5.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"charcodes": "^0.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
|
||||
@ -1,23 +1,16 @@
|
||||
import type SourceMap from "./source-map";
|
||||
import type * as t from "@babel/types";
|
||||
import * as charcodes from "charcodes";
|
||||
|
||||
const SPACES_RE = /^[ \t]+$/;
|
||||
|
||||
/**
|
||||
* The Buffer class exists to manage the queue of tokens being pushed onto the output string
|
||||
* in such a way that the final string buffer is treated as write-only until the final .get()
|
||||
* call. This allows V8 to optimize the output efficiently by not requiring it to store the
|
||||
* string in contiguous memory.
|
||||
*/
|
||||
|
||||
export default class Buffer {
|
||||
constructor(map?: SourceMap | null) {
|
||||
this._map = map;
|
||||
}
|
||||
|
||||
_map: SourceMap = null;
|
||||
_buf: Array<any> = [];
|
||||
_last: string = "";
|
||||
_buf: string = "";
|
||||
_last: number = 0;
|
||||
_queue: Array<
|
||||
[
|
||||
str: string,
|
||||
@ -52,7 +45,7 @@ export default class Buffer {
|
||||
const result = {
|
||||
// Whatever trim is used here should not execute a regex against the
|
||||
// source string since it may be arbitrarily large after all transformations
|
||||
code: this._buf.join("").trimRight(),
|
||||
code: this._buf.trimRight(),
|
||||
map: null,
|
||||
rawMappings: map?.getRawMappings(),
|
||||
};
|
||||
@ -125,8 +118,8 @@ export default class Buffer {
|
||||
filename?: string | null,
|
||||
force?: boolean,
|
||||
): void {
|
||||
this._buf.push(str);
|
||||
this._last = str[str.length - 1];
|
||||
this._buf += str;
|
||||
this._last = str.charCodeAt(str.length - 1);
|
||||
|
||||
// Search for newline chars. We search only for `\n`, since both `\r` and
|
||||
// `\r\n` are normalized to `\n` during parse. We exclude `\u2028` and
|
||||
@ -187,29 +180,39 @@ export default class Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
endsWith(suffix: string): boolean {
|
||||
// Fast path to avoid iterating over this._queue.
|
||||
if (suffix.length === 1) {
|
||||
let last;
|
||||
if (this._queue.length > 0) {
|
||||
const str = this._queue[0][0];
|
||||
last = str[str.length - 1];
|
||||
getLastChar(): number {
|
||||
let last;
|
||||
if (this._queue.length > 0) {
|
||||
const str = this._queue[0][0];
|
||||
last = str.charCodeAt(0);
|
||||
} else {
|
||||
last = this._last;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if current _last + queue ends with newline, return the character before newline
|
||||
*
|
||||
* @param {*} ch
|
||||
* @memberof Buffer
|
||||
*/
|
||||
endsWithCharAndNewline(): number {
|
||||
const queue = this._queue;
|
||||
if (queue.length > 0) {
|
||||
const last = queue[0][0];
|
||||
// every element in queue is one-length whitespace string
|
||||
const lastCp = last.charCodeAt(0);
|
||||
if (lastCp !== charcodes.lineFeed) return;
|
||||
if (queue.length > 1) {
|
||||
const secondLast = queue[1][0];
|
||||
return secondLast.charCodeAt(0);
|
||||
} else {
|
||||
last = this._last;
|
||||
return this._last;
|
||||
}
|
||||
|
||||
return last === suffix;
|
||||
}
|
||||
|
||||
const end =
|
||||
this._last + this._queue.reduce((acc, item) => item[0] + acc, "");
|
||||
if (suffix.length <= end.length) {
|
||||
return end.slice(-suffix.length) === suffix;
|
||||
}
|
||||
|
||||
// We assume that everything being matched is at most a single token plus some whitespace,
|
||||
// which everything currently is, but otherwise we'd have to expand _last or check _buf.
|
||||
return false;
|
||||
}
|
||||
|
||||
hasContent(): boolean {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type Printer from "../printer";
|
||||
import * as t from "@babel/types";
|
||||
import * as charCodes from "charcodes";
|
||||
|
||||
export function File(this: Printer, node: t.File) {
|
||||
if (node.program) {
|
||||
@ -37,7 +38,7 @@ export function BlockStatement(this: Printer, node: t.BlockStatement) {
|
||||
|
||||
this.source("end", node.loc);
|
||||
|
||||
if (!this.endsWith("\n")) this.newline();
|
||||
if (!this.endsWith(charCodes.lineFeed)) this.newline();
|
||||
|
||||
this.rightBrace();
|
||||
} else {
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import type Printer from "../printer";
|
||||
import * as t from "@babel/types";
|
||||
import * as charCodes from "charcodes";
|
||||
|
||||
const { isExportDefaultDeclaration, isExportNamedDeclaration } = t;
|
||||
|
||||
export function ClassDeclaration(
|
||||
this: Printer,
|
||||
@ -8,8 +11,7 @@ export function ClassDeclaration(
|
||||
) {
|
||||
if (
|
||||
!this.format.decoratorsBeforeExport ||
|
||||
(!t.isExportDefaultDeclaration(parent) &&
|
||||
!t.isExportNamedDeclaration(parent))
|
||||
(!isExportDefaultDeclaration(parent) && !isExportNamedDeclaration(parent))
|
||||
) {
|
||||
this.printJoin(node.decorators, node);
|
||||
}
|
||||
@ -68,7 +70,7 @@ export function ClassBody(this: Printer, node: t.ClassBody) {
|
||||
this.printSequence(node.body, node);
|
||||
this.dedent();
|
||||
|
||||
if (!this.endsWith("\n")) this.newline();
|
||||
if (!this.endsWith(charCodes.lineFeed)) this.newline();
|
||||
|
||||
this.rightBrace();
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import type Printer from "../printer";
|
||||
import * as t from "@babel/types";
|
||||
import * as n from "../node";
|
||||
|
||||
const { isCallExpression, isLiteral, isMemberExpression, isNewExpression } = t;
|
||||
export function UnaryExpression(this: Printer, node: t.UnaryExpression) {
|
||||
if (
|
||||
node.operator === "void" ||
|
||||
@ -77,9 +78,9 @@ export function NewExpression(
|
||||
this.format.minified &&
|
||||
node.arguments.length === 0 &&
|
||||
!node.optional &&
|
||||
!t.isCallExpression(parent, { callee: node }) &&
|
||||
!t.isMemberExpression(parent) &&
|
||||
!t.isNewExpression(parent)
|
||||
!isCallExpression(parent, { callee: node }) &&
|
||||
!isMemberExpression(parent) &&
|
||||
!isNewExpression(parent)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -119,13 +120,13 @@ export function OptionalMemberExpression(
|
||||
) {
|
||||
this.print(node.object, node);
|
||||
|
||||
if (!node.computed && t.isMemberExpression(node.property)) {
|
||||
if (!node.computed && isMemberExpression(node.property)) {
|
||||
throw new TypeError("Got a MemberExpression for MemberExpression property");
|
||||
}
|
||||
|
||||
let computed = node.computed;
|
||||
// @ts-expect-error todo(flow->ts) maybe instead of typeof check specific literal types?
|
||||
if (t.isLiteral(node.property) && typeof node.property.value === "number") {
|
||||
if (isLiteral(node.property) && typeof node.property.value === "number") {
|
||||
computed = true;
|
||||
}
|
||||
if (node.optional) {
|
||||
@ -266,13 +267,13 @@ export {
|
||||
export function MemberExpression(this: Printer, node: t.MemberExpression) {
|
||||
this.print(node.object, node);
|
||||
|
||||
if (!node.computed && t.isMemberExpression(node.property)) {
|
||||
if (!node.computed && isMemberExpression(node.property)) {
|
||||
throw new TypeError("Got a MemberExpression for MemberExpression property");
|
||||
}
|
||||
|
||||
let computed = node.computed;
|
||||
// @ts-expect-error todo(flow->ts) maybe use specific literal types
|
||||
if (t.isLiteral(node.property) && typeof node.property.value === "number") {
|
||||
if (isLiteral(node.property) && typeof node.property.value === "number") {
|
||||
computed = true;
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import type Printer from "../printer";
|
||||
import * as t from "@babel/types";
|
||||
import { ExportAllDeclaration } from "./modules";
|
||||
|
||||
const { isDeclareExportDeclaration, isStatement } = t;
|
||||
export function AnyTypeAnnotation(this: Printer) {
|
||||
this.word("any");
|
||||
}
|
||||
@ -35,7 +36,7 @@ export function DeclareClass(
|
||||
node: t.DeclareClass,
|
||||
parent: t.Node,
|
||||
) {
|
||||
if (!t.isDeclareExportDeclaration(parent)) {
|
||||
if (!isDeclareExportDeclaration(parent)) {
|
||||
this.word("declare");
|
||||
this.space();
|
||||
}
|
||||
@ -49,7 +50,7 @@ export function DeclareFunction(
|
||||
node: t.DeclareFunction,
|
||||
parent: any,
|
||||
) {
|
||||
if (!t.isDeclareExportDeclaration(parent)) {
|
||||
if (!isDeclareExportDeclaration(parent)) {
|
||||
this.word("declare");
|
||||
this.space();
|
||||
}
|
||||
@ -119,7 +120,7 @@ export function DeclareOpaqueType(
|
||||
node: t.DeclareOpaqueType,
|
||||
parent: any,
|
||||
) {
|
||||
if (!t.isDeclareExportDeclaration(parent)) {
|
||||
if (!isDeclareExportDeclaration(parent)) {
|
||||
this.word("declare");
|
||||
this.space();
|
||||
}
|
||||
@ -131,7 +132,7 @@ export function DeclareVariable(
|
||||
node: t.DeclareVariable,
|
||||
parent: any,
|
||||
) {
|
||||
if (!t.isDeclareExportDeclaration(parent)) {
|
||||
if (!isDeclareExportDeclaration(parent)) {
|
||||
this.word("declare");
|
||||
this.space();
|
||||
}
|
||||
@ -261,7 +262,7 @@ function FlowExportDeclaration(node: any) {
|
||||
if (node.declaration) {
|
||||
const declar = node.declaration;
|
||||
this.print(declar, node);
|
||||
if (!t.isStatement(declar)) this.semicolon();
|
||||
if (!isStatement(declar)) this.semicolon();
|
||||
} else {
|
||||
this.token("{");
|
||||
if (node.specifiers.length) {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type Printer from "../printer";
|
||||
import * as t from "@babel/types";
|
||||
|
||||
const { isIdentifier } = t;
|
||||
export function _params(this: Printer, node: any) {
|
||||
this.print(node.typeParameters, node);
|
||||
this.token("(");
|
||||
@ -120,7 +121,7 @@ export function ArrowFunctionExpression(
|
||||
!this.format.auxiliaryCommentBefore &&
|
||||
!this.format.auxiliaryCommentAfter &&
|
||||
node.params.length === 1 &&
|
||||
t.isIdentifier(firstParam) &&
|
||||
isIdentifier(firstParam) &&
|
||||
!hasTypesOrComments(node, firstParam)
|
||||
) {
|
||||
this.print(firstParam, node);
|
||||
|
||||
@ -1,6 +1,15 @@
|
||||
import type Printer from "../printer";
|
||||
import * as t from "@babel/types";
|
||||
|
||||
const {
|
||||
isClassDeclaration,
|
||||
isExportDefaultSpecifier,
|
||||
isExportNamespaceSpecifier,
|
||||
isImportDefaultSpecifier,
|
||||
isImportNamespaceSpecifier,
|
||||
isStatement,
|
||||
} = t;
|
||||
|
||||
export function ImportSpecifier(this: Printer, node: t.ImportSpecifier) {
|
||||
if (node.importKind === "type" || node.importKind === "typeof") {
|
||||
this.word(node.importKind);
|
||||
@ -78,7 +87,7 @@ export function ExportNamedDeclaration(
|
||||
) {
|
||||
if (
|
||||
this.format.decoratorsBeforeExport &&
|
||||
t.isClassDeclaration(node.declaration)
|
||||
isClassDeclaration(node.declaration)
|
||||
) {
|
||||
this.printJoin(node.declaration.decorators, node);
|
||||
}
|
||||
@ -94,7 +103,7 @@ export function ExportDefaultDeclaration(
|
||||
) {
|
||||
if (
|
||||
this.format.decoratorsBeforeExport &&
|
||||
t.isClassDeclaration(node.declaration)
|
||||
isClassDeclaration(node.declaration)
|
||||
) {
|
||||
this.printJoin(node.declaration.decorators, node);
|
||||
}
|
||||
@ -110,7 +119,7 @@ function ExportDeclaration(node: any) {
|
||||
if (node.declaration) {
|
||||
const declar = node.declaration;
|
||||
this.print(declar, node);
|
||||
if (!t.isStatement(declar)) this.semicolon();
|
||||
if (!isStatement(declar)) this.semicolon();
|
||||
} else {
|
||||
if (node.exportKind === "type") {
|
||||
this.word("type");
|
||||
@ -124,8 +133,8 @@ function ExportDeclaration(node: any) {
|
||||
for (;;) {
|
||||
const first = specifiers[0];
|
||||
if (
|
||||
t.isExportDefaultSpecifier(first) ||
|
||||
t.isExportNamespaceSpecifier(first)
|
||||
isExportDefaultSpecifier(first) ||
|
||||
isExportNamespaceSpecifier(first)
|
||||
) {
|
||||
hasSpecial = true;
|
||||
this.print(specifiers.shift(), node);
|
||||
@ -175,8 +184,8 @@ export function ImportDeclaration(this: Printer, node: t.ImportDeclaration) {
|
||||
for (;;) {
|
||||
const first = specifiers[0];
|
||||
if (
|
||||
t.isImportDefaultSpecifier(first) ||
|
||||
t.isImportNamespaceSpecifier(first)
|
||||
isImportDefaultSpecifier(first) ||
|
||||
isImportNamespaceSpecifier(first)
|
||||
) {
|
||||
this.print(specifiers.shift(), node);
|
||||
if (specifiers.length) {
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import type Printer from "../printer";
|
||||
import * as t from "@babel/types";
|
||||
import * as charCodes from "charcodes";
|
||||
|
||||
const { isFor, isForStatement, isIfStatement, isStatement } = t;
|
||||
export function WithStatement(this: Printer, node: t.WithStatement) {
|
||||
this.word("with");
|
||||
this.space();
|
||||
@ -19,7 +21,7 @@ export function IfStatement(this: Printer, node: t.IfStatement) {
|
||||
this.space();
|
||||
|
||||
const needsBlock =
|
||||
node.alternate && t.isIfStatement(getLastStatement(node.consequent));
|
||||
node.alternate && isIfStatement(getLastStatement(node.consequent));
|
||||
if (needsBlock) {
|
||||
this.token("{");
|
||||
this.newline();
|
||||
@ -35,7 +37,7 @@ export function IfStatement(this: Printer, node: t.IfStatement) {
|
||||
}
|
||||
|
||||
if (node.alternate) {
|
||||
if (this.endsWith("}")) this.space();
|
||||
if (this.endsWith(charCodes.rightCurlyBrace)) this.space();
|
||||
this.word("else");
|
||||
this.space();
|
||||
this.printAndIndentOnComments(node.alternate, node);
|
||||
@ -44,7 +46,7 @@ export function IfStatement(this: Printer, node: t.IfStatement) {
|
||||
|
||||
// Recursively get the last statement.
|
||||
function getLastStatement(statement) {
|
||||
if (!t.isStatement(statement.body)) return statement;
|
||||
if (!isStatement(statement.body)) return statement;
|
||||
return getLastStatement(statement.body);
|
||||
}
|
||||
|
||||
@ -229,14 +231,18 @@ function variableDeclarationIndent() {
|
||||
// "let " or "var " indentation.
|
||||
this.token(",");
|
||||
this.newline();
|
||||
if (this.endsWith("\n")) for (let i = 0; i < 4; i++) this.space(true);
|
||||
if (this.endsWith(charCodes.lineFeed)) {
|
||||
for (let i = 0; i < 4; i++) this.space(true);
|
||||
}
|
||||
}
|
||||
|
||||
function constDeclarationIndent() {
|
||||
// "const " indentation.
|
||||
this.token(",");
|
||||
this.newline();
|
||||
if (this.endsWith("\n")) for (let i = 0; i < 6; i++) this.space(true);
|
||||
if (this.endsWith(charCodes.lineFeed)) {
|
||||
for (let i = 0; i < 6; i++) this.space(true);
|
||||
}
|
||||
}
|
||||
|
||||
export function VariableDeclaration(
|
||||
@ -255,7 +261,7 @@ export function VariableDeclaration(
|
||||
|
||||
let hasInits = false;
|
||||
// don't add whitespace to loop heads
|
||||
if (!t.isFor(parent)) {
|
||||
if (!isFor(parent)) {
|
||||
for (const declar of node.declarations as Array<any>) {
|
||||
if (declar.init) {
|
||||
// has an init so let's split it up over multiple lines
|
||||
@ -288,9 +294,9 @@ export function VariableDeclaration(
|
||||
|
||||
this.printList(node.declarations, node, { separator });
|
||||
|
||||
if (t.isFor(parent)) {
|
||||
if (isFor(parent)) {
|
||||
// don't give semicolons to these nodes since they'll be inserted in the parent generator
|
||||
if (t.isForStatement(parent)) {
|
||||
if (isForStatement(parent)) {
|
||||
if (parent.init === node) return;
|
||||
} else {
|
||||
if (parent.left === node) return;
|
||||
|
||||
@ -2,6 +2,7 @@ import type Printer from "../printer";
|
||||
import * as t from "@babel/types";
|
||||
import jsesc from "jsesc";
|
||||
|
||||
const { isAssignmentPattern, isIdentifier } = t;
|
||||
export function Identifier(this: Printer, node: t.Identifier) {
|
||||
this.exactSource(node.loc, () => {
|
||||
this.word(node.name);
|
||||
@ -53,8 +54,8 @@ export function ObjectProperty(this: Printer, node: t.ObjectProperty) {
|
||||
} else {
|
||||
// print `({ foo: foo = 5 } = {})` as `({ foo = 5 } = {});`
|
||||
if (
|
||||
t.isAssignmentPattern(node.value) &&
|
||||
t.isIdentifier(node.key) &&
|
||||
isAssignmentPattern(node.value) &&
|
||||
isIdentifier(node.key) &&
|
||||
// @ts-expect-error todo(flow->ts) `.name` does not exist on some types in union
|
||||
node.key.name === node.value.left.name
|
||||
) {
|
||||
@ -67,8 +68,8 @@ export function ObjectProperty(this: Printer, node: t.ObjectProperty) {
|
||||
// shorthand!
|
||||
if (
|
||||
node.shorthand &&
|
||||
t.isIdentifier(node.key) &&
|
||||
t.isIdentifier(node.value) &&
|
||||
isIdentifier(node.key) &&
|
||||
isIdentifier(node.value) &&
|
||||
node.key.name === node.value.name
|
||||
) {
|
||||
return;
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
import * as whitespace from "./whitespace";
|
||||
import * as parens from "./parentheses";
|
||||
import * as t from "@babel/types";
|
||||
|
||||
const {
|
||||
isCallExpression,
|
||||
isExpressionStatement,
|
||||
isMemberExpression,
|
||||
isNewExpression,
|
||||
} = t;
|
||||
function expandAliases(obj) {
|
||||
const newObj = {};
|
||||
|
||||
@ -42,17 +47,17 @@ function find(obj, node, parent, printStack?) {
|
||||
}
|
||||
|
||||
function isOrHasCallExpression(node) {
|
||||
if (t.isCallExpression(node)) {
|
||||
if (isCallExpression(node)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return t.isMemberExpression(node) && isOrHasCallExpression(node.object);
|
||||
return isMemberExpression(node) && isOrHasCallExpression(node.object);
|
||||
}
|
||||
|
||||
export function needsWhitespace(node, parent, type) {
|
||||
if (!node) return 0;
|
||||
|
||||
if (t.isExpressionStatement(node)) {
|
||||
if (isExpressionStatement(node)) {
|
||||
node = node.expression;
|
||||
}
|
||||
|
||||
@ -86,7 +91,7 @@ export function needsWhitespaceAfter(node, parent) {
|
||||
export function needsParens(node, parent, printStack?) {
|
||||
if (!parent) return false;
|
||||
|
||||
if (t.isNewExpression(parent) && parent.callee === node) {
|
||||
if (isNewExpression(parent) && parent.callee === node) {
|
||||
if (isOrHasCallExpression(node)) return true;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,54 @@
|
||||
import * as t from "@babel/types";
|
||||
|
||||
const {
|
||||
isArrayTypeAnnotation,
|
||||
isArrowFunctionExpression,
|
||||
isAssignmentExpression,
|
||||
isAwaitExpression,
|
||||
isBinary,
|
||||
isBinaryExpression,
|
||||
isCallExpression,
|
||||
isClassDeclaration,
|
||||
isClassExpression,
|
||||
isConditional,
|
||||
isConditionalExpression,
|
||||
isExportDeclaration,
|
||||
isExportDefaultDeclaration,
|
||||
isExpressionStatement,
|
||||
isFor,
|
||||
isForInStatement,
|
||||
isForOfStatement,
|
||||
isForStatement,
|
||||
isIfStatement,
|
||||
isIndexedAccessType,
|
||||
isIntersectionTypeAnnotation,
|
||||
isLogicalExpression,
|
||||
isMemberExpression,
|
||||
isNewExpression,
|
||||
isNullableTypeAnnotation,
|
||||
isObjectPattern,
|
||||
isOptionalCallExpression,
|
||||
isOptionalMemberExpression,
|
||||
isReturnStatement,
|
||||
isSequenceExpression,
|
||||
isSwitchStatement,
|
||||
isTSArrayType,
|
||||
isTSAsExpression,
|
||||
isTSIntersectionType,
|
||||
isTSNonNullExpression,
|
||||
isTSOptionalType,
|
||||
isTSRestType,
|
||||
isTSTypeAssertion,
|
||||
isTSUnionType,
|
||||
isTaggedTemplateExpression,
|
||||
isThrowStatement,
|
||||
isTypeAnnotation,
|
||||
isUnaryLike,
|
||||
isUnionTypeAnnotation,
|
||||
isVariableDeclarator,
|
||||
isWhileStatement,
|
||||
isYieldExpression,
|
||||
} = t;
|
||||
const PRECEDENCE = {
|
||||
"||": 0,
|
||||
"??": 0,
|
||||
@ -29,21 +78,21 @@ const PRECEDENCE = {
|
||||
};
|
||||
|
||||
const isClassExtendsClause = (node: any, parent: any): boolean =>
|
||||
(t.isClassDeclaration(parent) || t.isClassExpression(parent)) &&
|
||||
(isClassDeclaration(parent) || isClassExpression(parent)) &&
|
||||
parent.superClass === node;
|
||||
|
||||
const hasPostfixPart = (node: any, parent: any) =>
|
||||
((t.isMemberExpression(parent) || t.isOptionalMemberExpression(parent)) &&
|
||||
((isMemberExpression(parent) || isOptionalMemberExpression(parent)) &&
|
||||
parent.object === node) ||
|
||||
((t.isCallExpression(parent) ||
|
||||
t.isOptionalCallExpression(parent) ||
|
||||
t.isNewExpression(parent)) &&
|
||||
((isCallExpression(parent) ||
|
||||
isOptionalCallExpression(parent) ||
|
||||
isNewExpression(parent)) &&
|
||||
parent.callee === node) ||
|
||||
(t.isTaggedTemplateExpression(parent) && parent.tag === node) ||
|
||||
t.isTSNonNullExpression(parent);
|
||||
(isTaggedTemplateExpression(parent) && parent.tag === node) ||
|
||||
isTSNonNullExpression(parent);
|
||||
|
||||
export function NullableTypeAnnotation(node: any, parent: any): boolean {
|
||||
return t.isArrayTypeAnnotation(parent);
|
||||
return isArrayTypeAnnotation(parent);
|
||||
}
|
||||
|
||||
export function FunctionTypeAnnotation(
|
||||
@ -53,15 +102,15 @@ export function FunctionTypeAnnotation(
|
||||
): boolean {
|
||||
return (
|
||||
// (() => A) | (() => B)
|
||||
t.isUnionTypeAnnotation(parent) ||
|
||||
isUnionTypeAnnotation(parent) ||
|
||||
// (() => A) & (() => B)
|
||||
t.isIntersectionTypeAnnotation(parent) ||
|
||||
isIntersectionTypeAnnotation(parent) ||
|
||||
// (() => A)[]
|
||||
t.isArrayTypeAnnotation(parent) ||
|
||||
isArrayTypeAnnotation(parent) ||
|
||||
// <T>(A: T): (T => T[]) => B => [A, B]
|
||||
(t.isTypeAnnotation(parent) &&
|
||||
(isTypeAnnotation(parent) &&
|
||||
// Check grandparent
|
||||
t.isArrowFunctionExpression(printStack[printStack.length - 3]))
|
||||
isArrowFunctionExpression(printStack[printStack.length - 3]))
|
||||
);
|
||||
}
|
||||
|
||||
@ -94,7 +143,7 @@ export function DoExpression(
|
||||
export function Binary(node: any, parent: any): boolean {
|
||||
if (
|
||||
node.operator === "**" &&
|
||||
t.isBinaryExpression(parent, { operator: "**" })
|
||||
isBinaryExpression(parent, { operator: "**" })
|
||||
) {
|
||||
return parent.left === node;
|
||||
}
|
||||
@ -105,13 +154,13 @@ export function Binary(node: any, parent: any): boolean {
|
||||
|
||||
if (
|
||||
hasPostfixPart(node, parent) ||
|
||||
t.isUnaryLike(parent) ||
|
||||
t.isAwaitExpression(parent)
|
||||
isUnaryLike(parent) ||
|
||||
isAwaitExpression(parent)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (t.isBinary(parent)) {
|
||||
if (isBinary(parent)) {
|
||||
const parentOp = parent.operator;
|
||||
const parentPos = PRECEDENCE[parentOp];
|
||||
|
||||
@ -122,7 +171,7 @@ export function Binary(node: any, parent: any): boolean {
|
||||
// Logical expressions with the same precedence don't need parens.
|
||||
(parentPos === nodePos &&
|
||||
parent.right === node &&
|
||||
!t.isLogicalExpression(parent)) ||
|
||||
!isLogicalExpression(parent)) ||
|
||||
parentPos > nodePos
|
||||
) {
|
||||
return true;
|
||||
@ -132,17 +181,17 @@ export function Binary(node: any, parent: any): boolean {
|
||||
|
||||
export function UnionTypeAnnotation(node: any, parent: any): boolean {
|
||||
return (
|
||||
t.isArrayTypeAnnotation(parent) ||
|
||||
t.isNullableTypeAnnotation(parent) ||
|
||||
t.isIntersectionTypeAnnotation(parent) ||
|
||||
t.isUnionTypeAnnotation(parent)
|
||||
isArrayTypeAnnotation(parent) ||
|
||||
isNullableTypeAnnotation(parent) ||
|
||||
isIntersectionTypeAnnotation(parent) ||
|
||||
isUnionTypeAnnotation(parent)
|
||||
);
|
||||
}
|
||||
|
||||
export { UnionTypeAnnotation as IntersectionTypeAnnotation };
|
||||
|
||||
export function OptionalIndexedAccessType(node: any, parent: any): boolean {
|
||||
return t.isIndexedAccessType(parent, { objectType: node });
|
||||
return isIndexedAccessType(parent, { objectType: node });
|
||||
}
|
||||
|
||||
export function TSAsExpression() {
|
||||
@ -155,26 +204,25 @@ export function TSTypeAssertion() {
|
||||
|
||||
export function TSUnionType(node: any, parent: any): boolean {
|
||||
return (
|
||||
t.isTSArrayType(parent) ||
|
||||
t.isTSOptionalType(parent) ||
|
||||
t.isTSIntersectionType(parent) ||
|
||||
t.isTSUnionType(parent) ||
|
||||
t.isTSRestType(parent)
|
||||
isTSArrayType(parent) ||
|
||||
isTSOptionalType(parent) ||
|
||||
isTSIntersectionType(parent) ||
|
||||
isTSUnionType(parent) ||
|
||||
isTSRestType(parent)
|
||||
);
|
||||
}
|
||||
|
||||
export { TSUnionType as TSIntersectionType };
|
||||
|
||||
export function TSInferType(node: any, parent: any): boolean {
|
||||
return t.isTSArrayType(parent) || t.isTSOptionalType(parent);
|
||||
return isTSArrayType(parent) || isTSOptionalType(parent);
|
||||
}
|
||||
|
||||
export function BinaryExpression(node: any, parent: any): boolean {
|
||||
// let i = (1 in []);
|
||||
// for ((1 in []);;);
|
||||
return (
|
||||
node.operator === "in" &&
|
||||
(t.isVariableDeclarator(parent) || t.isFor(parent))
|
||||
node.operator === "in" && (isVariableDeclarator(parent) || isFor(parent))
|
||||
);
|
||||
}
|
||||
|
||||
@ -184,14 +232,14 @@ export function SequenceExpression(node: any, parent: any): boolean {
|
||||
// expressions in the head of for loops, traditional style
|
||||
// dictates that e.g. i++, j++ should not be wrapped with
|
||||
// parentheses.
|
||||
t.isForStatement(parent) ||
|
||||
t.isThrowStatement(parent) ||
|
||||
t.isReturnStatement(parent) ||
|
||||
(t.isIfStatement(parent) && parent.test === node) ||
|
||||
(t.isWhileStatement(parent) && parent.test === node) ||
|
||||
(t.isForInStatement(parent) && parent.right === node) ||
|
||||
(t.isSwitchStatement(parent) && parent.discriminant === node) ||
|
||||
(t.isExpressionStatement(parent) && parent.expression === node)
|
||||
isForStatement(parent) ||
|
||||
isThrowStatement(parent) ||
|
||||
isReturnStatement(parent) ||
|
||||
(isIfStatement(parent) && parent.test === node) ||
|
||||
(isWhileStatement(parent) && parent.test === node) ||
|
||||
(isForInStatement(parent) && parent.right === node) ||
|
||||
(isSwitchStatement(parent) && parent.discriminant === node) ||
|
||||
(isExpressionStatement(parent) && parent.expression === node)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@ -203,11 +251,11 @@ export function SequenceExpression(node: any, parent: any): boolean {
|
||||
|
||||
export function YieldExpression(node: any, parent: any): boolean {
|
||||
return (
|
||||
t.isBinary(parent) ||
|
||||
t.isUnaryLike(parent) ||
|
||||
isBinary(parent) ||
|
||||
isUnaryLike(parent) ||
|
||||
hasPostfixPart(node, parent) ||
|
||||
(t.isAwaitExpression(parent) && t.isYieldExpression(node)) ||
|
||||
(t.isConditionalExpression(parent) && node === parent.test) ||
|
||||
(isAwaitExpression(parent) && isYieldExpression(node)) ||
|
||||
(isConditionalExpression(parent) && node === parent.test) ||
|
||||
isClassExtendsClause(node, parent)
|
||||
);
|
||||
}
|
||||
@ -228,7 +276,7 @@ export function ClassExpression(
|
||||
export function UnaryLike(node: any, parent: any): boolean {
|
||||
return (
|
||||
hasPostfixPart(node, parent) ||
|
||||
t.isBinaryExpression(parent, { operator: "**", left: node }) ||
|
||||
isBinaryExpression(parent, { operator: "**", left: node }) ||
|
||||
isClassExtendsClause(node, parent)
|
||||
);
|
||||
}
|
||||
@ -245,17 +293,17 @@ export function FunctionExpression(
|
||||
}
|
||||
|
||||
export function ArrowFunctionExpression(node: any, parent: any): boolean {
|
||||
return t.isExportDeclaration(parent) || ConditionalExpression(node, parent);
|
||||
return isExportDeclaration(parent) || ConditionalExpression(node, parent);
|
||||
}
|
||||
|
||||
export function ConditionalExpression(node: any, parent?): boolean {
|
||||
if (
|
||||
t.isUnaryLike(parent) ||
|
||||
t.isBinary(parent) ||
|
||||
t.isConditionalExpression(parent, { test: node }) ||
|
||||
t.isAwaitExpression(parent) ||
|
||||
t.isTSTypeAssertion(parent) ||
|
||||
t.isTSAsExpression(parent)
|
||||
isUnaryLike(parent) ||
|
||||
isBinary(parent) ||
|
||||
isConditionalExpression(parent, { test: node }) ||
|
||||
isAwaitExpression(parent) ||
|
||||
isTSTypeAssertion(parent) ||
|
||||
isTSAsExpression(parent)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@ -265,15 +313,15 @@ export function ConditionalExpression(node: any, parent?): boolean {
|
||||
|
||||
export function OptionalMemberExpression(node: any, parent: any): boolean {
|
||||
return (
|
||||
t.isCallExpression(parent, { callee: node }) ||
|
||||
t.isMemberExpression(parent, { object: node })
|
||||
isCallExpression(parent, { callee: node }) ||
|
||||
isMemberExpression(parent, { object: node })
|
||||
);
|
||||
}
|
||||
|
||||
export { OptionalMemberExpression as OptionalCallExpression };
|
||||
|
||||
export function AssignmentExpression(node: any, parent: any): boolean {
|
||||
if (t.isObjectPattern(node.left)) {
|
||||
if (isObjectPattern(node.left)) {
|
||||
return true;
|
||||
} else {
|
||||
return ConditionalExpression(node, parent);
|
||||
@ -283,12 +331,12 @@ export function AssignmentExpression(node: any, parent: any): boolean {
|
||||
export function LogicalExpression(node: any, parent: any): boolean {
|
||||
switch (node.operator) {
|
||||
case "||":
|
||||
if (!t.isLogicalExpression(parent)) return false;
|
||||
if (!isLogicalExpression(parent)) return false;
|
||||
return parent.operator === "??" || parent.operator === "&&";
|
||||
case "&&":
|
||||
return t.isLogicalExpression(parent, { operator: "??" });
|
||||
return isLogicalExpression(parent, { operator: "??" });
|
||||
case "??":
|
||||
return t.isLogicalExpression(parent) && parent.operator !== "??";
|
||||
return isLogicalExpression(parent) && parent.operator !== "??";
|
||||
}
|
||||
}
|
||||
|
||||
@ -303,11 +351,11 @@ export function Identifier(
|
||||
// Some contexts only forbid `let [`, so check if the next token would
|
||||
// be the left bracket of a computed member expression.
|
||||
const isFollowedByBracket =
|
||||
t.isMemberExpression(parent, {
|
||||
isMemberExpression(parent, {
|
||||
object: node,
|
||||
computed: true,
|
||||
}) ||
|
||||
t.isOptionalMemberExpression(parent, {
|
||||
isOptionalMemberExpression(parent, {
|
||||
object: node,
|
||||
computed: true,
|
||||
optional: false,
|
||||
@ -329,7 +377,7 @@ export function Identifier(
|
||||
// some tools (including earlier Babel versions) can't parse
|
||||
// `for await (async of [])` without them.
|
||||
return (
|
||||
node.name === "async" && t.isForOfStatement(parent) && node === parent.left
|
||||
node.name === "async" && isForOfStatement(parent) && node === parent.left
|
||||
);
|
||||
}
|
||||
|
||||
@ -353,23 +401,23 @@ function isFirstInContext(
|
||||
while (i >= 0) {
|
||||
if (
|
||||
(expressionStatement &&
|
||||
t.isExpressionStatement(parent, { expression: node })) ||
|
||||
isExpressionStatement(parent, { expression: node })) ||
|
||||
(exportDefault &&
|
||||
t.isExportDefaultDeclaration(parent, { declaration: node })) ||
|
||||
(arrowBody && t.isArrowFunctionExpression(parent, { body: node })) ||
|
||||
(forHead && t.isForStatement(parent, { init: node })) ||
|
||||
(forInHead && t.isForInStatement(parent, { left: node })) ||
|
||||
(forOfHead && t.isForOfStatement(parent, { left: node }))
|
||||
isExportDefaultDeclaration(parent, { declaration: node })) ||
|
||||
(arrowBody && isArrowFunctionExpression(parent, { body: node })) ||
|
||||
(forHead && isForStatement(parent, { init: node })) ||
|
||||
(forInHead && isForInStatement(parent, { left: node })) ||
|
||||
(forOfHead && isForOfStatement(parent, { left: node }))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
(hasPostfixPart(node, parent) && !t.isNewExpression(parent)) ||
|
||||
(t.isSequenceExpression(parent) && parent.expressions[0] === node) ||
|
||||
t.isConditional(parent, { test: node }) ||
|
||||
t.isBinary(parent, { left: node }) ||
|
||||
t.isAssignmentExpression(parent, { left: node })
|
||||
(hasPostfixPart(node, parent) && !isNewExpression(parent)) ||
|
||||
(isSequenceExpression(parent) && parent.expressions[0] === node) ||
|
||||
isConditional(parent, { test: node }) ||
|
||||
isBinary(parent, { left: node }) ||
|
||||
isAssignmentExpression(parent, { left: node })
|
||||
) {
|
||||
node = parent;
|
||||
i--;
|
||||
|
||||
@ -1,5 +1,20 @@
|
||||
import * as t from "@babel/types";
|
||||
|
||||
const {
|
||||
isArrayExpression,
|
||||
isAssignmentExpression,
|
||||
isBinary,
|
||||
isBlockStatement,
|
||||
isCallExpression,
|
||||
isFunction,
|
||||
isIdentifier,
|
||||
isLiteral,
|
||||
isMemberExpression,
|
||||
isObjectExpression,
|
||||
isOptionalCallExpression,
|
||||
isOptionalMemberExpression,
|
||||
isStringLiteral,
|
||||
} = t;
|
||||
type WhitespaceObject = {
|
||||
before?: boolean;
|
||||
after?: boolean;
|
||||
@ -17,18 +32,18 @@ function crawl(
|
||||
node: t.Node,
|
||||
state: { hasCall?: boolean; hasFunction?: boolean; hasHelper?: boolean } = {},
|
||||
) {
|
||||
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
|
||||
if (isMemberExpression(node) || isOptionalMemberExpression(node)) {
|
||||
crawl(node.object, state);
|
||||
if (node.computed) crawl(node.property, state);
|
||||
} else if (t.isBinary(node) || t.isAssignmentExpression(node)) {
|
||||
} else if (isBinary(node) || isAssignmentExpression(node)) {
|
||||
crawl(node.left, state);
|
||||
crawl(node.right, state);
|
||||
} else if (t.isCallExpression(node) || t.isOptionalCallExpression(node)) {
|
||||
} else if (isCallExpression(node) || isOptionalCallExpression(node)) {
|
||||
state.hasCall = true;
|
||||
crawl(node.callee, state);
|
||||
} else if (t.isFunction(node)) {
|
||||
} else if (isFunction(node)) {
|
||||
state.hasFunction = true;
|
||||
} else if (t.isIdentifier(node)) {
|
||||
} else if (isIdentifier(node)) {
|
||||
// @ts-expect-error todo(flow->ts): node.callee is not really expected here…
|
||||
state.hasHelper = state.hasHelper || isHelper(node.callee);
|
||||
}
|
||||
@ -41,15 +56,15 @@ function crawl(
|
||||
*/
|
||||
|
||||
function isHelper(node: t.Node): boolean {
|
||||
if (t.isMemberExpression(node)) {
|
||||
if (isMemberExpression(node)) {
|
||||
return isHelper(node.object) || isHelper(node.property);
|
||||
} else if (t.isIdentifier(node)) {
|
||||
} else if (isIdentifier(node)) {
|
||||
return node.name === "require" || node.name[0] === "_";
|
||||
} else if (t.isCallExpression(node)) {
|
||||
} else if (isCallExpression(node)) {
|
||||
return isHelper(node.callee);
|
||||
} else if (t.isBinary(node) || t.isAssignmentExpression(node)) {
|
||||
} else if (isBinary(node) || isAssignmentExpression(node)) {
|
||||
return (
|
||||
(t.isIdentifier(node.left) && isHelper(node.left)) || isHelper(node.right)
|
||||
(isIdentifier(node.left) && isHelper(node.left)) || isHelper(node.right)
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
@ -58,11 +73,11 @@ function isHelper(node: t.Node): boolean {
|
||||
|
||||
function isType(node) {
|
||||
return (
|
||||
t.isLiteral(node) ||
|
||||
t.isObjectExpression(node) ||
|
||||
t.isArrayExpression(node) ||
|
||||
t.isIdentifier(node) ||
|
||||
t.isMemberExpression(node)
|
||||
isLiteral(node) ||
|
||||
isObjectExpression(node) ||
|
||||
isArrayExpression(node) ||
|
||||
isIdentifier(node) ||
|
||||
isMemberExpression(node)
|
||||
);
|
||||
}
|
||||
|
||||
@ -114,7 +129,7 @@ export const nodes: {
|
||||
*/
|
||||
|
||||
LogicalExpression(node: t.LogicalExpression): WhitespaceObject | undefined {
|
||||
if (t.isFunction(node.left) || t.isFunction(node.right)) {
|
||||
if (isFunction(node.left) || isFunction(node.right)) {
|
||||
return {
|
||||
after: true,
|
||||
};
|
||||
@ -126,7 +141,7 @@ export const nodes: {
|
||||
*/
|
||||
|
||||
Literal(node: t.Literal): WhitespaceObject | undefined | null {
|
||||
if (t.isStringLiteral(node) && node.value === "use strict") {
|
||||
if (isStringLiteral(node) && node.value === "use strict") {
|
||||
return {
|
||||
after: true,
|
||||
};
|
||||
@ -138,7 +153,7 @@ export const nodes: {
|
||||
*/
|
||||
|
||||
CallExpression(node: t.CallExpression): WhitespaceObject | undefined | null {
|
||||
if (t.isFunction(node.callee) || isHelper(node)) {
|
||||
if (isFunction(node.callee) || isHelper(node)) {
|
||||
return {
|
||||
before: true,
|
||||
after: true,
|
||||
@ -149,7 +164,7 @@ export const nodes: {
|
||||
OptionalCallExpression(
|
||||
node: t.OptionalCallExpression,
|
||||
): WhitespaceObject | undefined | null {
|
||||
if (t.isFunction(node.callee)) {
|
||||
if (isFunction(node.callee)) {
|
||||
return {
|
||||
before: true,
|
||||
after: true,
|
||||
@ -187,7 +202,7 @@ export const nodes: {
|
||||
*/
|
||||
|
||||
IfStatement(node: t.IfStatement): WhitespaceObject | undefined | null {
|
||||
if (t.isBlockStatement(node.consequent)) {
|
||||
if (isBlockStatement(node.consequent)) {
|
||||
return {
|
||||
before: true,
|
||||
after: true,
|
||||
|
||||
@ -4,12 +4,16 @@ import * as t from "@babel/types";
|
||||
|
||||
import * as generatorFunctions from "./generators";
|
||||
import type SourceMap from "./source-map";
|
||||
import * as charCodes from "charcodes";
|
||||
|
||||
const SCIENTIFIC_NOTATION = /e/i;
|
||||
const ZERO_DECIMAL_INTEGER = /\.0+$/;
|
||||
const NON_DECIMAL_LITERAL = /^0[box]/;
|
||||
const PURE_ANNOTATION_RE = /^\s*[@#]__PURE__\s*$/;
|
||||
|
||||
const { isProgram, isFile, isEmptyStatement } = t;
|
||||
const { needsParens, needsWhitespaceAfter, needsWhitespaceBefore } = n;
|
||||
|
||||
export type Format = {
|
||||
shouldPrintComment: (comment: string) => boolean;
|
||||
retainLines: boolean;
|
||||
@ -105,11 +109,13 @@ class Printer {
|
||||
space(force: boolean = false): void {
|
||||
if (this.format.compact) return;
|
||||
|
||||
if (
|
||||
(this._buf.hasContent() && !this.endsWith(" ") && !this.endsWith("\n")) ||
|
||||
force
|
||||
) {
|
||||
if (force) {
|
||||
this._space();
|
||||
} else if (this._buf.hasContent()) {
|
||||
const lastCp = this.getLastChar();
|
||||
if (lastCp !== charCodes.space && lastCp !== charCodes.lineFeed) {
|
||||
this._space();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,7 +125,10 @@ class Printer {
|
||||
|
||||
word(str: string): void {
|
||||
// prevent concatenating words and creating // comment out of division and regex
|
||||
if (this._endsWithWord || (this.endsWith("/") && str.indexOf("/") === 0)) {
|
||||
if (
|
||||
this._endsWithWord ||
|
||||
(this.endsWith(charCodes.slash) && str.charCodeAt(0) === charCodes.slash)
|
||||
) {
|
||||
this._space();
|
||||
}
|
||||
|
||||
@ -143,7 +152,7 @@ class Printer {
|
||||
!NON_DECIMAL_LITERAL.test(str) &&
|
||||
!SCIENTIFIC_NOTATION.test(str) &&
|
||||
!ZERO_DECIMAL_INTEGER.test(str) &&
|
||||
str[str.length - 1] !== ".";
|
||||
str.charCodeAt(str.length - 1) !== charCodes.dot;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -153,13 +162,15 @@ class Printer {
|
||||
token(str: string): void {
|
||||
// space is mandatory to avoid outputting <!--
|
||||
// http://javascript.spec.whatwg.org/#comment-syntax
|
||||
const lastChar = this.getLastChar();
|
||||
const strFirst = str.charCodeAt(0);
|
||||
if (
|
||||
(str === "--" && this.endsWith("!")) ||
|
||||
(str === "--" && lastChar === charCodes.exclamationMark) ||
|
||||
// Need spaces for operators of the same kind to avoid: `a+++b`
|
||||
(str[0] === "+" && this.endsWith("+")) ||
|
||||
(str[0] === "-" && this.endsWith("-")) ||
|
||||
(strFirst === charCodes.plusSign && lastChar === charCodes.plusSign) ||
|
||||
(strFirst === charCodes.dash && lastChar === charCodes.dash) ||
|
||||
// Needs spaces to avoid changing '34' to '34.', which would still be a valid number.
|
||||
(str[0] === "." && this._endsWithInteger)
|
||||
(strFirst === charCodes.dot && this._endsWithInteger)
|
||||
) {
|
||||
this._space();
|
||||
}
|
||||
@ -172,7 +183,7 @@ class Printer {
|
||||
* Add a newline (or many newlines), maintaining formatting.
|
||||
*/
|
||||
|
||||
newline(i?: number): void {
|
||||
newline(i: number = 1): void {
|
||||
if (this.format.retainLines || this.format.compact) return;
|
||||
|
||||
if (this.format.concise) {
|
||||
@ -180,13 +191,16 @@ class Printer {
|
||||
return;
|
||||
}
|
||||
|
||||
const charBeforeNewline = this.endsWithCharAndNewline();
|
||||
// never allow more than two lines
|
||||
if (this.endsWith("\n\n")) return;
|
||||
if (charBeforeNewline === charCodes.lineFeed) return;
|
||||
|
||||
if (typeof i !== "number") i = 1;
|
||||
|
||||
i = Math.min(2, i);
|
||||
if (this.endsWith("{\n") || this.endsWith(":\n")) i--;
|
||||
if (
|
||||
charBeforeNewline === charCodes.leftCurlyBrace ||
|
||||
charBeforeNewline === charCodes.colon
|
||||
) {
|
||||
i--;
|
||||
}
|
||||
if (i <= 0) return;
|
||||
|
||||
for (let j = 0; j < i; j++) {
|
||||
@ -194,8 +208,16 @@ class Printer {
|
||||
}
|
||||
}
|
||||
|
||||
endsWith(str: string): boolean {
|
||||
return this._buf.endsWith(str);
|
||||
endsWith(char: number): boolean {
|
||||
return this.getLastChar() === char;
|
||||
}
|
||||
|
||||
getLastChar(): number {
|
||||
return this._buf.getLastChar();
|
||||
}
|
||||
|
||||
endsWithCharAndNewline(): number {
|
||||
return this._buf.endsWithCharAndNewline();
|
||||
}
|
||||
|
||||
removeTrailingNewline(): void {
|
||||
@ -241,7 +263,11 @@ class Printer {
|
||||
|
||||
_maybeIndent(str: string): void {
|
||||
// we've got a newline before us so prepend on the indentation
|
||||
if (this._indent && this.endsWith("\n") && str[0] !== "\n") {
|
||||
if (
|
||||
this._indent &&
|
||||
this.endsWith(charCodes.lineFeed) &&
|
||||
str.charCodeAt(0) !== charCodes.lineFeed
|
||||
) {
|
||||
this._buf.queue(this._getIndent());
|
||||
}
|
||||
}
|
||||
@ -391,27 +417,27 @@ class Printer {
|
||||
this._insideAux = !node.loc;
|
||||
this._maybeAddAuxComment(this._insideAux && !oldInAux);
|
||||
|
||||
let needsParens = n.needsParens(node, parent, this._printStack);
|
||||
let shouldPrintParens = needsParens(node, parent, this._printStack);
|
||||
if (
|
||||
this.format.retainFunctionParens &&
|
||||
node.type === "FunctionExpression" &&
|
||||
node.extra &&
|
||||
node.extra.parenthesized
|
||||
) {
|
||||
needsParens = true;
|
||||
shouldPrintParens = true;
|
||||
}
|
||||
if (needsParens) this.token("(");
|
||||
if (shouldPrintParens) this.token("(");
|
||||
|
||||
this._printLeadingComments(node);
|
||||
|
||||
const loc = t.isProgram(node) || t.isFile(node) ? null : node.loc;
|
||||
const loc = isProgram(node) || isFile(node) ? null : node.loc;
|
||||
this.withSource("start", loc, () => {
|
||||
printMethod.call(this, node, parent);
|
||||
});
|
||||
|
||||
this._printTrailingComments(node);
|
||||
|
||||
if (needsParens) this.token(")");
|
||||
if (shouldPrintParens) this.token(")");
|
||||
|
||||
// end
|
||||
this._printStack.pop();
|
||||
@ -504,7 +530,7 @@ class Printer {
|
||||
printBlock(parent) {
|
||||
const node = parent.body;
|
||||
|
||||
if (!t.isEmptyStatement(node)) {
|
||||
if (!isEmptyStatement(node)) {
|
||||
this.space();
|
||||
}
|
||||
|
||||
@ -572,11 +598,11 @@ class Printer {
|
||||
if (!leading) lines++; // always include at least a single line after
|
||||
if (opts.addNewlines) lines += opts.addNewlines(leading, node) || 0;
|
||||
|
||||
const needs = leading ? n.needsWhitespaceBefore : n.needsWhitespaceAfter;
|
||||
const needs = leading ? needsWhitespaceBefore : needsWhitespaceAfter;
|
||||
if (needs(node, parent)) lines++;
|
||||
}
|
||||
|
||||
this.newline(lines);
|
||||
this.newline(Math.min(2, lines));
|
||||
}
|
||||
|
||||
_getComments(leading, node) {
|
||||
@ -606,7 +632,13 @@ class Printer {
|
||||
|
||||
if (printNewLines && this._buf.hasContent()) this.newline(1);
|
||||
|
||||
if (!this.endsWith("[") && !this.endsWith("{")) this.space();
|
||||
const lastCharCode = this.getLastChar();
|
||||
if (
|
||||
lastCharCode !== charCodes.leftSquareBracket &&
|
||||
lastCharCode !== charCodes.leftCurlyBrace
|
||||
) {
|
||||
this.space();
|
||||
}
|
||||
|
||||
let val =
|
||||
!isBlockComment && !this._noLineTerminator
|
||||
@ -628,7 +660,7 @@ class Printer {
|
||||
}
|
||||
|
||||
// Avoid creating //* comments
|
||||
if (this.endsWith("/")) this._space();
|
||||
if (this.endsWith(charCodes.slash)) this._space();
|
||||
|
||||
this.withSource("start", comment.loc, () => {
|
||||
this._append(val);
|
||||
@ -648,7 +680,7 @@ class Printer {
|
||||
this._printComment(
|
||||
comments[0],
|
||||
// Keep newlines if the comment marks a standalone call
|
||||
this._buf.hasContent() && !this.endsWith("\n"),
|
||||
this._buf.hasContent() && !this.endsWith(charCodes.lineFeed),
|
||||
);
|
||||
} else {
|
||||
for (const comment of comments) {
|
||||
|
||||
26
yarn.lock
26
yarn.lock
@ -5,6 +5,17 @@ __metadata:
|
||||
version: 4
|
||||
cacheKey: 7
|
||||
|
||||
"@babel-baseline/generator@npm:@babel/generator@7.14.5, @babel/generator@npm:^7.12.5, @babel/generator@npm:^7.14.5, @babel/generator@npm:^7.7.2":
|
||||
version: 7.14.5
|
||||
resolution: "@babel/generator@npm:7.14.5"
|
||||
dependencies:
|
||||
"@babel/types": ^7.14.5
|
||||
jsesc: ^2.5.1
|
||||
source-map: ^0.5.0
|
||||
checksum: 3ba48b75f7680d17b4c3657063339252cf63ea0038b05e24d1611dff2c8f136fc8ca5cb1c293fbc1abc79b153e264bc23a01dc5f440030282e4da0631f12e0b7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel-baseline/parser@npm:@babel/parser@^7.14.5":
|
||||
version: 7.14.5
|
||||
resolution: "@babel/parser@npm:7.14.5"
|
||||
@ -331,26 +342,18 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@babel/generator@npm:^7.12.5, @babel/generator@npm:^7.14.5, @babel/generator@npm:^7.7.2":
|
||||
version: 7.14.5
|
||||
resolution: "@babel/generator@npm:7.14.5"
|
||||
dependencies:
|
||||
"@babel/types": ^7.14.5
|
||||
jsesc: ^2.5.1
|
||||
source-map: ^0.5.0
|
||||
checksum: 3ba48b75f7680d17b4c3657063339252cf63ea0038b05e24d1611dff2c8f136fc8ca5cb1c293fbc1abc79b153e264bc23a01dc5f440030282e4da0631f12e0b7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@babel/generator@workspace:*, @babel/generator@workspace:^7.14.5, @babel/generator@workspace:^7.14.8, @babel/generator@workspace:packages/babel-generator":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@babel/generator@workspace:packages/babel-generator"
|
||||
dependencies:
|
||||
"@babel-baseline/generator": "npm:@babel/generator@7.14.5"
|
||||
"@babel/helper-fixtures": "workspace:*"
|
||||
"@babel/parser": "workspace:*"
|
||||
"@babel/types": "workspace:^7.14.8"
|
||||
"@types/jsesc": ^2.5.0
|
||||
"@types/source-map": ^0.5.0
|
||||
benchmark: ^2.1.4
|
||||
charcodes: ^0.2.0
|
||||
jsesc: "condition: BABEL_8_BREAKING ? ^3.0.2 : ^2.5.1"
|
||||
source-map: ^0.5.0
|
||||
languageName: unknown
|
||||
@ -657,6 +660,7 @@ __metadata:
|
||||
resolution: "@babel/helper-module-transforms@condition:BABEL_8_BREAKING?:workspace:^7.14.8#d03fdb"
|
||||
dependencies:
|
||||
"@babel/helper-module-transforms-BABEL_8_BREAKING-false": "npm:@babel/helper-module-transforms@workspace:^7.14.8"
|
||||
checksum: dc554f178a54829c3510a740ff36c3bd4f29967c3daa7e22ee91eab4db892becafb538e433bcfada2cf82c57576c25ae7700fa29e5c780b7cc661697bd16de56
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user