Files

323 lines
9.3 KiB
JavaScript

import { parse } from "@babel/parser";
import path from "node:path";
import fixtures from "@babel/helper-fixtures";
import * as babel from "@babel/core";
import pluginTransformTypeScript from "@babel/plugin-transform-typescript";
import { commonJS } from "$repo-utils";
import { cloneNode } from "@babel/types";
import generate from "../lib/index.js";
const { __dirname } = commonJS(import.meta.url);
const suites = fixtures(path.join(__dirname, "fixtures"));
const FAILURES = [
// Todo: support trailing comments spanned across trailing comma
"import-phases/source-expression-comments/input.js",
"import-phases/source-expression-options-comments/input.js",
// These tests are either explicitly about re-formatting the decorators position,
// or about an old decorators version
"comments/decorators-after-export-to-before/input.js",
"comments/decorators-before-export-to-after/input.js",
"decoratorsBeforeExport/false-to-true/input.js",
"decoratorsBeforeExport/true-to-false/input.js",
// The 'experimental_preserveFormat' option does not fully support Flow
"flow/array-types/input.js",
"flow/arrow-functions/input.js",
"flow/call-properties/input.js",
"flow/declare-module/input.js",
"flow/declare-exports/input.js",
"flow/declare-statements/input.js",
"flow/indexed-access-types/input.js",
"flow/interfaces-module-and-script/input.js",
"flow/iterator-inside-declare/input.js",
"flow/iterator-inside-interface/input.js",
"flow/iterator-inside-types/input.js",
"flow/object-literal-types/input.js",
"flow/opaque-type-alias/input.js",
"flow/parentheses/input.js",
"flow/predicates/input.js",
"flow/this-param/input.js",
"flow/tuples/input.js",
"flow/type-alias/input.js",
"flow/type-annotations/input.js",
"flow/type-union-intersection/input.js",
"flow/typecasts/input.js",
"flowUsesCommas/ObjectExpression/input.js",
// These tests are explicitly about changing the output
"importAttributesKeyword/assertions-assert-to-with/input.js",
"importAttributesKeyword/assertions-assert-to-with-legacy/input.js",
"importAttributesKeyword/assertions-with-to-assert/input.js",
"importAttributesKeyword/assertions-with-to-with-legacy/input.js",
"importAttributesKeyword/attributes-assert-to-default/input.js",
"importAttributesKeyword/attributes-assert-to-default-babel-7/input.js",
"importAttributesKeyword/attributes-assert-to-with/input.js",
"importAttributesKeyword/attributes-assert-to-with-legacy/input.js",
"importAttributesKeyword/attributes-with-to-assert/input.js",
"importAttributesKeyword/attributes-with-to-with-legacy/input.js",
"importAttributesKeyword/legacy-module-attributes-to-assert/input.js",
"importAttributesKeyword/legacy-module-attributes-to-with/input.js",
// These tests are about old proposals
"types/Decorator/input.js",
];
describe("experimental_preserveFormat", () => {
describe("generation", () => {
suites.forEach(testSuite => {
describe(`${testSuite.title}`, () => {
testSuite.tests.forEach(function (task) {
if (task.options.throws) return;
const testFn = task.disabled ? it.skip : it;
const shouldFail = FAILURES.some(f =>
task.actual.loc.replace(/\\/g, "/").endsWith(f),
);
const parserOpts = {
filename: task.actual.loc,
plugins: task.options.plugins || [],
strictMode: task.options.strictMode === false ? false : true,
sourceType: "module",
...task.options.parserOpts,
createParenthesizedExpressions: true,
tokens: true,
};
const generatorOpts = {
sourceFileName: path.relative(__dirname, task.actual.loc),
...task.options,
retainLines: true,
experimental_preserveFormat: true,
comments: true,
jsescOption: null,
minified: false,
compact: false,
};
const input = task.actual.code.replace(/\r\n?/g, "\n");
testFn(task.title, () => {
const ast = parse(input, parserOpts);
const ok = generate(ast, generatorOpts, input).code === input;
if (!ok && shouldFail) {
expect(1).toBe(1);
return;
}
if (ok) {
expect(shouldFail).toBe(false);
}
expect(generate(ast, generatorOpts, input).code).toBe(input);
});
if (!shouldFail) {
testFn(`${task.title} without loc`, () => {
const ast = parse(input, parserOpts);
ast.program = cloneNode(ast.program, true, true);
const code = generate(ast, generatorOpts, input).code;
try {
parse(code, parserOpts);
} catch (error) {
expect([code, error]).toEqual([input, null]);
}
});
}
});
});
});
});
describe("code transforms", () => {
it("type stripping with arrow functions", () => {
const input = `
const a = (x: string):
number => 2 as const;
`;
const expected = `
const a = (x )=>
2 ;
`;
const out = babel.transformSync(input, {
configFile: false,
plugins: [pluginTransformTypeScript],
sourceMaps: true,
parserOpts: {
createParenthesizedExpressions: true,
tokens: true,
},
generatorOpts: {
retainLines: true,
experimental_preserveFormat: true,
},
});
expect(out.code.trimEnd()).toBe(expected.trimEnd());
expect(out.map.mappings).toMatchInlineSnapshot(
`";QACQ,MAAM,EAAE,EAAE,CAAC,SAAS;kCACM,UAAU"`,
);
});
it("identifier renaming", () => {
const input = `
const foo = 3;
const bar = 3;
foo( x, y, z );
bar( x, y, z );
`;
const expected = `
const x = 3;
const longer=3;
x ( x, y, z );
longer(x,y,z );
`;
const out = babel.transformSync(input, {
configFile: false,
plugins: [
() => ({
visitor: {
Program(path) {
path.scope.rename("foo", "x");
path.scope.rename("bar", "longer");
},
},
}),
],
parserOpts: {
createParenthesizedExpressions: true,
tokens: true,
},
generatorOpts: {
retainLines: true,
experimental_preserveFormat: true,
},
});
expect(out.code.trimEnd()).toBe(expected.trimEnd());
});
it("node injection", () => {
const input = `
const foo
= 3;
const bar =
3;
bax
`;
const expected = `
const foo
= 3;hello;
const bar =
3;
bax
`;
const out = babel.transformSync(input, {
configFile: false,
plugins: [
({ types: t }) => ({
visitor: {
Program(path) {
path
.get("body.0")
.insertAfter(t.expressionStatement(t.identifier("hello")));
},
},
}),
],
parserOpts: {
createParenthesizedExpressions: true,
tokens: true,
},
generatorOpts: {
retainLines: true,
experimental_preserveFormat: true,
},
});
expect(out.code.trimEnd()).toBe(expected.trimEnd());
});
it("injects new semicolon when needed", () => {
const input = `
const foo = 3
`;
const expected = `
const foo = 3;hello;
`;
const out = babel.transformSync(input, {
configFile: false,
plugins: [
({ types: t }) => ({
visitor: {
Program(path) {
path.pushContainer(
"body",
t.expressionStatement(t.identifier("hello")),
);
},
},
}),
],
parserOpts: {
createParenthesizedExpressions: true,
tokens: true,
},
generatorOpts: {
retainLines: true,
experimental_preserveFormat: true,
},
});
expect(out.code.trimEnd()).toBe(expected.trimEnd());
});
it("injects new semicolon when needed, after a comment", () => {
const input = `
const foo = 3 /* comment */
`;
const expected = `
const foo = 3 /* comment */;hello;
`;
const out = babel.transformSync(input, {
configFile: false,
plugins: [
({ types: t }) => ({
visitor: {
Program(path) {
path.pushContainer(
"body",
t.expressionStatement(t.identifier("hello")),
);
},
},
}),
],
parserOpts: {
createParenthesizedExpressions: true,
tokens: true,
},
generatorOpts: {
retainLines: true,
experimental_preserveFormat: true,
},
});
expect(out.code.trimEnd()).toBe(expected.trimEnd());
});
});
});