Open dsherret opened 4 years 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]);
});
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:
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.
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.
@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. 😞
See https://twitter.com/OliverJAsh/status/1317024049252438018