Skip to content

Commit

Permalink
feat(diagnostics): add option to enable/disable first-TS-error-throws
Browse files Browse the repository at this point in the history
  • Loading branch information
huafu committed Sep 10, 2018
1 parent 66633d5 commit 2c840c2
Show file tree
Hide file tree
Showing 13 changed files with 109 additions and 40 deletions.
53 changes: 47 additions & 6 deletions docs/user/config/diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,23 @@ A diagnostic can be:
- syntax errors in some of your TypeScript files (source or tests)
- type/semantic errors, what TypeScript has actually been made for 😁

If a diagnostic is not filtered out, it'll fail the compilatin within TSJest, and so will your related test.
If a diagnostic is not filtered out, it'll fail the compilation within TSJest, and so will your related test.

### Disabling/enabling

By default all diagnostic are enabled. This is the same as setting the `diagnostics` option to `true`. To disable all diagnostics, set `diagnostics` to `false` (you might experience slightly better performence as well, especially if you disabled Jest cache).
By default all diagnostic are enabled. This is the same as setting the `diagnostics` option to `true`. To disable all diagnostics, set `diagnostics` to `false` (you might experience slightly better performance as well, especially if you disabled Jest cache).

### Advanced configuration

The option's value can also accpet an object for more advanced configuration. Each config. key is optional:
The option's value can also accept an object for more advanced configuration. Each config. key is optional:

- **`pretty`**: Enables/disable colorful and pretty output of errors (default: _enabled_).
- **`warnOnly`**: If specified and `true`, diagnostics will be reported but won't stop compilation (default: _disabled_).
- **`ignoreCodes`**: List of TypeScript error codes to ignore. Complete list can be found [there](https://github.com/Microsoft/TypeScript/blob/master/src/compiler/diagnosticMessages.json). By default here are the ones ignored:
- `6059`: _'rootDir' is expected to contain all source files._
- `18002`: _The 'files' list in config file is empty._ (it is strongly recommanded to include this one)
- `18002`: _The 'files' list in config file is empty._ (it is strongly recommended to include this one)
- `18003`: _No inputs were found in config file._
- **`pathRegex`**: If specified, diagnostics of source files which path does **not** match will be ignored.
- **`pretty`**: Enables/disables colorful and pretty output of errors (default: _enabled_).

### Examples

Expand Down Expand Up @@ -77,7 +78,7 @@ module.exports = {
globals: {
'ts-jest': {
diagnostics: {
pathRegex: /\.(spec|test).ts$/
pathRegex: /\.(spec|test)\.ts$/
}
}
}
Expand All @@ -104,6 +105,46 @@ module.exports = {

</div></div>

##### Do not fail on first error

While some diagnostics are stop-blockers for the compilation, most of them are not. If you want the compilation (and so your tests) to continue when encountering those, set the `warnOnly` to `true`:

<div class="row"><div class="col-md-6" markdown="block">

```js
// jest.config.js
module.exports = {
// [...]
globals: {
'ts-jest': {
diagnostics: {
warnOnly: true
}
}
}
};
```

</div><div class="col-md-6" markdown="block">

```js
// OR package.json
{
// [...]
"jest": {
"globals": {
"ts-jest": {
"diagnostics": {
"warnOnly": true
}
}
}
}
}
```

</div></div>

##### Ignoring some error codes:

All TypeScript error codes can be found [there](https://github.com/Microsoft/TypeScript/blob/master/src/compiler/diagnosticMessages.json). The `ignoreCodes` option accepts this values:
Expand Down
10 changes: 5 additions & 5 deletions e2e/__tests__/__snapshots__/type-checking.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ exports[`With type checking should fail using template "default" 1`] = `
FAIL ./main.spec.ts
● Test suite failed to run
Unable to compile TypeScript (add code(s) in \`[jest-config].globals.ts-jest.diagnostics.ignoreCodes\` to ignore):
Unable to compile TypeScript (customize using \`[jest-config].globals.ts-jest.diagnostics\` option):
main.ts:2:3 - error TS2322: Type 'string' is not assignable to type 'number'.
2 return input
Expand All @@ -32,7 +32,7 @@ exports[`With type checking should fail using template "with-babel-6" 1`] = `
FAIL ./main.spec.ts
● Test suite failed to run
Unable to compile TypeScript (add code(s) in \`[jest-config].globals.ts-jest.diagnostics.ignoreCodes\` to ignore):
Unable to compile TypeScript (customize using \`[jest-config].globals.ts-jest.diagnostics\` option):
main.ts:2:3 - error TS2322: Type 'string' is not assignable to type 'number'.
2 return input
Expand All @@ -55,7 +55,7 @@ exports[`With type checking should fail using template "with-babel-7" 1`] = `
FAIL ./main.spec.ts
● Test suite failed to run
Unable to compile TypeScript (add code(s) in \`[jest-config].globals.ts-jest.diagnostics.ignoreCodes\` to ignore):
Unable to compile TypeScript (customize using \`[jest-config].globals.ts-jest.diagnostics\` option):
main.ts:2:3 - error TS2322: Type 'string' is not assignable to type 'number'.
2 return input
Expand All @@ -78,7 +78,7 @@ exports[`With type checking should fail using template "with-jest-22" 1`] = `
FAIL ./main.spec.ts
● Test suite failed to run
Unable to compile TypeScript (add code(s) in \`[jest-config].globals.ts-jest.diagnostics.ignoreCodes\` to ignore):
Unable to compile TypeScript (customize using \`[jest-config].globals.ts-jest.diagnostics\` option):
main.ts:2:3 - error TS2322: Type 'string' is not assignable to type 'number'.
2 return input
Expand All @@ -101,7 +101,7 @@ exports[`With type checking should fail using template "with-typescript-2-7" 1`]
FAIL ./main.spec.ts
● Test suite failed to run
Unable to compile TypeScript (add code(s) in \`[jest-config].globals.ts-jest.diagnostics.ignoreCodes\` to ignore):
Unable to compile TypeScript (customize using \`[jest-config].globals.ts-jest.diagnostics\` option):
main.ts:2:3 - error TS2322: Type 'string' is not assignable to type 'number'.
2 return input
Expand Down
2 changes: 1 addition & 1 deletion src/__helpers__/fakers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function tsJestConfig(options?: Partial<TsJestConfig>): TsJestConfig {
babelConfig: undefined,
tsConfig: undefined,
stringifyContentPathRegex: undefined,
diagnostics: { ignoreCodes: [], pretty: false },
diagnostics: { ignoreCodes: [], pretty: false, throws: true },
...options,
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const v: boolean = t
'foo.ts',
),
).toThrowErrorMatchingInlineSnapshot(`
"Unable to compile TypeScript (add code(s) in \`[jest-config].globals.ts-jest.diagnostics.ignoreCodes\` to ignore):
"Unable to compile TypeScript (customize using \`[jest-config].globals.ts-jest.diagnostics\` option):
foo.ts(3,7): error TS2322: Type 'number' is not assignable to type 'string'.
foo.ts(4,7): error TS2322: Type 'string' is not assignable to type 'boolean'."
`)
Expand Down
13 changes: 3 additions & 10 deletions src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,7 @@ export function createCompiler(configs: ConfigSet): TsCompiler {
reportDiagnostics: configs.shouldReportDiagnostic(fileName),
})

const diagnosticList = result.diagnostics ? configs.filterDiagnostics(result.diagnostics) : []

if (diagnosticList.length) {
throw configs.createTsError(diagnosticList)
}
if (result.diagnostics) configs.raiseDiagnostics(result.diagnostics, fileName, logger)

return [result.outputText, result.sourceMapText as string]
}
Expand Down Expand Up @@ -190,11 +186,8 @@ export function createCompiler(configs: ConfigSet): TsCompiler {
.concat(service.getSyntacticDiagnostics(fileName))
.concat(service.getSemanticDiagnostics(fileName))

const diagnosticList = configs.filterDiagnostics(diagnostics)

if (diagnosticList.length) {
throw configs.createTsError(diagnosticList)
}
// will raise or just warn diagnostics depending on config
configs.raiseDiagnostics(diagnostics, fileName, logger)
}

if (output.emitSkipped) {
Expand Down
1 change: 1 addition & 0 deletions src/config/__snapshots__/config-set.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Object {
18003,
],
"pretty": true,
"throws": true,
},
"isolatedModules": false,
"stringifyContentPathRegex": undefined,
Expand Down
11 changes: 11 additions & 0 deletions src/config/config-set.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ describe('tsJest', () => {
const EXPECTED = {
ignoreCodes: IGNORE_DIAGNOSTIC_CODES,
pretty: true,
throws: true,
}
expect(get().diagnostics).toEqual(EXPECTED)
expect(get({ diagnostics: true }).diagnostics).toEqual(EXPECTED)
Expand All @@ -139,6 +140,7 @@ describe('tsJest', () => {
ignoreCodes: IGNORE_DIAGNOSTIC_CODES,
pretty: true,
pathRegex: MATCH_NOTHING.source,
throws: false,
}
expect(get({ diagnostics: false }).diagnostics).toEqual(EXPECTED)
})
Expand All @@ -148,6 +150,7 @@ describe('tsJest', () => {
ignoreCodes: [...IGNORE_DIAGNOSTIC_CODES, 10, 25],
pretty: false,
pathRegex: '\\.test\\.ts',
throws: true,
}
expect(
get({
Expand All @@ -168,6 +171,14 @@ describe('tsJest', () => {
}).diagnostics,
).toEqual(EXPECTED)
})
it('should have correct throws value', () => {
const EXPECTED = {
ignoreCodes: IGNORE_DIAGNOSTIC_CODES,
pretty: true,
}
expect(get({ diagnostics: { warnOnly: true } }).diagnostics).toEqual({ ...EXPECTED, throws: false })
expect(get({ diagnostics: { warnOnly: false } }).diagnostics).toEqual({ ...EXPECTED, throws: true })
})
}) // diagnostics

describe('stringifyContentPathRegex', () => {
Expand Down
40 changes: 29 additions & 11 deletions src/config/config-set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* version of the `jest.ProjectConfig`, and then later it calls `process()`
* with the complete, object version of it.
*/
import { Logger } from 'bs-logger'
import { LogContexts, Logger } from 'bs-logger'
import { existsSync, readFileSync } from 'fs'
import json5 from 'json5'
import { dirname, isAbsolute, join, resolve } from 'path'
Expand Down Expand Up @@ -123,7 +123,7 @@ export class ConfigSet {
readonly parentOptions?: TsJestGlobalOptions,
parentLogger?: Logger,
) {
this.logger = parentLogger ? parentLogger.child({ namespace: 'config' }) : logger
this.logger = parentLogger ? parentLogger.child({ [LogContexts.namespace]: 'config' }) : logger
}

@Memoize()
Expand Down Expand Up @@ -187,9 +187,10 @@ export class ConfigSet {
const ignoreList: any[] = [IGNORE_DIAGNOSTIC_CODES, process.env.TS_JEST_IGNORE_DIAGNOSTICS]

if (diagnosticsOpt === true || diagnosticsOpt == null) {
diagnostics = { ignoreCodes: [], pretty: true }
diagnostics = { ignoreCodes: [], pretty: true, throws: true }
} else if (diagnosticsOpt === false) {
diagnostics = {
throws: false,
pretty: true,
ignoreCodes: [],
pathRegex: MATCH_NOTHING.source, // matches nothing
Expand All @@ -200,9 +201,10 @@ export class ConfigSet {
pretty: diagnosticsOpt.pretty == null ? true : !!diagnosticsOpt.pretty,
ignoreCodes: [],
pathRegex: normalizeRegex(diagnosticsOpt.pathRegex),
throws: !diagnosticsOpt.warnOnly,
}
}
// now we clean and flaten the list
// now we clean and flatten the list
diagnostics.ignoreCodes = toDiagnosticCodeList(ignoreList)

// stringifyContentPathRegex option
Expand Down Expand Up @@ -250,21 +252,37 @@ export class ConfigSet {
const {
tsJest: { tsConfig },
} = this
const configFilePath = tsConfig && tsConfig.kind === 'file' ? tsConfig.value : undefined
const result = this.readTsConfig(
tsConfig && tsConfig.kind === 'inline' ? tsConfig.value : undefined,
tsConfig && tsConfig.kind === 'file' ? tsConfig.value : undefined,
configFilePath,
tsConfig == null,
)
// throw errors if any matching wanted diagnostics
const configDiagnosticList = this.filterDiagnostics(result.resolved.errors)
if (configDiagnosticList.length) {
throw this.createTsError(configDiagnosticList)
}
this.raiseDiagnostics(result.resolved.errors, configFilePath)

this.logger.debug({ tsconfig: result }, 'normalized typescript config')
return result
}

@Memoize()
get raiseDiagnostics() {
const {
createTsError,
filterDiagnostics,
tsJest: {
diagnostics: { throws },
},
} = this
return (diagnostics: Diagnostic[], filePath?: string, logger: Logger = this.logger): void | never => {
const filteredDiagnostics = filterDiagnostics(diagnostics, filePath)
if (filteredDiagnostics.length === 0) return
const error = createTsError(filteredDiagnostics)
if (throws) throw error
logger.warn({ error }, error.message)
}
}

@Memoize()
get babel(): BabelConfig | undefined {
const {
Expand Down Expand Up @@ -416,7 +434,7 @@ export class ConfigSet {
logger.debug('file caching disabled')
return
}
const cacheSufix = sha1(
const cacheSuffix = sha1(
stringify({
version: this.compilerModule.version,
compiler: this.tsJest.compiler,
Expand All @@ -425,7 +443,7 @@ export class ConfigSet {
ignoreDiagnostics: this.tsJest.diagnostics.ignoreCodes,
}),
)
const res = join(this.jest.cacheDirectory, `ts-jest-${cacheSufix}`)
const res = join(this.jest.cacheDirectory, `ts-jest-${cacheSuffix}`)
logger.debug({ cacheDirectory: res }, `will use file caching`)
return res
}
Expand Down
2 changes: 1 addition & 1 deletion src/config/paths-to-module-name-mapper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Object {
Array [
"[level:40] Not mapping \\"no-target\\" because it has no target.
",
"[level:40] Mapping only to first target of \\"too-many-target\\" becuase it has more than one (2).
"[level:40] Mapping only to first target of \\"too-many-target\\" because it has more than one (2).
",
"[level:40] Not mapping \\"too/*/many/*/stars\\" because it has more than one star (\`*\`).
",
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface TsJestGlobalOptions {
pretty?: boolean
ignoreCodes?: number | string | Array<number | string>
pathRegex?: RegExp | string
warnOnly?: boolean
}

/**
Expand Down Expand Up @@ -81,6 +82,7 @@ interface TsJestConfig$diagnostics {
pretty: boolean
ignoreCodes: number[]
pathRegex?: string | undefined
throws: boolean
}
interface TsJestConfig$babelConfig$file {
kind: 'file'
Expand Down
5 changes: 4 additions & 1 deletion src/util/__snapshots__/backports.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Object {
"ts-jest": Object {
"diagnostics": Object {
"pathRegex": "\\\\.spec\\\\.ts$",
"warnOnly": true,
},
},
},
Expand Down Expand Up @@ -157,7 +158,9 @@ exports[`backportJestConfig with "globals.ts-jest.enableTsDiagnostics" set to tr
Object {
"globals": Object {
"ts-jest": Object {
"diagnostics": true,
"diagnostics": Object {
"warnOnly": true,
},
},
},
}
Expand Down
4 changes: 2 additions & 2 deletions src/util/backports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ export const backportJestConfig = <T extends jest.InitialOptions | jest.ProjectC
if ('enableTsDiagnostics' in tsJest) {
warnConfig('globals.ts-jest.enableTsDiagnostics', 'globals.ts-jest.diagnostics')
if (tsJest.enableTsDiagnostics) {
mergeTsJest.diagnostics =
typeof tsJest.enableTsDiagnostics === 'string' ? { pathRegex: tsJest.enableTsDiagnostics } : true
mergeTsJest.diagnostics = { warnOnly: true }
if (typeof tsJest.enableTsDiagnostics === 'string') mergeTsJest.diagnostics.pathRegex = tsJest.enableTsDiagnostics
} else {
mergeTsJest.diagnostics = false
}
Expand Down
4 changes: 2 additions & 2 deletions src/util/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ export enum Errors {
UnableToCompileTypeScript = 'Unable to compile TypeScript ({{help}}):\n{{diagnostics}}',
NotMappingMultiStarPath = 'Not mapping "{{path}}" because it has more than one star (`*`).',
NotMappingPathWithEmptyMap = 'Not mapping "{{path}}" because it has no target.',
MappingOnlyFirstTargetOfPath = 'Mapping only to first target of "{{path}}" becuase it has more than one ({{count}}).',
MappingOnlyFirstTargetOfPath = 'Mapping only to first target of "{{path}}" because it has more than one ({{count}}).',
}

export enum Helps {
FixMissingModule = '{{label}}: `npm i -D {{module}}` (or `yarn add --dev {{module}}`)',
IgnoreDiagnosticCode = 'add code(s) in `[jest-config].globals.ts-jest.diagnostics.ignoreCodes` to ignore',
IgnoreDiagnosticCode = 'customize using `[jest-config].globals.ts-jest.diagnostics` option',
MigrateConfigUsingCLI = 'Your Jest configuration is outdated. Use the CLI to help migrating it: ts-jest config:migrate <config-file>.',
}

Expand Down

0 comments on commit 2c840c2

Please sign in to comment.