diff --git a/source/runner/runners/utils/_tests/_fixtures/tsconfig.base.json b/source/runner/runners/utils/_tests/_fixtures/tsconfig.base.json new file mode 100644 index 000000000..9bc1fe55d --- /dev/null +++ b/source/runner/runners/utils/_tests/_fixtures/tsconfig.base.json @@ -0,0 +1,6 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "ES2015" + } +} diff --git a/source/runner/runners/utils/_tests/_fixtures/tsconfig.json b/source/runner/runners/utils/_tests/_fixtures/tsconfig.json new file mode 100644 index 000000000..ffcbb9477 --- /dev/null +++ b/source/runner/runners/utils/_tests/_fixtures/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.base.json" +} diff --git a/source/runner/runners/utils/_tests/_transpiler.test.ts b/source/runner/runners/utils/_tests/_transpiler.test.ts index f50324c52..4d32ef86b 100644 --- a/source/runner/runners/utils/_tests/_transpiler.test.ts +++ b/source/runner/runners/utils/_tests/_transpiler.test.ts @@ -1,16 +1,12 @@ -jest.mock("fs", () => ({ - readFileSync: jest.fn(), - realpathSync: {}, - existsSync: jest.fn(), -})) jest.mock("path", () => { const path = jest.requireActual("path") return { ...path, resolve: jest.fn(path.resolve) } }) import { typescriptify, lookupTSConfig, dirContains } from "../transpiler" -import * as fs from "fs" +import fs from "fs" import * as path from "path" +import ts from "typescript" describe("typescriptify", () => { it("removes the module option in a tsconfig", () => { @@ -21,11 +17,25 @@ describe("typescriptify", () => { module: "es2015", }, } - const fsMock = fs.readFileSync as jest.Mock - fsMock.mockImplementationOnce(() => JSON.stringify(fakeTSConfig)) - + jest.spyOn(ts.sys, "readFile").mockImplementationOnce(() => JSON.stringify(fakeTSConfig)) expect(typescriptify(dangerfile, "/a/b")).not.toContain("import") }) + + it("resolves extended tsconfigs", () => { + const actualPath = jest.requireActual("path") as typeof path + const resolve = path.resolve as jest.Mock + resolve.mockImplementation((p: string = "") => actualPath.resolve(__dirname, p)) + + const dangerfile = `import { a } from 'lodash'; (() => a())()` + + const transpiledCode = typescriptify(dangerfile, actualPath.resolve(__dirname, "./_fixtures")) + console.log(transpiledCode) + expect(transpiledCode).not.toContain("import") + + // Arrow functions (=>) are not compiled to functions when the target is ES6. + // The ES6 target is defined in the base tsconfig so it must be inheriting from it. + expect(transpiledCode).toContain("=>") + }) }) /** Normalizes path to platform-specific */ @@ -45,9 +55,9 @@ describe("lookupTSConfig", () => { const resolve = path.resolve as jest.Mock resolve.mockImplementation((p: string = "") => actualPath.resolve(cwd, p)) - const existsSync = fs.existsSync as jest.Mock + const existsSync = jest.spyOn(fs, "existsSync") const tsconfigPath = path.resolve(path.join(configDir, "tsconfig.json")) - existsSync.mockImplementation((f: string) => path.resolve(f) === tsconfigPath) + existsSync.mockImplementation((f: fs.PathLike) => path.resolve(f as string) === tsconfigPath) } it("can find in the same folder as dangerfile", () => { diff --git a/source/runner/runners/utils/transpiler.ts b/source/runner/runners/utils/transpiler.ts index ab4c434ce..c633dff2a 100644 --- a/source/runner/runners/utils/transpiler.ts +++ b/source/runner/runners/utils/transpiler.ts @@ -1,6 +1,6 @@ import * as fs from "fs" import * as path from "path" -import JSON5 from "json5" +import ts from "typescript"; import { debug } from "../../../debug" const enum BabelPackagePrefix { @@ -120,40 +120,41 @@ export const lookupTSConfig = (dir: string): string | null => { } export const typescriptify = (content: string, dir: string): string => { - const ts = require("typescript") - // Support custom TSC options, but also fallback to defaults - let compilerOptions: any + let compilerOptions: ts.CompilerOptions; const tsConfigPath = lookupTSConfig(dir) if (tsConfigPath) { - compilerOptions = JSON5.parse(fs.readFileSync(tsConfigPath, "utf8")) + compilerOptions = ts.parseJsonConfigFileContent( + ts.readConfigFile(tsConfigPath, ts.sys.readFile).config, + { + fileExists: ts.sys.fileExists, + readFile: ts.sys.readFile, + readDirectory: ts.sys.readDirectory, + useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames, + }, + dir, + ).options; } else { compilerOptions = ts.getDefaultCompilerOptions() } - let result = ts.transpileModule(content, sanitizeTSConfig(compilerOptions)) + let result = ts.transpileModule(content, { compilerOptions: sanitizeTSConfig(compilerOptions)}) return result.outputText } -const sanitizeTSConfig = (config: any) => { - if (!config.compilerOptions) { - return config - } - - const safeConfig = config - - // It can make sense to ship TS code with modules - // for `import`/`export` syntax, but as we're running - // the transpiled code on vanilla node - it'll need to - // be used with plain old commonjs - // - // @see https://github.com/apollographql/react-apollo/pull/1402#issuecomment-351810274 - // - if (safeConfig.compilerOptions.module) { - safeConfig.compilerOptions.module = "commonjs" +const sanitizeTSConfig = (config: ts.CompilerOptions) => { + if (config.module) { + // It can make sense to ship TS code with modules + // for `import`/`export` syntax, but as we're running + // the transpiled code on vanilla node - it'll need to + // be used with plain old commonjs + // + // @see https://github.com/apollographql/react-apollo/pull/1402#issuecomment-351810274 + // + config.module = ts.ModuleKind.CommonJS; } - return safeConfig + return config } export const babelify = (content: string, filename: string, extraPlugins: string[]): string => {