It's very similar to the one from Babel: f5b52208f0/packages/babel-generator/src/generators/classes.ts (L123-L166). However, `ast-types` doesn't seem to think a `definite` attribute exists on this node type so the type checking fails.
2486 lines
65 KiB
TypeScript
2486 lines
65 KiB
TypeScript
import assert from "assert";
|
|
import * as types from "ast-types";
|
|
import { EOL as eol } from "os";
|
|
import { fromString } from "../lib/lines";
|
|
import { parse } from "../lib/parser";
|
|
import { Printer } from "../lib/printer";
|
|
import * as recast from "../main";
|
|
const n = types.namedTypes;
|
|
const b = types.builders;
|
|
const linesModule = require("../lib/lines");
|
|
const nodeMajorVersion = parseInt(process.versions.node, 10);
|
|
|
|
import * as tsParser from "../parsers/typescript";
|
|
|
|
describe("printer", function () {
|
|
it("Printer", function testPrinter(done) {
|
|
const code = testPrinter + "";
|
|
const ast = parse(code);
|
|
const printer = new Printer();
|
|
|
|
assert.strictEqual(typeof printer.print, "function");
|
|
assert.strictEqual(printer.print(null).code, "");
|
|
|
|
let string = printer.printGenerically(ast).code;
|
|
assert.ok(string.indexOf("done();") > 0);
|
|
|
|
string = printer.print(ast).code;
|
|
|
|
// TODO
|
|
|
|
assert.ok(string.indexOf("// TODO") > 0);
|
|
|
|
done();
|
|
});
|
|
|
|
const uselessSemicolons = [
|
|
"function a() {",
|
|
' return "a";',
|
|
"};",
|
|
"",
|
|
"function b() {",
|
|
' return "b";',
|
|
"};",
|
|
].join(eol);
|
|
|
|
it("EmptyStatements", function () {
|
|
const ast = parse(uselessSemicolons);
|
|
const printer = new Printer({ tabWidth: 2 });
|
|
|
|
const reprinted = printer.print(ast).code;
|
|
assert.strictEqual(typeof reprinted, "string");
|
|
assert.strictEqual(reprinted, uselessSemicolons);
|
|
|
|
const generic = printer.printGenerically(ast).code;
|
|
const withoutTrailingSemicolons = uselessSemicolons.replace(/\};/g, "}");
|
|
assert.strictEqual(typeof generic, "string");
|
|
assert.strictEqual(generic, withoutTrailingSemicolons);
|
|
});
|
|
|
|
const importantSemicolons = [
|
|
"var a = {};", // <--- this trailing semi-colon is very important
|
|
"(function() {})();",
|
|
].join(eol);
|
|
|
|
it("IffeAfterVariableDeclarationEndingInObjectLiteral", function () {
|
|
const ast = parse(importantSemicolons);
|
|
const printer = new Printer({ tabWidth: 2 });
|
|
|
|
const reprinted = printer.printGenerically(ast).code;
|
|
assert.strictEqual(typeof reprinted, "string");
|
|
assert.strictEqual(reprinted, importantSemicolons);
|
|
});
|
|
|
|
const arrayExprWithTrailingComma = "[1, 2,];";
|
|
const arrayExprWithoutTrailingComma = "[1, 2];";
|
|
|
|
it("ArrayExpressionWithTrailingComma", function () {
|
|
const ast = parse(arrayExprWithTrailingComma);
|
|
const printer = new Printer({ tabWidth: 2 });
|
|
|
|
const body = ast.program.body;
|
|
const arrayExpr = body[0].expression;
|
|
n.ArrayExpression.assert(arrayExpr);
|
|
|
|
// This causes the array expression to be reprinted.
|
|
const arrayExprOrig = arrayExpr.original;
|
|
arrayExpr.original = null;
|
|
|
|
assert.strictEqual(printer.print(ast).code, arrayExprWithoutTrailingComma);
|
|
|
|
arrayExpr.original = arrayExprOrig;
|
|
|
|
assert.strictEqual(printer.print(ast).code, arrayExprWithTrailingComma);
|
|
});
|
|
|
|
const arrayExprWithHoles = "[,,];";
|
|
|
|
it("ArrayExpressionWithHoles", function () {
|
|
const ast = parse(arrayExprWithHoles);
|
|
const printer = new Printer({ tabWidth: 2 });
|
|
|
|
const body = ast.program.body;
|
|
const arrayExpr = body[0].expression;
|
|
n.ArrayExpression.assert(arrayExpr);
|
|
|
|
// This causes the array expression to be reprinted.
|
|
const arrayExprOrig = arrayExpr.original;
|
|
arrayExpr.original = null;
|
|
|
|
assert.strictEqual(printer.print(ast).code, arrayExprWithHoles);
|
|
|
|
arrayExpr.original = arrayExprOrig;
|
|
|
|
assert.strictEqual(printer.print(ast).code, arrayExprWithHoles);
|
|
});
|
|
|
|
const objectExprWithTrailingComma = "({x: 1, y: 2,});";
|
|
const objectExprWithoutTrailingComma =
|
|
"({" + eol + " x: 1," + eol + " y: 2" + eol + "});";
|
|
|
|
it("ArrayExpressionWithTrailingComma", function () {
|
|
const ast = parse(objectExprWithTrailingComma);
|
|
const printer = new Printer({ tabWidth: 2 });
|
|
|
|
const body = ast.program.body;
|
|
const objectExpr = body[0].expression;
|
|
n.ObjectExpression.assert(objectExpr);
|
|
|
|
// This causes the array expression to be reprinted.
|
|
const objectExprOrig = objectExpr.original;
|
|
objectExpr.original = null;
|
|
|
|
assert.strictEqual(printer.print(ast).code, objectExprWithoutTrailingComma);
|
|
|
|
objectExpr.original = objectExprOrig;
|
|
|
|
assert.strictEqual(printer.print(ast).code, objectExprWithTrailingComma);
|
|
});
|
|
|
|
const switchCase = [
|
|
"switch (test) {",
|
|
" default:",
|
|
" case a: break",
|
|
"",
|
|
" case b:",
|
|
" break;",
|
|
"}",
|
|
].join(eol);
|
|
|
|
const switchCaseReprinted = [
|
|
"if (test) {",
|
|
" switch (test) {",
|
|
" default:",
|
|
" case a: break",
|
|
" case b:",
|
|
" break;",
|
|
" }",
|
|
"}",
|
|
].join(eol);
|
|
|
|
const switchCaseGeneric = [
|
|
"if (test) {",
|
|
" switch (test) {",
|
|
" default:",
|
|
" case a:",
|
|
" break;",
|
|
" case b:",
|
|
" break;",
|
|
" }",
|
|
"}",
|
|
].join(eol);
|
|
|
|
it("SwitchCase", function () {
|
|
const ast = parse(switchCase);
|
|
const printer = new Printer({ tabWidth: 2 });
|
|
|
|
const body = ast.program.body;
|
|
const switchStmt = body[0];
|
|
n.SwitchStatement.assert(switchStmt);
|
|
|
|
// This causes the switch statement to be reprinted.
|
|
switchStmt.original = null;
|
|
|
|
body[0] = b.ifStatement(
|
|
b.identifier("test"),
|
|
b.blockStatement([switchStmt]),
|
|
);
|
|
|
|
assert.strictEqual(printer.print(ast).code, switchCaseReprinted);
|
|
|
|
assert.strictEqual(printer.printGenerically(ast).code, switchCaseGeneric);
|
|
});
|
|
|
|
const tryCatch = ["try {", " a();", "} catch (e) {", " b(e);", "}"].join(
|
|
eol,
|
|
);
|
|
|
|
it("IndentTryCatch", function () {
|
|
const ast = parse(tryCatch);
|
|
const printer = new Printer({ tabWidth: 2 });
|
|
const body = ast.program.body;
|
|
const tryStmt = body[0];
|
|
n.TryStatement.assert(tryStmt);
|
|
|
|
// Force reprinting.
|
|
assert.strictEqual(printer.printGenerically(ast).code, tryCatch);
|
|
});
|
|
|
|
const classBody = [
|
|
"class A {",
|
|
" foo(x) { return x }",
|
|
" bar(y) { this.foo(y); }",
|
|
" baz(x, y) {",
|
|
" this.foo(x);",
|
|
" this.bar(y);",
|
|
" }",
|
|
"}",
|
|
];
|
|
|
|
const classBodyExpected = [
|
|
"class A {",
|
|
" foo(x) { return x }",
|
|
" bar(y) { this.foo(y); }",
|
|
" baz(x, y) {",
|
|
" this.foo(x);",
|
|
" this.bar(y);",
|
|
" }",
|
|
" foo(x) { return x }",
|
|
"}",
|
|
];
|
|
|
|
it("MethodPrinting", function () {
|
|
const code = classBody.join(eol);
|
|
let ast;
|
|
try {
|
|
ast = parse(code);
|
|
} catch (e) {
|
|
// ES6 not supported, silently finish
|
|
return;
|
|
}
|
|
const printer = new Printer({ tabWidth: 2 });
|
|
const cb = ast.program.body[0].body;
|
|
n.ClassBody.assert(cb);
|
|
|
|
// Trigger reprinting of the class body.
|
|
cb.body.push(cb.body[0]);
|
|
|
|
assert.strictEqual(printer.print(ast).code, classBodyExpected.join(eol));
|
|
});
|
|
|
|
const multiLineParams = [
|
|
"function f(/* first",
|
|
" xxx",
|
|
" param */ a,",
|
|
" // other params",
|
|
" b, c, // see?",
|
|
" d",
|
|
") {",
|
|
" return a + b + c + d;",
|
|
"}",
|
|
];
|
|
|
|
const multiLineParamsExpected = [
|
|
"function f(",
|
|
" /* first",
|
|
" xxx",
|
|
" param */ a,",
|
|
" // other params",
|
|
" b,",
|
|
" // see?",
|
|
" c,",
|
|
" d",
|
|
") {",
|
|
" return a + b + c + d;",
|
|
"}",
|
|
];
|
|
|
|
it("MultiLineParams", function () {
|
|
const code = multiLineParams.join(eol);
|
|
const ast = parse(code);
|
|
const printer = new Printer({ tabWidth: 2 });
|
|
|
|
recast.visit(ast, {
|
|
visitNode: function (path) {
|
|
path.value.original = null;
|
|
this.traverse(path);
|
|
},
|
|
});
|
|
|
|
assert.strictEqual(
|
|
printer.print(ast).code,
|
|
multiLineParamsExpected.join(eol),
|
|
);
|
|
});
|
|
|
|
it("SimpleVarPrinting", function () {
|
|
const printer = new Printer({ tabWidth: 2 });
|
|
const varDecl = b.variableDeclaration("var", [
|
|
b.variableDeclarator(b.identifier("x"), null),
|
|
b.variableDeclarator(b.identifier("y"), null),
|
|
b.variableDeclarator(b.identifier("z"), null),
|
|
]);
|
|
|
|
assert.strictEqual(
|
|
printer.print(b.program([varDecl])).code,
|
|
"var x, y, z;",
|
|
);
|
|
|
|
const z: any = varDecl.declarations.pop();
|
|
varDecl.declarations.pop();
|
|
varDecl.declarations.push(z);
|
|
|
|
assert.strictEqual(printer.print(b.program([varDecl])).code, "var x, z;");
|
|
});
|
|
|
|
it("MultiLineVarPrinting", function () {
|
|
const printer = new Printer({ tabWidth: 2 });
|
|
const varDecl = b.variableDeclaration("var", [
|
|
b.variableDeclarator(b.identifier("x"), null),
|
|
b.variableDeclarator(
|
|
b.identifier("y"),
|
|
b.objectExpression([
|
|
b.property("init", b.identifier("why"), b.literal("not")),
|
|
]),
|
|
),
|
|
b.variableDeclarator(b.identifier("z"), null),
|
|
]);
|
|
|
|
assert.strictEqual(
|
|
printer.print(b.program([varDecl])).code,
|
|
["var x,", " y = {", ' why: "not"', " },", " z;"].join(eol),
|
|
);
|
|
});
|
|
|
|
it("ForLoopPrinting", function () {
|
|
const printer = new Printer({ tabWidth: 2 });
|
|
const loop = b.forStatement(
|
|
b.variableDeclaration("var", [
|
|
b.variableDeclarator(b.identifier("i"), b.literal(0)),
|
|
]),
|
|
b.binaryExpression("<", b.identifier("i"), b.literal(3)),
|
|
b.updateExpression("++", b.identifier("i"), /* prefix: */ false),
|
|
b.expressionStatement(
|
|
b.callExpression(b.identifier("log"), [b.identifier("i")]),
|
|
),
|
|
);
|
|
|
|
assert.strictEqual(
|
|
printer.print(loop).code,
|
|
"for (var i = 0; i < 3; i++)" + eol + " log(i);",
|
|
);
|
|
});
|
|
|
|
it("EmptyForLoopPrinting", function () {
|
|
const printer = new Printer({ tabWidth: 2 });
|
|
const loop = b.forStatement(
|
|
b.variableDeclaration("var", [
|
|
b.variableDeclarator(b.identifier("i"), b.literal(0)),
|
|
]),
|
|
b.binaryExpression("<", b.identifier("i"), b.literal(3)),
|
|
b.updateExpression("++", b.identifier("i"), /* prefix: */ false),
|
|
b.emptyStatement(),
|
|
);
|
|
|
|
assert.strictEqual(
|
|
printer.print(loop).code,
|
|
"for (var i = 0; i < 3; i++)" + eol + " ;",
|
|
);
|
|
});
|
|
|
|
it("ForInLoopPrinting", function () {
|
|
const printer = new Printer({ tabWidth: 2 });
|
|
const loop = b.forInStatement(
|
|
b.variableDeclaration("var", [
|
|
b.variableDeclarator(b.identifier("key"), null),
|
|
]),
|
|
b.identifier("obj"),
|
|
b.expressionStatement(
|
|
b.callExpression(b.identifier("log"), [b.identifier("key")]),
|
|
),
|
|
);
|
|
|
|
assert.strictEqual(
|
|
printer.print(loop).code,
|
|
"for (var key in obj)" + eol + " log(key);",
|
|
);
|
|
});
|
|
|
|
it("GuessTabWidth", function () {
|
|
const code = ["function identity(x) {", " return x;", "}"].join(eol);
|
|
|
|
const guessedTwo = [
|
|
"function identity(x) {",
|
|
" log(x);",
|
|
" return x;",
|
|
"}",
|
|
].join(eol);
|
|
|
|
const explicitFour = [
|
|
"function identity(x) {",
|
|
" log(x);",
|
|
" return x;",
|
|
"}",
|
|
].join(eol);
|
|
|
|
const ast = parse(code);
|
|
|
|
const funDecl = ast.program.body[0];
|
|
n.FunctionDeclaration.assert(funDecl);
|
|
|
|
const funBody = funDecl.body.body;
|
|
|
|
funBody.unshift(
|
|
b.expressionStatement(
|
|
b.callExpression(b.identifier("log"), funDecl.params),
|
|
),
|
|
);
|
|
|
|
assert.strictEqual(new Printer().print(ast).code, guessedTwo);
|
|
|
|
assert.strictEqual(
|
|
new Printer({
|
|
tabWidth: 4,
|
|
}).print(ast).code,
|
|
explicitFour,
|
|
);
|
|
});
|
|
|
|
it("FunctionDefaultsAndRest", function () {
|
|
const printer = new Printer();
|
|
const funExpr = b.functionExpression(
|
|
b.identifier("a"),
|
|
[b.identifier("b"), b.identifier("c")],
|
|
b.blockStatement([]),
|
|
false,
|
|
false,
|
|
);
|
|
|
|
funExpr.defaults = [null, b.literal(1)];
|
|
funExpr.rest = b.identifier("d");
|
|
|
|
assert.strictEqual(
|
|
printer.print(funExpr).code,
|
|
"function a(b, c = 1, ...d) {}",
|
|
);
|
|
|
|
const arrowFunExpr = b.arrowFunctionExpression(
|
|
[b.identifier("b"), b.identifier("c")],
|
|
b.blockStatement([]),
|
|
false,
|
|
);
|
|
|
|
arrowFunExpr.defaults = [null, b.literal(1)];
|
|
arrowFunExpr.rest = b.identifier("d");
|
|
|
|
assert.strictEqual(
|
|
printer.print(arrowFunExpr).code,
|
|
"(b, c = 1, ...d) => {}",
|
|
);
|
|
});
|
|
|
|
it("generically prints parsed code and generated code the same way", function () {
|
|
const printer = new Printer();
|
|
const ast = b.program([
|
|
b.expressionStatement(b.literal(1)),
|
|
b.expressionStatement(b.literal(2)),
|
|
]);
|
|
|
|
assert.strictEqual(
|
|
printer.printGenerically(parse("1; 2;")).code,
|
|
printer.printGenerically(ast).code,
|
|
);
|
|
});
|
|
|
|
it("ExportDeclaration semicolons", function () {
|
|
const printer = new Printer();
|
|
let code = "export var foo = 42;";
|
|
let ast = parse(code);
|
|
|
|
assert.strictEqual(printer.print(ast).code, code);
|
|
assert.strictEqual(printer.printGenerically(ast).code, code);
|
|
|
|
code = "export var foo = 42";
|
|
ast = parse(code);
|
|
|
|
assert.strictEqual(printer.print(ast).code, code);
|
|
assert.strictEqual(printer.printGenerically(ast).code, code + ";");
|
|
|
|
code = "export function foo() {}";
|
|
ast = parse(code);
|
|
|
|
assert.strictEqual(printer.print(ast).code, code);
|
|
assert.strictEqual(printer.printGenerically(ast).code, code);
|
|
|
|
code = 'export * from "./lib";';
|
|
ast = parse(code);
|
|
|
|
assert.strictEqual(printer.print(ast).code, code);
|
|
assert.strictEqual(printer.printGenerically(ast).code, code);
|
|
});
|
|
|
|
it("empty ExportDeclaration", function () {
|
|
const printer = new Printer();
|
|
const code = "export {};";
|
|
const ast = parse(code);
|
|
|
|
assert.strictEqual(printer.print(ast).code, code);
|
|
assert.strictEqual(printer.printGenerically(ast).code, code);
|
|
});
|
|
|
|
it("export default of IIFE", function () {
|
|
const printer = new Printer();
|
|
let ast = b.exportDefaultDeclaration(
|
|
b.callExpression(
|
|
b.functionExpression(null, [], b.blockStatement([])),
|
|
[],
|
|
),
|
|
);
|
|
const code = printer.print(ast).code;
|
|
ast = parse(code);
|
|
|
|
assert.strictEqual(printer.print(ast).code, code);
|
|
assert.strictEqual(printer.printGenerically(ast).code, code);
|
|
});
|
|
|
|
const stmtListSpaces = [
|
|
"",
|
|
"var x = 1;",
|
|
"",
|
|
"",
|
|
"// y summation",
|
|
"var y = x + 1;",
|
|
"var z = x + y;",
|
|
"// after z",
|
|
"",
|
|
"console.log(x, y, z);",
|
|
"",
|
|
"",
|
|
].join(eol);
|
|
|
|
const stmtListSpacesExpected = [
|
|
"",
|
|
"debugger;",
|
|
"var x = 1;",
|
|
"",
|
|
"",
|
|
"// y summation",
|
|
"var y = x + 1;",
|
|
"debugger;",
|
|
"var z = x + y;",
|
|
"// after z",
|
|
"",
|
|
"console.log(x, y, z);",
|
|
"",
|
|
"debugger;",
|
|
"",
|
|
"",
|
|
].join(eol);
|
|
|
|
it("Statement list whitespace reuse", function () {
|
|
const ast = parse(stmtListSpaces);
|
|
const printer = new Printer({ tabWidth: 2 });
|
|
const debugStmt = b.expressionStatement(b.identifier("debugger"));
|
|
|
|
ast.program.body.splice(2, 0, debugStmt);
|
|
ast.program.body.unshift(debugStmt);
|
|
ast.program.body.push(debugStmt);
|
|
|
|
assert.strictEqual(printer.print(ast).code, stmtListSpacesExpected);
|
|
|
|
const funDecl = b.functionDeclaration(
|
|
b.identifier("foo"),
|
|
[],
|
|
b.blockStatement(ast.program.body),
|
|
);
|
|
|
|
assert.strictEqual(
|
|
printer.print(funDecl).code,
|
|
linesModule
|
|
.concat([
|
|
"function foo() {" + eol,
|
|
linesModule
|
|
.fromString(stmtListSpacesExpected.replace(/^\s+|\s+$/g, ""))
|
|
.indent(2),
|
|
eol + "}",
|
|
])
|
|
.toString(),
|
|
);
|
|
});
|
|
|
|
it("should print static methods with the static keyword", function () {
|
|
const printer = new Printer({ tabWidth: 4 });
|
|
const ast = parse(["class A {", " static foo() {}", "}"].join(eol));
|
|
|
|
const classBody = ast.program.body[0].body;
|
|
n.ClassBody.assert(classBody);
|
|
|
|
const foo = classBody.body[0];
|
|
n.MethodDefinition.assert(foo);
|
|
|
|
classBody.body.push(foo);
|
|
|
|
foo.key.name = "formerlyFoo";
|
|
|
|
assert.strictEqual(
|
|
printer.print(ast).code,
|
|
[
|
|
"class A {",
|
|
" static formerlyFoo() {}",
|
|
" static formerlyFoo() {}",
|
|
"}",
|
|
].join(eol),
|
|
);
|
|
});
|
|
|
|
it("should print string literals with the specified delimiter", function () {
|
|
const ast = parse(
|
|
[
|
|
"var obj = {",
|
|
" \"foo's\": 'bar',",
|
|
" '\"bar\\'s\"': /regex/m",
|
|
"};",
|
|
].join(eol),
|
|
);
|
|
|
|
const variableDeclaration = ast.program.body[0];
|
|
n.VariableDeclaration.assert(variableDeclaration);
|
|
|
|
const printer = new Printer({ quote: "single" });
|
|
assert.strictEqual(
|
|
printer.printGenerically(ast).code,
|
|
[
|
|
"var obj = {",
|
|
" 'foo\\'s': 'bar',",
|
|
" '\"bar\\'s\"': /regex/m",
|
|
"};",
|
|
].join(eol),
|
|
);
|
|
|
|
const printer2 = new Printer({ quote: "double" });
|
|
assert.strictEqual(
|
|
printer2.printGenerically(ast).code,
|
|
[
|
|
"var obj = {",
|
|
' "foo\'s": "bar",',
|
|
' "\\"bar\'s\\"": /regex/m',
|
|
"};",
|
|
].join(eol),
|
|
);
|
|
|
|
const printer3 = new Printer({ quote: "auto" });
|
|
assert.strictEqual(
|
|
printer3.printGenerically(ast).code,
|
|
[
|
|
"var obj = {",
|
|
' "foo\'s": "bar",',
|
|
" '\"bar\\'s\"': /regex/m",
|
|
"};",
|
|
].join(eol),
|
|
);
|
|
});
|
|
|
|
it("pretty-prints U+2028 and U+2029 as Unicode escapes", function () {
|
|
const ast = parse('"\\u2028";');
|
|
const printer = new Printer();
|
|
assert.strictEqual(printer.printGenerically(ast).code, '"\\u2028";');
|
|
|
|
const ast2 = parse('"\\u2029";');
|
|
const printer2 = new Printer();
|
|
assert.strictEqual(printer2.printGenerically(ast2).code, '"\\u2029";');
|
|
});
|
|
|
|
it("should print block comments at head of class once", function () {
|
|
// Given.
|
|
const ast = parse(
|
|
[
|
|
"/**",
|
|
" * This class was in an IIFE and returned an instance of itself.",
|
|
" */",
|
|
"function SimpleClass() {",
|
|
"};",
|
|
].join(eol),
|
|
);
|
|
|
|
const classIdentifier = b.identifier("SimpleClass");
|
|
const exportsExpression = b.memberExpression(
|
|
b.identifier("module"),
|
|
b.identifier("exports"),
|
|
false,
|
|
);
|
|
const assignmentExpression = b.assignmentExpression(
|
|
"=",
|
|
exportsExpression,
|
|
classIdentifier,
|
|
);
|
|
const exportStatement = b.expressionStatement(assignmentExpression);
|
|
|
|
ast.program.body.push(exportStatement);
|
|
|
|
// When.
|
|
const printedClass = new Printer().print(ast).code;
|
|
|
|
// Then.
|
|
assert.strictEqual(
|
|
printedClass,
|
|
[
|
|
"/**",
|
|
" * This class was in an IIFE and returned an instance of itself.",
|
|
" */",
|
|
"function SimpleClass() {",
|
|
"}",
|
|
"module.exports = SimpleClass;",
|
|
].join(eol),
|
|
);
|
|
});
|
|
|
|
it("should support computed properties", function () {
|
|
let code = [
|
|
"class A {",
|
|
' ["a"]() {}',
|
|
' [ID("b")]() {}',
|
|
" [0]() {}",
|
|
" [ID(1)]() {}",
|
|
' get ["a"]() {}',
|
|
' get [ID("b")]() {}',
|
|
" get [0]() {}",
|
|
" get [ID(1)]() {}",
|
|
' set ["a"](x) {}',
|
|
' set [ID("b")](x) {}',
|
|
" set [0](x) {}",
|
|
" set [ID(1)](x) {}",
|
|
' static ["a"]() {}',
|
|
' static [ID("b")]() {}',
|
|
" static [0]() {}",
|
|
" static [ID(1)]() {}",
|
|
' static get ["a"]() {}',
|
|
' static get [ID("b")]() {}',
|
|
" static get [0]() {}",
|
|
" static get [ID(1)]() {}",
|
|
' static set ["a"](x) {}',
|
|
' static set [ID("b")](x) {}',
|
|
" static set [0](x) {}",
|
|
" static set [ID(1)](x) {}",
|
|
"}",
|
|
].join(eol);
|
|
|
|
let ast = parse(code);
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
});
|
|
|
|
assert.strictEqual(printer.printGenerically(ast).code, code);
|
|
|
|
code = [
|
|
"var obj = {",
|
|
' ["a"]: 1,',
|
|
' [ID("b")]: 2,',
|
|
" [0]: 3,",
|
|
" [ID(1)]: 4,",
|
|
' ["a"]() {},',
|
|
' [ID("b")]() {},',
|
|
" [0]() {},",
|
|
" [ID(1)]() {},",
|
|
' get ["a"]() {},',
|
|
' get [ID("b")]() {},',
|
|
" get [0]() {},",
|
|
" get [ID(1)]() {},",
|
|
' set ["a"](x) {},',
|
|
' set [ID("b")](x) {},',
|
|
" set [0](x) {},",
|
|
" set [ID(1)](x) {}",
|
|
"};",
|
|
].join(eol);
|
|
|
|
ast = parse(code);
|
|
|
|
assert.strictEqual(printer.printGenerically(ast).code, code);
|
|
|
|
ast = parse(
|
|
[
|
|
"var o = {",
|
|
" // This foo will become a computed method name.",
|
|
" foo() { return bar }",
|
|
"};",
|
|
].join(eol),
|
|
);
|
|
|
|
const objExpr = ast.program.body[0].declarations[0].init;
|
|
n.ObjectExpression.assert(objExpr);
|
|
|
|
assert.strictEqual(objExpr.properties[0].computed, false);
|
|
objExpr.properties[0].computed = true;
|
|
objExpr.properties[0].kind = "get";
|
|
|
|
assert.strictEqual(
|
|
recast.print(ast).code,
|
|
[
|
|
"var o = {",
|
|
" // This foo will become a computed method name.",
|
|
" get [foo]() { return bar }",
|
|
"};",
|
|
].join(eol),
|
|
);
|
|
});
|
|
|
|
it("prints trailing commas in object literals", function () {
|
|
const code = ["({", " foo: bar,", " bar: foo,", "});"].join(eol);
|
|
|
|
const ast = parse(code, {
|
|
// Supports trailing commas whereas plain esprima does not.
|
|
parser: require("esprima-fb"),
|
|
});
|
|
|
|
let printer = new Printer({
|
|
tabWidth: 2,
|
|
trailingComma: true,
|
|
});
|
|
|
|
let pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
|
|
// It should also work when using the `trailingComma` option as an object.
|
|
printer = new Printer({
|
|
tabWidth: 2,
|
|
trailingComma: { objects: true },
|
|
});
|
|
|
|
pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints trailing commas in function calls", function () {
|
|
const code = ["call(", " 1,", " 2,", ");"].join(eol);
|
|
|
|
const ast = parse(code, {
|
|
// Supports trailing commas whereas plain esprima does not.
|
|
parser: require("esprima-fb"),
|
|
});
|
|
|
|
let printer = new Printer({
|
|
tabWidth: 2,
|
|
wrapColumn: 1,
|
|
trailingComma: true,
|
|
});
|
|
|
|
let pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
|
|
// It should also work when using the `trailingComma` option as an object.
|
|
printer = new Printer({
|
|
tabWidth: 2,
|
|
wrapColumn: 1,
|
|
trailingComma: { parameters: true },
|
|
});
|
|
|
|
pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints trailing commas in array expressions", function () {
|
|
const code = ["[", " 1,", " 2,", "];"].join(eol);
|
|
|
|
const ast = parse(code, {
|
|
// Supports trailing commas whereas plain esprima does not.
|
|
parser: require("esprima-fb"),
|
|
});
|
|
|
|
let printer = new Printer({
|
|
tabWidth: 2,
|
|
wrapColumn: 1,
|
|
trailingComma: true,
|
|
});
|
|
|
|
let pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
|
|
// It should also work when using the `trailingComma` option as an object.
|
|
printer = new Printer({
|
|
tabWidth: 2,
|
|
wrapColumn: 1,
|
|
trailingComma: { arrays: true },
|
|
});
|
|
|
|
pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints trailing commas in function definitions", function () {
|
|
const code = ["function foo(", " a,", " b,", ") {}"].join(eol);
|
|
|
|
const ast = parse(code, {
|
|
// Supports trailing commas whereas plain esprima does not.
|
|
parser: require("esprima-fb"),
|
|
});
|
|
|
|
let printer = new Printer({
|
|
tabWidth: 2,
|
|
wrapColumn: 1,
|
|
trailingComma: true,
|
|
});
|
|
|
|
let pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
|
|
// It should also work when using the `trailingComma` option as an object.
|
|
printer = new Printer({
|
|
tabWidth: 2,
|
|
wrapColumn: 1,
|
|
trailingComma: { parameters: true },
|
|
});
|
|
|
|
pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
(nodeMajorVersion >= 6 ? it : xit)(
|
|
"shouldn't print a trailing comma for a RestElement",
|
|
function () {
|
|
const code = ["function foo(", " a,", " b,", " ...rest", ") {}"].join(
|
|
eol,
|
|
);
|
|
|
|
const ast = parse(code, {
|
|
// The flow parser and Babylon recognize `...rest` as a `RestElement`
|
|
parser: require("@babel/parser"),
|
|
});
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
wrapColumn: 1,
|
|
trailingComma: true,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
},
|
|
);
|
|
|
|
it("shouldn't print a trailing comma for a RestElement in destructuring", function () {
|
|
const code = [
|
|
"const {",
|
|
" foo,",
|
|
" bar,",
|
|
" ...rest",
|
|
"} = input;",
|
|
].join(eol);
|
|
|
|
const ast = parse(code, {
|
|
parser: require("@babel/parser"),
|
|
});
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
trailingComma: true,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("should support AssignmentPattern and RestElement", function () {
|
|
const code = [
|
|
"function foo(a, [b, c] = d(a), ...[e, f, ...rest]) {",
|
|
" return [a, b, c, e, f, rest];",
|
|
"}",
|
|
].join(eol);
|
|
|
|
const ast = parse(code, {
|
|
// Supports rest parameter destructuring whereas plain esprima
|
|
// does not.
|
|
parser: require("esprima-fb"),
|
|
});
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("adds parenthesis around spread patterns", function () {
|
|
const code = "(...rest) => rest;";
|
|
|
|
let ast = b.program([
|
|
b.expressionStatement(
|
|
b.arrowFunctionExpression(
|
|
[b.spreadElementPattern(b.identifier("rest"))],
|
|
b.identifier("rest"),
|
|
false,
|
|
),
|
|
),
|
|
]);
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
});
|
|
|
|
let pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
|
|
// Print RestElement the same way
|
|
ast = b.program([
|
|
b.expressionStatement(
|
|
b.arrowFunctionExpression(
|
|
[b.restElement(b.identifier("rest"))],
|
|
b.identifier("rest"),
|
|
false,
|
|
),
|
|
),
|
|
]);
|
|
|
|
pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
|
|
// Do the same for the `rest` field.
|
|
const arrowFunction = b.arrowFunctionExpression(
|
|
[],
|
|
b.identifier("rest"),
|
|
false,
|
|
);
|
|
arrowFunction.rest = b.identifier("rest");
|
|
ast = b.program([b.expressionStatement(arrowFunction)]);
|
|
|
|
pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("adds parenthesis around single arrow function arg when options.arrowParensAlways is true", function () {
|
|
const code = "(a) => {};";
|
|
|
|
const fn = b.arrowFunctionExpression(
|
|
[b.identifier("a")],
|
|
b.blockStatement([]),
|
|
false,
|
|
);
|
|
|
|
const ast = b.program([b.expressionStatement(fn)]);
|
|
|
|
const printer = new Printer({
|
|
arrowParensAlways: true,
|
|
});
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("adds parenthesis around arrow function when binding", function () {
|
|
const code = "var a = (x => y).bind(z);";
|
|
|
|
const fn = b.arrowFunctionExpression(
|
|
[b.identifier("x")],
|
|
b.identifier("y"),
|
|
);
|
|
|
|
const declaration = b.variableDeclaration("var", [
|
|
b.variableDeclarator(
|
|
b.identifier("a"),
|
|
b.callExpression(b.memberExpression(fn, b.identifier("bind"), false), [
|
|
b.identifier("z"),
|
|
]),
|
|
),
|
|
]);
|
|
|
|
const ast = b.program([declaration]);
|
|
|
|
const printer = new Printer();
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("adds parenthesis around async arrow functions with args", function () {
|
|
let code = "async () => {};";
|
|
|
|
const fn = b.arrowFunctionExpression([], b.blockStatement([]), false);
|
|
fn.async = true;
|
|
|
|
const ast = b.program([b.expressionStatement(fn)]);
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
});
|
|
|
|
let pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
|
|
// No parenthesis for single params if they are identifiers
|
|
code = "async foo => {};";
|
|
fn.params = [b.identifier("foo")];
|
|
|
|
pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
|
|
// Add parenthesis for destructuring
|
|
code = "async ([a, b]) => {};";
|
|
fn.params = [b.arrayPattern([b.identifier("a"), b.identifier("b")])];
|
|
|
|
pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("adds parenthesis around arrow functions with single arg and a type", function () {
|
|
const code = "(a: b) => {};";
|
|
|
|
const arg = b.identifier("a");
|
|
arg.typeAnnotation = b.typeAnnotation(
|
|
b.genericTypeAnnotation(b.identifier("b"), null),
|
|
);
|
|
|
|
const fn = b.arrowFunctionExpression([arg], b.blockStatement([]), false);
|
|
|
|
const ast = b.program([b.expressionStatement(fn)]);
|
|
|
|
const printer = new Printer();
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("adds parenthesis around arrow functions with single arg and a return type", function () {
|
|
const code = "(a): void => {};";
|
|
|
|
const arg = b.identifier("a");
|
|
|
|
const fn = b.arrowFunctionExpression([arg], b.blockStatement([]), false);
|
|
|
|
fn.returnType = b.typeAnnotation(b.voidTypeAnnotation());
|
|
|
|
const ast = b.program([b.expressionStatement(fn)]);
|
|
|
|
const printer = new Printer();
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("adds parenthesis around arrow function body when returning object literal with typescript typecast", function () {
|
|
const code = "() => ({} as object);";
|
|
|
|
const fn = b.arrowFunctionExpression(
|
|
[],
|
|
b.tsAsExpression(b.objectExpression([]), b.tsObjectKeyword()),
|
|
false,
|
|
);
|
|
|
|
const ast = b.program([b.expressionStatement(fn)]);
|
|
|
|
const printer = new Printer();
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("adds parenthesis around nullish expression, when nullish expression is BinaryExpression's child", function () {
|
|
const code = "let a = (c?.b ?? 1) + 1;";
|
|
|
|
const b = recast.types.builders;
|
|
|
|
const ast = b.variableDeclaration("let", [
|
|
b.variableDeclarator(
|
|
b.identifier("a"),
|
|
b.binaryExpression(
|
|
"+",
|
|
b.logicalExpression(
|
|
"??",
|
|
b.optionalMemberExpression(b.identifier("c"), b.identifier("b")),
|
|
b.numericLiteral(1),
|
|
),
|
|
b.numericLiteral(1),
|
|
),
|
|
),
|
|
]);
|
|
|
|
const printer = new Printer();
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints class property initializers with type annotations correctly", function () {
|
|
const code = ["class A {", " foo = (a: b): void => {};", "}"].join(eol);
|
|
|
|
const arg = b.identifier("a");
|
|
arg.typeAnnotation = b.typeAnnotation(
|
|
b.genericTypeAnnotation(b.identifier("b"), null),
|
|
);
|
|
|
|
const fn = b.arrowFunctionExpression([arg], b.blockStatement([]), false);
|
|
fn.returnType = b.typeAnnotation(b.voidTypeAnnotation());
|
|
|
|
const ast = b.program([
|
|
b.classDeclaration(
|
|
b.identifier("A"),
|
|
b.classBody([b.classProperty(b.identifier("foo"), fn, null, false)]),
|
|
),
|
|
]);
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints ClassProperty correctly", function () {
|
|
const code = ["class A {", " foo: Type = Bar;", "}"].join(eol);
|
|
|
|
const ast = b.program([
|
|
b.classDeclaration(
|
|
b.identifier("A"),
|
|
b.classBody([
|
|
b.classProperty(
|
|
b.identifier("foo"),
|
|
b.identifier("Bar"),
|
|
b.typeAnnotation(
|
|
b.genericTypeAnnotation(b.identifier("Type"), null),
|
|
),
|
|
),
|
|
]),
|
|
),
|
|
]);
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints 'definite' ClassProperty correctly", function () {
|
|
const code = ["class A {", " foo!: string;", "}"].join(eol);
|
|
|
|
const ast = b.program([
|
|
b.classDeclaration(
|
|
b.identifier("A"),
|
|
b.classBody([
|
|
Object.assign(
|
|
b.classProperty(
|
|
b.identifier("foo"),
|
|
null,
|
|
b.typeAnnotation(b.stringTypeAnnotation()),
|
|
),
|
|
{ definite: true },
|
|
),
|
|
]),
|
|
),
|
|
]);
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints static ClassProperty correctly", function () {
|
|
const code = ["class A {", " static foo = Bar;", "}"].join(eol);
|
|
|
|
const ast = b.program([
|
|
b.classDeclaration(
|
|
b.identifier("A"),
|
|
b.classBody([
|
|
b.classProperty(b.identifier("foo"), b.identifier("Bar"), null, true),
|
|
]),
|
|
),
|
|
]);
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints ClassAccessorProperty correctly", function () {
|
|
const code = ["class A {", " accessor foo: Type = Bar;", "}"].join(eol);
|
|
|
|
const ast = b.program([
|
|
b.classDeclaration(
|
|
b.identifier("A"),
|
|
b.classBody([
|
|
b.classAccessorProperty.from({
|
|
key: b.identifier("foo"),
|
|
value: b.identifier("Bar"),
|
|
typeAnnotation: b.tsTypeAnnotation(
|
|
b.tsTypeReference(b.identifier("Type")),
|
|
)
|
|
})
|
|
]),
|
|
),
|
|
]);
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints 'definite' ClassAccessorProperty correctly", function () {
|
|
const code = ["class A {", " foo!: string;", "}"].join(eol);
|
|
|
|
const ast = b.program([
|
|
b.classDeclaration(
|
|
b.identifier("A"),
|
|
b.classBody([
|
|
Object.assign(
|
|
b.classAccessorProperty.from({
|
|
key: b.identifier("foo"),
|
|
definite: true,
|
|
}),
|
|
{ definite: true },
|
|
),
|
|
]),
|
|
),
|
|
]);
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints static ClassAccessorProperty correctly", function () {
|
|
const code = ["class A {", " static accessor foo = Bar;", "}"].join(eol);
|
|
|
|
const ast = b.program([
|
|
b.classDeclaration(
|
|
b.identifier("A"),
|
|
b.classBody([
|
|
b.classAccessorProperty.from({
|
|
key: b.identifier("foo"),
|
|
value: b.identifier("Bar"),
|
|
static: true,
|
|
}),
|
|
]),
|
|
),
|
|
]);
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints template expressions correctly", function () {
|
|
let code = ["graphql`query`;"].join(eol);
|
|
|
|
let ast = b.program([
|
|
b.taggedTemplateStatement(
|
|
b.identifier("graphql"),
|
|
b.templateLiteral(
|
|
[b.templateElement({ cooked: "query", raw: "query" }, false)],
|
|
[],
|
|
),
|
|
),
|
|
]);
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
});
|
|
|
|
let pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
|
|
code = ["graphql`query${foo.getQuery()}field${bar}`;"].join(eol);
|
|
|
|
ast = b.program([
|
|
b.taggedTemplateStatement(
|
|
b.identifier("graphql"),
|
|
b.templateLiteral(
|
|
[
|
|
b.templateElement({ cooked: "query", raw: "query" }, false),
|
|
b.templateElement({ cooked: "field", raw: "field" }, false),
|
|
b.templateElement({ cooked: "", raw: "" }, true),
|
|
],
|
|
[
|
|
b.callExpression(
|
|
b.memberExpression(
|
|
b.identifier("foo"),
|
|
b.identifier("getQuery"),
|
|
false,
|
|
),
|
|
[],
|
|
),
|
|
b.identifier("bar"),
|
|
],
|
|
),
|
|
),
|
|
]);
|
|
|
|
pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
|
|
code = [
|
|
"graphql`",
|
|
" query {",
|
|
" ${foo.getQuery()},",
|
|
" field,",
|
|
" ${bar},",
|
|
" }",
|
|
"`;",
|
|
].join(eol);
|
|
|
|
ast = parse(code);
|
|
pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("preserves newlines at the beginning/end of files", function () {
|
|
const code = ["", "f();", ""].join(eol);
|
|
|
|
const lines = fromString(code);
|
|
const ast = parse(code, {
|
|
esprima: {
|
|
parse: function (source: string, options?: any) {
|
|
const program = require("esprima").parse(source, options);
|
|
n.Program.assert(program);
|
|
// Expand ast.program.loc to include any
|
|
// leading/trailing whitespace, to simulate the
|
|
// behavior of some parsers, e.g. babel-core.
|
|
lines.skipSpaces(program.loc.start, true, true);
|
|
lines.skipSpaces(program.loc.end, false, true);
|
|
return program;
|
|
},
|
|
},
|
|
});
|
|
|
|
ast.program.body.unshift(b.debuggerStatement());
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
});
|
|
|
|
assert.strictEqual(
|
|
printer.print(ast).code,
|
|
["", "debugger;", "f();", ""].join(eol),
|
|
);
|
|
});
|
|
|
|
it("respects options.lineTerminator", function () {
|
|
const lines = ["var first = 1;", "var second = 2;"];
|
|
const code = lines.join("\n");
|
|
const ast = parse(code);
|
|
|
|
assert.strictEqual(
|
|
new Printer({
|
|
lineTerminator: "\r\n",
|
|
}).print(ast).code,
|
|
lines.join("\r\n"),
|
|
);
|
|
});
|
|
|
|
it("preserves indentation in unmodified template expressions", function () {
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
});
|
|
|
|
const code = [
|
|
"var x = {",
|
|
" y: () => Relay.QL`",
|
|
" query {",
|
|
" ${foo},",
|
|
" field,",
|
|
" }",
|
|
" `",
|
|
"};",
|
|
].join(eol);
|
|
|
|
const ast = parse(code);
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("preserves indentation in modified template expressions", function () {
|
|
const code = [
|
|
"const fragments = {",
|
|
" viewer: Relay.QL`",
|
|
" fragment on Viewer { // 2 extraneous spaces.",
|
|
" actor { // 2 extraneous spaces.",
|
|
" id, // 2 extraneous spaces.",
|
|
" ${ foo}, // 3 extraneous spaces.",
|
|
" ${bar }, // Correct!",
|
|
" name, // Correct!",
|
|
" ${baz}, // Correct!",
|
|
" address { // 2 extraneous spaces.",
|
|
" id, // 2 extraneous spaces.",
|
|
" }, // 2 extraneous spaces.",
|
|
" } // 2 extraneous spaces.",
|
|
" } // 2 extraneous spaces.",
|
|
"<~ This line should not be indented.",
|
|
" `, // 2 extraneous spaces.",
|
|
"};",
|
|
].join(eol);
|
|
|
|
const ast = parse(code);
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
});
|
|
|
|
recast.visit(ast, {
|
|
visitTaggedTemplateExpression: function (path) {
|
|
function replaceIdWithNodeId(path: any) {
|
|
path.replace(path.value.replace(/\bid\b/g, "nodeID"));
|
|
}
|
|
|
|
path.get("quasi", "quasis").each(function (quasiPath: any) {
|
|
replaceIdWithNodeId(quasiPath.get("value", "cooked"));
|
|
replaceIdWithNodeId(quasiPath.get("value", "raw"));
|
|
});
|
|
|
|
this.traverse(path);
|
|
},
|
|
});
|
|
|
|
const actual = printer.print(ast).code;
|
|
const expected = code.replace(/\bid\b/g, "nodeID");
|
|
|
|
assert.strictEqual(actual, expected);
|
|
});
|
|
|
|
it("prints commas for flow object types by default", function () {
|
|
const code = [
|
|
"type MyType = {",
|
|
" message: string,",
|
|
" isAwesome: boolean,",
|
|
"};",
|
|
].join(eol);
|
|
|
|
const ast = b.typeAlias(
|
|
b.identifier("MyType"),
|
|
null,
|
|
b.objectTypeAnnotation([
|
|
b.objectTypeProperty(
|
|
b.identifier("message"),
|
|
b.stringTypeAnnotation(),
|
|
false,
|
|
),
|
|
b.objectTypeProperty(
|
|
b.identifier("isAwesome"),
|
|
b.booleanTypeAnnotation(),
|
|
false,
|
|
),
|
|
]),
|
|
);
|
|
|
|
const printer = new Printer();
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("shouldn't print a trailing comma for single-line flow object types", function () {
|
|
const code1 = "type MyType = { message: string };";
|
|
const code2 = "type MyType = { [key: string]: string };";
|
|
|
|
const ast1 = b.typeAlias(
|
|
b.identifier("MyType"),
|
|
null,
|
|
b.objectTypeAnnotation([
|
|
b.objectTypeProperty(
|
|
b.identifier("message"),
|
|
b.stringTypeAnnotation(),
|
|
false,
|
|
),
|
|
]),
|
|
);
|
|
|
|
const ast2 = b.typeAlias(
|
|
b.identifier("MyType"),
|
|
null,
|
|
b.objectTypeAnnotation(
|
|
[],
|
|
[
|
|
b.objectTypeIndexer(
|
|
b.identifier("key"),
|
|
b.stringTypeAnnotation(),
|
|
b.stringTypeAnnotation(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
const printer = new Printer({ trailingComma: true });
|
|
const pretty1 = printer.printGenerically(ast1).code;
|
|
const pretty2 = printer.printGenerically(ast2).code;
|
|
assert.strictEqual(pretty1, code1);
|
|
assert.strictEqual(pretty2, code2);
|
|
});
|
|
|
|
it("prints semicolons for flow object types when options.flowObjectCommas is falsy", function () {
|
|
const code = [
|
|
"type MyType = {",
|
|
" message: string;",
|
|
" isAwesome: boolean;",
|
|
"};",
|
|
].join(eol);
|
|
|
|
const ast = b.typeAlias(
|
|
b.identifier("MyType"),
|
|
null,
|
|
b.objectTypeAnnotation([
|
|
b.objectTypeProperty(
|
|
b.identifier("message"),
|
|
b.stringTypeAnnotation(),
|
|
false,
|
|
),
|
|
b.objectTypeProperty(
|
|
b.identifier("isAwesome"),
|
|
b.booleanTypeAnnotation(),
|
|
false,
|
|
),
|
|
]),
|
|
);
|
|
|
|
const printer = new Printer({ flowObjectCommas: false });
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints parens for nullable union/intersection types", function () {
|
|
const code = "type MyType = ?(string | number);";
|
|
|
|
const ast = b.typeAlias(
|
|
b.identifier("MyType"),
|
|
null,
|
|
b.nullableTypeAnnotation(
|
|
b.unionTypeAnnotation([
|
|
b.stringTypeAnnotation(),
|
|
b.numberTypeAnnotation(),
|
|
]),
|
|
),
|
|
);
|
|
|
|
const printer = new Printer({});
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
(nodeMajorVersion >= 6 ? it : xit)(
|
|
"uses the `arrayBracketSpacing` and the `objectCurlySpacing` option",
|
|
function () {
|
|
const babelParser = require("@babel/parser");
|
|
const parseOptions = {
|
|
parser: {
|
|
parse: (source: string) =>
|
|
babelParser.parse(source, {
|
|
sourceType: "module",
|
|
plugins: ["flow"],
|
|
}),
|
|
},
|
|
};
|
|
|
|
const testCaseList = [
|
|
{
|
|
printerConfig: {
|
|
arrayBracketSpacing: false,
|
|
objectCurlySpacing: false,
|
|
},
|
|
code: [
|
|
'import {java, script} from "javascript";',
|
|
"",
|
|
"function foo(a) {",
|
|
" type MyType = {message: string};",
|
|
" return [1, 2, 3];",
|
|
"}",
|
|
"",
|
|
"export {foo};",
|
|
].join(eol),
|
|
},
|
|
{
|
|
printerConfig: {
|
|
arrayBracketSpacing: true,
|
|
objectCurlySpacing: false,
|
|
},
|
|
code: [
|
|
'import {java, script} from "javascript";',
|
|
"",
|
|
"function foo(a) {",
|
|
" type MyType = {message: string};",
|
|
" return [ 1, 2, 3 ];",
|
|
"}",
|
|
"",
|
|
"export {foo};",
|
|
].join(eol),
|
|
},
|
|
{
|
|
printerConfig: {
|
|
arrayBracketSpacing: false,
|
|
objectCurlySpacing: true,
|
|
},
|
|
code: [
|
|
'import { java, script } from "javascript";',
|
|
"",
|
|
"function foo(a) {",
|
|
" type MyType = { message: string };",
|
|
" return [1, 2, 3];",
|
|
"}",
|
|
"",
|
|
"export { foo };",
|
|
].join(eol),
|
|
},
|
|
{
|
|
printerConfig: {
|
|
arrayBracketSpacing: true,
|
|
objectCurlySpacing: true,
|
|
},
|
|
code: [
|
|
'import { java, script } from "javascript";',
|
|
"",
|
|
"function foo(a) {",
|
|
" type MyType = { message: string };",
|
|
" return [ 1, 2, 3 ];",
|
|
"}",
|
|
"",
|
|
"export { foo };",
|
|
].join(eol),
|
|
},
|
|
];
|
|
|
|
testCaseList.forEach(function (testCase) {
|
|
const code = testCase.code;
|
|
const printer = new Printer(testCase.printerConfig);
|
|
|
|
const ast = parse(code, parseOptions);
|
|
const pretty = printer.printGenerically(ast).code;
|
|
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
},
|
|
);
|
|
|
|
it("prints no extra semicolons in for-loop heads (#377)", function () {
|
|
function check(head: any, parser: any) {
|
|
const source = "for (" + head + ") console.log(i);";
|
|
const ast = recast.parse(source, { parser: parser });
|
|
const loop = ast.program.body[0];
|
|
assert.strictEqual(loop.type, "ForStatement");
|
|
loop.body = b.blockStatement([]);
|
|
|
|
const reprinted = recast.print(ast).code;
|
|
|
|
const openParenIndex = reprinted.indexOf("(");
|
|
assert.notStrictEqual(openParenIndex, -1);
|
|
|
|
const closeParenIndex = reprinted.indexOf(")", openParenIndex);
|
|
assert.notStrictEqual(closeParenIndex, -1);
|
|
|
|
const newHead = reprinted.slice(openParenIndex + 1, closeParenIndex);
|
|
|
|
assert.strictEqual(newHead.split(";").length, 3);
|
|
}
|
|
|
|
function checkWith(parser: any) {
|
|
check("let i = 0; i < 1; i++", parser);
|
|
check("let i = 0 ; i < 1; i++", parser);
|
|
check("let i = 0; ; i++", parser);
|
|
check("let i = 0 ; ; i++", parser);
|
|
check("let i = 0; i < 1; ", parser);
|
|
check("let i = 0 ; i < 1; ", parser);
|
|
check("let i = 0; ; ", parser);
|
|
check("let i = 0 ; ; ", parser);
|
|
}
|
|
|
|
checkWith(require("../parsers/esprima"));
|
|
checkWith(require("../parsers/acorn"));
|
|
|
|
if (nodeMajorVersion >= 6) {
|
|
checkWith(require("../parsers/babel"));
|
|
checkWith(require("../parsers/typescript"));
|
|
checkWith(require("../parsers/flow"));
|
|
}
|
|
});
|
|
|
|
it("parenthesizes NumericLiteral MemberExpression objects", function () {
|
|
const nonBabelNode = b.memberExpression(b.literal(1), b.identifier("foo"));
|
|
|
|
const babelNode = b.memberExpression(
|
|
b.numericLiteral(1),
|
|
b.identifier("foo"),
|
|
);
|
|
|
|
assert.strictEqual(recast.print(nonBabelNode).code, "(1).foo");
|
|
|
|
assert.strictEqual(recast.print(babelNode).code, "(1).foo");
|
|
});
|
|
|
|
it("obeys 'optional' property of OptionalMemberExpression", function () {
|
|
const node = b.optionalMemberExpression(
|
|
b.identifier("foo"),
|
|
b.identifier("bar"),
|
|
);
|
|
|
|
assert.strictEqual(recast.print(node).code, "foo?.bar");
|
|
|
|
const nonOptionalNode = b.optionalMemberExpression(
|
|
b.identifier("foo"),
|
|
b.identifier("bar"),
|
|
false,
|
|
false,
|
|
);
|
|
|
|
assert.strictEqual(recast.print(nonOptionalNode).code, "foo.bar");
|
|
});
|
|
|
|
it("prints chained expression elements", function () {
|
|
const node = b.chainExpression(
|
|
b.memberExpression(b.identifier("foo"), b.identifier("bar"), false),
|
|
);
|
|
|
|
assert.strictEqual(recast.print(node).code, "foo.bar");
|
|
});
|
|
|
|
it("prints optional ChainExpressions", function () {
|
|
const node = b.chainExpression(
|
|
b.optionalMemberExpression(
|
|
b.identifier("foo"),
|
|
b.identifier("bar"),
|
|
false,
|
|
true,
|
|
),
|
|
);
|
|
|
|
assert.strictEqual(recast.print(node).code, "foo?.bar");
|
|
});
|
|
|
|
it("reprints various optional member/call expressions", function () {
|
|
const parser = require("../parsers/babel");
|
|
|
|
function check(code: string) {
|
|
const ast = recast.parse(code, { parser });
|
|
const exprStmt = ast.program.body[0];
|
|
n.ExpressionStatement.assert(exprStmt);
|
|
const expr = exprStmt.expression;
|
|
const output = recast.prettyPrint(expr, { tabWidth: 2 }).code;
|
|
assert.strictEqual(code, output);
|
|
}
|
|
|
|
check("a.b");
|
|
check("a?.b");
|
|
check("a?.b.c");
|
|
check("a.b?.c");
|
|
check("a?.b?.c");
|
|
check("a?.(b)");
|
|
check("a?.b(c)");
|
|
check("a?.b?.(c)");
|
|
check("a.b?.(c)");
|
|
check("a.b?.(c)?.d");
|
|
check("a.b?.(c)?.d(e)");
|
|
check("a.b?.(c)?.d?.(e)");
|
|
check("a?.b?.(c).d?.(e)");
|
|
check("a?.b?.(c)?.d?.(e)");
|
|
check("(a?.b)?.(c)?.d?.(e)");
|
|
check("(a?.b?.(c)?.d)?.(e)");
|
|
});
|
|
|
|
it("prints numbers in bases other than 10 without converting them", function () {
|
|
const code = [
|
|
"let base10 = 6;",
|
|
"let hex = 0xf00d;",
|
|
"let binary = 0b1010;",
|
|
"let octal = 0o744;",
|
|
].join(eol);
|
|
const ast = parse(code);
|
|
const printer = new Printer({});
|
|
const pretty = printer.printGenerically(ast).code;
|
|
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("reprints modified numeric literals", function () {
|
|
const code = "3 + 4;";
|
|
const ast = parse(code);
|
|
const expr = ast.program.body[0].expression;
|
|
const left = expr.left;
|
|
const right = expr.right;
|
|
|
|
left.value++;
|
|
right.value++;
|
|
|
|
assert.strictEqual(recast.print(ast).code, "4 + 5;");
|
|
});
|
|
|
|
it("prints flow tuple type annotations correctly, respecting array options", function () {
|
|
const code = [
|
|
"type MyTupleType = [",
|
|
' "tuple element 1",',
|
|
' "tuple element 2",',
|
|
' "tuple element 3",',
|
|
"];",
|
|
].join(eol);
|
|
|
|
const ast = b.program([
|
|
b.typeAlias(
|
|
b.identifier("MyTupleType"),
|
|
null,
|
|
b.tupleTypeAnnotation([
|
|
b.stringLiteralTypeAnnotation("tuple element 1", "tuple element 1"),
|
|
b.stringLiteralTypeAnnotation("tuple element 2", "tuple element 2"),
|
|
b.stringLiteralTypeAnnotation("tuple element 3", "tuple element 3"),
|
|
]),
|
|
),
|
|
]);
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
wrapColumn: 40,
|
|
trailingComma: true,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("adds parenthesis around object expression", function () {
|
|
const code = "({}).x = 1;";
|
|
|
|
const assignment = b.assignmentExpression(
|
|
"=",
|
|
b.memberExpression(b.objectExpression([]), b.identifier("x"), false),
|
|
b.literal(1),
|
|
);
|
|
|
|
const ast = b.program([b.expressionStatement(assignment)]);
|
|
|
|
const printer = new Printer({
|
|
arrowParensAlways: true,
|
|
});
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("adds parenthesis around conditional", function () {
|
|
const code = "new (typeof a ? b : c)();";
|
|
const callee = recast.parse("typeof a ? b : c").program.body[0].expression;
|
|
|
|
const newExpression = b.newExpression(callee, []);
|
|
|
|
const ast = b.program([b.expressionStatement(newExpression)]);
|
|
|
|
const printer = new Printer({
|
|
arrowParensAlways: true,
|
|
});
|
|
const pretty = printer.print(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints flow object type internal slots correctly", function () {
|
|
const code = [
|
|
"type MyObjectType = {",
|
|
" [myIndexer: string]: any,",
|
|
" (myParameter: any): any,",
|
|
" (myOptionalParameter?: any): any,",
|
|
" (myParameterWithRest: any, ...rest: any[]): any,",
|
|
" [[myInternalSlot]]: any,",
|
|
" static [[myStaticOptionalInternalSlot]]?: (arg: any) => any,",
|
|
" static [[myStaticMethodOptionalInternalSlot]]?(arg: any): any,",
|
|
" myProperty: any,",
|
|
"};",
|
|
].join(eol);
|
|
|
|
const ast = b.program([
|
|
b.typeAlias(
|
|
b.identifier("MyObjectType"),
|
|
null,
|
|
b.objectTypeAnnotation.from({
|
|
properties: [
|
|
b.objectTypeProperty(
|
|
b.identifier("myProperty"),
|
|
b.anyTypeAnnotation(),
|
|
false,
|
|
),
|
|
],
|
|
indexers: [
|
|
b.objectTypeIndexer(
|
|
b.identifier("myIndexer"),
|
|
b.stringTypeAnnotation(),
|
|
b.anyTypeAnnotation(),
|
|
),
|
|
],
|
|
callProperties: [
|
|
b.objectTypeCallProperty(
|
|
b.functionTypeAnnotation(
|
|
[
|
|
b.functionTypeParam(
|
|
b.identifier("myParameter"),
|
|
b.anyTypeAnnotation(),
|
|
false,
|
|
),
|
|
],
|
|
b.anyTypeAnnotation(),
|
|
null,
|
|
null,
|
|
),
|
|
),
|
|
b.objectTypeCallProperty(
|
|
b.functionTypeAnnotation(
|
|
[
|
|
b.functionTypeParam(
|
|
b.identifier("myOptionalParameter"),
|
|
b.anyTypeAnnotation(),
|
|
true,
|
|
),
|
|
],
|
|
b.anyTypeAnnotation(),
|
|
null,
|
|
null,
|
|
),
|
|
),
|
|
b.objectTypeCallProperty(
|
|
b.functionTypeAnnotation(
|
|
[
|
|
b.functionTypeParam(
|
|
b.identifier("myParameterWithRest"),
|
|
b.anyTypeAnnotation(),
|
|
false,
|
|
),
|
|
],
|
|
b.anyTypeAnnotation(),
|
|
b.functionTypeParam(
|
|
b.identifier("rest"),
|
|
b.arrayTypeAnnotation(b.anyTypeAnnotation()),
|
|
false,
|
|
),
|
|
null,
|
|
),
|
|
),
|
|
],
|
|
internalSlots: [
|
|
b.objectTypeInternalSlot.from({
|
|
id: b.identifier("myInternalSlot"),
|
|
value: b.anyTypeAnnotation(),
|
|
static: false,
|
|
method: false,
|
|
optional: false,
|
|
}),
|
|
b.objectTypeInternalSlot.from({
|
|
id: b.identifier("myStaticOptionalInternalSlot"),
|
|
value: b.functionTypeAnnotation(
|
|
[
|
|
b.functionTypeParam(
|
|
b.identifier("arg"),
|
|
b.anyTypeAnnotation(),
|
|
false,
|
|
),
|
|
],
|
|
b.anyTypeAnnotation(),
|
|
null,
|
|
null,
|
|
),
|
|
static: true,
|
|
method: false,
|
|
optional: true,
|
|
}),
|
|
b.objectTypeInternalSlot.from({
|
|
id: b.identifier("myStaticMethodOptionalInternalSlot"),
|
|
value: b.functionTypeAnnotation(
|
|
[
|
|
b.functionTypeParam(
|
|
b.identifier("arg"),
|
|
b.anyTypeAnnotation(),
|
|
false,
|
|
),
|
|
],
|
|
b.anyTypeAnnotation(),
|
|
null,
|
|
null,
|
|
),
|
|
static: true,
|
|
method: true,
|
|
optional: true,
|
|
}),
|
|
],
|
|
}),
|
|
),
|
|
]);
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
wrapColumn: 40,
|
|
trailingComma: true,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints flow type alias to function correctly", function () {
|
|
const code = ["type MyTypeAlias = (x?: ?number) => string;"].join(eol);
|
|
|
|
const ast = b.program([
|
|
b.typeAlias(
|
|
b.identifier("MyTypeAlias"),
|
|
null,
|
|
b.functionTypeAnnotation(
|
|
[
|
|
b.functionTypeParam(
|
|
b.identifier("x"),
|
|
b.nullableTypeAnnotation(b.numberTypeAnnotation()),
|
|
true,
|
|
),
|
|
],
|
|
b.stringTypeAnnotation(),
|
|
null,
|
|
null,
|
|
),
|
|
),
|
|
]);
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
wrapColumn: 40,
|
|
trailingComma: true,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints class private methods and properties correctly", function () {
|
|
const code = [
|
|
"class MyClassWithPrivate {",
|
|
" #myPrivateProperty: any;",
|
|
" #myPrivatePropertyWithValue: any = value;",
|
|
" #myPrivateMethod() {}",
|
|
"}",
|
|
].join(eol);
|
|
|
|
const ast = b.program([
|
|
b.classDeclaration(
|
|
b.identifier("MyClassWithPrivate"),
|
|
b.classBody([
|
|
b.classPrivateProperty.from({
|
|
key: b.privateName(b.identifier("myPrivateProperty")),
|
|
typeAnnotation: b.typeAnnotation(b.anyTypeAnnotation()),
|
|
value: null,
|
|
}),
|
|
b.classPrivateProperty.from({
|
|
key: b.privateName(b.identifier("myPrivatePropertyWithValue")),
|
|
typeAnnotation: b.typeAnnotation(b.anyTypeAnnotation()),
|
|
value: b.identifier("value"),
|
|
}),
|
|
b.classPrivateMethod(
|
|
b.privateName(b.identifier("myPrivateMethod")),
|
|
[],
|
|
b.blockStatement([]),
|
|
),
|
|
]),
|
|
),
|
|
]);
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
wrapColumn: 40,
|
|
trailingComma: true,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints an interpreter directive correctly", function () {
|
|
const code = ["#!/usr/bin/env node", 'console.log("Hello, world!");'].join(
|
|
eol,
|
|
);
|
|
|
|
const ast = b.program.from({
|
|
interpreter: b.interpreterDirective("/usr/bin/env node"),
|
|
body: [
|
|
b.expressionStatement(
|
|
b.callExpression(
|
|
b.memberExpression(b.identifier("console"), b.identifier("log")),
|
|
[b.stringLiteral("Hello, world!")],
|
|
),
|
|
),
|
|
],
|
|
});
|
|
|
|
const pretty = new Printer().printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("prints an interface type annotation correctly", function () {
|
|
const code = [
|
|
"let myVar: interface extends MyOtherInterface { myProperty: any };",
|
|
].join(eol);
|
|
|
|
const ast = b.program([
|
|
b.variableDeclaration("let", [
|
|
b.variableDeclarator(
|
|
b.identifier.from({
|
|
name: "myVar",
|
|
typeAnnotation: b.typeAnnotation(
|
|
b.interfaceTypeAnnotation(
|
|
b.objectTypeAnnotation([
|
|
b.objectTypeProperty(
|
|
b.identifier("myProperty"),
|
|
b.anyTypeAnnotation(),
|
|
false,
|
|
),
|
|
]),
|
|
[b.interfaceExtends(b.identifier("MyOtherInterface"))],
|
|
),
|
|
),
|
|
}),
|
|
),
|
|
]),
|
|
]);
|
|
|
|
const pretty = new Printer().printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("object destructuring for function parameters", function () {
|
|
const code = [
|
|
"function myFunction(",
|
|
" {",
|
|
' yes = "y",',
|
|
' no: no = "n",',
|
|
" short,",
|
|
" long: longHand",
|
|
" }",
|
|
") {}",
|
|
].join(eol);
|
|
|
|
const simplifiedDefaultArgProperty = b.property(
|
|
"init",
|
|
b.identifier("yes"),
|
|
b.assignmentPattern(b.identifier("yes"), b.literal("y")),
|
|
);
|
|
simplifiedDefaultArgProperty.shorthand = true;
|
|
|
|
const notSimplifiedDefaultArgProperty = b.property(
|
|
"init",
|
|
b.identifier("no"),
|
|
b.assignmentPattern(b.identifier("no"), b.literal("n")),
|
|
);
|
|
|
|
const simplifiedProperty = b.property(
|
|
"init",
|
|
b.identifier("short"),
|
|
b.identifier("short"),
|
|
);
|
|
simplifiedProperty.shorthand = true;
|
|
|
|
const notSimplifiedProperty = b.property(
|
|
"init",
|
|
b.identifier("long"),
|
|
b.identifier("longHand"),
|
|
);
|
|
|
|
const ast = b.program([
|
|
b.functionDeclaration(
|
|
b.identifier("myFunction"),
|
|
[
|
|
b.objectPattern([
|
|
simplifiedDefaultArgProperty,
|
|
notSimplifiedDefaultArgProperty,
|
|
simplifiedProperty,
|
|
notSimplifiedProperty,
|
|
]),
|
|
],
|
|
b.blockStatement([]),
|
|
),
|
|
]);
|
|
|
|
const pretty = new Printer().printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("does not print shorthand properties if key != value", function () {
|
|
const code = ["var o = {", " value: value0", "};"].join(eol);
|
|
|
|
const ast = b.program([
|
|
b.variableDeclaration("var", [
|
|
b.variableDeclarator(
|
|
b.identifier("o"),
|
|
b.objectExpression([
|
|
b.property.from({
|
|
kind: "init",
|
|
key: b.identifier("value"),
|
|
value: b.identifier("value0"),
|
|
shorthand: true,
|
|
}),
|
|
]),
|
|
),
|
|
]),
|
|
]);
|
|
|
|
const pretty = new Printer().printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("can pretty-print StaticBlock nodes in class bodies", function () {
|
|
const code = [
|
|
"class A {",
|
|
" static a = 1;",
|
|
" static #b = 2;",
|
|
"",
|
|
" static {",
|
|
" ++this.a;",
|
|
" this.#b++;",
|
|
" }",
|
|
"}",
|
|
].join(eol);
|
|
|
|
const printer = new Printer({ tabWidth: 2 });
|
|
const ast = parse(code, {
|
|
parser: tsParser,
|
|
});
|
|
const pretty = printer.printGenerically(ast).code;
|
|
|
|
assert.strictEqual(pretty, code);
|
|
|
|
types.visit(ast, {
|
|
visitStaticBlock(path) {
|
|
assert.strictEqual(path.get("body", "length").value, 2);
|
|
while (path.get("body").shift()) {}
|
|
assert.strictEqual(path.get("body", "length").value, 0);
|
|
return false;
|
|
},
|
|
});
|
|
|
|
const emptyBlockReprinted = printer.print(ast).code;
|
|
assert.strictEqual(
|
|
emptyBlockReprinted,
|
|
[
|
|
"class A {",
|
|
" static a = 1;",
|
|
" static #b = 2;",
|
|
"", // Empty line preserved because of conservative printer.print reprinting.
|
|
" static {}",
|
|
"}",
|
|
].join(eol),
|
|
);
|
|
|
|
const emptyBlockPrettyPrinted = printer.printGenerically(ast).code;
|
|
assert.strictEqual(
|
|
emptyBlockPrettyPrinted,
|
|
[
|
|
"class A {",
|
|
" static a = 1;",
|
|
" static #b = 2;",
|
|
" static {}",
|
|
"}",
|
|
].join(eol),
|
|
);
|
|
});
|
|
|
|
it("can pretty-print ImportAttribute syntax", function () {
|
|
const code = [
|
|
'import * as noAssertions from "./module";',
|
|
'import * as emptyAssert from "./module" assert {};',
|
|
'import json from "./module" assert { type: "json" };',
|
|
'import * as ns from "./module" assert { type: "reallyLongStringLiteralThatShouldTriggerReflowOntoMultipleLines" }',
|
|
].join(eol);
|
|
|
|
const expectedPretty = [
|
|
'import * as noAssertions from "./module";',
|
|
'import * as emptyAssert from "./module";',
|
|
'import json from "./module" assert { type: "json" };',
|
|
"",
|
|
'import * as ns from "./module" assert {',
|
|
' type: "reallyLongStringLiteralThatShouldTriggerReflowOntoMultipleLines"',
|
|
"};",
|
|
].join(eol);
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
wrapColumn: 60,
|
|
});
|
|
|
|
const ast = parse(code, {
|
|
parser: tsParser,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, expectedPretty);
|
|
|
|
types.visit(ast, {
|
|
visitImportAttribute(path) {
|
|
this.traverse(path);
|
|
const valuePath = path.get("value");
|
|
const strLit = valuePath.value;
|
|
types.namedTypes.StringLiteral.assert(strLit);
|
|
if (strLit.value.startsWith("reallyLong")) {
|
|
valuePath.replace(b.stringLiteral("shorter"));
|
|
}
|
|
},
|
|
});
|
|
|
|
const reprinted = printer.print(ast).code;
|
|
assert.strictEqual(
|
|
reprinted,
|
|
[
|
|
'import * as noAssertions from "./module";',
|
|
'import * as emptyAssert from "./module" assert {};',
|
|
'import json from "./module" assert { type: "json" };',
|
|
'import * as ns from "./module" assert { type: "shorter" }',
|
|
].join(eol),
|
|
);
|
|
});
|
|
|
|
it("can pretty-print RecordExpression syntax", function () {
|
|
const code = [
|
|
"const rec = #{",
|
|
" a: #{",
|
|
" b: 1234",
|
|
" },",
|
|
"",
|
|
" c: #{",
|
|
' d: "dee"',
|
|
" }",
|
|
"};",
|
|
].join(eol);
|
|
|
|
const ast = parse(code, {
|
|
parser: tsParser,
|
|
});
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
wrapColumn: 60,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("can pretty-print TupleExpression syntax", function () {
|
|
const code = [
|
|
"const keyArgs = #[",
|
|
' "query",',
|
|
' "type",',
|
|
' "@connection",',
|
|
' #["key", "filter"]',
|
|
"];",
|
|
].join(eol);
|
|
|
|
const ast = parse(code, {
|
|
parser: tsParser,
|
|
});
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
wrapColumn: 20,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
|
|
assert.strictEqual(pretty, code);
|
|
});
|
|
|
|
it("can pretty-print ModuleExpression syntax", function () {
|
|
const code = [
|
|
'import { log } from "logger";',
|
|
"export const url = import.meta.url;",
|
|
"log(url);",
|
|
].join(eol);
|
|
|
|
const ast = parse(code, {
|
|
parser: tsParser,
|
|
});
|
|
|
|
const printer = new Printer({
|
|
tabWidth: 2,
|
|
wrapColumn: 20,
|
|
});
|
|
|
|
const pretty = printer.printGenerically(ast).code;
|
|
assert.strictEqual(pretty, code);
|
|
|
|
const reprinted = printer.print(b.moduleExpression(ast.program)).code;
|
|
assert.strictEqual(
|
|
reprinted,
|
|
["module {", ...code.split(eol).map((line) => " " + line), "}"].join(
|
|
eol,
|
|
),
|
|
);
|
|
});
|
|
});
|