From 71532466824b6e72630bcd0bf8d7967a1e3c4a87 Mon Sep 17 00:00:00 2001 From: Ahn Date: Thu, 19 Mar 2020 06:14:20 +0100 Subject: [PATCH] feat(compiler): expose internal ts `Program` via ConfigSet `TsCompiler` (#1433) --- .../simple/with-dependency/package.json | 3 +- package-lock.json | 40 +++--- package.json | 1 + src/__mocks__/tsconfig.json | 6 - .../language-service.spec.ts.snap | 29 ++++- .../__snapshots__/program.spec.ts.snap | 66 +++++++++- .../transpile-module.spec.ts.snap | 29 ++++- src/compiler/instance.ts | 12 +- src/compiler/language-service.spec.ts | 28 ++++- src/compiler/language-service.ts | 79 ++++++------ src/compiler/program.spec.ts | 63 ++++++++-- src/compiler/program.ts | 116 +++++++++--------- src/compiler/transpile-module.spec.ts | 31 ++++- src/compiler/transpile-module.ts | 28 +++-- src/transformers/hoist-jest.ts | 1 + src/types.ts | 8 +- tsconfig.json | 3 +- 17 files changed, 380 insertions(+), 163 deletions(-) delete mode 100644 src/__mocks__/tsconfig.json diff --git a/e2e/__monorepos__/simple/with-dependency/package.json b/e2e/__monorepos__/simple/with-dependency/package.json index d636e4c7bf..1f4d55b21a 100644 --- a/e2e/__monorepos__/simple/with-dependency/package.json +++ b/e2e/__monorepos__/simple/with-dependency/package.json @@ -32,8 +32,7 @@ "ts-jest": { "diagnostics": true, "tsConfig": "/tsconfig.json", - "compilerHost": true, - "incremental": true + "compilerHost": true } } }, diff --git a/package-lock.json b/package-lock.json index fbe7c62444..d785bea475 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1455,6 +1455,22 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", + "dev": true + }, + "@types/react": { + "version": "16.9.23", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.23.tgz", + "integrity": "sha512-SsGVT4E7L2wLN3tPYLiF20hmZTPGuzaayVunfgXzUn1x4uHVsKH6QDJQ/TdpHqwsTLd4CwrmQ2vOgxN7gE24gw==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, "@types/resolve": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.14.0.tgz", @@ -2737,6 +2753,12 @@ } } }, + "csstype": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz", + "integrity": "sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==", + "dev": true + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -3102,12 +3124,6 @@ } } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, "mkdirp": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", @@ -9053,12 +9069,6 @@ "tsutils": "^2.29.0" }, "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, "mkdirp": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", @@ -9470,12 +9480,6 @@ "mkdirp": "^0.5.1" }, "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, "mkdirp": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", diff --git a/package.json b/package.json index aeeed2bfa6..ca300d1d55 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "@types/lodash.set": "4.x", "@types/mkdirp": "latest", "@types/node": "10.x", + "@types/react": "^16.x", "@types/resolve": "latest", "@types/semver": "latest", "@types/yargs": "latest", diff --git a/src/__mocks__/tsconfig.json b/src/__mocks__/tsconfig.json deleted file mode 100644 index 191e175490..0000000000 --- a/src/__mocks__/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "compilerOptions": { - "jsx": "preserve", - "outDir": "$$ts-jest$$" - } -} diff --git a/src/compiler/__snapshots__/language-service.spec.ts.snap b/src/compiler/__snapshots__/language-service.spec.ts.snap index dfe4494771..cf43a288db 100644 --- a/src/compiler/__snapshots__/language-service.spec.ts.snap +++ b/src/compiler/__snapshots__/language-service.spec.ts.snap @@ -20,13 +20,14 @@ exports[`language service should compile js file for allowJs true 1`] = ` exports[`language service should compile tsx file for jsx preserve 1`] = ` ===[ FILE: test-jsx-preserve.tsx ]============================================== + "use strict"; var App = function () { return <>Test; }; - //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtcHJlc2VydmUudHN4IiwibWFwcGluZ3MiOiJBQUNRLElBQU0sR0FBRyxHQUFHO0lBQ1YsT0FBTyxFQUFFLElBQUksR0FBRyxDQUFBO0FBQ2xCLENBQUMsQ0FBQSIsIm5hbWVzIjpbXSwic291cmNlcyI6WyJ0ZXN0LWpzeC1wcmVzZXJ2ZS50c3giXSwic291cmNlc0NvbnRlbnQiOlsiXG4gICAgICAgIGNvbnN0IEFwcCA9ICgpID0+IHtcbiAgICAgICAgICByZXR1cm4gPD5UZXN0PC8+XG4gICAgICAgIH1cbiAgICAgICJdLCJ2ZXJzaW9uIjozfQ== + //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtcHJlc2VydmUudHN4IiwibWFwcGluZ3MiOiI7QUFDUSxJQUFNLEdBQUcsR0FBRztJQUNWLE9BQU8sRUFBRSxJQUFJLEdBQUcsQ0FBQTtBQUNsQixDQUFDLENBQUEiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsidGVzdC1qc3gtcHJlc2VydmUudHN4Il0sInNvdXJjZXNDb250ZW50IjpbIlxuICAgICAgICBjb25zdCBBcHAgPSAoKSA9PiB7XG4gICAgICAgICAgcmV0dXJuIDw+VGVzdDwvPlxuICAgICAgICB9XG4gICAgICAiXSwidmVyc2lvbiI6M30= ===[ INLINE SOURCE MAPS ]======================================================= file: test-jsx-preserve.tsx - mappings: 'AACQ,IAAM,GAAG,GAAG;IACV,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA' + mappings: ';AACQ,IAAM,GAAG,GAAG;IACV,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA' names: [] sources: - test-jsx-preserve.tsx @@ -41,6 +42,30 @@ exports[`language service should compile tsx file for jsx preserve 1`] = ` ================================================================================ `; +exports[`language service should compile tsx file for other jsx options 1`] = ` + ===[ FILE: test-jsx-options.tsx ]=============================================== + "use strict"; + var App = function () { + return React.createElement(React.Fragment, null, "Test"); + }; + //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtb3B0aW9ucy50c3giLCJtYXBwaW5ncyI6IjtBQUNRLElBQU0sR0FBRyxHQUFHO0lBQ1YsT0FBTyxpREFBUyxDQUFBO0FBQ2xCLENBQUMsQ0FBQSIsIm5hbWVzIjpbXSwic291cmNlcyI6WyJ0ZXN0LWpzeC1vcHRpb25zLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJcbiAgICAgICAgY29uc3QgQXBwID0gKCkgPT4ge1xuICAgICAgICAgIHJldHVybiA8PlRlc3Q8Lz5cbiAgICAgICAgfVxuICAgICAgIl0sInZlcnNpb24iOjN9 + ===[ INLINE SOURCE MAPS ]======================================================= + file: test-jsx-options.tsx + mappings: ';AACQ,IAAM,GAAG,GAAG;IACV,OAAO,iDAAS,CAAA;AAClB,CAAC,CAAA' + names: [] + sources: + - test-jsx-options.tsx + sourcesContent: + - |2- + + const App = () => { + return <>Test + } + + version: 3 + ================================================================================ +`; + exports[`language service should report diagnostics related to typings with pathRegex config matches file name 1`] = ` "TypeScript diagnostics (customize using \`[jest-config].globals.ts-jest.diagnostics\` option): test-match-regex-diagnostics.ts(3,7): error TS2322: Type 'number' is not assignable to type 'string'." diff --git a/src/compiler/__snapshots__/program.spec.ts.snap b/src/compiler/__snapshots__/program.spec.ts.snap index a1d3e3bf83..c47d8cf779 100644 --- a/src/compiler/__snapshots__/program.spec.ts.snap +++ b/src/compiler/__snapshots__/program.spec.ts.snap @@ -41,20 +41,25 @@ exports[`cannot compile should throw error with incremental program 1`] = ` This is usually the result of a faulty configuration or import. Make sure there is a \`.js\`, \`.json\` or another executable extension available alongside \`test-cannot-compile.d.ts\`." `; +exports[`cannot compile should throw error with incremental program 2`] = `"Unable to read file: test-cannot-compile.jsx"`; + exports[`cannot compile should throw error with normal program 1`] = ` "Unable to require \`.d.ts\` file for file: test-cannot-compile.d.ts. This is usually the result of a faulty configuration or import. Make sure there is a \`.js\`, \`.json\` or another executable extension available alongside \`test-cannot-compile.d.ts\`." `; -exports[`jsx preserve should compile tsx file for jsx preserve with incremental program 1`] = ` +exports[`cannot compile should throw error with normal program 2`] = `"Unable to read file: test-cannot-compile.jsx"`; + +exports[`jsx preserve should compile tsx file for with incremental program 1`] = ` ===[ FILE: test-jsx-preserve.tsx ]============================================== + "use strict"; var App = function () { return <>Test; }; - //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtcHJlc2VydmUudHN4IiwibWFwcGluZ3MiOiJBQUNNLElBQU0sR0FBRyxHQUFHO0lBQ1YsT0FBTyxFQUFFLElBQUksR0FBRyxDQUFBO0FBQ2xCLENBQUMsQ0FBQSIsIm5hbWVzIjpbXSwic291cmNlcyI6WyJ0ZXN0LWpzeC1wcmVzZXJ2ZS50c3giXSwic291cmNlc0NvbnRlbnQiOlsiXG4gICAgICBjb25zdCBBcHAgPSAoKSA9PiB7XG4gICAgICAgIHJldHVybiA8PlRlc3Q8Lz5cbiAgICAgIH1cbiAgICAiXSwidmVyc2lvbiI6M30= + //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtcHJlc2VydmUudHN4IiwibWFwcGluZ3MiOiI7QUFDTSxJQUFNLEdBQUcsR0FBRztJQUNWLE9BQU8sRUFBRSxJQUFJLEdBQUcsQ0FBQTtBQUNsQixDQUFDLENBQUEiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsidGVzdC1qc3gtcHJlc2VydmUudHN4Il0sInNvdXJjZXNDb250ZW50IjpbIlxuICAgICAgY29uc3QgQXBwID0gKCkgPT4ge1xuICAgICAgICByZXR1cm4gPD5UZXN0PC8+XG4gICAgICB9XG4gICAgIl0sInZlcnNpb24iOjN9 ===[ INLINE SOURCE MAPS ]======================================================= file: test-jsx-preserve.tsx - mappings: 'AACM,IAAM,GAAG,GAAG;IACV,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA' + mappings: ';AACM,IAAM,GAAG,GAAG;IACV,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA' names: [] sources: - test-jsx-preserve.tsx @@ -69,15 +74,16 @@ exports[`jsx preserve should compile tsx file for jsx preserve with incremental ================================================================================ `; -exports[`jsx preserve should compile tsx file for jsx preserve with program 1`] = ` +exports[`jsx preserve should compile tsx file with program 1`] = ` ===[ FILE: test-jsx-preserve.tsx ]============================================== + "use strict"; var App = function () { return <>Test; }; - //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtcHJlc2VydmUudHN4IiwibWFwcGluZ3MiOiJBQUNNLElBQU0sR0FBRyxHQUFHO0lBQ1YsT0FBTyxFQUFFLElBQUksR0FBRyxDQUFBO0FBQ2xCLENBQUMsQ0FBQSIsIm5hbWVzIjpbXSwic291cmNlcyI6WyJ0ZXN0LWpzeC1wcmVzZXJ2ZS50c3giXSwic291cmNlc0NvbnRlbnQiOlsiXG4gICAgICBjb25zdCBBcHAgPSAoKSA9PiB7XG4gICAgICAgIHJldHVybiA8PlRlc3Q8Lz5cbiAgICAgIH1cbiAgICAiXSwidmVyc2lvbiI6M30= + //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtcHJlc2VydmUudHN4IiwibWFwcGluZ3MiOiI7QUFDTSxJQUFNLEdBQUcsR0FBRztJQUNWLE9BQU8sRUFBRSxJQUFJLEdBQUcsQ0FBQTtBQUNsQixDQUFDLENBQUEiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsidGVzdC1qc3gtcHJlc2VydmUudHN4Il0sInNvdXJjZXNDb250ZW50IjpbIlxuICAgICAgY29uc3QgQXBwID0gKCkgPT4ge1xuICAgICAgICByZXR1cm4gPD5UZXN0PC8+XG4gICAgICB9XG4gICAgIl0sInZlcnNpb24iOjN9 ===[ INLINE SOURCE MAPS ]======================================================= file: test-jsx-preserve.tsx - mappings: 'AACM,IAAM,GAAG,GAAG;IACV,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA' + mappings: ';AACM,IAAM,GAAG,GAAG;IACV,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA' names: [] sources: - test-jsx-preserve.tsx @@ -92,6 +98,54 @@ exports[`jsx preserve should compile tsx file for jsx preserve with program 1`] ================================================================================ `; +exports[`other jsx options should compile tsx file for with incremental program 1`] = ` + ===[ FILE: test-jsx-options.tsx ]=============================================== + "use strict"; + var App = function () { + return React.createElement(React.Fragment, null, "Test"); + }; + //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtb3B0aW9ucy50c3giLCJtYXBwaW5ncyI6IjtBQUNNLElBQU0sR0FBRyxHQUFHO0lBQ1YsT0FBTyxpREFBUyxDQUFBO0FBQ2xCLENBQUMsQ0FBQSIsIm5hbWVzIjpbXSwic291cmNlcyI6WyJ0ZXN0LWpzeC1vcHRpb25zLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJcbiAgICAgIGNvbnN0IEFwcCA9ICgpID0+IHtcbiAgICAgICAgcmV0dXJuIDw+VGVzdDwvPlxuICAgICAgfVxuICAgICJdLCJ2ZXJzaW9uIjozfQ== + ===[ INLINE SOURCE MAPS ]======================================================= + file: test-jsx-options.tsx + mappings: ';AACM,IAAM,GAAG,GAAG;IACV,OAAO,iDAAS,CAAA;AAClB,CAAC,CAAA' + names: [] + sources: + - test-jsx-options.tsx + sourcesContent: + - |2- + + const App = () => { + return <>Test + } + + version: 3 + ================================================================================ +`; + +exports[`other jsx options should compile tsx file for with program 1`] = ` + ===[ FILE: test-jsx-options.tsx ]=============================================== + "use strict"; + var App = function () { + return React.createElement(React.Fragment, null, "Test"); + }; + //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoidGVzdC1qc3gtb3B0aW9ucy50c3giLCJtYXBwaW5ncyI6IjtBQUNNLElBQU0sR0FBRyxHQUFHO0lBQ1YsT0FBTyxpREFBUyxDQUFBO0FBQ2xCLENBQUMsQ0FBQSIsIm5hbWVzIjpbXSwic291cmNlcyI6WyJ0ZXN0LWpzeC1vcHRpb25zLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJcbiAgICAgIGNvbnN0IEFwcCA9ICgpID0+IHtcbiAgICAgICAgcmV0dXJuIDw+VGVzdDwvPlxuICAgICAgfVxuICAgICJdLCJ2ZXJzaW9uIjozfQ== + ===[ INLINE SOURCE MAPS ]======================================================= + file: test-jsx-options.tsx + mappings: ';AACM,IAAM,GAAG,GAAG;IACV,OAAO,iDAAS,CAAA;AAClB,CAAC,CAAA' + names: [] + sources: + - test-jsx-options.tsx + sourcesContent: + - |2- + + const App = () => { + return <>Test + } + + version: 3 + ================================================================================ +`; + exports[`typings incremental program should report diagnostics with pathRegex config matches file name 1`] = `"test-typings.ts: Emit skipped"`; exports[`typings normal program should report diagnostics with pathRegex config matches file name 1`] = ` diff --git a/src/compiler/__snapshots__/transpile-module.spec.ts.snap b/src/compiler/__snapshots__/transpile-module.spec.ts.snap index 21783324d1..5900da9ae3 100644 --- a/src/compiler/__snapshots__/transpile-module.spec.ts.snap +++ b/src/compiler/__snapshots__/transpile-module.spec.ts.snap @@ -20,13 +20,38 @@ exports[`transpile module with isolatedModule: true should compile js file for a exports[`transpile module with isolatedModule: true should compile tsx file for jsx preserve 1`] = ` ===[ FILE: foo.tsx ]============================================================ + "use strict"; var App = function () { return <>Test; }; - //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoiZm9vLnRzeCIsIm1hcHBpbmdzIjoiQUFDUSxJQUFNLEdBQUcsR0FBRztJQUNWLE9BQU8sRUFBRSxJQUFJLEdBQUcsQ0FBQTtBQUNsQixDQUFDLENBQUEiLCJuYW1lcyI6W10sInNvdXJjZXMiOlsiZm9vLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJcbiAgICAgICAgY29uc3QgQXBwID0gKCkgPT4ge1xuICAgICAgICAgIHJldHVybiA8PlRlc3Q8Lz5cbiAgICAgICAgfVxuICAgICAgIl0sInZlcnNpb24iOjN9 + //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoiZm9vLnRzeCIsIm1hcHBpbmdzIjoiO0FBQ1EsSUFBTSxHQUFHLEdBQUc7SUFDVixPQUFPLEVBQUUsSUFBSSxHQUFHLENBQUE7QUFDbEIsQ0FBQyxDQUFBIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbImZvby50c3giXSwic291cmNlc0NvbnRlbnQiOlsiXG4gICAgICAgIGNvbnN0IEFwcCA9ICgpID0+IHtcbiAgICAgICAgICByZXR1cm4gPD5UZXN0PC8+XG4gICAgICAgIH1cbiAgICAgICJdLCJ2ZXJzaW9uIjozfQ== + ===[ INLINE SOURCE MAPS ]======================================================= + file: foo.tsx + mappings: ';AACQ,IAAM,GAAG,GAAG;IACV,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA' + names: [] + sources: + - foo.tsx + sourcesContent: + - |2- + + const App = () => { + return <>Test + } + + version: 3 + ================================================================================ +`; + +exports[`transpile module with isolatedModule: true should compile tsx file for other jsx options 1`] = ` + ===[ FILE: foo.tsx ]============================================================ + "use strict"; + var App = function () { + return React.createElement(React.Fragment, null, "Test"); + }; + //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJmaWxlIjoiZm9vLnRzeCIsIm1hcHBpbmdzIjoiO0FBQ1EsSUFBTSxHQUFHLEdBQUc7SUFDVixPQUFPLGlEQUFTLENBQUE7QUFDbEIsQ0FBQyxDQUFBIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbImZvby50c3giXSwic291cmNlc0NvbnRlbnQiOlsiXG4gICAgICAgIGNvbnN0IEFwcCA9ICgpID0+IHtcbiAgICAgICAgICByZXR1cm4gPD5UZXN0PC8+XG4gICAgICAgIH1cbiAgICAgICJdLCJ2ZXJzaW9uIjozfQ== ===[ INLINE SOURCE MAPS ]======================================================= file: foo.tsx - mappings: 'AACQ,IAAM,GAAG,GAAG;IACV,OAAO,EAAE,IAAI,GAAG,CAAA;AAClB,CAAC,CAAA' + mappings: ';AACQ,IAAM,GAAG,GAAG;IACV,OAAO,iDAAS,CAAA;AAClB,CAAC,CAAA' names: [] sources: - foo.tsx diff --git a/src/compiler/instance.ts b/src/compiler/instance.ts index 807283bb3a..9c1815ddb1 100644 --- a/src/compiler/instance.ts +++ b/src/compiler/instance.ts @@ -35,7 +35,7 @@ import mkdirp = require('mkdirp') import { basename, extname, join, normalize } from 'path' import { ConfigSet } from '../config/config-set' -import { CompileResult, MemoryCache, TsCompiler } from '../types' +import { CompileFn, CompileResult, MemoryCache, TsCompiler } from '../types' import { sha1 } from '../util/sha1' import { compileUsingLanguageService } from './language-service' @@ -93,7 +93,7 @@ const isValidCacheContent = (contents: string): boolean => { const readThrough = ( cachedir: string | undefined, memoryCache: MemoryCache, - compile: CompileResult, + compileFn: CompileFn, getExtension: (fileName: string) => string, logger: Logger, ) => { @@ -101,7 +101,7 @@ const readThrough = ( return (code: string, fileName: string, lineOffset?: number) => { const normalizedFileName = normalize(fileName) logger.debug({ normalizedFileName }, 'readThrough(): no cache') - const [value, sourceMap] = compile(code, normalizedFileName, lineOffset) + const [value, sourceMap] = compileFn(code, normalizedFileName, lineOffset) const output = updateOutput(value, fileName, sourceMap, getExtension) memoryCache.outputs.set(normalizedFileName, output) @@ -129,7 +129,7 @@ const readThrough = ( } catch (err) {} logger.debug({ fileName }, 'readThrough(): cache miss') - const [value, sourceMap] = compile(code, normalizedFileName, lineOffset) + const [value, sourceMap] = compileFn(code, normalizedFileName, lineOffset) const output = updateOutput(value, normalizedFileName, sourceMap, getExtension) logger.debug({ normalizedFileName, outputPath }, 'readThrough(): writing caches') @@ -181,7 +181,7 @@ export const createCompiler = (configs: ConfigSet): TsCompiler => { } else { compileResult = compileUsingTranspileModule(configs, logger) } - const compile = readThrough(cachedir, memoryCache, compileResult, getExtension, logger) + const compile = readThrough(cachedir, memoryCache, compileResult.compileFn, getExtension, logger) - return { cwd: configs.cwd, compile, extensions, cachedir, ts } + return { cwd: configs.cwd, compile, extensions, cachedir, ts, program: compileResult.program } } diff --git a/src/compiler/language-service.spec.ts b/src/compiler/language-service.spec.ts index be00c492da..a8de50b2e0 100644 --- a/src/compiler/language-service.spec.ts +++ b/src/compiler/language-service.spec.ts @@ -80,7 +80,33 @@ describe('language service', () => { it('should compile tsx file for jsx preserve', () => { const fileName = 'test-jsx-preserve.tsx', compiler = makeCompiler({ - tsJestConfig: { tsConfig: 'src/__mocks__/tsconfig.json' }, + tsJestConfig: { + tsConfig: { + jsx: 'preserve' as any, + }, + }, + }), + source = ` + const App = () => { + return <>Test + } + ` + writeFileSync(fileName, source, 'utf8') + const compiled = compiler.compile(source, fileName) + + expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot() + + removeSync(fileName) + }) + + it('should compile tsx file for other jsx options', () => { + const fileName = 'test-jsx-options.tsx', + compiler = makeCompiler({ + tsJestConfig: { + tsConfig: { + jsx: 'react' as any, + }, + }, }), source = ` const App = () => { diff --git a/src/compiler/language-service.ts b/src/compiler/language-service.ts index 2ba934855d..8130f0be27 100644 --- a/src/compiler/language-service.ts +++ b/src/compiler/language-service.ts @@ -88,50 +88,53 @@ export const compileUsingLanguageService = ( } let previousProgram: _ts.Program | undefined - return (code: string, fileName: string): SourceOutput => { - const normalizedFileName = normalize(fileName) - // Must set memory cache before attempting to read file. - updateMemoryCache(code, normalizedFileName) - const programBefore = service.getProgram() - - if (programBefore !== previousProgram) { - logger.debug({ normalizedFileName }, `compiler rebuilt Program instance when getting output`) - } + return { + compileFn: (code: string, fileName: string): SourceOutput => { + const normalizedFileName = normalize(fileName) + // Must set memory cache before attempting to read file. + updateMemoryCache(code, normalizedFileName) + const programBefore = service.getProgram() - const output: _ts.EmitOutput = service.getEmitOutput(normalizedFileName) - if (configs.shouldReportDiagnostic(normalizedFileName)) { - logger.debug({ normalizedFileName }, 'getOutput(): computing diagnostics for language service') - // Get the relevant diagnostics - this is 3x faster than `getPreEmitDiagnostics`. - const diagnostics = service - .getCompilerOptionsDiagnostics() - .concat(service.getSyntacticDiagnostics(normalizedFileName)) - .concat(service.getSemanticDiagnostics(normalizedFileName)) - // will raise or just warn diagnostics depending on config - configs.raiseDiagnostics(diagnostics, normalizedFileName, logger) - } + if (programBefore !== previousProgram) { + logger.debug({ normalizedFileName }, `compiler rebuilt Program instance when getting output`) + } - /* istanbul ignore next (this should never happen but is kept for security) */ - if (output.emitSkipped) { - throw new TypeError(`${relative(cwd, normalizedFileName)}: Emit skipped for language service`) - } + const output: _ts.EmitOutput = service.getEmitOutput(normalizedFileName) + if (configs.shouldReportDiagnostic(normalizedFileName)) { + logger.debug({ normalizedFileName }, 'getOutput(): computing diagnostics for language service') + // Get the relevant diagnostics - this is 3x faster than `getPreEmitDiagnostics`. + const diagnostics = service + .getCompilerOptionsDiagnostics() + .concat(service.getSyntacticDiagnostics(normalizedFileName)) + .concat(service.getSemanticDiagnostics(normalizedFileName)) + // will raise or just warn diagnostics depending on config + configs.raiseDiagnostics(diagnostics, normalizedFileName, logger) + } - const programAfter = service.getProgram() + /* istanbul ignore next (this should never happen but is kept for security) */ + if (output.emitSkipped) { + throw new TypeError(`${relative(cwd, normalizedFileName)}: Emit skipped for language service`) + } - logger.debug( - 'invariant: Is service.getProject() identical before and after getting emit output and diagnostics? (should always be true) ', - programBefore === programAfter, - ) + const programAfter = service.getProgram() - previousProgram = programAfter - // Throw an error when requiring `.d.ts` files. - if (!output.outputFiles.length) { - throw new TypeError( - interpolate(Errors.UnableToRequireDefinitionFile, { - file: basename(normalizedFileName), - }), + logger.debug( + 'invariant: Is service.getProject() identical before and after getting emit output and diagnostics? (should always be true) ', + programBefore === programAfter, ) - } - return [output.outputFiles[1].text, output.outputFiles[0].text] + previousProgram = programAfter + // Throw an error when requiring `.d.ts` files. + if (!output.outputFiles.length) { + throw new TypeError( + interpolate(Errors.UnableToRequireDefinitionFile, { + file: basename(normalizedFileName), + }), + ) + } + + return [output.outputFiles[1].text, output.outputFiles[0].text] + }, + program: service.getProgram(), } } diff --git a/src/compiler/program.spec.ts b/src/compiler/program.spec.ts index 02c2eef230..39d821b18e 100644 --- a/src/compiler/program.spec.ts +++ b/src/compiler/program.spec.ts @@ -268,7 +268,9 @@ describe('jsx preserve', () => { return <>Test } `, - tsConfig = 'src/__mocks__/tsconfig.json' + tsConfig = { + jsx: 'preserve' as any, + } beforeAll(() => { writeFileSync(fileName, source, 'utf8') @@ -278,7 +280,7 @@ describe('jsx preserve', () => { removeSync(fileName) }) - it('should compile tsx file for jsx preserve with program', () => { + it('should compile tsx file with program', () => { const compiler = makeCompiler({ tsJestConfig: { ...baseTsJestConfig, incremental: false, tsConfig }, }) @@ -288,7 +290,47 @@ describe('jsx preserve', () => { expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot() }) - it('should compile tsx file for jsx preserve with incremental program', () => { + it('should compile tsx file for with incremental program', () => { + const compiler = makeCompiler({ + tsJestConfig: { ...baseTsJestConfig, incremental: true, tsConfig }, + }) + + const compiled = compiler.compile(source, fileName) + + expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot() + }) +}) + +describe('other jsx options', () => { + const fileName = 'test-jsx-options.tsx', + source = ` + const App = () => { + return <>Test + } + `, + tsConfig = { + jsx: 'react' as any, + } + + beforeAll(() => { + writeFileSync(fileName, source, 'utf8') + }) + + afterAll(() => { + removeSync(fileName) + }) + + it('should compile tsx file for with program', () => { + const compiler = makeCompiler({ + tsJestConfig: { ...baseTsJestConfig, incremental: false, tsConfig }, + }) + + const compiled = compiler.compile(source, fileName) + + expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot() + }) + + it('should compile tsx file for with incremental program', () => { const compiler = makeCompiler({ tsJestConfig: { ...baseTsJestConfig, incremental: true, tsConfig }, }) @@ -300,7 +342,8 @@ describe('jsx preserve', () => { }) describe('cannot compile', () => { - const fileName = 'test-cannot-compile.d.ts', + const fileName1 = 'test-cannot-compile.d.ts', + fileName2 = 'test-cannot-compile.jsx', source = ` interface Foo { a: string @@ -308,11 +351,13 @@ describe('cannot compile', () => { ` beforeAll(() => { - writeFileSync(fileName, source, 'utf8') + writeFileSync(fileName1, source, 'utf8') + writeFileSync(fileName2, source, 'utf8') }) afterAll(() => { - removeSync(fileName) + removeSync(fileName1) + removeSync(fileName2) }) it('should throw error with normal program', () => { @@ -320,7 +365,8 @@ describe('cannot compile', () => { tsJestConfig: { ...baseTsJestConfig, incremental: false, tsConfig: false }, }) - expect(() => compiler.compile(source, fileName)).toThrowErrorMatchingSnapshot() + expect(() => compiler.compile(source, fileName1)).toThrowErrorMatchingSnapshot() + expect(() => compiler.compile(source, fileName2)).toThrowErrorMatchingSnapshot() }) it('should throw error with incremental program', () => { @@ -328,6 +374,7 @@ describe('cannot compile', () => { tsJestConfig: { ...baseTsJestConfig, incremental: true, tsConfig: false }, }) - expect(() => compiler.compile(source, fileName)).toThrowErrorMatchingSnapshot() + expect(() => compiler.compile(source, fileName1)).toThrowErrorMatchingSnapshot() + expect(() => compiler.compile(source, fileName2)).toThrowErrorMatchingSnapshot() }) }) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 4cf8a1d079..e696fd215a 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -112,70 +112,70 @@ export const compileUsingProgram = (configs: ConfigSet, logger: Logger, memoryCa } } - return (code: string, fileName: string): SourceOutput => { - const normalizedFileName = normalize(fileName), - output: [string, string] = ['', ''] - // Must set memory cache before attempting to read file. - updateMemoryCache(code, normalizedFileName) - const sourceFile = incremental - ? builderProgram.getSourceFile(normalizedFileName) - : program.getSourceFile(normalizedFileName) + return { + compileFn: (code: string, fileName: string): SourceOutput => { + const normalizedFileName = normalize(fileName), + output: [string, string] = ['', ''] + // Must set memory cache before attempting to read file. + updateMemoryCache(code, normalizedFileName) + const sourceFile = incremental + ? builderProgram.getSourceFile(normalizedFileName) + : program.getSourceFile(normalizedFileName) - if (!sourceFile) throw new TypeError(`Unable to read file: ${fileName}`) + if (!sourceFile) throw new TypeError(`Unable to read file: ${fileName}`) - const result: _ts.EmitResult = incremental - ? builderProgram.emit( - sourceFile, - (path, file, _writeByteOrderMark) => { - output[path.endsWith('.map') ? 1 : 0] = file - }, - undefined, - undefined, - customTransformers, - ) - : program.emit( - sourceFile, - (path, file, _writeByteOrderMark) => { - output[path.endsWith('.map') ? 1 : 0] = file - }, - undefined, - undefined, - customTransformers, + const result: _ts.EmitResult = incremental + ? builderProgram.emit( + sourceFile, + (path, file, _writeByteOrderMark) => { + output[path.endsWith('.map') ? 1 : 0] = file + }, + undefined, + undefined, + customTransformers, + ) + : program.emit( + sourceFile, + (path, file, _writeByteOrderMark) => { + output[path.endsWith('.map') ? 1 : 0] = file + }, + undefined, + undefined, + customTransformers, + ) + if (configs.shouldReportDiagnostic(normalizedFileName)) { + logger.debug( + { normalizedFileName }, + `getOutput(): computing diagnostics for ${incremental ? 'incremental program' : 'program'}`, ) - if (configs.shouldReportDiagnostic(normalizedFileName)) { - logger.debug( - { normalizedFileName }, - `getOutput(): computing diagnostics for ${incremental ? 'incremental program' : 'program'}`, - ) - const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile).slice() - // will raise or just warn diagnostics depending on config - configs.raiseDiagnostics(diagnostics, normalizedFileName, logger) - } - - if (result.emitSkipped) { - throw new TypeError(`${relative(cwd, fileName)}: Emit skipped`) - } + const diagnostics = ts.getPreEmitDiagnostics(program, sourceFile).slice() + // will raise or just warn diagnostics depending on config + configs.raiseDiagnostics(diagnostics, normalizedFileName, logger) + } - // Throw an error when requiring files that cannot be compiled. - if (output[0] === '') { - if (program.isSourceFileFromExternalLibrary(sourceFile)) { - throw new TypeError(`Unable to compile file from external library: ${relative(cwd, fileName)}`) + if (result.emitSkipped) { + throw new TypeError(`${relative(cwd, fileName)}: Emit skipped`) } - throw new TypeError( - interpolate(Errors.UnableToRequireDefinitionFile, { - file: basename(normalizedFileName), - }), - ) - } - if (configs.tsJest.emit && incremental) { - process.on('exit', () => { - // Emits `.tsbuildinfo` to filesystem. - // @ts-ignore - program.emitBuildInfo() - }) - } + // Throw an error when requiring files that cannot be compiled. + if (output[0] === '') { + throw new TypeError( + interpolate(Errors.UnableToRequireDefinitionFile, { + file: basename(normalizedFileName), + }), + ) + } + /* istanbul ignore next */ + if (configs.tsJest.emit && incremental) { + process.on('exit', () => { + // Emits `.tsbuildinfo` to filesystem. + // @ts-ignore + program.emitBuildInfo() + }) + } - return output + return output + }, + program, } } diff --git a/src/compiler/transpile-module.spec.ts b/src/compiler/transpile-module.spec.ts index dbaaf33e92..855998628b 100644 --- a/src/compiler/transpile-module.spec.ts +++ b/src/compiler/transpile-module.spec.ts @@ -57,7 +57,36 @@ describe('transpile module with isolatedModule: true', () => { it('should compile tsx file for jsx preserve', () => { const fileName = `foo.tsx`, compiler = makeCompiler({ - tsJestConfig: { ...baseTsJestConfig, tsConfig: 'src/__mocks__/tsconfig.json' }, + tsJestConfig: { + ...baseTsJestConfig, + tsConfig: { + jsx: 'preserve' as any, + }, + }, + }), + source = ` + const App = () => { + return <>Test + } + ` + + writeFileSync(fileName, source, 'utf8') + const compiled = compiler.compile(source, fileName) + + expect(new ProcessedSource(compiled, fileName)).toMatchSnapshot() + + removeSync(fileName) + }) + + it('should compile tsx file for other jsx options', () => { + const fileName = `foo.tsx`, + compiler = makeCompiler({ + tsJestConfig: { + ...baseTsJestConfig, + tsConfig: { + jsx: 'react' as any, + }, + }, }), source = ` const App = () => { diff --git a/src/compiler/transpile-module.ts b/src/compiler/transpile-module.ts index 8ca30bc4e5..47a50b310a 100644 --- a/src/compiler/transpile-module.ts +++ b/src/compiler/transpile-module.ts @@ -10,21 +10,23 @@ import { CompileResult, SourceOutput } from '../types' export const compileUsingTranspileModule = (configs: ConfigSet, logger: Logger): CompileResult => { logger.debug('compileUsingTranspileModule(): create typescript compiler') - return (code: string, fileName: string): SourceOutput => { - logger.debug({ fileName }, 'getOutput(): compiling as isolated module') + return { + compileFn: (code: string, fileName: string): SourceOutput => { + logger.debug({ fileName }, 'getOutput(): compiling as isolated module') - const normalizedFileName = normalize(fileName) - const result = configs.compilerModule.transpileModule(code, { - fileName: normalizedFileName, - transformers: configs.tsCustomTransformers, - compilerOptions: configs.typescript.options, - reportDiagnostics: configs.shouldReportDiagnostic(normalizedFileName), - }) + const normalizedFileName = normalize(fileName) + const result = configs.compilerModule.transpileModule(code, { + fileName: normalizedFileName, + transformers: configs.tsCustomTransformers, + compilerOptions: configs.typescript.options, + reportDiagnostics: configs.shouldReportDiagnostic(normalizedFileName), + }) - if (result.diagnostics && configs.shouldReportDiagnostic(normalizedFileName)) { - configs.raiseDiagnostics(result.diagnostics, normalizedFileName, logger) - } + if (result.diagnostics && configs.shouldReportDiagnostic(normalizedFileName)) { + configs.raiseDiagnostics(result.diagnostics, normalizedFileName, logger) + } - return [result.outputText, result.sourceMapText!] + return [result.outputText, result.sourceMapText!] + }, } } diff --git a/src/transformers/hoist-jest.ts b/src/transformers/hoist-jest.ts index 7b74ff383d..8fdd95747a 100644 --- a/src/transformers/hoist-jest.ts +++ b/src/transformers/hoist-jest.ts @@ -38,6 +38,7 @@ export function factory(cs: ConfigSet) { const logger = cs.logger.child({ namespace: 'ts-hoisting' }) /** * Our compiler (typescript, or a module with typescript-like interface) + * To access Program or TypeChecker, do: cs.tsCompiler.program or cs.tsCompiler.program.getTypeChecker() */ const ts = cs.compilerModule diff --git a/src/types.ts b/src/types.ts index 831130d3ef..7d043e4660 100644 --- a/src/types.ts +++ b/src/types.ts @@ -196,6 +196,7 @@ export interface TsCompiler { cachedir: string | undefined ts: TSCommon compile(code: string, fileName: string, lineOffset?: number): string + program: _ts.Program | undefined } /** @@ -213,7 +214,12 @@ export interface MemoryCache { outputs: Map } -export type CompileResult = (code: string, fileName: string, lineOffset?: number) => SourceOutput +export type CompileFn = (code: string, fileName: string, lineOffset?: number) => SourceOutput + +export interface CompileResult { + compileFn: CompileFn + program?: _ts.Program +} export interface AstTransformerDesc { name: string diff --git a/tsconfig.json b/tsconfig.json index ffa58802a5..0053873f94 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,7 +27,8 @@ "target": "es5", "types": [ "jest", - "node" + "node", + "react" ] }, "include": [