Files

1697 lines
47 KiB
JavaScript

import { parse, parseExpression } from "@babel/parser";
import * as t from "@babel/types";
import fs from "node:fs";
import path from "node:path";
import fixtures from "@babel/helper-fixtures";
import { TraceMap, originalPositionFor } from "@jridgewell/trace-mapping";
import { commonJS } from "$repo-utils";
import { encode } from "@jridgewell/sourcemap-codec";
import generate from "../lib/index.js";
const { __dirname } = commonJS(import.meta.url);
describe("generation", function () {
it("multiple sources", function () {
const sources = {
"a.js": "function hi (msg) { console.log(msg); }\n",
"b.js": "hi('hello');\n",
};
const parsed = Object.keys(sources).reduce(function (_parsed, filename) {
_parsed[filename] = parse(sources[filename], {
sourceFilename: filename,
});
return _parsed;
}, {});
const combinedAst = {
type: "File",
program: {
type: "Program",
sourceType: "module",
body: [].concat(
parsed["a.js"].program.body,
parsed["b.js"].program.body,
),
},
};
const generated = generate(combinedAst, { sourceMaps: true }, sources);
expect(generated.map).toMatchInlineSnapshot(`
{
"file": undefined,
"ignoreList": [],
"mappings": "AAAA,SAAS,EAAE,CAAE,GAAG,EAAE;EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;AAAE;ACAtC,EAAE,CAAC,OAAO,CAAC",
"names": [],
"rangeMappings": undefined,
"sourceRoot": undefined,
"sources": [
"a.js",
"b.js",
],
"sourcesContent": [
"function hi (msg) { console.log(msg); }
",
"hi('hello');
",
],
"version": 3,
}
`);
expect(generated.rawMappings).toMatchInlineSnapshot(`
[
{
"generated": {
"column": 0,
"line": 1,
},
"name": undefined,
"original": {
"column": 0,
"line": 1,
},
"source": "a.js",
},
{
"generated": {
"column": 9,
"line": 1,
},
"name": undefined,
"original": {
"column": 9,
"line": 1,
},
"source": "a.js",
},
{
"generated": {
"column": 11,
"line": 1,
},
"name": undefined,
"original": {
"column": 11,
"line": 1,
},
"source": "a.js",
},
{
"generated": {
"column": 12,
"line": 1,
},
"name": undefined,
"original": {
"column": 13,
"line": 1,
},
"source": "a.js",
},
{
"generated": {
"column": 15,
"line": 1,
},
"name": undefined,
"original": {
"column": 16,
"line": 1,
},
"source": "a.js",
},
{
"generated": {
"column": 17,
"line": 1,
},
"name": undefined,
"original": {
"column": 18,
"line": 1,
},
"source": "a.js",
},
{
"generated": {
"column": 2,
"line": 2,
},
"name": undefined,
"original": {
"column": 20,
"line": 1,
},
"source": "a.js",
},
{
"generated": {
"column": 9,
"line": 2,
},
"name": undefined,
"original": {
"column": 27,
"line": 1,
},
"source": "a.js",
},
{
"generated": {
"column": 10,
"line": 2,
},
"name": undefined,
"original": {
"column": 28,
"line": 1,
},
"source": "a.js",
},
{
"generated": {
"column": 13,
"line": 2,
},
"name": undefined,
"original": {
"column": 31,
"line": 1,
},
"source": "a.js",
},
{
"generated": {
"column": 14,
"line": 2,
},
"name": undefined,
"original": {
"column": 32,
"line": 1,
},
"source": "a.js",
},
{
"generated": {
"column": 17,
"line": 2,
},
"name": undefined,
"original": {
"column": 35,
"line": 1,
},
"source": "a.js",
},
{
"generated": {
"column": 18,
"line": 2,
},
"name": undefined,
"original": {
"column": 36,
"line": 1,
},
"source": "a.js",
},
{
"generated": {
"column": 0,
"line": 3,
},
"name": undefined,
"original": {
"column": 38,
"line": 1,
},
"source": "a.js",
},
{
"generated": {
"column": 0,
"line": 4,
},
"name": undefined,
"original": {
"column": 0,
"line": 1,
},
"source": "b.js",
},
{
"generated": {
"column": 2,
"line": 4,
},
"name": undefined,
"original": {
"column": 2,
"line": 1,
},
"source": "b.js",
},
{
"generated": {
"column": 3,
"line": 4,
},
"name": undefined,
"original": {
"column": 3,
"line": 1,
},
"source": "b.js",
},
{
"generated": {
"column": 10,
"line": 4,
},
"name": undefined,
"original": {
"column": 10,
"line": 1,
},
"source": "b.js",
},
{
"generated": {
"column": 11,
"line": 4,
},
"name": undefined,
"original": {
"column": 11,
"line": 1,
},
"source": "b.js",
},
]
`);
expect(generated.code).toBe(
"function hi(msg) {\n console.log(msg);\n}\nhi('hello');",
);
});
it("identifierName", function () {
const code = "function foo() { bar; }\n";
const ast = parse(code, { filename: "inline" }).program;
const fn = ast.body[0];
const id = fn.id;
id.name += "2";
id.loc.identifierName = "foo";
const id2 = fn.body.body[0].expression;
id2.name += "2";
id2.loc.identifierName = "bar";
const generated = generate(
ast,
{
filename: "inline",
sourceFileName: "inline",
sourceMaps: true,
},
code,
);
expect(generated).toMatchInlineSnapshot(`
{
"code": "function foo2() {
bar2;
}",
"decodedMap": {
"file": undefined,
"ignoreList": [],
"mappings": [
[
[
0,
0,
0,
0,
],
[
9,
0,
0,
9,
0,
],
[
13,
0,
0,
12,
0,
],
[
14,
0,
0,
12,
],
[
16,
0,
0,
15,
],
],
[
[
2,
0,
0,
17,
1,
],
[
6,
0,
0,
20,
],
],
[
[
0,
0,
0,
22,
],
],
],
"names": [
"foo",
"bar",
],
"rangeMappings": undefined,
"sourceRoot": undefined,
"sources": [
"inline",
],
"sourcesContent": [
"function foo() { bar; }
",
],
"version": 3,
},
"map": {
"file": undefined,
"ignoreList": [],
"mappings": "AAAA,SAASA,IAAGA,CAAA,EAAG;EAAEC,IAAG;AAAE",
"names": [
"foo",
"bar",
],
"rangeMappings": undefined,
"sourceRoot": undefined,
"sources": [
"inline",
],
"sourcesContent": [
"function foo() { bar; }
",
],
"version": 3,
},
"rawMappings": [
{
"generated": {
"column": 0,
"line": 1,
},
"name": undefined,
"original": {
"column": 0,
"line": 1,
},
"source": "inline",
},
{
"generated": {
"column": 9,
"line": 1,
},
"name": "foo",
"original": {
"column": 9,
"line": 1,
},
"source": "inline",
},
{
"generated": {
"column": 13,
"line": 1,
},
"name": "foo",
"original": {
"column": 12,
"line": 1,
},
"source": "inline",
},
{
"generated": {
"column": 14,
"line": 1,
},
"name": undefined,
"original": {
"column": 12,
"line": 1,
},
"source": "inline",
},
{
"generated": {
"column": 16,
"line": 1,
},
"name": undefined,
"original": {
"column": 15,
"line": 1,
},
"source": "inline",
},
{
"generated": {
"column": 2,
"line": 2,
},
"name": "bar",
"original": {
"column": 17,
"line": 1,
},
"source": "inline",
},
{
"generated": {
"column": 6,
"line": 2,
},
"name": undefined,
"original": {
"column": 20,
"line": 1,
},
"source": "inline",
},
{
"generated": {
"column": 0,
"line": 3,
},
"name": undefined,
"original": {
"column": 22,
"line": 1,
},
"source": "inline",
},
],
}
`);
});
it("newline in template literal", () => {
const code = "`before\n\nafter`;";
const ast = parse(code, { filename: "inline" }).program;
const generated = generate(
ast,
{
filename: "inline",
sourceFileName: "inline",
sourceMaps: true,
},
code,
);
const consumer = new TraceMap(generated.map);
const loc = originalPositionFor(consumer, { line: 2, column: 1 });
expect(loc).toMatchObject({
column: 0,
line: 2,
});
});
it("newline in string literal", () => {
const code = "'before\\\n\\\nafter';";
const ast = parse(code, { filename: "inline" }).program;
const generated = generate(
ast,
{
filename: "inline",
sourceFileName: "inline",
sourceMaps: true,
},
code,
);
const consumer = new TraceMap(generated.map);
const loc = originalPositionFor(consumer, { line: 2, column: 1 });
expect(loc).toMatchObject({
column: 0,
line: 2,
});
});
it("lazy source map generation", function () {
const code = "function hi (msg) { console.log(msg); }\n";
const ast = parse(code, { filename: "a.js" }).program;
const generated = generate(ast, {
sourceFileName: "a.js",
sourceMaps: true,
});
expect(Array.isArray(generated.rawMappings)).toBe(true);
expect(
Object.getOwnPropertyDescriptor(generated, "map"),
).not.toHaveProperty("value");
expect(generated).toHaveProperty("map");
expect(typeof generated.map).toBe("object");
});
it("wraps around infer inside an array type", () => {
const type = t.tsArrayType(
t.tsInferType(t.tsTypeParameter(null, null, t.identifier("T"))),
);
const output = generate(type).code;
expect(output).toBe("(infer T)[]");
});
it("should not deduplicate comments with same start index", () => {
const code1 = "/*#__PURE__*/ a();";
const code2 = "/*#__PURE__*/ b();";
const ast1 = parse(code1).program;
const ast2 = parse(code2).program;
const ast = t.program([...ast1.body, ...ast2.body]);
expect(generate(ast).code).toBe("/*#__PURE__*/a();\n/*#__PURE__*/b();");
});
it("comments with null or undefined loc", () => {
const code = "/*#__PURE__*/ /*#__PURE__*/";
const ast = parse(code);
ast.comments[0].loc = null;
ast.comments[1].loc = undefined;
expect(generate(ast).code).toBe("/*#__PURE__*/\n/*#__PURE__*/");
});
it("comments without loc", () => {
const ast = parse(
`
import {
Attribute,
AttributeSDKType
}
from "../../base/v1beta1/attribute";
import {
Rpc
}
from "../../../helpers";
import * as _m0 from "protobufjs/minimal";
import {
MsgSignProviderAttributes,
MsgSignProviderAttributesSDKType,
MsgSignProviderAttributesResponse,
MsgSignProviderAttributesResponseSDKType,
MsgDeleteProviderAttributes,
MsgDeleteProviderAttributesSDKType,
MsgDeleteProviderAttributesResponse,
MsgDeleteProviderAttributesResponseSDKType
}
from "./audit";
/** Msg defines the provider Msg service */
export interface Msg {
/** SignProviderAttributes defines a method that signs provider attributes */
signProviderAttributes(request: MsgSignProviderAttributes): Promise < MsgSignProviderAttributesResponse > ;
/** DeleteProviderAttributes defines a method that deletes provider attributes */
deleteProviderAttributes(request: MsgDeleteProviderAttributes): Promise < MsgDeleteProviderAttributesResponse > ;
}
export class MsgClientImpl implements Msg {
private readonly rpc: Rpc;
constructor(rpc: Rpc) {
this.rpc = rpc;
}
/* SignProviderAttributes defines a method that signs provider attributes */
signProviderAttributes = async(request: MsgSignProviderAttributes): Promise < MsgSignProviderAttributesResponse > => {
const data = MsgSignProviderAttributes.encode(request).finish();
const promise = this.rpc.request("akash.audit.v1beta1.Msg", "SignProviderAttributes", data);
return promise.then(data => MsgSignProviderAttributesResponse.decode(new _m0.Reader(data)));
};
/* DeleteProviderAttributes defines a method that deletes provider attributes */
deleteProviderAttributes = async(request: MsgDeleteProviderAttributes): Promise < MsgDeleteProviderAttributesResponse > => {
const data = MsgDeleteProviderAttributes.encode(request).finish();
const promise = this.rpc.request("akash.audit.v1beta1.Msg", "DeleteProviderAttributes", data);
return promise.then(data => MsgDeleteProviderAttributesResponse.decode(new _m0.Reader(data)));
};
}
`,
{ sourceType: "module", plugins: ["typescript"] },
);
for (const comment of ast.comments) {
comment.loc = undefined;
}
expect(generate(ast).code).toMatchInlineSnapshot(`
"import { Attribute, AttributeSDKType } from "../../base/v1beta1/attribute";
import { Rpc } from "../../../helpers";
import * as _m0 from "protobufjs/minimal";
import { MsgSignProviderAttributes, MsgSignProviderAttributesSDKType, MsgSignProviderAttributesResponse, MsgSignProviderAttributesResponseSDKType, MsgDeleteProviderAttributes, MsgDeleteProviderAttributesSDKType, MsgDeleteProviderAttributesResponse, MsgDeleteProviderAttributesResponseSDKType } from "./audit";
/** Msg defines the provider Msg service */
export interface Msg {
/** SignProviderAttributes defines a method that signs provider attributes */
signProviderAttributes(request: MsgSignProviderAttributes): Promise<MsgSignProviderAttributesResponse>;
/** DeleteProviderAttributes defines a method that deletes provider attributes */
deleteProviderAttributes(request: MsgDeleteProviderAttributes): Promise<MsgDeleteProviderAttributesResponse>;
}
export class MsgClientImpl implements Msg {
private readonly rpc: Rpc;
constructor(rpc: Rpc) {
this.rpc = rpc;
}
/* SignProviderAttributes defines a method that signs provider attributes */
signProviderAttributes = async (request: MsgSignProviderAttributes): Promise<MsgSignProviderAttributesResponse> => {
const data = MsgSignProviderAttributes.encode(request).finish();
const promise = this.rpc.request("akash.audit.v1beta1.Msg", "SignProviderAttributes", data);
return promise.then(data => MsgSignProviderAttributesResponse.decode(new _m0.Reader(data)));
};
/* DeleteProviderAttributes defines a method that deletes provider attributes */
deleteProviderAttributes = async (request: MsgDeleteProviderAttributes): Promise<MsgDeleteProviderAttributesResponse> => {
const data = MsgDeleteProviderAttributes.encode(request).finish();
const promise = this.rpc.request("akash.audit.v1beta1.Msg", "DeleteProviderAttributes", data);
return promise.then(data => MsgDeleteProviderAttributesResponse.decode(new _m0.Reader(data)));
};
}"
`);
});
it("comments without loc2", () => {
const ast = parse(
`
(function (_templateFactory) {
"use strict";
const template = (0, _templateFactory.createTemplateFactory)(
/*{{somevalue}}*/
{
"id": null,
"block": "[[[1,[34,0]]],[],false,[\\"somevalue\\"]]",
"moduleName": "(unknown template module)",
"isStrictMode": false
});
});
const template = (0, _templateFactory.createTemplateFactory)(
/*
{{somevalue}}
*/
{
"id": null,
"block": "[[[1,[34,0]]],[],false,[\\"somevalue\\"]]",
"moduleName": "(unknown template module)",
"isStrictMode": false
});
`,
{ sourceType: "module" },
);
for (const comment of ast.comments) {
comment.loc = undefined;
}
expect(generate(ast).code).toMatchInlineSnapshot(`
"(function (_templateFactory) {
"use strict";
const template = (0, _templateFactory.createTemplateFactory)(
/*{{somevalue}}*/
{
"id": null,
"block": "[[[1,[34,0]]],[],false,[\\"somevalue\\"]]",
"moduleName": "(unknown template module)",
"isStrictMode": false
});
});
const template = (0, _templateFactory.createTemplateFactory)(
/*
{{somevalue}}
*/
{
"id": null,
"block": "[[[1,[34,0]]],[],false,[\\"somevalue\\"]]",
"moduleName": "(unknown template module)",
"isStrictMode": false
});"
`);
});
it("comments without loc3", () => {
const ast = parse(
`
/** This describes how the endpoint is implemented when the lease is deployed */
export enum Endpoint_Kind {
/** SHARED_HTTP - Describes an endpoint that becomes a Kubernetes Ingress */
SHARED_HTTP = 0,
/** RANDOM_PORT - Describes an endpoint that becomes a Kubernetes NodePort */
RANDOM_PORT = 1,
UNRECOGNIZED = -1,
}
`,
{ sourceType: "module", plugins: ["typescript"] },
);
for (const comment of ast.comments) {
comment.loc = undefined;
}
expect(generate(ast).code).toMatchInlineSnapshot(`
"/** This describes how the endpoint is implemented when the lease is deployed */
export enum Endpoint_Kind {
/** SHARED_HTTP - Describes an endpoint that becomes a Kubernetes Ingress */
SHARED_HTTP = 0,
/** RANDOM_PORT - Describes an endpoint that becomes a Kubernetes NodePort */
RANDOM_PORT = 1,
UNRECOGNIZED = -1
}"
`);
});
it("comments without node.loc", () => {
const ast = parse(
`
(function (_templateFactory) {
"use strict";
const template = (0, _templateFactory.createTemplateFactory)(
/*{{somevalue}}*/
{
"id": null,
"block": "[[[1,[34,0]]],[],false,[\\"somevalue\\"]]",
"moduleName": "(unknown template module)",
"isStrictMode": false
});
});
const template = (0, _templateFactory.createTemplateFactory)(
/*
{{somevalue}}
*/
{
"id": null,
"block": "[[[1,[34,0]]],[],false,[\\"somevalue\\"]]",
"moduleName": "(unknown template module)",
"isStrictMode": false
});
`,
{ sourceType: "module" },
);
const ast2 = t.cloneNode(ast, true, true);
for (let i = 0; i < ast.comments.length; i++) {
ast2.comments[i].loc = ast.comments[i].loc;
}
expect(generate(ast2).code).toMatchInlineSnapshot(`
"(function (_templateFactory) {
"use strict";
const template = (0, _templateFactory.createTemplateFactory)(
/*{{somevalue}}*/
{
"id": null,
"block": "[[[1,[34,0]]],[],false,[\\"somevalue\\"]]",
"moduleName": "(unknown template module)",
"isStrictMode": false
});
});
const template = (0, _templateFactory.createTemplateFactory)(
/*
{{somevalue}}
*/
{
"id": null,
"block": "[[[1,[34,0]]],[],false,[\\"somevalue\\"]]",
"moduleName": "(unknown template module)",
"isStrictMode": false
});"
`);
});
it("inputSourceMap without sourcesContent", () => {
const ast = parse("var t = x => x * x;");
expect(
generate(ast, {
sourceMaps: true,
inputSourceMap: {
version: 3,
names: ["t", "x"],
sources: ["source-maps/arrow-function/input.js"],
mappings:
"AAAA,IAAIA,CAAC,GAAG,SAAJA,CAACA,CAAGC,CAAC;EAAA,OAAIA,CAAC,GAAGA,CAAC;AAAA",
},
}).map,
).toMatchInlineSnapshot(`
{
"file": undefined,
"ignoreList": [],
"mappings": "AAAA,IAAI,CAAC,GAAG,SAAJ,CAAC",
"names": [],
"rangeMappings": undefined,
"sourceRoot": undefined,
"sources": [
"source-maps/arrow-function/input.js",
],
"sourcesContent": [
undefined,
],
"version": 3,
}
`);
});
it("should not throw when loc.column === 0 with inputSourceMap", () => {
const ast = parseExpression("a(\n)");
ast.loc.end.column = 0;
expect(
generate(ast, {
sourceMaps: true,
inputSourceMap: {
version: 3,
names: [],
sources: ["input.js"],
// [ generatedCodeColumn, sourceIndex, sourceCodeLine, sourceCodeColumn, nameIndex ]
mappings: encode([[0, 0, 1, 0]]),
},
}).rawMappings,
).toMatchInlineSnapshot(`
[
{
"generated": {
"column": 0,
"line": 1,
},
"name": undefined,
"original": {
"column": 0,
"line": 1,
},
"source": "input.js",
},
{
"generated": {
"column": 2,
"line": 1,
},
"name": undefined,
"original": undefined,
"source": undefined,
},
]
`);
});
it("source maps should emit names for renamed identifiers", () => {
const input = `
let a = 1;
let b = 2;
function c() {}
function d() {}
class C {
#e() {};
#f() {};
}
`;
const ast = parse(input);
ast.program.body[0].declarations[0].id.name = "x";
ast.program.body[2].id.name = "y";
ast.program.body[4].body.body[0].key.id.name = "z";
const out = generate(
ast,
{ sourceMaps: true, sourceFileName: "input.js" },
input,
);
expect(out.code).toMatchInlineSnapshot(`
"let x = 1;
let b = 2;
function y() {}
function d() {}
class C {
#z() {}
#f() {}
}"
`);
expect(out.map).toMatchInlineSnapshot(`
{
"file": undefined,
"ignoreList": [],
"mappings": "AACM,IAAIA,CAAC,GAAG,CAAC;AACT,IAAI,CAAC,GAAG,CAAC;AACT,SAASC,CAACA,CAAA,EAAG,CAAC;AACd,SAAS,CAAC,GAAG,CAAC;AACd,MAAM,CAAC,CAAC;EACN,CAACC,CAACC,CAAA,EAAG,CAAC;EACN,CAAC,CAAC,GAAG,CAAC;AACR",
"names": [
"a",
"c",
"e",
"#e",
],
"rangeMappings": undefined,
"sourceRoot": undefined,
"sources": [
"input.js",
],
"sourcesContent": [
"
let a = 1;
let b = 2;
function c() {}
function d() {}
class C {
#e() {};
#f() {};
}
",
],
"version": 3,
}
`);
});
});
describe("programmatic generation", function () {
it("should add parenthesis when NullishCoalescing is used along with ||", function () {
// https://github.com/babel/babel/issues/10260
const nullishCoalesce = t.logicalExpression(
"??",
t.logicalExpression("||", t.identifier("a"), t.identifier("b")),
t.identifier("c"),
);
const output = generate(nullishCoalesce).code;
expect(output).toBe(`(a || b) ?? c`);
});
it("should add parenthesis when NullishCoalesing is used with &&", function () {
const nullishCoalesce = t.logicalExpression(
"??",
t.identifier("a"),
t.logicalExpression(
"&&",
t.identifier("b"),
t.logicalExpression("&&", t.identifier("c"), t.identifier("d")),
),
);
const output = generate(nullishCoalesce).code;
expect(output).toBe(`a ?? (b && c && d)`);
});
it("numeric member expression", function () {
// Should not generate `0.foo`
const mem = t.memberExpression(
t.numericLiteral(60702),
t.identifier("foo"),
);
new Function(generate(mem).code);
});
it("nested if statements needs block", function () {
const ifStatement = t.ifStatement(
t.stringLiteral("top cond"),
t.whileStatement(
t.stringLiteral("while cond"),
t.ifStatement(
t.stringLiteral("nested"),
t.expressionStatement(t.numericLiteral(1)),
),
),
t.expressionStatement(t.stringLiteral("alt")),
);
const ast = parse(generate(ifStatement).code);
expect(ast.program.body[0].consequent.type).toBe("BlockStatement");
});
it("prints directives in block with empty body", function () {
const blockStatement = t.blockStatement(
[],
[t.directive(t.directiveLiteral("use strict"))],
);
const output = generate(blockStatement).code;
expect(output).toBe(`{
"use strict";
}`);
});
it("flow object indentation", function () {
const objectStatement = t.objectTypeAnnotation([
t.objectTypeProperty(t.identifier("bar"), t.stringTypeAnnotation()),
]);
const output = generate(objectStatement).code;
expect(output).toBe(`{
bar: string
}`);
});
it("flow object exact", function () {
const objectStatement = t.objectTypeAnnotation(
[t.objectTypeProperty(t.identifier("bar"), t.stringTypeAnnotation())],
undefined,
undefined,
undefined,
true,
);
const output = generate(objectStatement).code;
expect(output).toBe(`{|
bar: string
|}`);
});
it("flow object indentation with empty leading ObjectTypeProperty", function () {
const objectStatement = t.objectTypeAnnotation(
[],
[
t.objectTypeIndexer(
t.identifier("key"),
t.anyTypeAnnotation(),
t.numberTypeAnnotation(),
),
],
);
const output = generate(objectStatement).code;
expect(output).toBe(`{
[key: any]: number
}`);
});
it("flow interface with nullish extends", () => {
const interfaceDeclaration = t.interfaceDeclaration(
t.identifier("A"),
undefined,
undefined,
t.objectTypeAnnotation([]),
);
const output = generate(interfaceDeclaration).code;
expect(output).toBe("interface A {}");
});
it("flow function type annotation with no parent", () => {
const functionTypeAnnotation = t.functionTypeAnnotation(
null,
[],
null,
t.voidTypeAnnotation(),
);
const output = generate(functionTypeAnnotation).code;
expect(output).toBe("() => void");
});
it("generate a child node with retainLines", () => {
const node = parse("a;\n\nexpect(a).toMatchInlineSnapshot(`[1, 2]`\n);")
.program.body[1].expression;
expect(node.type).toBe("CallExpression");
expect(generate(node, { retainLines: true }).code).toMatchInlineSnapshot(`
"
expect(a).toMatchInlineSnapshot(\`[1, 2]\`
)"
`);
node.loc.end.line = node.loc.start.line;
expect(generate(node, { retainLines: true }).code).toMatchInlineSnapshot(`
"
expect(a).toMatchInlineSnapshot(\`[1, 2]\`)"
`);
});
describe("directives", function () {
it("preserves escapes", function () {
const directive = t.directive(
t.directiveLiteral(String.raw`us\x65 strict`),
);
const output = generate(directive).code;
expect(output).toBe(String.raw`"us\x65 strict";`);
});
it("preserves escapes in minified output", function () {
// https://github.com/babel/babel/issues/4767
const directive = t.directive(t.directiveLiteral(String.raw`foo\n\t\r`));
const output = generate(directive, { minified: true }).code;
expect(output).toBe(String.raw`"foo\n\t\r";`);
});
it("unescaped single quote", function () {
const directive = t.directive(t.directiveLiteral(String.raw`'\'\"`));
const output = generate(directive).code;
expect(output).toBe(String.raw`"'\'\"";`);
});
it("unescaped double quote", function () {
const directive = t.directive(t.directiveLiteral(String.raw`"\'\"`));
const output = generate(directive).code;
expect(output).toBe(String.raw`'"\'\"';`);
});
it("unescaped single and double quotes together throw", function () {
const directive = t.directive(t.directiveLiteral(String.raw`'"`));
expect(() => {
generate(directive);
}).toThrow();
});
it("preserves single quotes if not minified", function () {
const directive = parse("'use strict';").program.directives[0];
const output = generate(directive).code;
expect(output).toBe("'use strict';");
});
it("converts single quotes to double quotes if minified", function () {
const directive = parse("'use strict';").program.directives[0];
const output = generate(directive, { minified: true }).code;
expect(output).toBe('"use strict";');
});
});
describe("typescript generate parentheses if necessary", function () {
it("wraps around union for array", () => {
const typeStatement = t.tsArrayType(
t.tsUnionType([
t.tsIntersectionType([t.tsNumberKeyword(), t.tsBooleanKeyword()]),
t.tsNullKeyword(),
]),
);
const output = generate(typeStatement).code;
expect(output).toBe("(number & boolean | null)[]");
});
it("wraps around intersection for array", () => {
const typeStatement = t.tsArrayType(
t.tsIntersectionType([t.tsNumberKeyword(), t.tsBooleanKeyword()]),
);
const output = generate(typeStatement).code;
expect(output).toBe("(number & boolean)[]");
});
it("wraps around optional type", () => {
const typeStatement = t.tsOptionalType(
t.tsIntersectionType([t.tsNumberKeyword(), t.tsBooleanKeyword()]),
);
const output = generate(typeStatement).code;
expect(output).toBe("(number & boolean)?");
});
});
describe("object expressions", () => {
it("not wrapped in parentheses when standalone", () => {
const objectExpression = t.objectExpression([]);
const output = generate(objectExpression).code;
expect(output).toBe("{}");
});
it("wrapped in parentheses in expression statement", () => {
const expressionStatement = t.expressionStatement(t.objectExpression([]));
const output = generate(expressionStatement).code;
expect(output).toBe("({});");
});
it("wrapped in parentheses in arrow function", () => {
const arrowFunctionExpression = t.arrowFunctionExpression(
[],
t.objectExpression([]),
);
const output = generate(arrowFunctionExpression).code;
expect(output).toBe("() => ({})");
});
it("not wrapped in parentheses in conditional", () => {
const conditionalExpression = t.conditionalExpression(
t.objectExpression([]),
t.booleanLiteral(true),
t.booleanLiteral(false),
);
const output = generate(conditionalExpression).code;
expect(output).toBe("{} ? true : false");
});
it("wrapped in parentheses in conditional in expression statement", () => {
const expressionStatement = t.expressionStatement(
t.conditionalExpression(
t.objectExpression([]),
t.booleanLiteral(true),
t.booleanLiteral(false),
),
);
const output = generate(expressionStatement).code;
expect(output).toBe("({}) ? true : false;");
});
it("wrapped in parentheses in conditional in arrow function", () => {
const arrowFunctionExpression = t.arrowFunctionExpression(
[],
t.conditionalExpression(
t.objectExpression([]),
t.booleanLiteral(true),
t.booleanLiteral(false),
),
);
const output = generate(arrowFunctionExpression).code;
expect(output).toBe("() => ({}) ? true : false");
});
it("not wrapped in parentheses in binary expression", () => {
const binaryExpression = t.binaryExpression(
"+",
t.objectExpression([]),
t.numericLiteral(1),
);
const output = generate(binaryExpression).code;
expect(output).toBe("{} + 1");
});
it("wrapped in parentheses in binary expression in expression statement", () => {
const expressionStatement = t.expressionStatement(
t.binaryExpression("+", t.objectExpression([]), t.numericLiteral(1)),
);
const output = generate(expressionStatement).code;
expect(output).toBe("({}) + 1;");
});
it("wrapped in parentheses in binary expression in arrow function", () => {
const arrowFunctionExpression = t.arrowFunctionExpression(
[],
t.binaryExpression("+", t.objectExpression([]), t.numericLiteral(1)),
);
const output = generate(arrowFunctionExpression).code;
expect(output).toBe("() => ({}) + 1");
});
it("not wrapped in parentheses in sequence expression", () => {
const sequenceExpression = t.sequenceExpression([
t.objectExpression([]),
t.numericLiteral(1),
]);
const output = generate(sequenceExpression).code;
expect(output).toBe("{}, 1");
});
it("wrapped in parentheses in sequence expression in expression statement", () => {
const expressionStatement = t.expressionStatement(
t.sequenceExpression([t.objectExpression([]), t.numericLiteral(1)]),
);
const output = generate(expressionStatement).code;
expect(output).toBe("({}), 1;");
});
it("wrapped in parentheses in sequence expression in arrow function", () => {
const arrowFunctionExpression = t.arrowFunctionExpression(
[],
t.sequenceExpression([t.objectExpression([]), t.numericLiteral(1)]),
);
const output = generate(arrowFunctionExpression).code;
expect(output).toBe("() => ({}, 1)");
});
});
describe("function expressions", () => {
it("not wrapped in parentheses when standalone", () => {
const functionExpression = t.functionExpression(
null,
[],
t.blockStatement([]),
);
const output = generate(functionExpression).code;
expect(output).toBe("function () {}");
});
it("wrapped in parentheses in expression statement", () => {
const expressionStatement = t.expressionStatement(
t.functionExpression(null, [], t.blockStatement([])),
);
const output = generate(expressionStatement).code;
expect(output).toBe("(function () {});");
});
it("wrapped in parentheses in export default declaration", () => {
const exportDefaultDeclaration = t.exportDefaultDeclaration(
t.functionExpression(null, [], t.blockStatement([])),
);
const output = generate(exportDefaultDeclaration).code;
expect(output).toBe("export default (function () {});");
});
});
describe("class expressions", () => {
it("not wrapped in parentheses when standalone", () => {
const classExpression = t.classExpression(null, null, t.classBody([]));
const output = generate(classExpression).code;
expect(output).toBe("class {}");
});
it("wrapped in parentheses in expression statement", () => {
const expressionStatement = t.expressionStatement(
t.classExpression(null, null, t.classBody([])),
);
const output = generate(expressionStatement).code;
expect(output).toBe("(class {});");
});
it("wrapped in parentheses in export default declaration", () => {
const exportDefaultDeclaration = t.exportDefaultDeclaration(
t.classExpression(null, null, t.classBody([])),
);
const output = generate(exportDefaultDeclaration).code;
expect(output).toBe("export default (class {});");
});
});
describe("jsescOption.minimal", () => {
const string = t.stringLiteral("\u8868\u683C_\u526F\u672C");
it("true", () => {
const output = generate(string, { jsescOption: { minimal: true } }).code;
expect(output).toBe(`"表格_副本"`);
});
it("false", () => {
const output = generate(string, { jsescOption: { minimal: false } }).code;
expect(output).toBe(`"\\u8868\\u683C_\\u526F\\u672C"`);
});
it("default", () => {
const output = generate(string).code;
expect(output).toBe(`"表格_副本"`);
});
});
describe("typescript interface declaration", () => {
it("empty extends array", () => {
const tsInterfaceDeclaration = t.tsInterfaceDeclaration(
t.identifier("A"),
undefined,
[],
t.tsInterfaceBody([]),
);
const output = generate(tsInterfaceDeclaration).code;
expect(output).toBe("interface A {}");
});
});
describe("identifier let", () => {
it("detects open bracket from non-optional OptionalMemberExpression", () => {
const ast = parse(`for (let?.[x];;);`, {
sourceType: "script",
strictMode: "false",
});
ast.program.body[0].init.optional = false;
const output = generate(ast).code;
expect(output).toBe("for ((let)[x];;);");
});
});
describe("should print inner comments even if there are no suitable inner locations", () => {
it("atomic node", () => {
const id = t.identifier("foo");
id.innerComments = [{ type: "CommentBlock", value: "foo" }];
expect(generate(id).code).toMatchInlineSnapshot(`"foo /*foo*/"`);
});
it("node without inner locations", () => {
const expr = t.binaryExpression(
"+",
t.numericLiteral(1),
t.numericLiteral(2),
);
expr.innerComments = [{ type: "CommentBlock", value: "foo" }];
expect(generate(expr).code).toMatchInlineSnapshot(`"1 + 2 /*foo*/"`);
});
it("comment skipped in arrow function because of newlines", () => {
const arrow = t.arrowFunctionExpression(
[t.identifier("x"), t.identifier("y")],
t.identifier("z"),
);
arrow.innerComments = [
{ type: "CommentBlock", value: "foo" },
{ type: "CommentBlock", value: "new\nline" },
];
expect(generate(arrow).code).toMatchInlineSnapshot(`
"(x, y) /*foo*/ => z
/*new
line*/"
`);
});
});
it("comment in arrow function with return type", () => {
const arrow = t.arrowFunctionExpression(
[t.identifier("x"), t.identifier("y")],
t.identifier("z"),
);
arrow.returnType = t.tsTypeAnnotation(t.tsAnyKeyword());
arrow.returnType.trailingComments = [
{ type: "CommentBlock", value: "foo" },
// This comment is dropped. There is no way to safely print it
// as a trailingComment of the return type.
{ type: "CommentBlock", value: "new\nline" },
];
expect(generate(arrow).code).toMatchInlineSnapshot(
`"(x, y): any /*foo*/ => z"`,
);
});
it("multi-line leading comment after return", () => {
const val = t.identifier("val");
val.leadingComments = [{ type: "CommentBlock", value: "new\nline" }];
expect(generate(t.returnStatement(val)).code).toMatch(`return (
/*new
line*/
val
);`);
});
it("multi-line leading comment after return 2", () => {
const ast = parse(
`return (
/*new
line*/ val);`,
{
allowReturnOutsideFunction: true,
},
);
// Remove `parenthesized`
ast.program.body[0].argument.extra = null;
expect(generate(ast).code).toMatchInlineSnapshot(`
"return (
/*new
line*/
val
);"
`);
});
it("multi-line leading comment after return compact", () => {
const val = t.identifier("val");
val.leadingComments = [{ type: "CommentBlock", value: "new\nline" }];
expect(
generate(t.returnStatement(val), {
compact: true,
}).code,
).toMatchInlineSnapshot(`
"return(/*new
line*/val);"
`);
});
it("multi-line leading comment after return concise", () => {
const val = t.identifier("val");
val.leadingComments = [{ type: "CommentBlock", value: "new\nline" }];
expect(
generate(t.returnStatement(val), {
concise: true,
}).code,
).toMatchInlineSnapshot(`
"return ( /*new
line*/ val );"
`);
});
it("correctly indenting when `retainLines`", () => {
const ast = parse(
`
export const App = () => {
return (
/**
* First
*/
2
);
};
/**
* Second
*/`,
{
sourceType: "module",
},
);
expect(generate(ast, { retainLines: true }).code).toMatchInlineSnapshot(`
"
export const App = () => {
return (
/**
* First
*/
2);
};
/**
* Second
*/"
`);
});
});
const suites = fixtures(new URL("./fixtures", import.meta.url));
afterEach(() => {
jest.restoreAllMocks();
});
suites.forEach(function (testSuite) {
describe("generation/" + testSuite.title, function () {
testSuite.tests.forEach(function (task) {
const testFn = task.disabled ? it.skip : it;
testFn(
task.title,
function () {
const expected = task.expect;
const actual = task.actual;
const actualCode = actual.code;
if (actualCode) {
const parserOpts = {
filename: actual.loc,
plugins: task.options.plugins || [],
strictMode: task.options.strictMode === false ? false : true,
sourceType: "module",
sourceMaps: !!task.sourceMap,
...task.options.parserOpts,
};
const actualAst = parse(actualCode, parserOpts);
const options = {
sourceFileName: path.relative(__dirname, actual.loc),
...task.options,
sourceMaps: task.sourceMap ? true : task.options.sourceMaps,
};
const run = () => {
return generate(actualAst, options, actualCode);
};
const throwMsg = options.throws;
if (throwMsg) {
expect(() => run()).toThrow(
throwMsg === true ? undefined : throwMsg,
);
} else {
jest.spyOn(console, "warn").mockImplementation(() => {});
const result = run();
if (
options.warns &&
(!process.env.IS_PUBLISH || !options.noWarnInPublishBuild)
) {
expect(console.warn).toHaveBeenCalledWith(
expect.stringContaining(options.warns),
);
} else {
expect(console.warn).not.toHaveBeenCalled();
}
if (options.sourceMaps) {
try {
expect(result.map).toEqual(task.sourceMap);
} catch (e) {
if (!process.env.OVERWRITE && task.sourceMap) throw e;
console.log(`Updated test file: ${task.sourceMapFile.loc}`);
fs.writeFileSync(
task.sourceMapFile.loc,
JSON.stringify(result.map, null, 2),
);
}
}
if (
!expected.code &&
result.code &&
fs.statSync(path.dirname(expected.loc)).isDirectory() &&
!process.env.CI
) {
console.log(`New test file created: ${expected.loc}`);
fs.writeFileSync(expected.loc, result.code);
} else {
try {
expect(result.code).toBe(expected.code);
if (!options.expectedReParseError) {
expect(() => {
parse(result.code, parserOpts);
}).not.toThrow();
}
} catch (e) {
if (!process.env.OVERWRITE) throw e;
console.log(`Updated test file: ${expected.loc}`);
fs.writeFileSync(expected.loc, result.code);
}
}
}
}
},
);
});
});
});