Description
Bug Report
project references
transitive
dependency
stale
🕗 Version & Regression Information
- This changed between versions 3.6.0-dev.20190621 (good) and 3.6.0-dev.20190622 (bad)
The incorrect behavior was observed in many versions since then, including:- 4.4.2
- 4.4.3
- 4.5.0-dev.20210928
⏯ Playground Link
Reproducing the problem requires transitive project references, and running tsc -b
from the command line, so I don't believe it can be done in the TypeScript playground.
💻 Code
My team observed that tsc -b
can incorrectly succeed when a problem is introduced by a transitive dependency. In fact, tsc -b
can give two different results (type check passes or type check fails) for the same codebase. We created a self-contained reproducible example to demonstrate the problem. This creates 3 projects using project references, where project A depends on project B and that, in turn, depends on project C. There is one TypeScript file in project A, none in B and one in C.
Step 1: Create the following 5 files, distributed into directories projectA/
, projectB/
and projectC/
.
// projectA/A.ts
import C from '../projectC/C';
class A extends C {
constructor() {
super( {} );
this.name.arbitraryFakeMethod();
}
}
// projectA/tsconfig.json
{
"references": [
{
"path": "../projectB"
}
],
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"composite": true,
"outDir": "./dist/",
"incremental": true,
"allowJs": true
},
"include": [
"A.ts"
]
}
// projectB/tsconfig.json
{
"references": [
{
"path": "../projectC"
}
],
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"composite": true,
"allowJs": true,
"outDir": "./dist/",
"incremental": true
}
}
// projectC/C.ts
class C {
name: string;
constructor( options ) {
}
}
export default C;
// projectC/tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"composite": true,
"allowJs": true,
"outDir": "./dist/",
"incremental": true
},
"include": [
"C.ts"
]
}
Step 2: clean and build project A
The clean step is not necessary on your first run, but we include it here in case you want to run through the steps again.
cd projectA
tsc -b --clean
tsc -b
In this case, the expected result is the same as the actual result, which is a build failure with this message:
A.ts:7:15 - error TS2339: Property 'arbitraryFakeMethod' does not exist on type 'string'.
7 this.name.arbitraryFakeMethod();
~~~~~~~~~~~~~~~~~~~
Found 1 error.
Step 3: Fix the error by updating C.ts.
In C.ts, change name: string;
to name: any;
Step 4: build project A
tsc -b
Again, the expected result matches the actual result. tsc
succeeds with no output.
Step 5. Reintroduce the problem in C.ts
In C.ts, change name: any;
back to name: string;
Step 6. Build Project A
tsc -b
🙁 Actual behavior
There is no output from tsc
because the type check and build passes successfully. This is incorrect because there is
a type error because string
does not have a method arbitraryFakeMethod
. Note that Step 6 is building the same code as in Step 2. However, in Step 2, the type error is correctly identified, but in Step 6 the type error is missed.
🙂 Expected behavior
Step 6 should produce the following error (as it correctly did in Step 2):
A.ts:7:15 - error TS2339: Property 'arbitraryFakeMethod' does not exist on type 'string'.
7 this.name.arbitraryFakeMethod();
~~~~~~~~~~~~~~~~~~~
Found 1 error.
Note that changing project A to depend on C directly (not through B) correctly identifies the problem when running tsc -b
in project A, so this bug does require the intermediate transitive dependency. TypeScript correctly caught this error through Version 3.6.0-dev.20190621 (good), but started missing it in 3.6.0-dev.20190622 (bad). For completeness, we will mention that we first detected the problem when projectC was *.js code, so it seems to affect both *.ts and *.js dependencies. This problem was discovered in phetsims/chipper#1067