Skip to content

Commit ae80e5f

Browse files
committed
feat(apiincrementalchecker): improve generation of diagnostics
when the options `checkSyntacticErrors: false` and `useTypescriptIncrementalApi: true` both were active, no semantic errors were emitted as soon as the first syntactic error was encountered this patches the `typescript` import to override that behavior see discussion in #257
1 parent d078278 commit ae80e5f

File tree

2 files changed

+72
-0
lines changed

2 files changed

+72
-0
lines changed

src/patchTypescript.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// tslint:disable-next-line:no-implicit-dependencies
2+
import * as ts from 'typescript'; // Imported for types alone
3+
4+
export interface TypeScriptPatchConfig {
5+
/**
6+
* Ususally, the compilerHost created with typescript.createWatchCompilerHost will bail out of diagnostics collection if there has been any syntactic error.
7+
* (see [`emitFilesAndReportErrors`](https://github.com/Microsoft/TypeScript/blob/89386ddda7dafc63cb35560e05412487f47cc267/src/compiler/watch.ts#L141) )
8+
* If this plugin is running with `checkSyntacticErrors: false`, this might lead to situations where no syntactic errors are reported within webpack
9+
* (because the file causing a syntactic error might not get processed by ts-loader), but there are semantic errors that would be missed due to this behavior.
10+
* This ensures that the compilerHost always assumes that there were no syntactic errors to be found and continues to check for semantic errors.
11+
*/
12+
skipGetSyntacticDiagnostics: boolean;
13+
}
14+
15+
/**
16+
* While it is often possible to pass a wrapped or modified copy of `typescript` or `typescript.sys` as a function argument to override/extend some typescript-internal behavior,
17+
* sometimes the typescript-internal code ignores these passed objects and directly references the internal `typescript` object reference.
18+
* In these situations, the only way of consistently overriding some behavior is to directly replace methods on the `typescript` object.
19+
*
20+
* So beware, this method directly modifies the passed `typescript` object!
21+
* @param typescript TypeScript instance to patch
22+
* @param config
23+
*/
24+
export function patchTypescript(
25+
typescript: typeof ts,
26+
config: TypeScriptPatchConfig
27+
) {
28+
if (config.skipGetSyntacticDiagnostics) {
29+
patchSkipGetSyntacticDiagnostics(typescript);
30+
}
31+
}
32+
33+
/**
34+
* Overrides the [`typescript.createEmitAndSemanticDiagnosticsBuilderProgram`](https://github.com/Microsoft/TypeScript/blob/89386ddda7dafc63cb35560e05412487f47cc267/src/compiler/builder.ts#L1176)
35+
* method to return a `ts.Program` instance that does not emit syntactic errors,
36+
* to prevent the [`typescript.createWatchCompilerHost`](https://github.com/Microsoft/TypeScript/blob/89386ddda7dafc63cb35560e05412487f47cc267/src/compiler/watch.ts#L333)
37+
* method from bailing during diagnostic collection in the [`emitFilesAndReportErrors`](https://github.com/Microsoft/TypeScript/blob/89386ddda7dafc63cb35560e05412487f47cc267/src/compiler/watch.ts#L141) callback.
38+
*
39+
* See the description of TypeScriptPatchConfig.skipGetSyntacticDiagnostics and
40+
* [this github discussion](https://github.com/Realytics/fork-ts-checker-webpack-plugin/issues/257#issuecomment-485414182)
41+
* for further information on this problem & solution.
42+
*/
43+
function patchSkipGetSyntacticDiagnostics(typescript: typeof ts) {
44+
const {
45+
createEmitAndSemanticDiagnosticsBuilderProgram: originalCreateEmitAndSemanticDiagnosticsBuilderProgram
46+
} = typescript;
47+
48+
const patchedMethods: Pick<
49+
typeof ts,
50+
'createEmitAndSemanticDiagnosticsBuilderProgram'
51+
> = {
52+
createEmitAndSemanticDiagnosticsBuilderProgram(...args: any[]) {
53+
const program = originalCreateEmitAndSemanticDiagnosticsBuilderProgram.apply(
54+
typescript,
55+
args as any
56+
);
57+
program.getSyntacticDiagnostics = () => [];
58+
return program;
59+
}
60+
};
61+
62+
// directly patch the typescript object!
63+
Object.assign(typescript, patchedMethods);
64+
}

src/service.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from './NormalizedMessageFactories';
1414
import { RpcProvider } from 'worker-rpc';
1515
import { RunPayload, RunResult, RUN } from './RpcTypes';
16+
import { TypeScriptPatchConfig, patchTypescript } from './patchTypescript';
1617

1718
const rpc = new RpcProvider(message => {
1819
try {
@@ -25,6 +26,13 @@ const rpc = new RpcProvider(message => {
2526
process.on('message', message => rpc.dispatch(message));
2627

2728
const typescript: typeof ts = require(process.env.TYPESCRIPT_PATH!);
29+
const patchConfig: TypeScriptPatchConfig = {
30+
skipGetSyntacticDiagnostics:
31+
process.env.USE_INCREMENTAL_API === 'true' &&
32+
process.env.CHECK_SYNTACTIC_ERRORS !== 'true'
33+
};
34+
35+
patchTypescript(typescript, patchConfig);
2836

2937
// message factories
3038
export const createNormalizedMessageFromDiagnostic = makeCreateNormalizedMessageFromDiagnostic(

0 commit comments

Comments
 (0)