Skip to content

Commit

Permalink
fix(compiler): make projectReferences work with LanguageService (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
ahnpnl committed Apr 19, 2020
1 parent 2ffa5f1 commit 3e8efbe
Show file tree
Hide file tree
Showing 16 changed files with 284 additions and 152 deletions.
2 changes: 1 addition & 1 deletion e2e/__external-repos__/simple/dependency/package.json
Expand Up @@ -2,7 +2,7 @@
"name": "dependency",
"version": "0.0.1",
"peerDependencies": {
"typescript": "^3.7.5"
"typescript": "^3.8.3"
},
"main": "./index.ts"
}
6 changes: 3 additions & 3 deletions e2e/__external-repos__/simple/with-dependency/package.json
Expand Up @@ -36,12 +36,12 @@
}
},
"devDependencies": {
"typescript": "^3.7.5"
"typescript": "^3.8.3"
},
"dependencies": {
"@types/jest": "^25.1.2",
"@types/jest": "^25.2.1",
"dependency": "file:../dependency",
"jest": "^25.1.0"
"jest": "^25.3.0"
},
"main": "./index.ts"
}
1 change: 1 addition & 0 deletions e2e/__external-repos__/yarn-workspace-composite/.gitignore
@@ -0,0 +1 @@
/target
8 changes: 4 additions & 4 deletions e2e/__external-repos__/yarn-workspace-composite/package.json
Expand Up @@ -6,11 +6,11 @@
"packages/*"
],
"scripts": {
"test": "jest --no-cache"
"test": "yarn tsc -b packages/my-app/tsconfig.json && jest --no-cache"
},
"devDependencies": {
"@types/jest": "^25.1.2",
"jest": "^25.1.0",
"typescript": "~3.7.5"
"@types/jest": "^25.2.1",
"jest": "^25.3.0",
"typescript": "~3.8.3"
}
}
Expand Up @@ -9,6 +9,7 @@
"noImplicitAny": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"outDir": "./target/",
"lib": [
"dom", "es2018"
]
Expand Down
18 changes: 9 additions & 9 deletions e2e/__tests__/__snapshots__/logger.test.ts.snap
Expand Up @@ -20,20 +20,20 @@ Array [
"[level:20] normalized typescript config",
"[level:20] processing <cwd>/Hello.spec.ts",
"[level:20] file caching disabled",
"[level:20] compileUsingLanguageService(): create typescript compiler",
"[level:20] initializeLanguageServiceInstance(): create typescript compiler",
"[level:20] compileUsingLanguageService(): creating language service",
"[level:20] readThrough(): no cache",
"[level:20] compileFn(): compiling using language service",
"[level:20] updateMemoryCache(): update memory cache for language service",
"[level:20] visitSourceFileNode(): hoisting",
"[level:20] diagnoseFn(): computing diagnostics for <cwd>/Hello.spec.ts using language service",
"[level:20] compileFn(): computing diagnostics for <cwd>/Hello.spec.ts using language service",
"[level:20] computing cache key for <cwd>/Hello.ts",
"[level:20] processing <cwd>/Hello.ts",
"[level:20] readThrough(): no cache",
"[level:20] compileFn(): compiling using language service",
"[level:20] updateMemoryCache(): update memory cache for language service",
"[level:20] visitSourceFileNode(): hoisting",
"[level:20] diagnoseFn(): computing diagnostics for <cwd>/Hello.ts using language service",
"[level:20] compileFn(): computing diagnostics for <cwd>/Hello.ts using language service",
]
`;
Expand Down Expand Up @@ -61,21 +61,21 @@ Array [
"[level:20] patching babel-jest",
"[level:20] checking version of babel-jest: OK",
"[level:20] file caching disabled",
"[level:20] compileUsingLanguageService(): create typescript compiler",
"[level:20] initializeLanguageServiceInstance(): create typescript compiler",
"[level:20] compileUsingLanguageService(): creating language service",
"[level:20] readThrough(): no cache",
"[level:20] compileFn(): compiling using language service",
"[level:20] updateMemoryCache(): update memory cache for language service",
"[level:20] visitSourceFileNode(): hoisting",
"[level:20] diagnoseFn(): computing diagnostics for <cwd>/Hello.spec.ts using language service",
"[level:20] compileFn(): computing diagnostics for <cwd>/Hello.spec.ts using language service",
"[level:20] calling babel-jest processor",
"[level:20] computing cache key for <cwd>/Hello.ts",
"[level:20] processing <cwd>/Hello.ts",
"[level:20] readThrough(): no cache",
"[level:20] compileFn(): compiling using language service",
"[level:20] updateMemoryCache(): update memory cache for language service",
"[level:20] visitSourceFileNode(): hoisting",
"[level:20] diagnoseFn(): computing diagnostics for <cwd>/Hello.ts using language service",
"[level:20] compileFn(): computing diagnostics for <cwd>/Hello.ts using language service",
"[level:20] calling babel-jest processor",
]
`;
Expand Down Expand Up @@ -105,21 +105,21 @@ Array [
"[level:20] patching babel-jest",
"[level:20] checking version of babel-jest: OK",
"[level:20] file caching disabled",
"[level:20] compileUsingLanguageService(): create typescript compiler",
"[level:20] initializeLanguageServiceInstance(): create typescript compiler",
"[level:20] compileUsingLanguageService(): creating language service",
"[level:20] readThrough(): no cache",
"[level:20] compileFn(): compiling using language service",
"[level:20] updateMemoryCache(): update memory cache for language service",
"[level:20] visitSourceFileNode(): hoisting",
"[level:20] diagnoseFn(): computing diagnostics for <cwd>/Hello.spec.ts using language service",
"[level:20] compileFn(): computing diagnostics for <cwd>/Hello.spec.ts using language service",
"[level:20] calling babel-jest processor",
"[level:20] computing cache key for <cwd>/Hello.ts",
"[level:20] processing <cwd>/Hello.ts",
"[level:20] readThrough(): no cache",
"[level:20] compileFn(): compiling using language service",
"[level:20] updateMemoryCache(): update memory cache for language service",
"[level:20] visitSourceFileNode(): hoisting",
"[level:20] diagnoseFn(): computing diagnostics for <cwd>/Hello.ts using language service",
"[level:20] compileFn(): computing diagnostics for <cwd>/Hello.ts using language service",
"[level:20] calling babel-jest processor",
]
`;
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/compiler-utils.spec.ts
Expand Up @@ -4,7 +4,7 @@ import { resolve } from 'path'

import { makeCompiler } from '../__helpers__/fakers'
import { tempDir } from '../__helpers__/path'
import { MemoryCache } from '../types'
import { MemoryCache, TSFile } from '../types'

import { cacheResolvedModules, getResolvedModulesCache } from './compiler-utils'

Expand All @@ -13,6 +13,7 @@ const memoryCache: MemoryCache = {
versions: Object.create(null),
outputs: Object.create(null),
resolvedModules: Object.create(null),
files: new Map<string, TSFile>(),
}

describe('cacheResolvedModules', () => {
Expand Down
48 changes: 45 additions & 3 deletions src/compiler/compiler-utils.ts
Expand Up @@ -4,8 +4,9 @@ import micromatch = require('micromatch')
import { dirname, join, normalize, relative, resolve } from 'path'
import * as _ts from 'typescript'

import { ConfigSet } from '../config/config-set'
import { EXTENSION_REGEX, JSON_REGEX, TS_TSX_REGEX } from '../constants'
import { MemoryCache, TSFiles } from '../types'
import { MemoryCache, SourceOutput, TSFiles } from '../types'
import { sha1 } from '../util/sha1'

/**
Expand Down Expand Up @@ -145,12 +146,11 @@ function getOutputJavaScriptFileName(inputFileName: string, projectReference: _t
}

/**
* @internal
* Gets the output JS file path for an input file governed by a composite project.
* Pulls from the cache if it exists; computes and caches the result otherwise.
*/
/* istanbul ignore next (we leave this for e2e) */
export function getAndCacheOutputJSFileName(
function getAndCacheOutputJSFileName(
inputFileName: string,
projectReference: _ts.ResolvedProjectReference,
files: TSFiles,
Expand All @@ -170,3 +170,45 @@ export function getAndCacheOutputJSFileName(

return outputFileName
}

/**
* @internal
*/
/* istanbul ignore next (we leave this for e2e) */
export function getCompileResultFromReferencedProject(
fileName: string,
configs: ConfigSet,
files: TSFiles,
referencedProject: _ts.ResolvedProjectReference,
): SourceOutput {
const [relativeProjectConfigPath, relativeFilePath] = [
configs.resolvePath(referencedProject.sourceFile.fileName),
configs.resolvePath(fileName),
]
if (referencedProject.commandLine.options.outFile !== undefined) {
throw new Error(
`The referenced project at ${relativeProjectConfigPath} is using ` +
`the outFile' option, which is not supported with ts-jest.`,
)
}

const jsFileName = getAndCacheOutputJSFileName(fileName, referencedProject, files)
const relativeJSFileName = configs.resolvePath(jsFileName)
if (!configs.compilerModule.sys.fileExists(jsFileName)) {
throw new Error(
// tslint:disable-next-line:prefer-template
`Could not find output JavaScript file for input ` +
`${relativeFilePath} (looked at ${relativeJSFileName}).\n` +
`The input file is part of a project reference located at ` +
`${relativeProjectConfigPath}, so ts-jest is looking for the ` +
'project’s pre-built output on disk. Try running `tsc --build` ' +
'to build project references.',
)
}

const mapFileName = `${jsFileName}.map`
const outputText = configs.compilerModule.sys.readFile(jsFileName)
const sourceMapText = configs.compilerModule.sys.readFile(mapFileName)

return [outputText!, sourceMapText!]
}
17 changes: 12 additions & 5 deletions src/compiler/instance.ts
Expand Up @@ -35,7 +35,7 @@ import mkdirp = require('mkdirp')
import { basename, extname, join, normalize } from 'path'

import { ConfigSet } from '../config/config-set'
import { CompileFn, CompilerInstance, MemoryCache, TsCompiler } from '../types'
import { CompileFn, CompilerInstance, MemoryCache, TSFile, TsCompiler } from '../types'
import { sha1 } from '../util/sha1'

import { getResolvedModulesCache } from './compiler-utils'
Expand Down Expand Up @@ -168,14 +168,21 @@ export const createCompilerInstance = (configs: ConfigSet): TsCompiler => {
versions: Object.create(null),
outputs: Object.create(null),
resolvedModules: Object.create(null),
files: new Map<string, TSFile>(),
}
// Enable `allowJs` when flag is set.
if (compilerOptions.allowJs) {
extensions.push('.js')
extensions.push('.jsx')
}
// Initialize files from TypeScript into project.
for (const path of fileNames) memoryCache.versions[normalize(path)] = 1
for (const path of fileNames) {
const normalizedFilePath = normalize(path)
memoryCache.versions[normalizedFilePath] = 1
memoryCache.files.set(normalizedFilePath, {
version: 0,
})
}
/**
* Get the extension for a transpiled file.
*/
Expand All @@ -187,10 +194,10 @@ export const createCompilerInstance = (configs: ConfigSet): TsCompiler => {
if (!tsJest.isolatedModules) {
// Use language services by default
compilerInstance = !tsJest.compilerHost
? initializeLanguageServiceInstance(configs, logger, memoryCache)
: initializeProgramInstance(configs, logger, memoryCache)
? initializeLanguageServiceInstance(configs, memoryCache, logger)
: initializeProgramInstance(configs, memoryCache, logger)
} else {
compilerInstance = initializeTranspilerInstance(configs, logger)
compilerInstance = initializeTranspilerInstance(configs, memoryCache, logger)
}
const compile = compileAndCacheResult(cachedir, memoryCache, compilerInstance.compileFn, getExtension, logger)

Expand Down
53 changes: 52 additions & 1 deletion src/compiler/language-service.spec.ts
Expand Up @@ -40,7 +40,7 @@ describe('Language service', () => {
",
"[level:20] visitSourceFileNode(): hoisting
",
"[level:20] diagnoseFn(): computing diagnostics for test-cache.ts using language service
"[level:20] compileFn(): computing diagnostics for test-cache.ts using language service
",
"[level:20] readThrough(): writing caches
",
Expand All @@ -63,6 +63,57 @@ describe('Language service', () => {
removeSync(fileName)
})

it('should get compile result from referenced project when there is a built reference project', () => {
const tmp = tempDir('compiler')
const compiler = makeCompiler({
jestConfig: { cache: true, cacheDirectory: tmp },
tsJestConfig: { tsConfig: false },
})
const source = 'console.log("hello")'
const fileName = 'test-reference-project.ts'
const getAndCacheProjectReferenceSpy = jest
.spyOn(compilerUtils, 'getAndCacheProjectReference')
.mockReturnValueOnce({} as any)
jest
.spyOn(compilerUtils, 'getCompileResultFromReferencedProject')
.mockImplementationOnce(() => [
source,
'{"version":3,"file":"test-reference-project.js","sourceRoot":"","sources":["test-reference-project.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA","sourcesContent":["console.log(\\"hello\\")"]}',
])
writeFileSync(fileName, source, 'utf8')

compiler.compile(source, fileName)

expect(getAndCacheProjectReferenceSpy).toHaveBeenCalled()
expect(compilerUtils.getCompileResultFromReferencedProject).toHaveBeenCalled()

jest.restoreAllMocks()
removeSync(fileName)
})

it('should get compile result from language service when there is no referenced project', () => {
const tmp = tempDir('compiler')
const compiler = makeCompiler({
jestConfig: { cache: true, cacheDirectory: tmp },
tsJestConfig: { tsConfig: false },
})
const source = 'console.log("hello")'
const fileName = 'test-no-reference-project.ts'
const getAndCacheProjectReferenceSpy = jest
.spyOn(compilerUtils, 'getAndCacheProjectReference')
.mockReturnValueOnce(undefined)
jest.spyOn(compilerUtils, 'getCompileResultFromReferencedProject')
writeFileSync(fileName, source, 'utf8')

compiler.compile(source, fileName)

expect(getAndCacheProjectReferenceSpy).toHaveBeenCalled()
expect(compilerUtils.getCompileResultFromReferencedProject).not.toHaveBeenCalled()

jest.restoreAllMocks()
removeSync(fileName)
})

it('should cache resolved modules for test file with testMatchPatterns from jest config when match', () => {
// tslint:disable-next-line:no-empty
const spy = jest.spyOn(compilerUtils, 'cacheResolvedModules').mockImplementationOnce(() => {})
Expand Down

0 comments on commit 3e8efbe

Please sign in to comment.