Fork-ts-checker-webpack-plugin: `useTypescriptIncrementalApi` mode choking on a function property declaration that indirectly references a non-exported interface

Created on 27 Mar 2019  ·  10Comments  ·  Source: TypeStrong/fork-ts-checker-webpack-plugin

I encountered this bug in a create-react-app project, so I first reported it there: https://github.com/facebook/create-react-app/issues/6716

But, it looks like the underlying problem is probably in fork-ts-checker-webpack-plugin. It seems like, in incremental mode, it's not correctly handling a particular set of types, spread across two files, and involving a property declaration on a function (which I was using to add React's defaultTypes to a function component).

Unfortunately I haven't got time right now to try to reproduce it separately from Create React App, but I have made a repository that reproduces the bug with CRA: https://github.com/agwells/create-react-app-bug-example

Steps to Reproduce

This bug seems to require a fairly specific set of steps to reproduce it. I discovered it after upgrading my existing project from react-scripts 2.1.3 to 2.1.8, and after some effort, I was able to pinpoint which part of the project was causing the problem. I then reproduced the problem in minimal form.

The steps seem to be:

  1. Set up a new Create React App project, with typescript.
  2. Create one TypeScript source code file that declares an interface, and implements a function that uses that interface as its return type. Export the function, but not the interface.
  3. Create another Typescript source code file, that implements a function. After the function body, add a static property to the function, that uses the return value of the function exported in step 2.
  4. Save everything and run npm run build

Expected Behavior

The build process finishes successfully, in under 10 seconds, with output like this:

$ time npm run build

> [email protected] build /path/to/my/project
> react-scripts build

Creating an optimized production build...
Compiled successfully.

File sizes after gzip:

  36.81 KB  build/static/js/2.f885ce20.chunk.js
  762 B     build/static/js/runtime~main.a8a9905a.js
  550 B     build/static/js/main.0748b6c2.chunk.js
  539 B     build/static/css/main.8e316ad3.chunk.css

The project was built assuming it is hosted at the server root.
You can control this with the homepage field in your package.json.
For example, add this to build it for GitHub Pages:

  "homepage" : "http://myname.github.io/myapp",

The build folder is ready to be deployed.
You may serve it with a static server:

  npm install -g serve
  serve -s build

Find out more about deployment here:

  https://bit.ly/CRA-deploy


real    0m6.182s
user    0m10.188s
sys 0m0.524s

Actual Behavior

The command runs, but never finishes until manually killed. A few seconds into its run, it prints this message:

$ time npm run build

> [email protected] build /code/cra-project
> react-scripts build

Creating an optimized production build...
(node:29773) UnhandledPromiseRejectionWarning: Error: Debug Failure. Diagnostic emitted without context
    at throwDiagnostic (/code/cra-project/node_modules/typescript/lib/typescript.js:80390:61)
    at handleSymbolAccessibilityError (/code/cra-project/node_modules/typescript/lib/typescript.js:80461:33)
    at Object.trackSymbol (/code/cra-project/node_modules/typescript/lib/typescript.js:80480:13)
    at lookupSymbolChain (/code/cra-project/node_modules/typescript/lib/typescript.js:34535:33)
    at symbolToTypeNode (/code/cra-project/node_modules/typescript/lib/typescript.js:34687:29)
    at typeToTypeNodeHelper (/code/cra-project/node_modules/typescript/lib/typescript.js:33966:27)
    at addPropertyToElementList (/code/cra-project/node_modules/typescript/lib/typescript.js:34352:59)
    at createTypeNodesFromResolvedType (/code/cra-project/node_modules/typescript/lib/typescript.js:34302:25)
    at createTypeNodeFromObjectType (/code/cra-project/node_modules/typescript/lib/typescript.js:34135:35)
    at createAnonymousTypeNode (/code/cra-project/node_modules/typescript/lib/typescript.js:34087:42)
(node:29773) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:29773) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Reproducible Demo

https://github.com/agwells/create-react-app-bug-example

// File from step 2 of reproduction steps
interface NotExportedInterface {}

export function exportedFunction(): NotExportedInterface {
  return {};
}
// File from step 3 of reproduction steps
import { exportedFunction } from './myfunction';

export function MyComponent() {
  return null;
}
MyComponent.defaultProps = {
  myProp: exportedFunction(),
};

Most helpful comment

Actually, it looks like it is probably a variation on this one: https://github.com/Microsoft/TypeScript/issues/29672

I'll go ahead and close this issue then.

All 10 comments

As best I can tell, CRA's npm run build command uses fork-ts-checker-webpack-plugin with this webpack plugin configuration:

        new ForkTsCheckerWebpackPlugin({
          typescript: resolve.sync("typescript", {
            basedir: paths.appNodeModules
          }),
          async: false,
          useTypescriptIncrementalApi: true,
          checkSyntacticErrors: true,
          tsconfig: paths.appTsConfig,
          reportFiles: [
            "**",
            "!**/*.json",
            "!**/__tests__/**",
            "!**/?(*.)(spec|test).*",
            "!**/src/setupProxy.*",
            "!**/src/setupTests.*"
          ],
          watch: paths.appSrc,
          silent: true,
          // const typescriptFormatter = require("react-dev-utils/typescriptFormatter");
          formatter: typescriptFormatter 
        })

If I remove this plugin from the webpack config, it builds successfully. Also, if I change useTypescriptIncrementalApi from true to false, it builds successfully.

react-scripts 2.1.8 is using fork-ts-checker-webpack-plugin version 1.0.0-alpha.6. However, I tried it after manually force-upgrading my node_modules to use version 1.0.0 instead (since the change notes mentioned a few fixes related to incremental mode), and I still encountered this problem.

Thanks for the detailed report! I don't have time to look at this directly myself but perhaps I can help you look into the issue.

A couple of questions occurred:

  1. From your report it looks as though no actual build error is being reported? The build is just collapsing with a non helpful "Diagnostic emitted without context" error. Is that correct? If so, that's surprising.

  2. Could you reproduce your error outside of create-react-app please? Here's a boilerplate for you to use: https://github.com/TypeStrong/ts-loader/tree/master/examples/fork-ts-checker-webpack-plugin I'm wondering if that might reveal more information

Thanks for the boilerplate! I don't have any experience setting up TypeScript myself (_because_ I've been using Create React App ;) ), so that is very helpful. I'll see if I can repro it without CRA.

From your report it looks as though no actual build error is being reported? The build is just collapsing with a non helpful "Diagnostic emitted without context" error. Is that correct? If so, that's surprising.

Yes. From the error stack, it looks like it's going the TypeScript package's handleSymbolAccessibilityError routine, but then it finds that the information that has been passed in is insufficient to generate a meaningful error message, so it falls back to the minimal throwDiagnostic method, which just prints that unhelpful "Diagnostic emitted without context" message.

From watching the processes that come up while this is going on, it looks like the reason it hangs indefinitely is because the build process is launching one or more child processes, and one of them dies with this message, but parent process doesn't seem to understand that this child is no longer doing anything, and so it continues waiting for it, forever.

To actually figure out which part of my (rather large) project was triggering this problem, I wound up putting some console.dir() statements into the TypeScript source code files in my node_modules, so it would emit the name of each file it was processing as it went along.

Okay, successfully replicated using the ts-loader/examples/fork-ts-checker-webpack-plugins boilerplate!

https://github.com/agwells/ts-loader/tree/master/examples/fork-ts-checker-webpack-plugin

$ npm start

> [email protected] start /code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin
> webpack-dev-server --progress --color --mode development --config webpack.config.development.js

Starting type checking and linting service...
Using 1 worker with 2048MB memory limit
 10% building 1/1 modules 0 activeℹ 「wds」: Project is running at http://localhost:8080/
ℹ 「wds」: webpack output is served from /
ℹ 「wds」: 404s will fallback to /index.html
 98% after emittingType checking and linting in progress...                       
ℹ 「wdm」: 
ℹ 「wdm」: Compiled successfully.
(node:7559) UnhandledPromiseRejectionWarning: Error: Debug Failure. Diagnostic emitted without context
    at throwDiagnostic (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/typescript.js:80390:61)
    at handleSymbolAccessibilityError (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/typescript.js:80461:33)
    at Object.trackSymbol (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/typescript.js:80480:13)
    at lookupSymbolChain (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/typescript.js:34535:33)
    at symbolToTypeNode (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/typescript.js:34687:29)
    at typeToTypeNodeHelper (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/typescript.js:33966:27)
    at addPropertyToElementList (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/typescript.js:34352:59)
    at createTypeNodesFromResolvedType (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/typescript.js:34302:25)
    at createTypeNodeFromObjectType (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/typescript.js:34135:35)
    at createAnonymousTypeNode (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/typescript.js:34087:42)
(node:7559) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:7559) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
^C

The compilation works successfully if I make any of these changes:

  • change useTypescriptIncrementalApi in webpack.config.development.js from true to false
  • Comment out line 14 of mycomponent.ts (where I call exportedFunction())
  • Call exportedFunction() anywhere else in mycomponent.ts (that is, anywhere _except_ in the declaration of a function property, like defaultProps)
  • Turn MyComponent into a class component, and set up the defaultProps by doing static const defaultProps = {...} in the class body
  • Explicitly export NotExportedInterface in myfunction.ts

Okay that's narrowed it down nicely; good work!

So it looks like there's an issue with the incremental API functionality.

I'm expecting that this is an issue with the fork-ts-checker-webpack-plugin but we should check one more thing. If you run tsc --watch in your project does the issue occur? If the issue presents it's an issue with TypeScript itself. (Unlikely but we should rule it out)

@0xorial I know you did some tricks to make the incremental API behave in the most useful manner possible. Do have any insight as to what the issue here might be?

Well, look at that! It does happen with tsc --watch by itself!

[7:22:15 PM] Starting compilation in watch mode...

/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/tsc.js:71495
                throw e;
                ^

Error: Debug Failure. Diagnostic emitted without context
    at throwDiagnostic (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/tsc.js:65136:61)
    at handleSymbolAccessibilityError (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/tsc.js:65202:33)
    at Object.trackSymbol (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/tsc.js:65221:13)
    at lookupSymbolChain (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/tsc.js:28411:33)
    at symbolToTypeNode (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/tsc.js:28544:29)
    at typeToTypeNodeHelper (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/tsc.js:27872:27)
    at addPropertyToElementList (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/tsc.js:28239:59)
    at createTypeNodesFromResolvedType (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/tsc.js:28190:25)
    at createTypeNodeFromObjectType (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/tsc.js:28030:35)
    at createAnonymousTypeNode (/code/reference/ts-loader/examples/fork-ts-checker-webpack-plugin/node_modules/typescript/lib/tsc.js:27984:42)

... and it looks like the same code edits I mentioned here will cause it to compile successfully with tsc --watch, as does using tsc without --watch.

So it looks like this actually is a bug in TypeScript itself! I'll see about filing a bug report there on Monday (it's Friday evening already, here in New Zealand).

Actually, it looks like it is probably a variation on this one: https://github.com/Microsoft/TypeScript/issues/29672

I'll go ahead and close this issue then.

Brilliant! Great investigation! Well done ❤️🌻

Was this page helpful?
0 / 5 - 0 ratings