TypeScript: tsc -b incorrectly succeeds when there is a problem introduced by a transitive dependency

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 https://github.com/phetsims/chipper/issues/1067

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 17 (10 by maintainers)

Most upvoted comments

@sheetalkamat I still think https://github.com/microsoft/TypeScript/issues/46153#issuecomment-1119857255 either sounds like a bug or (more likely) Iā€™m still not understanding something.

@samreid the way I have always set up project references is if I import directly from another project (as you imported projectC from projectA in your example), I list projectC as a reference of projectA. On the other hand, if I use exports from projectC only indirectly through projectB, I only list projectB as a reference of projectA.

Multiple members of my team have reported incorrect results when running tsc -b, it seems possible that it may be caused by (or related to) this issue. The latest occurrence was documented in https://github.com/phetsims/chipper/issues/1144 in case it is helpful. Our only remedy at this point is to run tsc -b --clean frequently, or when we suspect trouble. This takes much longer and hampers our development efforts. Itā€™s difficult for me to believe that mine is the only team that suffers from this problem. If that problem is truly caused by (or related to) this issue, and we know this problem was introduced between versions 3.6.0-dev.20190621 (good) and 3.6.0-dev.20190622 (bad), then hopefully we can get to the root cause.

This issue is quickly becoming detrimental to productivity in the project that I work on. Are there any updates on timeline here?

When making changes in a project C, I have to run type checking as tsc -b --clean && tsc -b in order for files in project A to be type checked. This comes at the cost of upwards of four minutes for each type check to complete.

There are a lot of commits on 21 Jun 2019. I think this was before we started squashing PRs into a single commit. The PRs that are likely relevant are #32027 (merged on the 21st) and #32028 (lots of commits on the 21st, but not merged until 24 Sep 2019).

#32027 is a really simple bug fix. In getUpToDateStatusWorker, the loop over a projectā€™s references previously skipped over references to projects whose status was ComputingUpstream with the comment ā€œitā€™s a circular referenceā€. The PR now also skips references with status ContainerOnly with the comment ā€œignore container-only projectsā€.

Iā€™ll need to repro this myself, but from the commentary above on previous investigation, it sounds like maybe B or C has status ContainerOnly when it shouldnā€™t.