Skip to content

Commit

Permalink
Add CLI flags for all Sucrase options
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 3ba92f0
Show file tree
Hide file tree
Showing 28 changed files with 183 additions and 10 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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"));
});
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class A {
x = 1;
constructor( y) {;this.y = y;}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class A {
x: number = 1;
constructor(readonly y: string) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"cliOptions": "--transforms typescript --disable-es-transforms"
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

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

function foo(): void {
console.log(A);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"cliOptions": "--transforms typescript --inject-create-require-for-import-require"
}
Original file line number Diff line number Diff line change
@@ -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}} ));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

function Foo() {
return <><div /></>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"cliOptions": "--transforms jsx"
}
Original file line number Diff line number Diff line change
@@ -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 );
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

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

function Foo() {
return h(React.Fragment, null, h('div', null ));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

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

console.log(A);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import A from '../A';
import B from '../B';

console.log(A);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"cliOptions": "--transforms typescript --keep-unused-imports"
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import A from "../A";

async function foo() {
const B = (await import("../B")).default;
console.log(A + B);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"cliOptions": "--transforms imports --preserve-dynamic-import"
}
27 changes: 27 additions & 0 deletions integration-test/util/assertDirectoriesEqual.ts
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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", "Opts 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 3ba92f0

Please sign in to comment.