Skip to content

Commit

Permalink
Add CLI flags for all Sucrase options (#812)
Browse files Browse the repository at this point in the history
Supersedes #670
Progress toward #792

This PR adds to the CLI a number of options that had been added. The CLI could
still use some work in terms of error reporting and behavior, but this should
hopefully get it closer to parity with other use cases.

The CLI previously had no tests, so this also adds an integration test suite
exercising each new and existing Sucrase option (though it's not yet at full
coverage).
  • Loading branch information
alangpierce committed Jul 18, 2023
1 parent 7c8f270 commit d617368
Show file tree
Hide file tree
Showing 28 changed files with 183 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -4,6 +4,7 @@ dist
dist-self-build
dist-types
example-runner/example-repos
integration-test/test-cases/**/dist-actual
spec-compliance-tests/test262/test262-checkout
spec-compliance-tests/babel-tests/babel-tests-checkout
integrations/gulp-plugin/dist
Expand Down
31 changes: 28 additions & 3 deletions integration-test/integration-tests.ts
@@ -1,11 +1,16 @@
import assert from "assert";
import {exec} from "child_process";
import {readdirSync, statSync} from "fs";
import {writeFile} from "fs/promises";
import {join, dirname} from "path";
import {rm, writeFile} from "fs/promises";
import {join, dirname, resolve} from "path";
import {promisify} from "util";

import {readFileContents, readJSONFileContentsIfExists} from "../script/util/readFileContents";
import {
readFileContents,
readJSONFileContents,
readJSONFileContentsIfExists,
} from "../script/util/readFileContents";
import assertDirectoriesEqual from "./util/assertDirectoriesEqual";

const execPromise = promisify(exec);

Expand Down Expand Up @@ -101,4 +106,24 @@ describe("integration tests", () => {
);
});
}

/**
* Find CLI integration tests.
*
* Each test must have a test.json file and directories "src" and
* "dist-expected". The Sucrase CLI is invoked with cliOptions and the result
* is expected to exactly match dist-expected.
*/
for (const testFile of discoverTests("test-cases/cli-cases", "test.json")) {
const testDir = dirname(testFile);
it(testDir, async () => {
process.chdir(testDir);
const testConfig = await readJSONFileContents("./test.json");
await rm("./dist-actual", {recursive: true, force: true});
await execPromise(
`${__dirname}/../bin/sucrase ./src --out-dir ./dist-actual ${testConfig.cliOptions}`,
);
await assertDirectoriesEqual(resolve("./dist-actual"), resolve("./dist-expected"));
});
}
});
@@ -0,0 +1,4 @@
class A {
x = 1;
constructor( y) {;this.y = y;}
}
@@ -0,0 +1,4 @@
class A {
x: number = 1;
constructor(readonly y: string) {}
}
@@ -0,0 +1,3 @@
{
"cliOptions": "--transforms typescript --disable-es-transforms"
}
@@ -0,0 +1,6 @@
import {createRequire as _createRequire} from "module"; const _require = _createRequire(import.meta.url);
const A = _require('../A');

function foo() {
console.log(A);
}
@@ -0,0 +1,6 @@

import A = require('../A');

function foo(): void {
console.log(A);
}
@@ -0,0 +1,3 @@
{
"cliOptions": "--transforms typescript --inject-create-require-for-import-require"
}
@@ -0,0 +1,5 @@
const _jsxFileName = "src/main.jsx";import React from 'react';

function Foo() {
return React.createElement(React.Fragment, null, React.createElement('div', {__self: this, __source: {fileName: _jsxFileName, lineNumber: 4}} ));
}
@@ -0,0 +1,5 @@
import React from 'react';

function Foo() {
return <><div /></>;
}
@@ -0,0 +1,3 @@
{
"cliOptions": "--transforms jsx"
}
@@ -0,0 +1,4 @@
const _jsxFileName = "src/main.tsx";import {jsxDEV as _jsxDEV} from "preact/jsx-dev-runtime";
function Foo() {
return _jsxDEV('div', {}, void 0, false, {fileName: _jsxFileName, lineNumber: 3}, this );
}
@@ -0,0 +1,4 @@

function Foo(): JSX.Element {
return <div />;
}
@@ -0,0 +1,3 @@
{
"cliOptions": "--transforms jsx,typescript --jsx-runtime automatic --jsx-import-source preact"
}
@@ -0,0 +1,4 @@

function Foo() {
return h(React.Fragment, null, h('div', null ));
}
@@ -0,0 +1,4 @@

function Foo() {
return <><div /></>;
}
@@ -0,0 +1,3 @@
{
"cliOptions": "--transforms jsx --production --jsx-pragma h jsx-fragment-pragma Frag"
}
@@ -0,0 +1,3 @@
function Foo() {
return <div />;
}
@@ -0,0 +1,3 @@
function Foo(): JSX.Element {
return <div />;
}
@@ -0,0 +1,3 @@
{
"cliOptions": "--transforms jsx,typescript --jsx-runtime preserve"
}
@@ -0,0 +1,4 @@
import A from '../A';
import B from '../B';

console.log(A);
@@ -0,0 +1,4 @@
import A from '../A';
import B from '../B';

console.log(A);
@@ -0,0 +1,3 @@
{
"cliOptions": "--transforms typescript --keep-unused-imports"
}
@@ -0,0 +1,6 @@
"use strict"; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }var _A = require('../A'); var _A2 = _interopRequireDefault(_A);

async function foo() {
const B = (await import("../B")).default;
console.log(_A2.default + B);
}
@@ -0,0 +1,6 @@
import A from "../A";

async function foo() {
const B = (await import("../B")).default;
console.log(A + B);
}
@@ -0,0 +1,3 @@
{
"cliOptions": "--transforms imports --preserve-dynamic-import"
}
27 changes: 27 additions & 0 deletions integration-test/util/assertDirectoriesEqual.ts
@@ -0,0 +1,27 @@
import * as assert from "assert";
import {readdir, readFile, stat} from "fs/promises";
import {join} from "path";

export default async function assertDirectoriesEqual(dir1: string, dir2: string): Promise<void> {
const dir1Files = (await readdir(dir1)).sort();
const dir2Files = (await readdir(dir2)).sort();
assert.strictEqual(
dir1Files.join(", "),
dir2Files.join(", "),
`Unexpected different lists of files for directories:\n${dir1}\n${dir2}`,
);
for (const filename of dir1Files) {
const path1 = join(dir1, filename);
const path2 = join(dir2, filename);
const isDir1 = (await stat(path1)).isDirectory();
const isDir2 = (await stat(path2)).isDirectory();
assert.strictEqual(isDir1, isDir2, `Paths are different types:\n${path1}\n${path2}`);
if (isDir1) {
await assertDirectoriesEqual(path1, path2);
} else {
const contents1 = (await readFile(path1)).toString();
const contents2 = (await readFile(path2)).toString();
assert.strictEqual(contents1, contents2, `File contents differed:\n${path1}\n${path2}`);
}
}
}
38 changes: 31 additions & 7 deletions src/cli.ts
Expand Up @@ -33,16 +33,34 @@ export default function run(): void {
)
.option("--out-extension <extension>", "File extension to use for all output files.", "js")
.option("--exclude-dirs <paths>", "Names of directories that should not be traversed.")
.option("-t, --transforms <transforms>", "Comma-separated list of transforms to run.")
.option("-q, --quiet", "Don't print the names of converted files.")
.option("-t, --transforms <transforms>", "Comma-separated list of transforms to run.")
.option("--disable-es-transforms", "Opt out of all ES syntax transforms.")
.option("--jsx-runtime <string>", "Transformation mode for the JSX transform.")
.option("--production", "Disable debugging information from JSX in output.")
.option(
"--jsx-import-source <string>",
"Automatic JSX transform import path prefix, defaults to `React.Fragment`.",
)
.option(
"--jsx-pragma <string>",
"Classic JSX transform element creation function, defaults to `React.createElement`.",
)
.option(
"--jsx-fragment-pragma <string>",
"Classic JSX transform fragment component, defaults to `React.Fragment`.",
)
.option("--keep-unused-imports", "Disable automatic removal of type-only imports/exports.")
.option("--preserve-dynamic-import", "Don't transpile dynamic import() to require.")
.option(
"--inject-create-require-for-import-require",
"Use `createRequire` when transpiling TS `import = require` to ESM.",
)
.option(
"--enable-legacy-typescript-module-interop",
"Use default TypeScript ESM/CJS interop strategy.",
)
.option("--enable-legacy-babel5-module-interop", "Use Babel 5 ESM/CJS interop strategy.")
.option("--jsx-pragma <string>", "Element creation function, defaults to `React.createElement`")
.option("--jsx-fragment-pragma <string>", "Fragment component, defaults to `React.Fragment`")
.option("--production", "Disable debugging information from JSX in output.")
.parse(process.argv);

if (commander.project) {
Expand Down Expand Up @@ -84,11 +102,17 @@ export default function run(): void {
quiet: commander.quiet,
sucraseOptions: {
transforms: commander.transforms ? commander.transforms.split(",") : [],
enableLegacyTypeScriptModuleInterop: commander.enableLegacyTypescriptModuleInterop,
enableLegacyBabel5ModuleInterop: commander.enableLegacyBabel5ModuleInterop,
disableESTransforms: commander.disableEsTransforms,
jsxRuntime: commander.jsxRuntime,
production: commander.production,
jsxImportSource: commander.jsxImportSource,
jsxPragma: commander.jsxPragma || "React.createElement",
jsxFragmentPragma: commander.jsxFragmentPragma || "React.Fragment",
production: commander.production,
keepUnusedImports: commander.keepUnusedImports,
preserveDynamicImport: commander.preserveDynamicImport,
injectCreateRequireForImportRequire: commander.injectCreateRequireForImportRequire,
enableLegacyTypeScriptModuleInterop: commander.enableLegacyTypescriptModuleInterop,
enableLegacyBabel5ModuleInterop: commander.enableLegacyBabel5ModuleInterop,
},
};

Expand Down

0 comments on commit d617368

Please sign in to comment.