dsherret / ts-morph

TypeScript Compiler API wrapper for static analysis and programmatic code changes.
https://ts-morph.com
MIT License
5k stars 195 forks source link

Resolving project references in tsconfig.json probably doesn't work #876

Open dsherret opened 4 years ago

dsherret commented 4 years ago

See https://twitter.com/OliverJAsh/status/1317024049252438018

rachel-church commented 1 year ago

Running into the same issue. The underlying implementation will need to utilize the createIncrementalProgram and createIncrementalCompilerHost methods instead of createProgram and createCompilerHost to build the incremental ts programs.

See https://github.com/microsoft/TypeScript/pull/31432 and https://github.com/TypeStrong/ts-loader/pull/935

Without fully understanding TS incremental programs and the ts compiler API, I think a unit test along these lines would encompass the expected behavior:

it("should resolve dependent composite projects", () => {
    /*
    A project with one modules (units) and a corresponding test file:
    /
    ├── src/
    │   ├── units.ts
    │   └── tsconfig.json
    ├── test/
    │   ├── units.tests.ts
    │   └── tsconfig.json
    ├── package.json
    └── tsconfig.json
     */
    const fileSystem = new InMemoryFileSystemHost();

    fileSystem.writeFileSync("/package.json", `{ "name": "testing", "version": "0.0.1" }`);
    fileSystem.writeFileSync("/tsconfig.json", `{ "compilerOptions": { "composite": true }, "references": [{ "path": "./src" }, { "path": "./test" }] }`);
    fileSystem.mkdirSync("/src");
    fileSystem.writeFileSync(
      "/src/units.ts",
      "export class Test {}",
    );
    fileSystem.writeFileSync("/src/tsconfig.json", `{ "files": ["units.ts"], "compilerOptions": { "composite": true } }`);
    fileSystem.writeFileSync("/tsconfig.json", `{ "compilerOptions": { "composite": true }, "references": [{ "path": "./src" }, { "path": "./test" }] }`);
    fileSystem.mkdirSync("/test");
    fileSystem.writeFileSync(
      "/test/units.tests.ts",
      `import { Test } from 'units';`,
    );
    fileSystem.writeFileSync("/test/tsconfig.json", `{ "files": ["units.tests.ts"], "compilerOptions": { "composite": true }, "references": [{ "path": "../src" }] }`);

    const initialFiles = ['/test/units.tests.ts'];
    const resolvedFiles = ['/test/units.tests.ts', '/src/units.ts'];

    const project = new Project({
      tsConfigFilePath: "test/tsconfig.json",
      fileSystem,
      skipLoadingLibFiles: true,
      skipFileDependencyResolution: true,
    });
    expect(project.getSourceFiles().map(s => s.getFilePath())).to.deep.equal([...initialFiles]);
    const result = project.resolveSourceFileDependencies();
    expect(result.map(s => s.getFilePath())).to.deep.equal(resolvedFiles);
    assertHasSourceFiles(project, [...initialFiles, ...resolvedFiles]);
  });
typeholes commented 1 year ago

Another way to handle this could be to treat each referenced project as its own Project. A property with a map of path to Project would be added to the Project class. This is implemented at https://github.com/typeholes/ts-morph/tree/project-references

There is much to think about here:

  1. is this even a feasible approach
  2. how much plumbing is possible or desirable to transparently recurse into referenced projects a. for example, saving the root project can and probably should save the referenced projects
  3. how to handle circular references
  4. how do we handle projects which can be reached from different references. a. probably need a project manager to memoize opened projects by path

I can play around with this on a couple of projects using references, but I wanted to get your thoughts before I dug in too deep.

typeholes commented 1 year ago

Do we have an example project with project references that fails when we simply open the project with

   compilerOptions: { composite: false },

It may well be that no fix is needed.

niieani commented 1 year ago

@typeholes This approach only works superficially, because you can have files in one project importing a file from another project, and thus these will be loaded multiple times, with different compiler instances. If you modify the file in one project, it won't get updated in the other. Uncertain how to solve this. 😞