From 311eaeb7ca45d088671c430fdb9619cbf438b78a Mon Sep 17 00:00:00 2001 From: Ahn Date: Tue, 7 Apr 2020 09:02:05 +0200 Subject: [PATCH] fix(compiler): make sure `LanguageService` updated with test file information before getting diagnostics for test file (#1507) --- e2e/__cases__/diagnostics/main.spec.ts | 10 +- e2e/__cases__/diagnostics/main.ts | 1 + .../__snapshots__/diagnostics.test.ts.snap | 426 ++++++++++++++++-- e2e/__tests__/diagnostics.test.ts | 81 +++- src/compiler/compiler-utils.spec.ts | 18 +- src/compiler/compiler-utils.ts | 5 +- src/compiler/language-service.ts | 24 +- src/compiler/program.ts | 24 +- src/types.ts | 7 +- 9 files changed, 492 insertions(+), 104 deletions(-) diff --git a/e2e/__cases__/diagnostics/main.spec.ts b/e2e/__cases__/diagnostics/main.spec.ts index 4eb3db74e5..9ff2d8a187 100644 --- a/e2e/__cases__/diagnostics/main.spec.ts +++ b/e2e/__cases__/diagnostics/main.spec.ts @@ -1,11 +1,7 @@ -import { Thing } from "./main"; +import { foo, Thing } from './main'; export const thing: Thing = { a: 1 }; -function doTheThing() { - return 1 + 2; -} - -it("should do the thing", () => { - expect(doTheThing()).toEqual(3); +test('foo is 42', () => { + expect(foo).toBe(42); }); diff --git a/e2e/__cases__/diagnostics/main.ts b/e2e/__cases__/diagnostics/main.ts index a4d045ad90..8aaf8e72ca 100644 --- a/e2e/__cases__/diagnostics/main.ts +++ b/e2e/__cases__/diagnostics/main.ts @@ -1 +1,2 @@ +export const foo = 42 export type Thing = { a: number, b: number } diff --git a/e2e/__tests__/__snapshots__/diagnostics.test.ts.snap b/e2e/__tests__/__snapshots__/diagnostics.test.ts.snap index 0941bc1649..d3c0933849 100644 --- a/e2e/__tests__/__snapshots__/diagnostics.test.ts.snap +++ b/e2e/__tests__/__snapshots__/diagnostics.test.ts.snap @@ -14,8 +14,8 @@ exports[`With diagnostics throw using incremental program first throw should fai 3 export const thing: Thing = { a: 1 }; ~~~~~ - main.ts:1:34 - 1 export type Thing = { a: number, b: number } + main.ts:2:34 + 2 export type Thing = { a: number, b: number } ~ 'b' is declared here. @@ -41,8 +41,8 @@ exports[`With diagnostics throw using incremental program first throw should fai 3 export const thing: Thing = { a: 1 }; ~~~~~ - main.ts:1:34 - 1 export type Thing = { a: number, b: number } + main.ts:2:34 + 2 export type Thing = { a: number, b: number } ~ 'b' is declared here. @@ -68,8 +68,8 @@ exports[`With diagnostics throw using incremental program first throw should fai 3 export const thing: Thing = { a: 1 }; ~~~~~ - main.ts:1:34 - 1 export type Thing = { a: number, b: number } + main.ts:2:34 + 2 export type Thing = { a: number, b: number } ~ 'b' is declared here. @@ -81,14 +81,113 @@ exports[`With diagnostics throw using incremental program first throw should fai ================================================================================ `; -exports[`With diagnostics throw using incremental program then passed when content has changed to valid base on cache of the previous run should pass using template "default" 1`] = ` +exports[`With diagnostics throw using incremental program then failed when code has changed to invalid base on cache of the previous run should fail using template "default" 1`] = ` + × jest + ↳ exit code: 1 + ===[ STDOUT ]=================================================================== + + ===[ STDERR ]=================================================================== + FAIL ./main.spec.ts + × foo is 42 + + ● foo is 42 + + expect(received).toBe(expected) // Object.is equality + + Expected: 42 + Received: 43 + + 4 | + 5 | test('foo is 42', () => { + > 6 | expect(foo).toBe(42); + | ^ + 7 | }); + 8 | + + at Object. (main.spec.ts:6:15) + + Test Suites: 1 failed, 1 total + Tests: 1 failed, 1 total + Snapshots: 0 total + Time: XXs + Ran all test suites. + ================================================================================ +`; + +exports[`With diagnostics throw using incremental program then failed when code has changed to invalid base on cache of the previous run should fail using template "with-babel-7" 1`] = ` + × jest + ↳ exit code: 1 + ===[ STDOUT ]=================================================================== + + ===[ STDERR ]=================================================================== + FAIL ./main.spec.ts + × foo is 42 + + ● foo is 42 + + expect(received).toBe(expected) // Object.is equality + + Expected: 42 + Received: 43 + + 4 | + 5 | test('foo is 42', () => { + > 6 | expect(foo).toBe(42); + | ^ + 7 | }); + 8 | + + at Object. (main.spec.ts:6:15) + + Test Suites: 1 failed, 1 total + Tests: 1 failed, 1 total + Snapshots: 0 total + Time: XXs + Ran all test suites. + ================================================================================ +`; + +exports[`With diagnostics throw using incremental program then failed when code has changed to invalid base on cache of the previous run should fail using template "with-babel-7-string-config" 1`] = ` + × jest + ↳ exit code: 1 + ===[ STDOUT ]=================================================================== + + ===[ STDERR ]=================================================================== + FAIL ./main.spec.ts + × foo is 42 + + ● foo is 42 + + expect(received).toBe(expected) // Object.is equality + + Expected: 42 + Received: 43 + + 4 | + 5 | test('foo is 42', () => { + > 6 | expect(foo).toBe(42); + | ^ + 7 | }); + 8 | + + at Object. (main.spec.ts:6:15) + + Test Suites: 1 failed, 1 total + Tests: 1 failed, 1 total + Snapshots: 0 total + Time: XXs + Ran all test suites. + ================================================================================ +`; + +exports[`With diagnostics throw using incremental program then pass when type has changed to valid base on cache of the previous run should pass using template "default" 1`] = ` √ jest ↳ exit code: 0 ===[ STDOUT ]=================================================================== ===[ STDERR ]=================================================================== PASS ./main.spec.ts - √ should do the thing + √ foo is 42 Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total @@ -98,14 +197,14 @@ exports[`With diagnostics throw using incremental program then passed when conte ================================================================================ `; -exports[`With diagnostics throw using incremental program then passed when content has changed to valid base on cache of the previous run should pass using template "with-babel-7" 1`] = ` +exports[`With diagnostics throw using incremental program then pass when type has changed to valid base on cache of the previous run should pass using template "with-babel-7" 1`] = ` √ jest ↳ exit code: 0 ===[ STDOUT ]=================================================================== ===[ STDERR ]=================================================================== PASS ./main.spec.ts - √ should do the thing + √ foo is 42 Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total @@ -115,14 +214,14 @@ exports[`With diagnostics throw using incremental program then passed when conte ================================================================================ `; -exports[`With diagnostics throw using incremental program then passed when content has changed to valid base on cache of the previous run should pass using template "with-babel-7-string-config" 1`] = ` +exports[`With diagnostics throw using incremental program then pass when type has changed to valid base on cache of the previous run should pass using template "with-babel-7-string-config" 1`] = ` √ jest ↳ exit code: 0 ===[ STDOUT ]=================================================================== ===[ STDERR ]=================================================================== PASS ./main.spec.ts - √ should do the thing + √ foo is 42 Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total @@ -189,8 +288,8 @@ exports[`With diagnostics throw using language service first throw should fail u 3 export const thing: Thing = { a: 1 }; ~~~~~ - main.ts:1:34 - 1 export type Thing = { a: number, b: number } + main.ts:2:34 + 2 export type Thing = { a: number, b: number } ~ 'b' is declared here. @@ -216,8 +315,8 @@ exports[`With diagnostics throw using language service first throw should fail u 3 export const thing: Thing = { a: 1 }; ~~~~~ - main.ts:1:34 - 1 export type Thing = { a: number, b: number } + main.ts:2:34 + 2 export type Thing = { a: number, b: number } ~ 'b' is declared here. @@ -243,8 +342,8 @@ exports[`With diagnostics throw using language service first throw should fail u 3 export const thing: Thing = { a: 1 }; ~~~~~ - main.ts:1:34 - 1 export type Thing = { a: number, b: number } + main.ts:2:34 + 2 export type Thing = { a: number, b: number } ~ 'b' is declared here. @@ -279,14 +378,146 @@ exports[`With diagnostics throw using language service first throw should fail u ================================================================================ `; -exports[`With diagnostics throw using language service then passed when content has changed to valid base on cache of the previous run should pass using template "default" 1`] = ` +exports[`With diagnostics throw using language service then fail when code has changed to invalid base on cache of the previous run should fail using template "default" 1`] = ` + × jest + ↳ exit code: 1 + ===[ STDOUT ]=================================================================== + + ===[ STDERR ]=================================================================== + FAIL ./main.spec.ts + × foo is 42 + + ● foo is 42 + + expect(received).toBe(expected) // Object.is equality + + Expected: 42 + Received: 43 + + 4 | + 5 | test('foo is 42', () => { + > 6 | expect(foo).toBe(42); + | ^ + 7 | }); + 8 | + + at Object. (main.spec.ts:6:15) + + Test Suites: 1 failed, 1 total + Tests: 1 failed, 1 total + Snapshots: 0 total + Time: XXs + Ran all test suites. + ================================================================================ +`; + +exports[`With diagnostics throw using language service then fail when code has changed to invalid base on cache of the previous run should fail using template "with-babel-7" 1`] = ` + × jest + ↳ exit code: 1 + ===[ STDOUT ]=================================================================== + + ===[ STDERR ]=================================================================== + FAIL ./main.spec.ts + × foo is 42 + + ● foo is 42 + + expect(received).toBe(expected) // Object.is equality + + Expected: 42 + Received: 43 + + 4 | + 5 | test('foo is 42', () => { + > 6 | expect(foo).toBe(42); + | ^ + 7 | }); + 8 | + + at Object. (main.spec.ts:6:15) + + Test Suites: 1 failed, 1 total + Tests: 1 failed, 1 total + Snapshots: 0 total + Time: XXs + Ran all test suites. + ================================================================================ +`; + +exports[`With diagnostics throw using language service then fail when code has changed to invalid base on cache of the previous run should fail using template "with-babel-7-string-config" 1`] = ` + × jest + ↳ exit code: 1 + ===[ STDOUT ]=================================================================== + + ===[ STDERR ]=================================================================== + FAIL ./main.spec.ts + × foo is 42 + + ● foo is 42 + + expect(received).toBe(expected) // Object.is equality + + Expected: 42 + Received: 43 + + 4 | + 5 | test('foo is 42', () => { + > 6 | expect(foo).toBe(42); + | ^ + 7 | }); + 8 | + + at Object. (main.spec.ts:6:15) + + Test Suites: 1 failed, 1 total + Tests: 1 failed, 1 total + Snapshots: 0 total + Time: XXs + Ran all test suites. + ================================================================================ +`; + +exports[`With diagnostics throw using language service then fail when code has changed to invalid base on cache of the previous run should fail using template "with-typescript-2-7" 1`] = ` + × jest + ↳ exit code: 1 + ===[ STDOUT ]=================================================================== + + ===[ STDERR ]=================================================================== + FAIL ./main.spec.ts + × foo is 42 + + ● foo is 42 + + expect(received).toBe(expected) // Object.is equality + + Expected: 42 + Received: 43 + + 4 | + 5 | test('foo is 42', () => { + > 6 | expect(foo).toBe(42); + | ^ + 7 | }); + 8 | + + at Object. (main.spec.ts:6:15) + + Test Suites: 1 failed, 1 total + Tests: 1 failed, 1 total + Snapshots: 0 total + Time: XXs + Ran all test suites. + ================================================================================ +`; + +exports[`With diagnostics throw using language service then pass when type has changed to valid base on cache of the previous run should pass using template "default" 1`] = ` √ jest ↳ exit code: 0 ===[ STDOUT ]=================================================================== ===[ STDERR ]=================================================================== PASS ./main.spec.ts - √ should do the thing + √ foo is 42 Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total @@ -296,14 +527,14 @@ exports[`With diagnostics throw using language service then passed when content ================================================================================ `; -exports[`With diagnostics throw using language service then passed when content has changed to valid base on cache of the previous run should pass using template "with-babel-7" 1`] = ` +exports[`With diagnostics throw using language service then pass when type has changed to valid base on cache of the previous run should pass using template "with-babel-7" 1`] = ` √ jest ↳ exit code: 0 ===[ STDOUT ]=================================================================== ===[ STDERR ]=================================================================== PASS ./main.spec.ts - √ should do the thing + √ foo is 42 Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total @@ -313,14 +544,14 @@ exports[`With diagnostics throw using language service then passed when content ================================================================================ `; -exports[`With diagnostics throw using language service then passed when content has changed to valid base on cache of the previous run should pass using template "with-babel-7-string-config" 1`] = ` +exports[`With diagnostics throw using language service then pass when type has changed to valid base on cache of the previous run should pass using template "with-babel-7-string-config" 1`] = ` √ jest ↳ exit code: 0 ===[ STDOUT ]=================================================================== ===[ STDERR ]=================================================================== PASS ./main.spec.ts - √ should do the thing + √ foo is 42 Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total @@ -330,14 +561,14 @@ exports[`With diagnostics throw using language service then passed when content ================================================================================ `; -exports[`With diagnostics throw using language service then passed when content has changed to valid base on cache of the previous run should pass using template "with-typescript-2-7" 1`] = ` +exports[`With diagnostics throw using language service then pass when type has changed to valid base on cache of the previous run should pass using template "with-typescript-2-7" 1`] = ` √ jest ↳ exit code: 0 ===[ STDOUT ]=================================================================== ===[ STDERR ]=================================================================== PASS ./main.spec.ts - √ should do the thing + √ foo is 42 Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total @@ -361,8 +592,8 @@ exports[`With diagnostics throw using program first throw should fail using temp 3 export const thing: Thing = { a: 1 }; ~~~~~ - main.ts:1:34 - 1 export type Thing = { a: number, b: number } + main.ts:2:34 + 2 export type Thing = { a: number, b: number } ~ 'b' is declared here. @@ -388,8 +619,8 @@ exports[`With diagnostics throw using program first throw should fail using temp 3 export const thing: Thing = { a: 1 }; ~~~~~ - main.ts:1:34 - 1 export type Thing = { a: number, b: number } + main.ts:2:34 + 2 export type Thing = { a: number, b: number } ~ 'b' is declared here. @@ -415,8 +646,8 @@ exports[`With diagnostics throw using program first throw should fail using temp 3 export const thing: Thing = { a: 1 }; ~~~~~ - main.ts:1:34 - 1 export type Thing = { a: number, b: number } + main.ts:2:34 + 2 export type Thing = { a: number, b: number } ~ 'b' is declared here. @@ -428,14 +659,113 @@ exports[`With diagnostics throw using program first throw should fail using temp ================================================================================ `; -exports[`With diagnostics throw using program then passed when content has changed to valid base on cache of the previous run should pass using template "default" 1`] = ` +exports[`With diagnostics throw using program then failed when type has changed to invalid base on cache of the previous run should fail using template "default" 1`] = ` + × jest + ↳ exit code: 1 + ===[ STDOUT ]=================================================================== + + ===[ STDERR ]=================================================================== + FAIL ./main.spec.ts + × foo is 42 + + ● foo is 42 + + expect(received).toBe(expected) // Object.is equality + + Expected: 42 + Received: 43 + + 4 | + 5 | test('foo is 42', () => { + > 6 | expect(foo).toBe(42); + | ^ + 7 | }); + 8 | + + at Object. (main.spec.ts:6:15) + + Test Suites: 1 failed, 1 total + Tests: 1 failed, 1 total + Snapshots: 0 total + Time: XXs + Ran all test suites. + ================================================================================ +`; + +exports[`With diagnostics throw using program then failed when type has changed to invalid base on cache of the previous run should fail using template "with-babel-7" 1`] = ` + × jest + ↳ exit code: 1 + ===[ STDOUT ]=================================================================== + + ===[ STDERR ]=================================================================== + FAIL ./main.spec.ts + × foo is 42 + + ● foo is 42 + + expect(received).toBe(expected) // Object.is equality + + Expected: 42 + Received: 43 + + 4 | + 5 | test('foo is 42', () => { + > 6 | expect(foo).toBe(42); + | ^ + 7 | }); + 8 | + + at Object. (main.spec.ts:6:15) + + Test Suites: 1 failed, 1 total + Tests: 1 failed, 1 total + Snapshots: 0 total + Time: XXs + Ran all test suites. + ================================================================================ +`; + +exports[`With diagnostics throw using program then failed when type has changed to invalid base on cache of the previous run should fail using template "with-babel-7-string-config" 1`] = ` + × jest + ↳ exit code: 1 + ===[ STDOUT ]=================================================================== + + ===[ STDERR ]=================================================================== + FAIL ./main.spec.ts + × foo is 42 + + ● foo is 42 + + expect(received).toBe(expected) // Object.is equality + + Expected: 42 + Received: 43 + + 4 | + 5 | test('foo is 42', () => { + > 6 | expect(foo).toBe(42); + | ^ + 7 | }); + 8 | + + at Object. (main.spec.ts:6:15) + + Test Suites: 1 failed, 1 total + Tests: 1 failed, 1 total + Snapshots: 0 total + Time: XXs + Ran all test suites. + ================================================================================ +`; + +exports[`With diagnostics throw using program then pass when type has changed to valid base on cache of the previous run should pass using template "default" 1`] = ` √ jest ↳ exit code: 0 ===[ STDOUT ]=================================================================== ===[ STDERR ]=================================================================== PASS ./main.spec.ts - √ should do the thing + √ foo is 42 Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total @@ -445,14 +775,14 @@ exports[`With diagnostics throw using program then passed when content has chang ================================================================================ `; -exports[`With diagnostics throw using program then passed when content has changed to valid base on cache of the previous run should pass using template "with-babel-7" 1`] = ` +exports[`With diagnostics throw using program then pass when type has changed to valid base on cache of the previous run should pass using template "with-babel-7" 1`] = ` √ jest ↳ exit code: 0 ===[ STDOUT ]=================================================================== ===[ STDERR ]=================================================================== PASS ./main.spec.ts - √ should do the thing + √ foo is 42 Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total @@ -462,14 +792,14 @@ exports[`With diagnostics throw using program then passed when content has chang ================================================================================ `; -exports[`With diagnostics throw using program then passed when content has changed to valid base on cache of the previous run should pass using template "with-babel-7-string-config" 1`] = ` +exports[`With diagnostics throw using program then pass when type has changed to valid base on cache of the previous run should pass using template "with-babel-7-string-config" 1`] = ` √ jest ↳ exit code: 0 ===[ STDOUT ]=================================================================== ===[ STDERR ]=================================================================== PASS ./main.spec.ts - √ should do the thing + √ foo is 42 Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total @@ -533,12 +863,12 @@ exports[`With diagnostics warn only should pass using template "default" 1`] = ` 3 export const thing: Thing = { a: 1 }; ~~~~~ - main.ts:1:34 - 1 export type Thing = { a: number, b: number } + main.ts:2:34 + 2 export type Thing = { a: number, b: number } ~ 'b' is declared here. PASS ./main.spec.ts - √ should do the thing + √ foo is 42 Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total @@ -559,12 +889,12 @@ exports[`With diagnostics warn only should pass using template "with-babel-7" 1` 3 export const thing: Thing = { a: 1 }; ~~~~~ - main.ts:1:34 - 1 export type Thing = { a: number, b: number } + main.ts:2:34 + 2 export type Thing = { a: number, b: number } ~ 'b' is declared here. PASS ./main.spec.ts - √ should do the thing + √ foo is 42 Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total @@ -585,12 +915,12 @@ exports[`With diagnostics warn only should pass using template "with-babel-7-str 3 export const thing: Thing = { a: 1 }; ~~~~~ - main.ts:1:34 - 1 export type Thing = { a: number, b: number } + main.ts:2:34 + 2 export type Thing = { a: number, b: number } ~ 'b' is declared here. PASS ./main.spec.ts - √ should do the thing + √ foo is 42 Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total @@ -612,7 +942,7 @@ exports[`With diagnostics warn only should pass using template "with-typescript- 3 export const thing: Thing = { a: 1 }; ~~~~~ PASS ./main.spec.ts - √ should do the thing + √ foo is 42 Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total diff --git a/e2e/__tests__/diagnostics.test.ts b/e2e/__tests__/diagnostics.test.ts index 73a67b4ba0..16e50ee59c 100644 --- a/e2e/__tests__/diagnostics.test.ts +++ b/e2e/__tests__/diagnostics.test.ts @@ -18,16 +18,13 @@ describe('With diagnostics throw', () => { }) }) - describe('then passed when content has changed to valid base on cache of the previous run', () => { + describe('then pass when type has changed to valid base on cache of the previous run', () => { beforeAll(() => { - writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export type Thing = { - a: number; - // b: number; -}`) + writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export const foo = 42\nexport type Thing = { a: number }`) }) afterAll(() => { - writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export type Thing = { a: number, b: number }\n`) + writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export const foo = 42\nexport type Thing = { a: number, b: number }\n`) }) testCase.runWithTemplates(allValidPackageSets, 0, (runTest, { testLabel }) => { @@ -38,6 +35,24 @@ describe('With diagnostics throw', () => { }) }) }) + + describe('then fail when code has changed to invalid base on cache of the previous run', () => { + beforeAll(() => { + writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export const foo = 43\nexport type Thing = { a: number }`) + }) + + afterAll(() => { + writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export const foo = 42\nexport type Thing = { a: number, b: number }\n`) + }) + + testCase.runWithTemplates(allValidPackageSets, 1, (runTest, { testLabel }) => { + it(testLabel, () => { + const result = runTest() + expect(result.status).toBe(1) + expect(result).toMatchSnapshot() + }) + }) + }) }) describe('using program', () => { @@ -58,16 +73,13 @@ describe('With diagnostics throw', () => { }) }) - describe('then passed when content has changed to valid base on cache of the previous run', () => { + describe('then pass when type has changed to valid base on cache of the previous run', () => { beforeAll(() => { - writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export type Thing = { - a: number; - // b: number; -}`) + writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export const foo = 42\nexport type Thing = { a: number }`) }) afterAll(() => { - writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export type Thing = { a: number, b: number }\n`) + writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export const foo = 42\nexport type Thing = { a: number, b: number }\n`) }) testCase.runWithTemplates(allPackageSetsWithProgram, 0, (runTest, { testLabel }) => { @@ -79,6 +91,24 @@ describe('With diagnostics throw', () => { }) }) + describe('then failed when type has changed to invalid base on cache of the previous run', () => { + beforeAll(() => { + writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export const foo = 43\nexport type Thing = { a: number }`) + }) + + afterAll(() => { + writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export const foo = 42\nexport type Thing = { a: number, b: number }\n`) + }) + + testCase.runWithTemplates(allPackageSetsWithProgram, 1, (runTest, { testLabel }) => { + it(testLabel, () => { + const result = runTest() + expect(result.status).toBe(1) + expect(result).toMatchSnapshot() + }) + }) + }) + describe('with unsupported version', () => { testCase.runWithTemplates(allPackageSetsWithoutProgram, 1, (runTest, { testLabel }) => { it(testLabel, () => { @@ -108,16 +138,13 @@ describe('With diagnostics throw', () => { }) }) - describe('then passed when content has changed to valid base on cache of the previous run', () => { + describe('then pass when type has changed to valid base on cache of the previous run', () => { beforeAll(() => { - writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export type Thing = { - a: number; - // b: number; -}`) + writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export const foo = 42\nexport type Thing = { a: number }`) }) afterAll(() => { - writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export type Thing = { a: number, b: number }\n`) + writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export const foo = 42\nexport type Thing = { a: number, b: number }\n`) }) testCase.runWithTemplates(allPackageSetsWithProgram, 0, (runTest, { testLabel }) => { @@ -129,6 +156,24 @@ describe('With diagnostics throw', () => { }) }) + describe('then failed when code has changed to invalid base on cache of the previous run', () => { + beforeAll(() => { + writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export const foo = 43\nexport type Thing = { a: number }`) + }) + + afterAll(() => { + writeFileSync(join(__dirname, '../__cases__/diagnostics/main.ts'), `export const foo = 42\nexport type Thing = { a: number, b: number }\n`) + }) + + testCase.runWithTemplates(allPackageSetsWithProgram, 1, (runTest, { testLabel }) => { + it(testLabel, () => { + const result = runTest() + expect(result.status).toBe(1) + expect(result).toMatchSnapshot() + }) + }) + }) + describe('with unsupported version', () => { testCase.runWithTemplates(allPackageSetsWithoutProgram, 1, (runTest, { testLabel }) => { it(testLabel, () => { diff --git a/src/compiler/compiler-utils.spec.ts b/src/compiler/compiler-utils.spec.ts index 7066393a30..9f4d9b88b9 100644 --- a/src/compiler/compiler-utils.spec.ts +++ b/src/compiler/compiler-utils.spec.ts @@ -3,7 +3,6 @@ import * as fs from 'fs' import { resolve } from 'path' import { makeCompiler } from '../__helpers__/fakers' -import { logTargetMock } from '../__helpers__/mocks' import { tempDir } from '../__helpers__/path' import { MemoryCache } from '../types' @@ -16,8 +15,6 @@ const memoryCache: MemoryCache = { resolvedModules: Object.create(null), } -const logTarget = logTargetMock() - describe('cacheResolvedModules', () => { let spy: jest.SpyInstance @@ -31,8 +28,6 @@ describe('cacheResolvedModules', () => { }) afterEach(() => { - spy.mockClear() - spy.mockReset() spy.mockRestore() }) @@ -46,11 +41,10 @@ describe('cacheResolvedModules', () => { const source = JSON.stringify(require('../__mocks__/main.spec')) compiler.compile(source, fileName) + cacheResolvedModules(fileName, source, memoryCache, compiler.program!, tmp, logger) - logTarget.clear() - cacheResolvedModules(fileName, memoryCache, compiler.program!, tmp, logger) - - expect(memoryCache.resolvedModules[fileName]).toContain(resolve('src/__mocks__/main.ts')) + expect(memoryCache.resolvedModules[fileName].modulePaths).toContain(resolve('src/__mocks__/main.ts')) + expect(memoryCache.resolvedModules[fileName].testFileContent).toEqual(source) expect(spy).toHaveBeenCalledWith(getResolvedModulesCache(tmp), JSON.stringify(memoryCache.resolvedModules)) }) @@ -64,11 +58,9 @@ describe('cacheResolvedModules', () => { const source = JSON.stringify(require('../__mocks__/thing.spec')) compiler.compile(source, fileName) + cacheResolvedModules(fileName, source, memoryCache, compiler.program!, tmp, logger) - logTarget.clear() - cacheResolvedModules(fileName, memoryCache, compiler.program!, tmp, logger) - - expect(memoryCache.resolvedModules[fileName]).toBe(undefined) + expect(memoryCache.resolvedModules[fileName]).toBeUndefined() expect(spy).not.toHaveBeenCalled() }) }) diff --git a/src/compiler/compiler-utils.ts b/src/compiler/compiler-utils.ts index 7bb3b19195..7254d4e141 100644 --- a/src/compiler/compiler-utils.ts +++ b/src/compiler/compiler-utils.ts @@ -23,6 +23,7 @@ export function getResolvedModulesCache(cachedir: string): string { */ export function cacheResolvedModules( fileName: string, + fileContent: string, memoryCache: MemoryCache, program: _ts.Program, cacheDir: string, @@ -36,7 +37,8 @@ export function cacheResolvedModules( if (importReferences.length) { logger.debug({ fileName }, `cacheResolvedModules(): get resolved modules of test file ${fileName}`) - memoryCache.resolvedModules[fileName] = importReferences + memoryCache.resolvedModules[fileName] = Object.create(null) + memoryCache.resolvedModules[fileName].modulePaths = importReferences .filter((importReference: any) => importReference.parent.parent.resolvedModules?.get(importReference.text)) .map((importReference: any) => { return normalize( @@ -45,6 +47,7 @@ export function cacheResolvedModules( ) }) .reduce((a: any, b: any) => a.concat(b), []) + memoryCache.resolvedModules[fileName].testFileContent = fileContent writeFileSync(getResolvedModulesCache(cacheDir), JSON.stringify(memoryCache.resolvedModules)) } } diff --git a/src/compiler/language-service.ts b/src/compiler/language-service.ts index ed18aef5b7..13aba246f4 100644 --- a/src/compiler/language-service.ts +++ b/src/compiler/language-service.ts @@ -40,20 +40,26 @@ export const compileUsingLanguageService = ( } let projectVersion = 1 // Set the file contents into cache. - const updateMemoryCache = (code: string, fileName: string) => { + const updateMemoryCache = (contents: string, fileName: string) => { logger.debug({ fileName }, `updateMemoryCache(): update memory cache for language service`) + let shouldIncrementProjectVersion = false const fileVersion = memoryCache.versions[fileName] ?? 0 const isFileInCache = fileVersion !== 0 if (!isFileInCache) { memoryCache.versions[fileName] = 1 + shouldIncrementProjectVersion = true } - if (memoryCache.contents[fileName] !== code) { - memoryCache.contents[fileName] = code + const previousContents = memoryCache.contents[fileName] + // Avoid incrementing cache when nothing has changed. + if (previousContents !== contents) { memoryCache.versions[fileName] = fileVersion + 1 - // Increment project version for every file change. - projectVersion++ + memoryCache.contents[fileName] = contents + // Only bump project version when file is modified in cache, not when discovered for the first time + if (isFileInCache) shouldIncrementProjectVersion = true } + + if (shouldIncrementProjectVersion) projectVersion++ } const serviceHost: _ts.LanguageServiceHost = { getProjectVersion: () => String(projectVersion), @@ -117,7 +123,7 @@ export const compileUsingLanguageService = ( /* istanbul ignore next (covered by e2e) */ if (cacheDir) { if (micromatch.isMatch(normalizedFileName, configs.testMatchPatterns)) { - cacheResolvedModules(normalizedFileName, memoryCache, service.getProgram()!, cacheDir, logger) + cacheResolvedModules(normalizedFileName, code, memoryCache, service.getProgram()!, cacheDir, logger) } else { /* istanbul ignore next (covered by e2e) */ Object.entries(memoryCache.resolvedModules) @@ -128,7 +134,7 @@ export const compileUsingLanguageService = ( * test file for 1st time run after clearing cache because */ return ( - entry[1].find(modulePath => modulePath === normalizedFileName) && + entry[1].modulePaths.find(modulePath => modulePath === normalizedFileName) && !hasOwn.call(memoryCache.outputs, entry[0]) ) }) @@ -137,7 +143,9 @@ export const compileUsingLanguageService = ( `diagnoseFn(): computing diagnostics for test file that imports ${normalizedFileName} using language service`, ) - doTypeChecking(configs, entry[0], service, logger) + const testFileName = entry[0] + updateMemoryCache(entry[1].testFileContent, testFileName) + doTypeChecking(configs, testFileName, service, logger) }) } } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index d4d925d643..7abc674757 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -86,19 +86,27 @@ export const compileUsingProgram = (configs: ConfigSet, logger: Logger, memoryCa } // Read and cache custom transformers. const customTransformers = configs.tsCustomTransformers - const updateMemoryCache = (code: string, fileName: string): void => { + const updateMemoryCache = (contents: string, fileName: string): void => { logger.debug({ fileName }, `updateMemoryCache(): update memory cache for ${programDebugText}`) const sourceFile = incremental ? builderProgram.getSourceFile(fileName) : program.getSourceFile(fileName) - if (!hasOwn.call(memoryCache.versions, fileName)) { + const fileVersion = memoryCache.versions[fileName] ?? 0 + const isFileInCache = fileVersion !== 0 + if (!isFileInCache) { memoryCache.versions[fileName] = 1 } - if (memoryCache.contents[fileName] !== code) { - memoryCache.contents[fileName] = code - memoryCache.versions[fileName] = (memoryCache.versions[fileName] || 0) + 1 + const previousContents = memoryCache.contents[fileName] + // Avoid incrementing cache when nothing has changed. + if (previousContents !== contents) { + memoryCache.versions[fileName] = fileVersion + 1 + memoryCache.contents[fileName] = contents } // Update program when file changes. - if (sourceFile === undefined || sourceFile.text !== code || program.isSourceFileFromExternalLibrary(sourceFile)) { + if ( + sourceFile === undefined || + sourceFile.text !== contents || + program.isSourceFileFromExternalLibrary(sourceFile) + ) { const programOptions = { rootNames: Object.keys(memoryCache.versions), options, @@ -161,7 +169,7 @@ export const compileUsingProgram = (configs: ConfigSet, logger: Logger, memoryCa /* istanbul ignore next (covered by e2e) */ if (cacheDir) { if (micromatch.isMatch(normalizedFileName, configs.testMatchPatterns)) { - cacheResolvedModules(normalizedFileName, memoryCache, program, cacheDir, logger) + cacheResolvedModules(normalizedFileName, code, memoryCache, program, cacheDir, logger) } else { /* istanbul ignore next (covered by e2e) */ Object.entries(memoryCache.resolvedModules) @@ -172,7 +180,7 @@ export const compileUsingProgram = (configs: ConfigSet, logger: Logger, memoryCa * test file for 1st time run after clearing cache because */ return ( - entry[1].find(modulePath => modulePath === normalizedFileName) && + entry[1].modulePaths.find(modulePath => modulePath === normalizedFileName) && !hasOwn.call(memoryCache.outputs, entry[0]) ) }) diff --git a/src/types.ts b/src/types.ts index 3f135fc337..ebcb3fac5d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -203,7 +203,12 @@ export interface MemoryCache { contents: { [filePath: string]: string | undefined } versions: { [filePath: string]: number } outputs: { [filePath: string]: string } - resolvedModules: { [testFilePath: string]: string[] } + resolvedModules: { + [testFilePath: string]: { + testFileContent: string + modulePaths: string[] + } + } } /** * @internal