Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(config): allow to override resolve tsconfig behavior (#2063)
This change is needed because some jest transformers which want to reuse/extend our `ConfigSet` but want to change the default behavior when reading and resolving tsconfig. 

This PR introduces:
- Change `_readTsConfig` to `_resolveTsConfig` and set `_resolveTsConfig` to `protected`
- Allow to override `_resolveTsConfig` via overload signature
- Expose `_transformCfgStr`, `_configSet`, `_overriddenCompilerOptions`
  • Loading branch information
ahnpnl committed Oct 26, 2020
1 parent 681bfef commit 9f46ace
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 92 deletions.
4 changes: 2 additions & 2 deletions src/compiler/language-service.ts
Expand Up @@ -75,8 +75,8 @@ export const initializeLanguageServiceInstance = (configs: ConfigSet, logger: Lo
}
// Initialize memory cache for typescript compiler
configs.parsedTsConfig.fileNames
.filter((fileName) => !configs.isTestFile(fileName))
.forEach((fileName) => {
.filter((fileName: string) => !configs.isTestFile(fileName))
.forEach((fileName: string) => {
memoryCache.files.set(fileName, {
version: 0,
})
Expand Down
104 changes: 52 additions & 52 deletions src/config/__snapshots__/config-set.spec.ts.snap
@@ -1,5 +1,57 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`_resolveTsConfig resolve configFileName normally module in tsConfig is not the same as forced module and allowSyntheticDefaultImports is false in tsConfig should use correct paths when searching 1`] = `
Array [
Object {
"category": 3,
"code": 151001,
"file": undefined,
"length": undefined,
"messageText": "If you have issues related to imports, you should consider setting \`esModuleInterop\` to \`true\` in your TypeScript configuration file (usually \`tsconfig.json\`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.",
"start": undefined,
},
]
`;

exports[`_resolveTsConfig resolve configFileName normally module in tsConfig is not the same as forced module and allowSyntheticDefaultImports is false in tsConfig should use given tsconfig path 1`] = `
Array [
Object {
"category": 3,
"code": 151001,
"file": undefined,
"length": undefined,
"messageText": "If you have issues related to imports, you should consider setting \`esModuleInterop\` to \`true\` in your TypeScript configuration file (usually \`tsconfig.json\`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.",
"start": undefined,
},
]
`;

exports[`_resolveTsConfig resolve configFileName normally module in tsConfig is not the same as forced module and esModuleInterop is not in tsConfig should use correct paths when searching 1`] = `
Array [
Object {
"category": 3,
"code": 151001,
"file": undefined,
"length": undefined,
"messageText": "If you have issues related to imports, you should consider setting \`esModuleInterop\` to \`true\` in your TypeScript configuration file (usually \`tsconfig.json\`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.",
"start": undefined,
},
]
`;

exports[`_resolveTsConfig resolve configFileName normally module in tsConfig is not the same as forced module and esModuleInterop is not in tsConfig should use given tsconfig path 1`] = `
Array [
Object {
"category": 3,
"code": 151001,
"file": undefined,
"length": undefined,
"messageText": "If you have issues related to imports, you should consider setting \`esModuleInterop\` to \`true\` in your TypeScript configuration file (usually \`tsconfig.json\`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.",
"start": undefined,
},
]
`;

exports[`customTransformers should return an object containing all resolved transformers 1`] = `
Object {
"before": Array [
Expand Down Expand Up @@ -55,55 +107,3 @@ exports[`isTestFile should return a boolean value whether the file matches test
exports[`isTestFile should return a boolean value whether the file matches test pattern 3`] = `true`;

exports[`isTestFile should return a boolean value whether the file matches test pattern 4`] = `true`;

exports[`readTsConfig resolve configFileName normally module in tsConfig is not the same as forced module and allowSyntheticDefaultImports is false in tsConfig should use correct paths when searching 1`] = `
Array [
Object {
"category": 3,
"code": 151001,
"file": undefined,
"length": undefined,
"messageText": "If you have issues related to imports, you should consider setting \`esModuleInterop\` to \`true\` in your TypeScript configuration file (usually \`tsconfig.json\`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.",
"start": undefined,
},
]
`;

exports[`readTsConfig resolve configFileName normally module in tsConfig is not the same as forced module and allowSyntheticDefaultImports is false in tsConfig should use given tsconfig path 1`] = `
Array [
Object {
"category": 3,
"code": 151001,
"file": undefined,
"length": undefined,
"messageText": "If you have issues related to imports, you should consider setting \`esModuleInterop\` to \`true\` in your TypeScript configuration file (usually \`tsconfig.json\`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.",
"start": undefined,
},
]
`;

exports[`readTsConfig resolve configFileName normally module in tsConfig is not the same as forced module and esModuleInterop is not in tsConfig should use correct paths when searching 1`] = `
Array [
Object {
"category": 3,
"code": 151001,
"file": undefined,
"length": undefined,
"messageText": "If you have issues related to imports, you should consider setting \`esModuleInterop\` to \`true\` in your TypeScript configuration file (usually \`tsconfig.json\`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.",
"start": undefined,
},
]
`;

exports[`readTsConfig resolve configFileName normally module in tsConfig is not the same as forced module and esModuleInterop is not in tsConfig should use given tsconfig path 1`] = `
Array [
Object {
"category": 3,
"code": 151001,
"file": undefined,
"length": undefined,
"messageText": "If you have issues related to imports, you should consider setting \`esModuleInterop\` to \`true\` in your TypeScript configuration file (usually \`tsconfig.json\`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.",
"start": undefined,
},
]
`;
2 changes: 1 addition & 1 deletion src/config/config-set.spec.ts
Expand Up @@ -653,7 +653,7 @@ describe('resolvePath', () => {
})
}) // resolvePath

describe('readTsConfig', () => {
describe('_resolveTsConfig', () => {
let findConfig!: jest.SpyInstance<string | undefined>
let readConfig!: jest.SpyInstance<{ config?: any; error?: ts.Diagnostic }>
let parseConfig!: jest.SpyInstance<ts.ParsedCommandLine>
Expand Down
22 changes: 11 additions & 11 deletions src/config/config-set.ts
Expand Up @@ -116,7 +116,7 @@ export class ConfigSet {
readonly isolatedModules: boolean
readonly cwd: string
tsCacheDir: string | undefined
parsedTsConfig!: ParsedCommandLine
parsedTsConfig!: ParsedCommandLine | Record<string, any>
customTransformers: CustomTransformers = Object.create(null)
readonly rootDir: string
/**
Expand All @@ -139,10 +139,7 @@ export class ConfigSet {
* @internal
*/
private _stringifyContentRegExp: RegExp | undefined
/**
* @internal
*/
private _overriddenCompilerOptions: Partial<CompilerOptions> = {
protected _overriddenCompilerOptions: Partial<CompilerOptions> = {
// we handle sourcemaps this way and not another
sourceMap: true,
inlineSourceMap: false,
Expand Down Expand Up @@ -282,7 +279,10 @@ export class ConfigSet {
}
const tsconfigOpt = options.tsConfig ?? options.tsconfig
const configFilePath = typeof tsconfigOpt === 'string' ? this.resolvePath(tsconfigOpt) : undefined
this.parsedTsConfig = this._readTsConfig(typeof tsconfigOpt === 'object' ? tsconfigOpt : undefined, configFilePath)
this.parsedTsConfig = this._resolveTsConfig(
typeof tsconfigOpt === 'object' ? tsconfigOpt : undefined,
configFilePath,
)
// throw errors if any matching wanted diagnostics
this.raiseDiagnostics(this.parsedTsConfig.errors, configFilePath)

Expand Down Expand Up @@ -389,10 +389,10 @@ export class ConfigSet {
/**
* Load TypeScript configuration. Returns the parsed TypeScript config and
* any `tsConfig` options specified in ts-jest tsConfig
*
* @internal
*/
private _readTsConfig(compilerOptions?: CompilerOptions, resolvedConfigFile?: string): ParsedCommandLine {
protected _resolveTsConfig(compilerOptions?: CompilerOptions, resolvedConfigFile?: string): Record<string, any>
// eslint-disable-next-line no-dupe-class-members
protected _resolveTsConfig(compilerOptions?: CompilerOptions, resolvedConfigFile?: string): ParsedCommandLine {
let config = { compilerOptions: Object.create(null) }
let basePath = normalizeSlashes(this.rootDir)
const ts = this.compilerModule
Expand All @@ -402,6 +402,7 @@ export class ConfigSet {
: ts.findConfigFile(normalizeSlashes(this.rootDir), ts.sys.fileExists)
if (configFileName) {
this.logger.debug({ tsConfigFileName: configFileName }, 'readTsConfig(): reading', configFileName)

const result = ts.readConfigFile(configFileName, ts.sys.readFile)
// Return diagnostics.
if (result.error) {
Expand All @@ -419,10 +420,8 @@ export class ConfigSet {

// parse json, merge config extending others, ...
const result = ts.parseJsonConfigFileContent(config, ts.sys, basePath, undefined, configFileName)

const { _overriddenCompilerOptions: forcedOptions } = this
const finalOptions = result.options

// Target ES5 output by default (instead of ES3).
if (finalOptions.target === undefined) {
finalOptions.target = ts.ScriptTarget.ES5
Expand Down Expand Up @@ -484,6 +483,7 @@ export class ConfigSet {
nodeJsVer: process.version,
compilationTarget: config.compilerOptions.target ?? TARGET_TO_VERSION_MAPPING[compilationTarget],
})

this.logger.warn(message)
}

Expand Down
4 changes: 1 addition & 3 deletions src/constants.ts
@@ -1,9 +1,7 @@
/**
* @internal
*/
export const LINE_FEED = '\n'
export const TS_TSX_REGEX = /\.tsx?$/
export const JS_JSX_REGEX = /\.jsx?$/
export const DECLARATION_TYPE_EXT = '.d.ts'
/**
* @internal
* See https://jestjs.io/docs/en/configuration#testmatch-arraystring
Expand Down
40 changes: 17 additions & 23 deletions src/ts-jest-transformer.ts
Expand Up @@ -4,7 +4,7 @@ import type { Config } from '@jest/types'
import type { Logger } from 'bs-logger'

import { ConfigSet } from './config/config-set'
import { JS_JSX_REGEX, TS_TSX_REGEX } from './constants'
import { DECLARATION_TYPE_EXT, JS_JSX_REGEX, TS_TSX_REGEX } from './constants'
import { stringify } from './utils/json'
import { JsonableValue } from './utils/jsonable-value'
import { rootLogger } from './utils/logger'
Expand All @@ -24,14 +24,8 @@ export class TsJestTransformer implements Transformer {
*/
private static readonly _cachedConfigSets: CachedConfigSet[] = []
protected readonly logger: Logger
/**
* @internal
*/
private _transformCfgStr!: string
/**
* @internal
*/
private _tsJestCfgSet!: ConfigSet
protected _transformCfgStr!: string
protected _configSet!: ConfigSet

constructor() {
this.logger = rootLogger.child({ namespace: 'ts-jest-transformer' })
Expand All @@ -49,10 +43,10 @@ export class TsJestTransformer implements Transformer {

let result: string | TransformedSource
const source: string = input
const { hooks } = this._tsJestCfgSet
const shouldStringifyContent = this._tsJestCfgSet.shouldStringifyContent(filePath)
const babelJest = shouldStringifyContent ? undefined : this._tsJestCfgSet.babelJestTransformer
const isDefinitionFile = filePath.endsWith('.d.ts')
const { hooks } = this._configSet
const shouldStringifyContent = this._configSet.shouldStringifyContent(filePath)
const babelJest = shouldStringifyContent ? undefined : this._configSet.babelJestTransformer
const isDefinitionFile = filePath.endsWith(DECLARATION_TYPE_EXT)
const isJsFile = JS_JSX_REGEX.test(filePath)
const isTsFile = !isDefinitionFile && TS_TSX_REGEX.test(filePath)
if (shouldStringifyContent) {
Expand All @@ -61,15 +55,15 @@ export class TsJestTransformer implements Transformer {
} else if (isDefinitionFile) {
// do not try to compile declaration files
result = ''
} else if (!this._tsJestCfgSet.parsedTsConfig.options.allowJs && isJsFile) {
} else if (!this._configSet.parsedTsConfig.options.allowJs && isJsFile) {
// we've got a '.js' but the compiler option `allowJs` is not set or set to false
this.logger.warn({ fileName: filePath }, interpolate(Errors.GotJsFileButAllowJsFalse, { path: filePath }))

result = source
} else if (isJsFile || isTsFile) {
// transpile TS code (source maps are included)
/* istanbul ignore if */
result = this._tsJestCfgSet.tsCompiler.compile(source, filePath)
result = this._configSet.tsCompiler.compile(source, filePath)
} else {
// we should not get called for files with other extension than js[x], ts[x] and d.ts,
// TypeScript will bail if we try to compile, and if it was to call babel, users can
Expand Down Expand Up @@ -131,7 +125,7 @@ export class TsJestTransformer implements Transformer {
)
if (ccs) {
this._transformCfgStr = ccs.transformerCfgStr
this._tsJestCfgSet = ccs.configSet
this._configSet = ccs.configSet
} else {
// try to look-it up by stringified version
const serializedJestCfg = stringify(jestConfig)
Expand All @@ -144,24 +138,24 @@ export class TsJestTransformer implements Transformer {
// the config, and then it calls the transformer with the proper object
serializedCcs.jestConfig.value = jestConfig
this._transformCfgStr = serializedCcs.transformerCfgStr
this._tsJestCfgSet = serializedCcs.configSet
this._configSet = serializedCcs.configSet
} else {
// create the new record in the index
this.logger.info('no matching config-set found, creating a new one')

this._tsJestCfgSet = new ConfigSet(jestConfig)
this._configSet = new ConfigSet(jestConfig)
this._transformCfgStr = new JsonableValue({
digest: this._tsJestCfgSet.tsJestDigest,
babel: this._tsJestCfgSet.babelConfig,
digest: this._configSet.tsJestDigest,
babel: this._configSet.babelConfig,
...jestConfig,
tsconfig: {
options: this._tsJestCfgSet.parsedTsConfig.options,
raw: this._tsJestCfgSet.parsedTsConfig.raw,
options: this._configSet.parsedTsConfig.options,
raw: this._configSet.parsedTsConfig.raw,
},
}).serialized
TsJestTransformer._cachedConfigSets.push({
jestConfig: new JsonableValue(jestConfig),
configSet: this._tsJestCfgSet,
configSet: this._configSet,
transformerCfgStr: this._transformCfgStr,
})
}
Expand Down

0 comments on commit 9f46ace

Please sign in to comment.