From cfa9b7a77d10c3b77270b342b0ded0a643e07df0 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Thu, 21 Feb 2019 12:09:42 +0100 Subject: [PATCH] chore: migrate jest-config to TypeScript --- packages/jest-config/package.json | 2 + .../src/{Defaults.js => Defaults.ts} | 11 +- .../src/{Deprecated.js => Deprecated.ts} | 14 +- .../src/{Descriptions.js => Descriptions.ts} | 10 +- ...nErrors.js => ReporterValidationErrors.ts} | 12 +- .../src/{ValidConfig.js => ValidConfig.ts} | 15 +- .../{Defaults.test.js => Defaults.test.ts} | 0 ...xWorkers.test.js => getMaxWorkers.test.ts} | 3 + ...{readConfig.test.js => readConfig.test.ts} | 1 + ...eadConfigs.test.js => readConfigs.test.ts} | 1 + ...Path.test.js => resolveConfigPath.test.ts} | 2 - ...etFromArgv.test.js => setFromArgv.test.ts} | 17 +- ...attern.test.js => validatePattern.test.ts} | 0 .../src/{constants.js => constants.ts} | 2 - ...CacheDirectory.js => getCacheDirectory.ts} | 7 +- .../{getMaxWorkers.js => getMaxWorkers.ts} | 17 +- .../jest-config/src/{index.js => index.ts} | 63 +- .../src/{normalize.js => normalize.ts} | 746 ++++++++++-------- ...tDir.js => readConfigFileAndSetRootDir.ts} | 9 +- ...olveConfigPath.js => resolveConfigPath.ts} | 22 +- .../src/{setFromArgv.js => setFromArgv.ts} | 13 +- .../jest-config/src/{utils.js => utils.ts} | 71 +- ...{validatePattern.js => validatePattern.ts} | 4 +- packages/jest-config/tsconfig.json | 17 + packages/jest-types/package.json | 5 +- packages/jest-types/src/Argv.ts | 100 +++ packages/jest-types/src/index.ts | 2 + yarn.lock | 2 +- 28 files changed, 665 insertions(+), 503 deletions(-) rename packages/jest-config/src/{Defaults.js => Defaults.ts} (94%) rename packages/jest-config/src/{Deprecated.js => Deprecated.ts} (86%) rename packages/jest-config/src/{Descriptions.js => Descriptions.ts} (97%) rename packages/jest-config/src/{ReporterValidationErrors.js => ReporterValidationErrors.ts} (91%) rename packages/jest-config/src/{ValidConfig.js => ValidConfig.ts} (93%) rename packages/jest-config/src/__tests__/{Defaults.test.js => Defaults.test.ts} (100%) rename packages/jest-config/src/__tests__/{getMaxWorkers.test.js => getMaxWorkers.test.ts} (91%) rename packages/jest-config/src/__tests__/{readConfig.test.js => readConfig.test.ts} (95%) rename packages/jest-config/src/__tests__/{readConfigs.test.js => readConfigs.test.ts} (94%) rename packages/jest-config/src/__tests__/{resolveConfigPath.test.js => resolveConfigPath.test.ts} (99%) rename packages/jest-config/src/__tests__/{setFromArgv.test.js => setFromArgv.test.ts} (83%) rename packages/jest-config/src/__tests__/{validatePattern.test.js => validatePattern.test.ts} (100%) rename packages/jest-config/src/{constants.js => constants.ts} (97%) rename packages/jest-config/src/{getCacheDirectory.js => getCacheDirectory.ts} (91%) rename packages/jest-config/src/{getMaxWorkers.js => getMaxWorkers.ts} (60%) rename packages/jest-config/src/{index.js => index.ts} (91%) rename packages/jest-config/src/{normalize.js => normalize.ts} (52%) rename packages/jest-config/src/{readConfigFileAndSetRootDir.js => readConfigFileAndSetRootDir.ts} (90%) rename packages/jest-config/src/{resolveConfigPath.js => resolveConfigPath.ts} (87%) rename packages/jest-config/src/{setFromArgv.js => setFromArgv.ts} (86%) rename packages/jest-config/src/{utils.js => utils.ts} (77%) rename packages/jest-config/src/{validatePattern.js => validatePattern.ts} (84%) create mode 100644 packages/jest-config/tsconfig.json create mode 100644 packages/jest-types/src/Argv.ts diff --git a/packages/jest-config/package.json b/packages/jest-config/package.json index fb97cfa3117a..a3886c4bcaf2 100644 --- a/packages/jest-config/package.json +++ b/packages/jest-config/package.json @@ -8,8 +8,10 @@ }, "license": "MIT", "main": "build/index.js", + "types": "build/index.d.ts", "dependencies": { "@babel/core": "^7.1.0", + "@jest/types": "^24.1.0", "babel-jest": "^24.1.0", "chalk": "^2.0.1", "glob": "^7.1.1", diff --git a/packages/jest-config/src/Defaults.js b/packages/jest-config/src/Defaults.ts similarity index 94% rename from packages/jest-config/src/Defaults.js rename to packages/jest-config/src/Defaults.ts index 3ef1d484ba24..b25ff9c149c0 100644 --- a/packages/jest-config/src/Defaults.js +++ b/packages/jest-config/src/Defaults.ts @@ -3,19 +3,16 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {DefaultOptions} from 'types/Config'; - +import {Config} from '@jest/types'; import {replacePathSepForRegex} from 'jest-regex-util'; import {NODE_MODULES} from './constants'; import getCacheDirectory from './getCacheDirectory'; const NODE_MODULES_REGEXP = replacePathSepForRegex(NODE_MODULES); -export default ({ +const defaultOptions: Config.DefaultOptions = { automock: false, bail: 0, browser: false, @@ -83,4 +80,6 @@ export default ({ watch: false, watchPathIgnorePatterns: [], watchman: true, -}: DefaultOptions); +}; + +export default defaultOptions; diff --git a/packages/jest-config/src/Deprecated.js b/packages/jest-config/src/Deprecated.ts similarity index 86% rename from packages/jest-config/src/Deprecated.js rename to packages/jest-config/src/Deprecated.ts index cc14996e5ad6..8ad752842d2a 100644 --- a/packages/jest-config/src/Deprecated.js +++ b/packages/jest-config/src/Deprecated.ts @@ -3,14 +3,12 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ import chalk from 'chalk'; import prettyFormat from 'pretty-format'; -const format = (value: mixed) => prettyFormat(value, {min: true}); +const format = (value: unknown) => prettyFormat(value, {min: true}); export default { mapCoverage: () => ` Option ${chalk.bold( @@ -20,7 +18,7 @@ export default { Please update your configuration.`, preprocessorIgnorePatterns: (options: { - preprocessorIgnorePatterns: Array, + preprocessorIgnorePatterns: Array; }) => ` Option ${chalk.bold( '"preprocessorIgnorePatterns"', )} was replaced by ${chalk.bold( @@ -37,7 +35,7 @@ export default { Please update your configuration.`, scriptPreprocessor: (options: { - scriptPreprocessor: string, + scriptPreprocessor: string; }) => ` Option ${chalk.bold( '"scriptPreprocessor"', )} was replaced by ${chalk.bold( @@ -53,8 +51,8 @@ export default { Please update your configuration.`, - setupTestFrameworkScriptFile: (options: { - setupTestFrameworkScriptFile: Array, + setupTestFrameworkScriptFile: (_options: { + setupTestFrameworkScriptFile: Array; }) => ` Option ${chalk.bold( '"setupTestFrameworkScriptFile"', )} was replaced by configuration ${chalk.bold( @@ -64,7 +62,7 @@ export default { Please update your configuration.`, testPathDirs: (options: { - testPathDirs: Array, + testPathDirs: Array; }) => ` Option ${chalk.bold('"testPathDirs"')} was replaced by ${chalk.bold( '"roots"', )}. diff --git a/packages/jest-config/src/Descriptions.js b/packages/jest-config/src/Descriptions.ts similarity index 97% rename from packages/jest-config/src/Descriptions.js rename to packages/jest-config/src/Descriptions.ts index 44c4e4d6c116..e9088b6849e3 100644 --- a/packages/jest-config/src/Descriptions.js +++ b/packages/jest-config/src/Descriptions.ts @@ -3,11 +3,11 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -export default ({ +import {Config} from '@jest/types'; + +const descriptions: {[key in keyof Config.InitialOptions]: string} = { automock: 'All imported modules in your tests should be mocked automatically', bail: 'Stop running tests after `n` failures', browser: 'Respect "browser" field in package.json when resolving modules', @@ -91,4 +91,6 @@ export default ({ watchPathIgnorePatterns: 'An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode', watchman: 'Whether to use watchman for file crawling', -}: {[string]: string}); +}; + +export default descriptions; diff --git a/packages/jest-config/src/ReporterValidationErrors.js b/packages/jest-config/src/ReporterValidationErrors.ts similarity index 91% rename from packages/jest-config/src/ReporterValidationErrors.js rename to packages/jest-config/src/ReporterValidationErrors.ts index 144b5f4ea950..0ded7dc3ef29 100644 --- a/packages/jest-config/src/ReporterValidationErrors.js +++ b/packages/jest-config/src/ReporterValidationErrors.ts @@ -6,8 +6,8 @@ * @flow */ -import type {ReporterConfig} from 'types/Config'; - +import {Config} from '@jest/types'; +// @ts-ignore: Not migrated to TS import {ValidationError} from 'jest-validate'; import chalk from 'chalk'; import getType from 'jest-get-type'; @@ -26,7 +26,7 @@ const ERROR = `${BULLET}Reporter Validation Error`; */ export function createReporterError( reporterIndex: number, - reporterValue: Array | string, + reporterValue: Array | string, ): ValidationError { const errorMessage = ` Reporter at index ${reporterIndex} must be of type:\n` + @@ -38,7 +38,7 @@ export function createReporterError( } export function createArrayReporterError( - arrayReporter: ReporterConfig, + arrayReporter: Config.ReporterConfig, reporterIndex: number, valueIndex: number, value: string | Object, @@ -63,7 +63,7 @@ export function createArrayReporterError( } export function validateReporters( - reporterConfig: Array, + reporterConfig: Array, ): boolean { return reporterConfig.every((reporter, index) => { if (Array.isArray(reporter)) { @@ -77,7 +77,7 @@ export function validateReporters( } function validateArrayReporter( - arrayReporter: ReporterConfig, + arrayReporter: Config.ReporterConfig, reporterIndex: number, ) { const [path, options] = arrayReporter; diff --git a/packages/jest-config/src/ValidConfig.js b/packages/jest-config/src/ValidConfig.ts similarity index 93% rename from packages/jest-config/src/ValidConfig.js rename to packages/jest-config/src/ValidConfig.ts index 883ebb0b8d56..eb1019e57ec9 100644 --- a/packages/jest-config/src/ValidConfig.js +++ b/packages/jest-config/src/ValidConfig.ts @@ -3,21 +3,19 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {InitialOptions} from 'types/Config'; - +import {Config} from '@jest/types'; import {replacePathSepForRegex} from 'jest-regex-util'; +// @ts-ignore: Not migrated to TS import {multipleValidOptions} from 'jest-validate'; import {NODE_MODULES} from './constants'; const NODE_MODULES_REGEXP = replacePathSepForRegex(NODE_MODULES); -export default ({ +const initialOptions: Config.InitialOptions = { automock: false, - bail: (multipleValidOptions(false, 0): any), + bail: multipleValidOptions(false, 0), browser: false, cache: true, cacheDirectory: '/tmp/user/jest', @@ -40,6 +38,7 @@ export default ({ statements: 100, }, }, + // @ts-ignore: Missing from initial options... https://github.com/facebook/jest/pull/7923 cwd: '/root', dependencyExtractor: '/dependencyExtractor.js', displayName: 'project-name', @@ -135,4 +134,6 @@ export default ({ ], ], watchman: true, -}: InitialOptions); +}; + +export default initialOptions; diff --git a/packages/jest-config/src/__tests__/Defaults.test.js b/packages/jest-config/src/__tests__/Defaults.test.ts similarity index 100% rename from packages/jest-config/src/__tests__/Defaults.test.js rename to packages/jest-config/src/__tests__/Defaults.test.ts diff --git a/packages/jest-config/src/__tests__/getMaxWorkers.test.js b/packages/jest-config/src/__tests__/getMaxWorkers.test.ts similarity index 91% rename from packages/jest-config/src/__tests__/getMaxWorkers.test.js rename to packages/jest-config/src/__tests__/getMaxWorkers.test.ts index 5ce2cf846dd1..aab3290c5357 100644 --- a/packages/jest-config/src/__tests__/getMaxWorkers.test.js +++ b/packages/jest-config/src/__tests__/getMaxWorkers.test.ts @@ -38,16 +38,19 @@ describe('getMaxWorkers', () => { describe('% based', () => { it('50% = 2 workers', () => { const argv = {maxWorkers: '50%'}; + // @ts-ignore: need to fix the typing expect(getMaxWorkers(argv)).toBe(2); }); it('< 0 workers should become 1', () => { const argv = {maxWorkers: '1%'}; + // @ts-ignore: need to fix the typing expect(getMaxWorkers(argv)).toBe(1); }); it("0% shouldn't break", () => { const argv = {maxWorkers: '0%'}; + // @ts-ignore: need to fix the typing expect(getMaxWorkers(argv)).toBe(1); }); }); diff --git a/packages/jest-config/src/__tests__/readConfig.test.js b/packages/jest-config/src/__tests__/readConfig.test.ts similarity index 95% rename from packages/jest-config/src/__tests__/readConfig.test.js rename to packages/jest-config/src/__tests__/readConfig.test.ts index f3914aa8b89c..58ff3551430b 100644 --- a/packages/jest-config/src/__tests__/readConfig.test.js +++ b/packages/jest-config/src/__tests__/readConfig.test.ts @@ -5,6 +5,7 @@ import {readConfig} from '../index'; test('readConfig() throws when an object is passed without a file path', () => { expect(() => { readConfig( + // @ts-ignore null /* argv */, {} /* packageRootOrConfig */, false /* skipArgvConfigOption */, diff --git a/packages/jest-config/src/__tests__/readConfigs.test.js b/packages/jest-config/src/__tests__/readConfigs.test.ts similarity index 94% rename from packages/jest-config/src/__tests__/readConfigs.test.js rename to packages/jest-config/src/__tests__/readConfigs.test.ts index 0e114e97b3c7..e08204487c07 100644 --- a/packages/jest-config/src/__tests__/readConfigs.test.js +++ b/packages/jest-config/src/__tests__/readConfigs.test.ts @@ -4,6 +4,7 @@ import {readConfigs} from '../index'; test('readConfigs() throws when called without project paths', () => { expect(() => { + // @ts-ignore readConfigs(null /* argv */, [] /* projectPaths */); }).toThrowError('jest: No configuration found for any project.'); }); diff --git a/packages/jest-config/src/__tests__/resolveConfigPath.test.js b/packages/jest-config/src/__tests__/resolveConfigPath.test.ts similarity index 99% rename from packages/jest-config/src/__tests__/resolveConfigPath.test.js rename to packages/jest-config/src/__tests__/resolveConfigPath.test.ts index 5000d5e58ee8..713c38def193 100644 --- a/packages/jest-config/src/__tests__/resolveConfigPath.test.js +++ b/packages/jest-config/src/__tests__/resolveConfigPath.test.ts @@ -3,8 +3,6 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ import os from 'os'; diff --git a/packages/jest-config/src/__tests__/setFromArgv.test.js b/packages/jest-config/src/__tests__/setFromArgv.test.ts similarity index 83% rename from packages/jest-config/src/__tests__/setFromArgv.test.js rename to packages/jest-config/src/__tests__/setFromArgv.test.ts index 182ab5bb570a..03c9a6809cb0 100644 --- a/packages/jest-config/src/__tests__/setFromArgv.test.js +++ b/packages/jest-config/src/__tests__/setFromArgv.test.ts @@ -6,16 +6,17 @@ * */ +import {Argv, Config} from '@jest/types'; import setFromArgv from '../setFromArgv'; test('maps special values to valid options', () => { - const options = {}; + const options = {} as Config.InitialOptions; const argv = { coverage: true, env: 'node', json: true, watchAll: true, - }; + } as Argv.Argv; expect(setFromArgv(options, argv)).toMatchObject({ collectCoverage: true, @@ -27,12 +28,12 @@ test('maps special values to valid options', () => { }); test('maps regular values to themselves', () => { - const options = {}; + const options = {} as Config.InitialOptions; const argv = { collectCoverageOnlyFrom: ['a', 'b'], coverageDirectory: 'covDir', watchman: true, - }; + } as Argv.Argv; expect(setFromArgv(options, argv)).toMatchObject({ collectCoverageOnlyFrom: ['a', 'b'], @@ -42,11 +43,11 @@ test('maps regular values to themselves', () => { }); test('works with string objects', () => { - const options = {}; + const options = {} as Config.InitialOptions; const argv = { moduleNameMapper: '{"types/(.*)": "/src/types/$1"}', transform: '{"*.js": "/transformer"}', - }; + } as Argv.Argv; expect(setFromArgv(options, argv)).toMatchObject({ moduleNameMapper: { 'types/(.*)': '/src/types/$1', @@ -58,10 +59,10 @@ test('works with string objects', () => { }); test('explicit flags override those from --config', () => { - const options = {}; + const options = {} as Config.InitialOptions; const argv = { config: '{"watch": false}', watch: true, - }; + } as Argv.Argv; expect(setFromArgv(options, argv)).toMatchObject({watch: true}); }); diff --git a/packages/jest-config/src/__tests__/validatePattern.test.js b/packages/jest-config/src/__tests__/validatePattern.test.ts similarity index 100% rename from packages/jest-config/src/__tests__/validatePattern.test.js rename to packages/jest-config/src/__tests__/validatePattern.test.ts diff --git a/packages/jest-config/src/constants.js b/packages/jest-config/src/constants.ts similarity index 97% rename from packages/jest-config/src/constants.js rename to packages/jest-config/src/constants.ts index 3877e91b3177..90f055b87320 100644 --- a/packages/jest-config/src/constants.js +++ b/packages/jest-config/src/constants.ts @@ -3,8 +3,6 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ import path from 'path'; diff --git a/packages/jest-config/src/getCacheDirectory.js b/packages/jest-config/src/getCacheDirectory.ts similarity index 91% rename from packages/jest-config/src/getCacheDirectory.js rename to packages/jest-config/src/getCacheDirectory.ts index 587d9c21fdb6..d9a4ca266460 100644 --- a/packages/jest-config/src/getCacheDirectory.js +++ b/packages/jest-config/src/getCacheDirectory.ts @@ -3,13 +3,10 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -const path = require('path'); -const os = require('os'); - +import path from 'path'; +import os from 'os'; import {sync as realpath} from 'realpath-native'; const getCacheDirectory = () => { diff --git a/packages/jest-config/src/getMaxWorkers.js b/packages/jest-config/src/getMaxWorkers.ts similarity index 60% rename from packages/jest-config/src/getMaxWorkers.js rename to packages/jest-config/src/getMaxWorkers.ts index b8419dfcbc7a..626ebb94c3f3 100644 --- a/packages/jest-config/src/getMaxWorkers.js +++ b/packages/jest-config/src/getMaxWorkers.ts @@ -3,23 +3,24 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {Argv} from 'types/Argv'; - import os from 'os'; +import {Argv} from '@jest/types'; -export default function getMaxWorkers(argv: Argv): number { +export default function getMaxWorkers( + argv: Partial>, +): number { if (argv.runInBand) { return 1; } else if (argv.maxWorkers) { - const parsed = parseInt(argv.maxWorkers, 10); + // TODO: How to type this properly? Should probably use `coerce` from `yargs` + const maxWorkers = (argv.maxWorkers as unknown) as number | string; + const parsed = parseInt(maxWorkers as string, 10); if ( - typeof argv.maxWorkers === 'string' && - argv.maxWorkers.trim().endsWith('%') && + typeof maxWorkers === 'string' && + maxWorkers.trim().endsWith('%') && parsed > 0 && parsed <= 100 ) { diff --git a/packages/jest-config/src/index.js b/packages/jest-config/src/index.ts similarity index 91% rename from packages/jest-config/src/index.js rename to packages/jest-config/src/index.ts index 81d6970908c5..8ff81bb67206 100644 --- a/packages/jest-config/src/index.js +++ b/packages/jest-config/src/index.ts @@ -3,26 +3,16 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {Argv} from 'types/Argv'; -import type { - GlobalConfig, - InitialOptions, - Path, - ProjectConfig, -} from 'types/Config'; - -import chalk from 'chalk'; import fs from 'fs'; import path from 'path'; +import {Argv, Config} from '@jest/types'; +import chalk from 'chalk'; import {isJSONString, replaceRootDirInPath} from './utils'; import normalize from './normalize'; import resolveConfigPath from './resolveConfigPath'; import readConfigFileAndSetRootDir from './readConfigFileAndSetRootDir'; - export {getTestEnvironment, isJSONString} from './utils'; export {default as normalize} from './normalize'; export {default as deprecationEntries} from './Deprecated'; @@ -30,22 +20,24 @@ export {replaceRootDirInPath} from './utils'; export {default as defaults} from './Defaults'; export {default as descriptions} from './Descriptions'; +type ReadConfig = { + configPath: Config.Path | null | undefined; + globalConfig: Config.GlobalConfig; + hasDeprecationWarnings: boolean; + projectConfig: Config.ProjectConfig; +}; + export function readConfig( - argv: Argv, - packageRootOrConfig: Path | InitialOptions, + argv: Argv.Argv, + packageRootOrConfig: Config.Path | Config.InitialOptions, // Whether it needs to look into `--config` arg passed to CLI. // It only used to read initial config. If the initial config contains // `project` property, we don't want to read `--config` value and rather // read individual configs for every project. skipArgvConfigOption?: boolean, - parentConfigPath: ?Path, - projectIndex?: number = Infinity, -): { - configPath: ?Path, - globalConfig: GlobalConfig, - hasDeprecationWarnings: boolean, - projectConfig: ProjectConfig, -} { + parentConfigPath?: Config.Path | null, + projectIndex: number = Infinity, +): ReadConfig { let rawOptions; let configPath = null; @@ -104,8 +96,11 @@ export function readConfig( } const groupOptions = ( - options: Object, -): {globalConfig: GlobalConfig, projectConfig: ProjectConfig} => ({ + options: Config.ProjectConfig & Config.GlobalConfig, +): { + globalConfig: Config.GlobalConfig; + projectConfig: Config.ProjectConfig; +} => ({ globalConfig: Object.freeze({ bail: options.bail, changedFilesWithAncestor: options.changedFilesWithAncestor, @@ -218,11 +213,15 @@ const groupOptions = ( }), }); -const ensureNoDuplicateConfigs = (parsedConfigs, projects) => { +const ensureNoDuplicateConfigs = ( + parsedConfigs: Array, + projects: Config.GlobalConfig['projects'], +) => { const configPathMap = new Map(); for (const config of parsedConfigs) { const {configPath} = config; + if (configPathMap.has(configPath)) { const message = `Whoops! Two projects resolved to the same config path: ${chalk.bold( String(configPath), @@ -256,18 +255,18 @@ This usually means that your ${chalk.bold( // If no projects are specified, process.cwd() will be used as the default // (and only) project. export function readConfigs( - argv: Argv, - projectPaths: Array, + argv: Argv.Argv, + projectPaths: Array, ): { - globalConfig: GlobalConfig, - configs: Array, - hasDeprecationWarnings: boolean, + globalConfig: Config.GlobalConfig; + configs: Array; + hasDeprecationWarnings: boolean; } { let globalConfig; let hasDeprecationWarnings; - let configs: Array = []; + let configs: Array = []; let projects = projectPaths; - let configPath: ?Path; + let configPath: Config.Path | null | undefined; if (projectPaths.length === 1) { const parsedConfig = readConfig(argv, projects[0]); diff --git a/packages/jest-config/src/normalize.js b/packages/jest-config/src/normalize.ts similarity index 52% rename from packages/jest-config/src/normalize.js rename to packages/jest-config/src/normalize.ts index f938d520e03d..014c6e38304f 100644 --- a/packages/jest-config/src/normalize.js +++ b/packages/jest-config/src/normalize.ts @@ -3,32 +3,22 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {Argv} from 'types/Argv'; -import type { - InitialOptions, - DefaultOptions, - ReporterConfig, - GlobalConfig, - Path, - ProjectConfig, -} from 'types/Config'; - import crypto from 'crypto'; -import glob from 'glob'; import path from 'path'; +import glob from 'glob'; +import {Argv, Config} from '@jest/types'; +// @ts-ignore: Not migrated to TS import {ValidationError, validate} from 'jest-validate'; -import validatePattern from './validatePattern'; import {clearLine, replacePathSepForGlob} from 'jest-util'; import chalk from 'chalk'; -import getMaxWorkers from './getMaxWorkers'; import micromatch from 'micromatch'; import {sync as realpath} from 'realpath-native'; import Resolver from 'jest-resolve'; import {replacePathSepForRegex} from 'jest-regex-util'; +import validatePattern from './validatePattern'; +import getMaxWorkers from './getMaxWorkers'; import { BULLET, DOCUMENTATION_NOTE, @@ -46,18 +36,19 @@ import DEFAULT_CONFIG from './Defaults'; import DEPRECATED_CONFIG from './Deprecated'; import setFromArgv from './setFromArgv'; import VALID_CONFIG from './ValidConfig'; - const ERROR = `${BULLET}Validation Error`; const PRESET_EXTENSIONS = ['.json', '.js']; const PRESET_NAME = 'jest-preset'; -const createConfigError = message => +type AllOptions = Config.ProjectConfig & Config.GlobalConfig; + +const createConfigError = (message: string) => new ValidationError(ERROR, message, DOCUMENTATION_NOTE); const mergeOptionWithPreset = ( - options: InitialOptions, - preset: InitialOptions, - optionName: string, + options: Config.InitialOptions, + preset: Config.InitialOptions, + optionName: keyof Config.InitialOptions, ) => { if (options[optionName] && preset[optionName]) { options[optionName] = { @@ -69,10 +60,10 @@ const mergeOptionWithPreset = ( }; const setupPreset = ( - options: InitialOptions, + options: Config.InitialOptions, optionsPreset: string, -): InitialOptions => { - let preset; +): Config.InitialOptions => { + let preset: Config.InitialOptions; const presetPath = replaceRootDirInPath(options.rootDir, optionsPreset); const presetModule = Resolver.findNodeModule( presetPath.startsWith('.') @@ -92,8 +83,8 @@ const setupPreset = ( } } catch (e) {} - // $FlowFixMe - preset = (require(presetModule): InitialOptions); + // @ts-ignore: `presetModule` can be null? + preset = require(presetModule); } catch (error) { if (error instanceof SyntaxError || error instanceof TypeError) { throw createConfigError( @@ -132,7 +123,7 @@ const setupPreset = ( return {...preset, ...options}; }; -const setupBabelJest = (options: InitialOptions) => { +const setupBabelJest = (options: Config.InitialOptions) => { const transform = options.transform; let babelJest; if (transform) { @@ -175,10 +166,10 @@ const setupBabelJest = (options: InitialOptions) => { }; const normalizeCollectCoverageOnlyFrom = ( - options: InitialOptions, - key: string, + options: Config.InitialOptions, + key: keyof Pick, ) => { - const collectCoverageOnlyFrom = Array.isArray(options[key]) + const collectCoverageOnlyFrom: Config.Glob[] = Array.isArray(options[key]) ? options[key] // passed from argv : Object.keys(options[key]); // passed from options return collectCoverageOnlyFrom.reduce((map, filePath) => { @@ -191,8 +182,11 @@ const normalizeCollectCoverageOnlyFrom = ( }, Object.create(null)); }; -const normalizeCollectCoverageFrom = (options: InitialOptions, key: string) => { - let value; +const normalizeCollectCoverageFrom = ( + options: Config.InitialOptions, + key: keyof Pick, +) => { + let value: Config.Glob[] | undefined; if (!options[key]) { value = []; } @@ -219,8 +213,16 @@ const normalizeCollectCoverageFrom = (options: InitialOptions, key: string) => { }; const normalizeUnmockedModulePathPatterns = ( - options: InitialOptions, - key: string, + options: Config.InitialOptions, + key: keyof Pick< + Config.InitialOptions, + | 'coveragePathIgnorePatterns' + | 'modulePathIgnorePatterns' + | 'testPathIgnorePatterns' + | 'transformIgnorePatterns' + | 'watchPathIgnorePatterns' + | 'unmockedModulePathPatterns' + >, ) => // _replaceRootDirTags is specifically well-suited for substituting // in paths (it deals with properly interpreting relative path @@ -228,11 +230,13 @@ const normalizeUnmockedModulePathPatterns = ( // // For patterns, direct global substitution is far more ideal, so we // special case substitutions for patterns here. - options[key].map(pattern => + options[key]!.map(pattern => replacePathSepForRegex(pattern.replace(//g, options.rootDir)), ); -const normalizePreprocessor = (options: InitialOptions): InitialOptions => { +const normalizePreprocessor = ( + options: Config.InitialOptions, +): Config.InitialOptions => { if (options.scriptPreprocessor && options.transform) { throw createConfigError( ` Options: ${chalk.bold('scriptPreprocessor')} and ${chalk.bold( @@ -269,10 +273,10 @@ const normalizePreprocessor = (options: InitialOptions): InitialOptions => { }; const normalizeMissingOptions = ( - options: InitialOptions, - configPath: ?Path, + options: Config.InitialOptions, + configPath: Config.Path | null, projectIndex: number, -): InitialOptions => { +): Config.InitialOptions => { if (!options.name) { options.name = crypto .createHash('md5') @@ -290,7 +294,9 @@ const normalizeMissingOptions = ( return options; }; -const normalizeRootDir = (options: InitialOptions): InitialOptions => { +const normalizeRootDir = ( + options: Config.InitialOptions, +): Config.InitialOptions => { // Assert that there *is* a rootDir if (!options.hasOwnProperty('rootDir')) { throw createConfigError( @@ -309,7 +315,7 @@ const normalizeRootDir = (options: InitialOptions): InitialOptions => { return options; }; -const normalizeReporters = (options: InitialOptions, basedir) => { +const normalizeReporters = (options: Config.InitialOptions) => { const reporters = options.reporters; if (!reporters || !Array.isArray(reporters)) { return options; @@ -317,7 +323,7 @@ const normalizeReporters = (options: InitialOptions, basedir) => { validateReporters(reporters); options.reporters = reporters.map(reporterConfig => { - const normalizedReporterConfig: ReporterConfig = + const normalizedReporterConfig: Config.ReporterConfig = typeof reporterConfig === 'string' ? // if reporter config is a string, we wrap it in an array // and pass an empty object for options argument, to normalize @@ -348,7 +354,7 @@ const normalizeReporters = (options: InitialOptions, basedir) => { return options; }; -const buildTestPathPattern = (argv: Argv): string => { +const buildTestPathPattern = (argv: Argv.Argv): string => { const patterns = []; if (argv._) { @@ -386,11 +392,14 @@ const showTestPathPatternError = (testPathPattern: string) => { }; export default function normalize( - options: InitialOptions, - argv: Argv, - configPath: ?Path, - projectIndex?: number = Infinity, -) { + options: Config.InitialOptions, + argv: Argv.Argv, + configPath?: Config.Path | null, + projectIndex: number = Infinity, +): { + hasDeprecationWarnings: boolean; + options: AllOptions; +} { const {hasDeprecationWarnings} = validate(options, { comment: DOCUMENTATION_NOTE, deprecatedConfig: DEPRECATED_CONFIG, @@ -429,14 +438,12 @@ export default function normalize( options.setupTestFrameworkScriptFile && options.setupFilesAfterEnv.length > 0 ) { - throw createConfigError( - ` Options: ${chalk.bold( - 'setupTestFrameworkScriptFile', - )} and ${chalk.bold('setupFilesAfterEnv')} cannot be used together. + throw createConfigError(` Options: ${chalk.bold( + 'setupTestFrameworkScriptFile', + )} and ${chalk.bold('setupFilesAfterEnv')} cannot be used together. Please change your configuration to only use ${chalk.bold( 'setupFilesAfterEnv', - )}.`, - ); + )}.`); } if (options.setupTestFrameworkScriptFile) { @@ -452,6 +459,7 @@ export default function normalize( options.roots = options.testPathDirs; delete options.testPathDirs; } + if (!options.roots) { options.roots = [options.rootDir]; } @@ -465,14 +473,13 @@ export default function normalize( } setupBabelJest(options); - - const newOptions: $Shape = { + const newOptions: Partial = { ...DEFAULT_CONFIG, }; try { // try to resolve windows short paths, ignoring errors (permission errors, mostly) - newOptions.cwd = realpath(newOptions.cwd); + newOptions.cwd = realpath(newOptions.cwd!); } catch (e) { // ignored } @@ -485,310 +492,344 @@ export default function normalize( }); } - Object.keys(options).reduce((newOptions, key: $Keys) => { - // The resolver has been resolved separately; skip it - if (key === 'resolver') { - return newOptions; - } - let value; - switch (key) { - case 'collectCoverageOnlyFrom': - value = normalizeCollectCoverageOnlyFrom(options, key); - break; - case 'setupFiles': - case 'setupFilesAfterEnv': - case 'snapshotSerializers': - value = - options[key] && - options[key].map(filePath => - resolve(newOptions.resolver, { - filePath, - key, - rootDir: options.rootDir, - }), - ); - break; - case 'modulePaths': - case 'roots': - value = - options[key] && - options[key].map(filePath => - path.resolve( - options.rootDir, - replaceRootDirInPath(options.rootDir, filePath), - ), - ); - break; - case 'collectCoverageFrom': - value = normalizeCollectCoverageFrom(options, key); - break; - case 'cacheDirectory': - case 'coverageDirectory': - value = - options[key] && - path.resolve( - options.rootDir, - replaceRootDirInPath(options.rootDir, options[key]), - ); - break; - case 'dependencyExtractor': - case 'globalSetup': - case 'globalTeardown': - case 'moduleLoader': - case 'snapshotResolver': - case 'testResultsProcessor': - case 'testRunner': - case 'filter': - value = - options[key] && - resolve(newOptions.resolver, { - filePath: options[key], - key, - rootDir: options.rootDir, - }); - break; - case 'runner': - value = - options[key] && - getRunner(newOptions.resolver, { - filePath: options[key], - rootDir: options.rootDir, - }); - break; - case 'prettierPath': - // We only want this to throw if "prettierPath" is explicitly passed - // from config or CLI, and the requested path isn't found. Otherwise we - // set it to null and throw an error lazily when it is used. - value = - options[key] && - resolve(newOptions.resolver, { - filePath: options[key], - key, - optional: options[key] === DEFAULT_CONFIG[key], - rootDir: options.rootDir, - }); - break; - case 'moduleNameMapper': - const moduleNameMapper = options[key]; - value = - moduleNameMapper && - Object.keys(moduleNameMapper).map(regex => { - const item = moduleNameMapper && moduleNameMapper[regex]; - return item && [regex, _replaceRootDirTags(options.rootDir, item)]; - }); - break; - case 'transform': - const transform = options[key]; - value = - transform && - Object.keys(transform).map(regex => [ - regex, - resolve(newOptions.resolver, { - filePath: transform[regex], - key, + Object.keys(options).reduce( + (newOptions, key: keyof Config.InitialOptions) => { + // The resolver has been resolved separately; skip it + if (key === 'resolver') { + return newOptions; + } + let value; + switch (key) { + case 'collectCoverageOnlyFrom': + value = normalizeCollectCoverageOnlyFrom(options, key); + break; + case 'setupFiles': + case 'setupFilesAfterEnv': + case 'snapshotSerializers': + { + const option = options[key]; + value = + option && + option.map(filePath => + resolve(newOptions.resolver, { + filePath, + key, + rootDir: options.rootDir, + }), + ); + } + break; + case 'modulePaths': + case 'roots': + { + const option = options[key]; + value = + option && + option.map(filePath => + path.resolve( + options.rootDir, + replaceRootDirInPath(options.rootDir, filePath), + ), + ); + } + break; + case 'collectCoverageFrom': + value = normalizeCollectCoverageFrom(options, key); + break; + case 'cacheDirectory': + case 'coverageDirectory': + { + const option = options[key]; + value = + option && + path.resolve( + options.rootDir, + replaceRootDirInPath(options.rootDir, option), + ); + } + break; + case 'dependencyExtractor': + case 'globalSetup': + case 'globalTeardown': + case 'moduleLoader': + case 'snapshotResolver': + case 'testResultsProcessor': + case 'testRunner': + case 'filter': + { + const option = options[key]; + value = + option && + resolve(newOptions.resolver, { + filePath: option, + key, + rootDir: options.rootDir, + }); + } + break; + case 'runner': + { + const option = options[key]; + value = + option && + getRunner(newOptions.resolver, { + filePath: option, + rootDir: options.rootDir, + }); + } + break; + case 'prettierPath': + { + // We only want this to throw if "prettierPath" is explicitly passed + // from config or CLI, and the requested path isn't found. Otherwise we + // set it to null and throw an error lazily when it is used. + + const option = options[key]; + + value = + option && + resolve(newOptions.resolver, { + filePath: option, + key, + optional: option === DEFAULT_CONFIG[key], + rootDir: options.rootDir, + }); + } + break; + case 'moduleNameMapper': + const moduleNameMapper = options[key]; + value = + moduleNameMapper && + Object.keys(moduleNameMapper).map(regex => { + const item = moduleNameMapper && moduleNameMapper[regex]; + return ( + item && [regex, _replaceRootDirTags(options.rootDir, item)] + ); + }); + break; + case 'transform': + const transform = options[key]; + value = + transform && + Object.keys(transform).map(regex => [ + regex, + resolve(newOptions.resolver, { + filePath: transform[regex], + key, + rootDir: options.rootDir, + }), + ]); + break; + case 'coveragePathIgnorePatterns': + case 'modulePathIgnorePatterns': + case 'testPathIgnorePatterns': + case 'transformIgnorePatterns': + case 'watchPathIgnorePatterns': + case 'unmockedModulePathPatterns': + value = normalizeUnmockedModulePathPatterns(options, key); + break; + case 'haste': + value = {...options[key]}; + if (value.hasteImplModulePath != null) { + value.hasteImplModulePath = resolve(newOptions.resolver, { + filePath: replaceRootDirInPath( + options.rootDir, + value.hasteImplModulePath, + ), + key: 'haste.hasteImplModulePath', rootDir: options.rootDir, - }), - ]); - break; - case 'coveragePathIgnorePatterns': - case 'modulePathIgnorePatterns': - case 'testPathIgnorePatterns': - case 'transformIgnorePatterns': - case 'watchPathIgnorePatterns': - case 'unmockedModulePathPatterns': - value = normalizeUnmockedModulePathPatterns(options, key); - break; - case 'haste': - value = {...options[key]}; - if (value.hasteImplModulePath != null) { - value.hasteImplModulePath = resolve(newOptions.resolver, { - filePath: replaceRootDirInPath( - options.rootDir, - value.hasteImplModulePath, - ), - key: 'haste.hasteImplModulePath', - rootDir: options.rootDir, - }); - } - break; - case 'projects': - value = (options[key] || []) - .map(project => - typeof project === 'string' - ? _replaceRootDirTags(options.rootDir, project) - : project, - ) - .reduce((projects, project) => { - // Project can be specified as globs. If a glob matches any files, - // We expand it to these paths. If not, we keep the original path - // for the future resolution. - const globMatches = - typeof project === 'string' ? glob.sync(project) : []; - return projects.concat(globMatches.length ? globMatches : project); - }, []); - break; - case 'moduleDirectories': - case 'testMatch': - { - const replacedRootDirTags = _replaceRootDirTags( - escapeGlobCharacters(options.rootDir), - options[key], - ); - - if (replacedRootDirTags) { - value = Array.isArray(replacedRootDirTags) - ? replacedRootDirTags.map(replacePathSepForGlob) - : replacePathSepForGlob(replacedRootDirTags); - } else { - value = replacedRootDirTags; + }); } - } - break; - case 'testRegex': - value = options[key] - ? [].concat(options[key]).map(replacePathSepForRegex) - : []; - break; - case 'moduleFileExtensions': { - value = options[key]; + break; + case 'projects': + value = (options[key] || []) + .map(project => + typeof project === 'string' + ? _replaceRootDirTags(options.rootDir, project) + : project, + ) + .reduce((projects, project) => { + // Project can be specified as globs. If a glob matches any files, + // We expand it to these paths. If not, we keep the original path + // for the future resolution. + const globMatches = + typeof project === 'string' ? glob.sync(project) : []; + return projects.concat( + globMatches.length ? globMatches : project, + ); + }, []); + break; + case 'moduleDirectories': + case 'testMatch': + { + const replacedRootDirTags = _replaceRootDirTags( + escapeGlobCharacters(options.rootDir), + options[key], + ); + + if (replacedRootDirTags) { + value = Array.isArray(replacedRootDirTags) + ? replacedRootDirTags.map(replacePathSepForGlob) + : replacePathSepForGlob(replacedRootDirTags); + } else { + value = replacedRootDirTags; + } + } + break; + case 'testRegex': + { + const option = options[key]; + value = option + ? (Array.isArray(option) ? option : [option]).map( + replacePathSepForRegex, + ) + : []; + } + break; + case 'moduleFileExtensions': { + value = options[key]; - if ( - Array.isArray(value) && // If it's the wrong type, it can throw at a later time - (options.runner === undefined || - options.runner === DEFAULT_CONFIG.runner) && // Only require 'js' for the default jest-runner - !value.includes('js') - ) { - const errorMessage = - ` moduleFileExtensions must include 'js':\n` + - ` but instead received:\n` + - ` ${chalk.bold.red(JSON.stringify(value))}`; - - // If `js` is not included, any dependency Jest itself injects into - // the environment, like jasmine or sourcemap-support, will need to - // `require` its modules with a file extension. This is not plausible - // in the long run, so it's way easier to just fail hard early. - // We might consider throwing if `json` is missing as well, as it's a - // fair assumption from modules that they can do - // `require('some-package/package') without the trailing `.json` as it - // works in Node normally. - throw createConfigError( - errorMessage + - "\n Please change your configuration to include 'js'.", - ); - } + if ( + Array.isArray(value) && // If it's the wrong type, it can throw at a later time + (options.runner === undefined || + options.runner === DEFAULT_CONFIG.runner) && // Only require 'js' for the default jest-runner + !value.includes('js') + ) { + const errorMessage = + ` moduleFileExtensions must include 'js':\n` + + ` but instead received:\n` + + ` ${chalk.bold.red(JSON.stringify(value))}`; + + // If `js` is not included, any dependency Jest itself injects into + // the environment, like jasmine or sourcemap-support, will need to + // `require` its modules with a file extension. This is not plausible + // in the long run, so it's way easier to just fail hard early. + // We might consider throwing if `json` is missing as well, as it's a + // fair assumption from modules that they can do + // `require('some-package/package') without the trailing `.json` as it + // works in Node normally. + throw createConfigError( + errorMessage + + "\n Please change your configuration to include 'js'.", + ); + } - break; - } - case 'bail': { - if (typeof options[key] === 'boolean') { - value = options[key] ? 1 : 0; - } else if (typeof options[key] === 'string') { - value = 1; - // If Jest is invoked as `jest --bail someTestPattern` then need to - // move the pattern from the `bail` configuration and into `argv._` - // to be processed as an extra parameter - argv._.push(options[key]); - } else { - value = options[key]; + break; } - break; - } - case 'automock': - case 'browser': - case 'cache': - case 'changedSince': - case 'changedFilesWithAncestor': - case 'clearMocks': - case 'collectCoverage': - case 'coverageReporters': - case 'coverageThreshold': - case 'detectLeaks': - case 'detectOpenHandles': - case 'displayName': - case 'errorOnDeprecated': - case 'expand': - case 'extraGlobals': - case 'globals': - case 'findRelatedTests': - case 'forceCoverageMatch': - case 'forceExit': - case 'lastCommit': - case 'listTests': - case 'logHeapUsage': - case 'maxConcurrency': - case 'mapCoverage': - case 'name': - case 'noStackTrace': - case 'notify': - case 'notifyMode': - case 'onlyChanged': - case 'outputFile': - case 'passWithNoTests': - case 'replname': - case 'reporters': - case 'resetMocks': - case 'resetModules': - case 'restoreMocks': - case 'rootDir': - case 'runTestsByPath': - case 'silent': - case 'skipFilter': - case 'skipNodeResolution': - case 'testEnvironment': - case 'testEnvironmentOptions': - case 'testFailureExitCode': - case 'testLocationInResults': - case 'testNamePattern': - case 'testURL': - case 'timers': - case 'useStderr': - case 'verbose': - case 'watch': - case 'watchAll': - case 'watchman': - value = options[key]; - break; - case 'watchPlugins': - value = (options[key] || []).map(watchPlugin => { - if (typeof watchPlugin === 'string') { - return { - config: {}, - path: getWatchPlugin(newOptions.resolver, { - filePath: watchPlugin, - rootDir: options.rootDir, - }), - }; + case 'bail': { + const bail = options[key]; + if (typeof bail === 'boolean') { + value = bail ? 1 : 0; + } else if (typeof bail === 'string') { + value = 1; + // If Jest is invoked as `jest --bail someTestPattern` then need to + // move the pattern from the `bail` configuration and into `argv._` + // to be processed as an extra parameter + argv._.push(bail); } else { - return { - config: watchPlugin[1] || {}, - path: getWatchPlugin(newOptions.resolver, { - filePath: watchPlugin[0], - rootDir: options.rootDir, - }), - }; + value = options[key]; } - }); - break; - } - // $FlowFixMe - automock is missing in GlobalConfig, so what - newOptions[key] = value; - return newOptions; - }, newOptions); + break; + } + case 'automock': + case 'browser': + case 'cache': + case 'changedSince': + case 'changedFilesWithAncestor': + case 'clearMocks': + case 'collectCoverage': + case 'coverageReporters': + case 'coverageThreshold': + case 'detectLeaks': + case 'detectOpenHandles': + case 'displayName': + case 'errorOnDeprecated': + case 'expand': + case 'extraGlobals': + case 'globals': + case 'findRelatedTests': + case 'forceCoverageMatch': + case 'forceExit': + case 'lastCommit': + case 'listTests': + case 'logHeapUsage': + case 'maxConcurrency': + case 'mapCoverage': + case 'name': + case 'noStackTrace': + case 'notify': + case 'notifyMode': + case 'onlyChanged': + case 'outputFile': + case 'passWithNoTests': + case 'replname': + case 'reporters': + case 'resetMocks': + case 'resetModules': + case 'restoreMocks': + case 'rootDir': + case 'runTestsByPath': + case 'silent': + case 'skipFilter': + case 'skipNodeResolution': + case 'testEnvironment': + case 'testEnvironmentOptions': + case 'testFailureExitCode': + case 'testLocationInResults': + case 'testNamePattern': + case 'testURL': + case 'timers': + case 'useStderr': + case 'verbose': + case 'watch': + case 'watchAll': + case 'watchman': + value = options[key]; + break; + case 'watchPlugins': + value = (options[key] || []).map(watchPlugin => { + if (typeof watchPlugin === 'string') { + return { + config: {}, + path: getWatchPlugin(newOptions.resolver, { + filePath: watchPlugin, + rootDir: options.rootDir, + }), + }; + } else { + return { + config: watchPlugin[1] || {}, + path: getWatchPlugin(newOptions.resolver, { + filePath: watchPlugin[0], + rootDir: options.rootDir, + }), + }; + } + }); + break; + } + // @ts-ignore: automock is missing in GlobalConfig, so what + newOptions[key] = value; + return newOptions; + }, + newOptions, + ); newOptions.nonFlagArgs = argv._; newOptions.testPathPattern = buildTestPathPattern(argv); newOptions.json = argv.json; - newOptions.testFailureExitCode = parseInt(newOptions.testFailureExitCode, 10); + newOptions.testFailureExitCode = parseInt( + (newOptions.testFailureExitCode as unknown) as string, + 10, + ); - for (const key of [ - 'lastCommit', - 'changedFilesWithAncestor', - 'changedSince', - ]) { - if (newOptions[key]) { - newOptions.onlyChanged = true; - } + if ( + newOptions.lastCommit || + newOptions.changedFilesWithAncestor || + newOptions.changedSince + ) { + newOptions.onlyChanged = true; } if (argv.all) { @@ -806,17 +847,20 @@ export default function normalize( ? 'all' : 'new'; - newOptions.maxConcurrency = parseInt(newOptions.maxConcurrency, 10); + newOptions.maxConcurrency = parseInt( + (newOptions.maxConcurrency as unknown) as string, + 10, + ); newOptions.maxWorkers = getMaxWorkers(argv); - if (newOptions.testRegex.length && options.testMatch) { + if (newOptions.testRegex!.length && options.testMatch) { throw createConfigError( ` Configuration options ${chalk.bold('testMatch')} and` + ` ${chalk.bold('testRegex')} cannot be used together.`, ); } - if (newOptions.testRegex.length && !options.testMatch) { + if (newOptions.testRegex!.length && !options.testMatch) { // Prevent the default testMatch conflicting with any explicitly // configured `testRegex` value newOptions.testMatch = []; @@ -849,7 +893,7 @@ export default function normalize( if ( !micromatch.some( replacePathSepForGlob(path.relative(options.rootDir, filename)), - newOptions.collectCoverageFrom, + newOptions.collectCoverageFrom!, ) ) { return patterns; diff --git a/packages/jest-config/src/readConfigFileAndSetRootDir.js b/packages/jest-config/src/readConfigFileAndSetRootDir.ts similarity index 90% rename from packages/jest-config/src/readConfigFileAndSetRootDir.js rename to packages/jest-config/src/readConfigFileAndSetRootDir.ts index a4a23f5eb02c..497a238a9281 100644 --- a/packages/jest-config/src/readConfigFileAndSetRootDir.js +++ b/packages/jest-config/src/readConfigFileAndSetRootDir.ts @@ -3,26 +3,23 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {InitialOptions, Path} from 'types/Config'; - import path from 'path'; import fs from 'fs'; +import {Config} from '@jest/types'; +// @ts-ignore: vendored import jsonlint from './vendor/jsonlint'; import {PACKAGE_JSON} from './constants'; // Read the configuration and set its `rootDir` // 1. If it's a `package.json` file, we look into its "jest" property // 2. For any other file, we just require it. -export default (configPath: Path): InitialOptions => { +export default (configPath: Config.Path): Config.InitialOptions => { const isJSON = configPath.endsWith('.json'); let configObject; try { - // $FlowFixMe dynamic require configObject = require(configPath); } catch (error) { if (isJSON) { diff --git a/packages/jest-config/src/resolveConfigPath.js b/packages/jest-config/src/resolveConfigPath.ts similarity index 87% rename from packages/jest-config/src/resolveConfigPath.js rename to packages/jest-config/src/resolveConfigPath.ts index a36dd758a807..d85cfb39e20c 100644 --- a/packages/jest-config/src/resolveConfigPath.js +++ b/packages/jest-config/src/resolveConfigPath.ts @@ -3,20 +3,17 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {Path} from 'types/Config'; - import path from 'path'; import fs from 'fs'; +import {Config} from '@jest/types'; import {JEST_CONFIG, PACKAGE_JSON} from './constants'; -const isFile = filePath => +const isFile = (filePath: Config.Path) => fs.existsSync(filePath) && !fs.lstatSync(filePath).isDirectory(); -export default (pathToResolve: Path, cwd: Path): Path => { +export default (pathToResolve: Config.Path, cwd: Config.Path): Config.Path => { if (!path.isAbsolute(cwd)) { throw new Error(`"cwd" must be an absolute path. cwd: ${cwd}`); } @@ -50,10 +47,10 @@ export default (pathToResolve: Path, cwd: Path): Path => { }; const resolveConfigPathByTraversing = ( - pathToResolve: Path, - initialPath: Path, - cwd: Path, -) => { + pathToResolve: Config.Path, + initialPath: Config.Path, + cwd: Config.Path, +): Config.Path => { const jestConfig = path.resolve(pathToResolve, JEST_CONFIG); if (isFile(jestConfig)) { return jestConfig; @@ -78,7 +75,10 @@ const resolveConfigPathByTraversing = ( ); }; -const makeResolutionErrorMessage = (initialPath: Path, cwd: Path) => +const makeResolutionErrorMessage = ( + initialPath: Config.Path, + cwd: Config.Path, +) => 'Could not find a config file based on provided values:\n' + `path: "${initialPath}"\n` + `cwd: "${cwd}"\n` + diff --git a/packages/jest-config/src/setFromArgv.js b/packages/jest-config/src/setFromArgv.ts similarity index 86% rename from packages/jest-config/src/setFromArgv.js rename to packages/jest-config/src/setFromArgv.ts index 2cde2ee37d33..0e7bbb3ea535 100644 --- a/packages/jest-config/src/setFromArgv.js +++ b/packages/jest-config/src/setFromArgv.ts @@ -3,24 +3,21 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {InitialOptions} from 'types/Config'; -import type {Argv} from 'types/Argv'; +import {Argv, Config} from '@jest/types'; const specialArgs = ['_', '$0', 'h', 'help', 'config']; import {isJSONString} from './utils'; export default function setFromArgv( - options: InitialOptions, - argv: Argv, -): InitialOptions { + options: Config.InitialOptions, + argv: Argv.Argv, +): Config.InitialOptions { // $FlowFixMe: Seems like flow doesn't approve of string values const argvToOptions = Object.keys(argv) .filter(key => argv[key] !== undefined && specialArgs.indexOf(key) === -1) - .reduce((options: {[key: string]: mixed}, key) => { + .reduce((options: {[key: string]: unknown}, key) => { switch (key) { case 'coverage': options.collectCoverage = argv[key]; diff --git a/packages/jest-config/src/utils.js b/packages/jest-config/src/utils.ts similarity index 77% rename from packages/jest-config/src/utils.js rename to packages/jest-config/src/utils.ts index 168c07657cb3..5a93c832969d 100644 --- a/packages/jest-config/src/utils.js +++ b/packages/jest-config/src/utils.ts @@ -3,23 +3,21 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -import type {Path, Glob} from 'types/Config'; - import path from 'path'; +import {Config} from '@jest/types'; +// @ts-ignore: Not migrated to TS import {ValidationError} from 'jest-validate'; import Resolver from 'jest-resolve'; import chalk from 'chalk'; -type ResolveOptions = {| - rootDir: string, - key: string, - filePath: Path, - optional?: boolean, -|}; +type ResolveOptions = { + rootDir: Config.Path; + key: string; + filePath: Config.Path; + optional?: boolean; +}; export const BULLET: string = chalk.bold('\u25cf '); export const DOCUMENTATION_NOTE = ` ${chalk.bold( @@ -32,14 +30,14 @@ const createValidationError = (message: string) => new ValidationError(`${BULLET}Validation Error`, message, DOCUMENTATION_NOTE); export const resolve = ( - resolver: ?string, + resolver: string | null | undefined, {key, filePath, rootDir, optional}: ResolveOptions, ) => { const module = Resolver.findNodeModule( replaceRootDirInPath(rootDir, filePath), { basedir: rootDir, - resolver, + resolver: resolver || undefined, }, ); @@ -55,12 +53,12 @@ export const resolve = ( return module; }; -export const escapeGlobCharacters = (path: Path): Glob => +export const escapeGlobCharacters = (path: Config.Path): Config.Glob => path.replace(/([()*{}\[\]!?\\])/g, '\\$1'); export const replaceRootDirInPath = ( - rootDir: string, - filePath: Path, + rootDir: Config.Path, + filePath: Config.Path, ): string => { if (!/^/.test(filePath)) { return filePath; @@ -72,9 +70,13 @@ export const replaceRootDirInPath = ( ); }; -const _replaceRootDirInObject = (rootDir: string, config: any): Object => { +// TODO: Type as returning same type as input +const _replaceRootDirInObject = ( + rootDir: Config.Path, + config: any, +): {[key: string]: unknown} => { if (config !== null) { - const newConfig = {}; + const newConfig: {[key: string]: unknown} = {}; for (const configKey in config) { newConfig[configKey] = configKey === 'rootDir' @@ -86,7 +88,8 @@ const _replaceRootDirInObject = (rootDir: string, config: any): Object => { return config; }; -export const _replaceRootDirTags = (rootDir: string, config: any) => { +// TODO: Type as returning same type as input +export const _replaceRootDirTags = (rootDir: Config.Path, config: any): any => { switch (typeof config) { case 'object': if (Array.isArray(config)) { @@ -103,7 +106,7 @@ export const _replaceRootDirTags = (rootDir: string, config: any) => { }; export const resolveWithPrefix = ( - resolver: ?string, + resolver: string | undefined | null, { filePath, humanOptionName, @@ -111,17 +114,17 @@ export const resolveWithPrefix = ( prefix, rootDir, }: { - filePath: string, - humanOptionName: string, - optionName: string, - prefix: string, - rootDir: string, + filePath: string; + humanOptionName: string; + optionName: string; + prefix: string; + rootDir: Config.Path; }, ) => { const fileName = replaceRootDirInPath(rootDir, filePath); let module = Resolver.findNodeModule(`${prefix}${fileName}`, { basedir: rootDir, - resolver, + resolver: resolver || undefined, }); if (module) { return module; @@ -133,7 +136,7 @@ export const resolveWithPrefix = ( module = Resolver.findNodeModule(fileName, { basedir: rootDir, - resolver, + resolver: resolver || undefined, }); if (module) { return module; @@ -164,10 +167,10 @@ export const getTestEnvironment = ({ rootDir, testEnvironment: filePath, }: { - rootDir: string, - testEnvironment: string, + rootDir: Config.Path; + testEnvironment: string; }) => - resolveWithPrefix(null, { + resolveWithPrefix(undefined, { filePath, humanOptionName: 'Test environment', optionName: 'testEnvironment', @@ -184,8 +187,8 @@ export const getTestEnvironment = ({ * 1. looks for relative to Jest. */ export const getWatchPlugin = ( - resolver: ?string, - {filePath, rootDir}: {filePath: string, rootDir: string}, + resolver: string | undefined | null, + {filePath, rootDir}: {filePath: string; rootDir: Config.Path}, ) => resolveWithPrefix(resolver, { filePath, @@ -204,8 +207,8 @@ export const getWatchPlugin = ( * 1. looks for relative to Jest. */ export const getRunner = ( - resolver: ?string, - {filePath, rootDir}: {filePath: string, rootDir: string}, + resolver: string | undefined | null, + {filePath, rootDir}: {filePath: string; rootDir: Config.Path}, ) => resolveWithPrefix(resolver, { filePath, @@ -215,7 +218,7 @@ export const getRunner = ( rootDir, }); -export const isJSONString = (text: ?string) => +export const isJSONString = (text?: string) => text && typeof text === 'string' && text.startsWith('{') && diff --git a/packages/jest-config/src/validatePattern.js b/packages/jest-config/src/validatePattern.ts similarity index 84% rename from packages/jest-config/src/validatePattern.js rename to packages/jest-config/src/validatePattern.ts index 483277ad9d6a..476a0b271f46 100644 --- a/packages/jest-config/src/validatePattern.js +++ b/packages/jest-config/src/validatePattern.ts @@ -3,11 +3,9 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - * - * @flow */ -export default function validatePattern(pattern: string) { +export default function validatePattern(pattern?: string) { if (pattern) { try { // eslint-disable-next-line no-new diff --git a/packages/jest-config/tsconfig.json b/packages/jest-config/tsconfig.json new file mode 100644 index 000000000000..50965f82bc07 --- /dev/null +++ b/packages/jest-config/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "build" + }, + // TODO: This is missing `jest-validate`, in addition to `jest-environment-jsdom`, `jest-environment-node` and + // `jest-jasmine2`, but those are just `require.resolve`d, so no real use for their types + "references": [ + {"path": "../jest-get-type"}, + {"path": "../jest-regex-util"}, + {"path": "../jest-resolve"}, + {"path": "../jest-types"}, + {"path": "../jest-util"}, + {"path": "../pretty-format"} + ] +} diff --git a/packages/jest-types/package.json b/packages/jest-types/package.json index 86018bd9b822..cdfc227fbd37 100644 --- a/packages/jest-types/package.json +++ b/packages/jest-types/package.json @@ -11,5 +11,8 @@ }, "license": "MIT", "main": "build/index.js", - "types": "build/index.d.ts" + "types": "build/index.d.ts", + "dependencies": { + "@types/yargs": "^12.0.9" + } } diff --git a/packages/jest-types/src/Argv.ts b/packages/jest-types/src/Argv.ts new file mode 100644 index 000000000000..a4d1ed880a0b --- /dev/null +++ b/packages/jest-types/src/Argv.ts @@ -0,0 +1,100 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// import from `@types` +// eslint-disable-next-line import/no-extraneous-dependencies +import {Arguments} from 'yargs'; + +export type Argv = Arguments<{ + all: boolean; + automock: boolean; + bail: boolean | number; + browser: boolean; + cache: boolean; + cacheDirectory: string; + changedFilesWithAncestor: boolean; + changedSince: string; + ci: boolean; + clearCache: boolean; + clearMocks: boolean; + collectCoverage: boolean; + collectCoverageFrom: Array; + collectCoverageOnlyFrom: Array; + config: string; + coverage: boolean; + coverageDirectory: string; + coveragePathIgnorePatterns: Array; + coverageReporters: Array; + coverageThreshold: string; + debug: boolean; + env: string; + expand: boolean; + findRelatedTests: boolean; + forceExit: boolean; + globals: string; + globalSetup: string | null | undefined; + globalTeardown: string | null | undefined; + h: boolean; + haste: string; + help: boolean; + init: boolean; + json: boolean; + lastCommit: boolean; + logHeapUsage: boolean; + maxWorkers: number; + moduleDirectories: Array; + moduleFileExtensions: Array; + moduleLoader: string; + moduleNameMapper: string; + modulePathIgnorePatterns: Array; + modulePaths: Array; + name: string; + noSCM: boolean; + noStackTrace: boolean; + notify: boolean; + notifyMode: string; + onlyChanged: boolean; + outputFile: string; + preset: string | null | undefined; + projects: Array; + prettierPath: string | null | undefined; + replname: string | null | undefined; + resetMocks: boolean; + resetModules: boolean; + resolver: string | null | undefined; + restoreMocks: boolean; + rootDir: string; + roots: Array; + runInBand: boolean; + setupFiles: Array; + setupFilesAfterEnv: Array; + showConfig: boolean; + silent: boolean; + snapshotSerializers: Array; + testEnvironment: string; + testFailureExitCode: string | null | undefined; + testMatch: Array; + testNamePattern: string; + testPathIgnorePatterns: Array; + testPathPattern: Array; + testRegex: string | Array; + testResultsProcessor: string | null | undefined; + testRunner: string; + testURL: string; + timers: 'real' | 'fake'; + transform: string; + transformIgnorePatterns: Array; + unmockedModulePathPatterns: Array | null | undefined; + updateSnapshot: boolean; + useStderr: boolean; + verbose: boolean | null | undefined; + version: boolean; + watch: boolean; + watchAll: boolean; + watchman: boolean; + watchPathIgnorePatterns: Array; +}>; diff --git a/packages/jest-types/src/index.ts b/packages/jest-types/src/index.ts index d845e25a2ff3..c4627174ecb4 100644 --- a/packages/jest-types/src/index.ts +++ b/packages/jest-types/src/index.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import * as Argv from './Argv'; import * as Config from './Config'; import * as Console from './Console'; import * as Matchers from './Matchers'; @@ -18,6 +19,7 @@ import * as Global from './Global'; import * as Environment from './Environment'; export { + Argv, Config, Console, Matchers, diff --git a/yarn.lock b/yarn.lock index 3a7d5cb3e17a..1ef2f07871ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1848,7 +1848,7 @@ dependencies: "@types/node" "*" -"@types/yargs@^12.0.2": +"@types/yargs@^12.0.2", "@types/yargs@^12.0.9": version "12.0.9" resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0" integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==