Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(compiler): make projectReferences work with LanguageService #1541

Merged
merged 4 commits into from Apr 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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