Skip to content

Commit 1cccd02

Browse files
authored
feat: Bundle typescript package in ts-morph/common (#934)
1 parent 3dd5c72 commit 1cccd02

37 files changed

+7600
-250
lines changed

docs/setup/file-system.md

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,25 +37,6 @@ The current working directory on this file system will be `/`.
3737

3838
This file system can also be imported and created via the `InMemoryFileSystemHost` export.
3939

40-
#### `lib.d.ts` files
41-
42-
Since ts-morph 6.0, the in memory file system will have the [`lib.d.ts` files](https://github.com/Microsoft/TypeScript/tree/master/lib) loaded into the `/node_modules/typescript/lib` folder by default.
43-
44-
If you want the old behaviour, you can specify to skip loading them by providing a `skipLoadingLibFiles` option:
45-
46-
```ts ignore-error: 1109
47-
import { Project, FileSystemHost } from "ts-morph";
48-
49-
const project = new Project({
50-
useInMemoryFileSystem: true,
51-
skipLoadingLibFiles: true
52-
});
53-
54-
console.log(project.getFileSystem().directoryExistsSync("/node_modules")); // false
55-
```
56-
57-
When using a non-default file system, the library will search for these files in `path.join(fs.getCurrentDirectory(), "node_modules/typescript/lib"))`.
58-
5940
### Custom File System
6041

6142
It's possible to use your own custom file system by implementing the `FileSystemHost` interface then passing in an instance of this when creating a new `Project` instance:

packages/bootstrap/lib/ts-morph-bootstrap.d.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,8 @@ export interface FileSystemHost {
7575
export declare class InMemoryFileSystemHost implements FileSystemHost {
7676
/**
7777
* Constructor.
78-
* @param options - Options for creating the file system.
7978
*/
80-
constructor(options?: InMemoryFileSystemHostOptions);
79+
constructor();
8180
/** @inheritdoc */
8281
isCaseSensitive(): boolean;
8382
/** @inheritdoc */
@@ -124,14 +123,6 @@ export declare class InMemoryFileSystemHost implements FileSystemHost {
124123
globSync(patterns: ReadonlyArray<string>): string[];
125124
}
126125

127-
export interface InMemoryFileSystemHostOptions {
128-
/**
129-
* Set this to true to not load the /node_modules/typescript/lib files on construction.
130-
* @default false
131-
*/
132-
skipLoadingLibFiles?: boolean;
133-
}
134-
135126
/** Host for implementing custom module and/or type reference directive resolution. */
136127
export interface ResolutionHost {
137128
resolveModuleNames?: ts.LanguageServiceHost["resolveModuleNames"];
@@ -194,8 +185,16 @@ export interface ProjectOptions {
194185
skipAddingFilesFromTsConfig?: boolean;
195186
/** Skip resolving file dependencies when providing a ts config file path and adding the files from tsconfig. @default false */
196187
skipFileDependencyResolution?: boolean;
197-
/** Skip loading the lib files when using an in-memory file system. @default false */
188+
/**
189+
* Skip loading the lib files. Unlike the compiler API, ts-morph does not load these
190+
* from the node_modules folder, but instead loads them from some other JS code
191+
* and uses a fake path for their existence. If you want to use a custom lib files
192+
* folder path, then provide one using the libFolderPath options.
193+
* @default false
194+
*/
198195
skipLoadingLibFiles?: boolean;
196+
/** The folder to use for loading lib files. */
197+
libFolderPath?: string;
199198
/** Whether to use an in-memory file system. */
200199
useInMemoryFileSystem?: boolean;
201200
/**

packages/bootstrap/src/Project.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,16 @@ export interface ProjectOptions {
1212
skipAddingFilesFromTsConfig?: boolean;
1313
/** Skip resolving file dependencies when providing a ts config file path and adding the files from tsconfig. @default false */
1414
skipFileDependencyResolution?: boolean;
15-
/** Skip loading the lib files when using an in-memory file system. @default false */
15+
/**
16+
* Skip loading the lib files. Unlike the compiler API, ts-morph does not load these
17+
* from the node_modules folder, but instead loads them from some other JS code
18+
* and uses a fake path for their existence. If you want to use a custom lib files
19+
* folder path, then provide one using the libFolderPath options.
20+
* @default false
21+
*/
1622
skipLoadingLibFiles?: boolean;
23+
/** The folder to use for loading lib files. */
24+
libFolderPath?: string;
1725
/** Whether to use an in-memory file system. */
1826
useInMemoryFileSystem?: boolean;
1927
/**
@@ -93,16 +101,11 @@ function createProjectCommon(options: ProjectOptions) {
93101
function verifyOptions() {
94102
if (options.fileSystem != null && options.useInMemoryFileSystem)
95103
throw new errors.InvalidOperationError("Cannot provide a file system when specifying to use an in-memory file system.");
96-
if (options.skipLoadingLibFiles && !options.useInMemoryFileSystem) {
97-
throw new errors.InvalidOperationError(
98-
`The ${nameof(options.skipLoadingLibFiles)} option can only be true when ${nameof(options.useInMemoryFileSystem)} is true.`,
99-
);
100-
}
101104
}
102105

103106
function getFileSystem() {
104107
if (options.useInMemoryFileSystem)
105-
return new InMemoryFileSystemHost({ skipLoadingLibFiles: options.skipLoadingLibFiles });
108+
return new InMemoryFileSystemHost();
106109
return options.fileSystem ?? new RealFileSystemHost();
107110
}
108111

@@ -159,6 +162,8 @@ export class Project {
159162
resolutionHost: resolutionHost || {},
160163
getProjectVersion: () => this._sourceFileCache.getProjectVersion().toString(),
161164
isKnownTypesPackageName: options.isKnownTypesPackageName,
165+
libFolderPath: options.libFolderPath,
166+
skipLoadingLibFiles: options.skipLoadingLibFiles,
162167
});
163168
this.languageServiceHost = languageServiceHost;
164169
this.compilerHost = compilerHost;

packages/bootstrap/src/SourceFileCache.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ export class SourceFileCache implements TsSourceFileContainer {
3838
return this.sourceFilesByFilePath.get(filePath);
3939
}
4040

41+
addLibFileToCacheByText(filePath: StandardizedFilePath, fileText: string, scriptKind: ScriptKind | undefined) {
42+
return this.documentRegistry.createOrUpdateSourceFile(
43+
filePath,
44+
this.compilerOptions.get(),
45+
ts.ScriptSnapshot.fromString(fileText),
46+
scriptKind,
47+
);
48+
}
49+
4150
async addOrGetSourceFileFromFilePath(filePath: StandardizedFilePath, options: { scriptKind: ScriptKind | undefined; }): Promise<ts.SourceFile | undefined> {
4251
let sourceFile = this.sourceFilesByFilePath.get(filePath);
4352
if (sourceFile == null && await this.fileSystemWrapper.fileExists(filePath)) {

packages/bootstrap/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
export { CompilerOptionsContainer, FileSystemHost, InMemoryFileSystemHost, InMemoryFileSystemHostOptions, ResolutionHost, ResolutionHostFactory,
2-
SettingsContainer, ts } from "@ts-morph/common";
1+
export { CompilerOptionsContainer, FileSystemHost, InMemoryFileSystemHost, ResolutionHost, ResolutionHostFactory, SettingsContainer,
2+
ts } from "@ts-morph/common";
33
export * from "./Project";

packages/bootstrap/src/tests/projectTests.ts

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { InMemoryFileSystemHost, ts } from "@ts-morph/common";
1+
import { getLibFiles, InMemoryFileSystemHost, ts } from "@ts-morph/common";
22
import { expect } from "chai";
33
import { EOL } from "os";
44
import { createProject, createProjectSync, Project, ProjectOptions } from "../Project";
@@ -10,32 +10,46 @@ describe(nameof(Project), () => {
1010

1111
function doTestsForProject(create: (options: ProjectOptions) => Promise<Project>) {
1212
it("should add the files from tsconfig.json by default with the target in the tsconfig.json", async () => {
13-
const fileSystem = new InMemoryFileSystemHost({ skipLoadingLibFiles: true });
13+
const fileSystem = new InMemoryFileSystemHost();
1414
fileSystem.writeFileSync("tsconfig.json", `{ "compilerOptions": { "rootDir": "test", "target": "ES5" }, "include": ["test"] }`);
1515
fileSystem.writeFileSync("/otherFile.ts", "");
1616
fileSystem.writeFileSync("/test/file.ts", "");
1717
fileSystem.writeFileSync("/test/test2/file2.ts", "");
18-
const project = await create({ tsConfigFilePath: "tsconfig.json", fileSystem });
18+
const project = await create({
19+
tsConfigFilePath: "tsconfig.json",
20+
fileSystem,
21+
skipLoadingLibFiles: true,
22+
});
1923
expect(project.getSourceFiles().map(s => s.fileName).sort()).to.deep.equal(["/test/file.ts", "/test/test2/file2.ts"].sort());
2024
expect(project.getSourceFiles().map(s => s.languageVersion)).to.deep.equal([ts.ScriptTarget.ES5, ts.ScriptTarget.ES5]);
2125
});
2226

2327
it("should add the files from tsconfig.json by default and also take into account the passed in compiler options", async () => {
24-
const fileSystem = new InMemoryFileSystemHost({ skipLoadingLibFiles: true });
28+
const fileSystem = new InMemoryFileSystemHost();
2529
fileSystem.writeFileSync("tsconfig.json", `{ "compilerOptions": { "target": "ES5" } }`);
2630
fileSystem.writeFileSync("/otherFile.ts", "");
2731
fileSystem.writeFileSync("/test/file.ts", "");
2832
fileSystem.writeFileSync("/test/test2/file2.ts", "");
29-
const project = await create({ tsConfigFilePath: "tsconfig.json", compilerOptions: { rootDir: "/test/test2" }, fileSystem });
33+
const project = await create({
34+
tsConfigFilePath: "tsconfig.json",
35+
compilerOptions: { rootDir: "/test/test2" },
36+
fileSystem,
37+
skipLoadingLibFiles: true,
38+
});
3039
expect(project.getSourceFiles().map(s => s.fileName).sort()).to.deep.equal(["/otherFile.ts", "/test/file.ts", "/test/test2/file2.ts"].sort());
3140
});
3241

3342
it("should not add the files from tsconfig.json when specifying not to", async () => {
34-
const fileSystem = new InMemoryFileSystemHost({ skipLoadingLibFiles: true });
43+
const fileSystem = new InMemoryFileSystemHost();
3544
fileSystem.writeFileSync("tsconfig.json", `{ "compilerOptions": { "rootDir": "test", "target": "ES5" } }`);
3645
fileSystem.writeFileSync("/test/file.ts", "");
3746
fileSystem.writeFileSync("/test/test2/file2.ts", "");
38-
const project = await create({ tsConfigFilePath: "tsconfig.json", skipAddingFilesFromTsConfig: true, fileSystem });
47+
const project = await create({
48+
tsConfigFilePath: "tsconfig.json",
49+
skipAddingFilesFromTsConfig: true,
50+
fileSystem,
51+
skipLoadingLibFiles: true,
52+
});
3953
expect(project.getSourceFiles().map(s => s.fileName).sort()).to.deep.equal([]);
4054
});
4155

@@ -211,24 +225,56 @@ describe(nameof(Project), () => {
211225
describe(nameof<ProjectOptions>(o => o.skipLoadingLibFiles), () => {
212226
it("should not skip loading lib files when empty", async () => {
213227
const project = await create({ useInMemoryFileSystem: true });
214-
const result = project.fileSystem.readDirSync("/node_modules/typescript/lib");
215-
expect(result.some(r => r.includes("lib.d.ts"))).to.be.true;
228+
const sourceFile = project.createSourceFile("test.ts", "const t: String = '';");
229+
const program = project.createProgram();
230+
expect(ts.getPreEmitDiagnostics(program).length).to.equal(0);
231+
232+
const typeChecker = program.getTypeChecker();
233+
const varDecl = (sourceFile.statements[0] as ts.VariableStatement).declarationList.declarations[0];
234+
const varDeclType = typeChecker.getTypeAtLocation(varDecl.type!);
235+
const stringDec = varDeclType.getSymbol()!.declarations[0];
236+
expect(stringDec.getSourceFile().fileName).to.equal("/node_modules/typescript/lib/lib.es5.d.ts");
216237
});
217238

218-
it("should not skip loading lib files when true", async () => {
239+
it("should skip loading lib files when true", async () => {
219240
const project = await create({ useInMemoryFileSystem: true, skipLoadingLibFiles: true });
220-
expect(project.fileSystem.directoryExistsSync("/node_modules")).to.be.false;
241+
const sourceFile = project.createSourceFile("test.ts", "const t: String = '';");
242+
const program = project.createProgram();
243+
expect(ts.getPreEmitDiagnostics(program).length).to.equal(10);
244+
245+
const typeChecker = program.getTypeChecker();
246+
const varDecl = (sourceFile.statements[0] as ts.VariableStatement).declarationList.declarations[0];
247+
const varDeclType = typeChecker.getTypeAtLocation(varDecl.type!);
248+
expect(varDeclType.getSymbol()).to.be.undefined;
221249
});
222250

223-
it("should throw when providing skipLoadingLibFiles without using n in-memory file system", async () => {
251+
it("should throw when providing skipLoadingLibFiles and a libFolderPath", async () => {
224252
try {
225-
await create({ skipLoadingLibFiles: true });
253+
await create({ skipLoadingLibFiles: true, libFolderPath: "" });
226254
expect.fail("should have thrown");
227255
} catch (err) {
228-
expect(err.message).to.equal("The skipLoadingLibFiles option can only be true when useInMemoryFileSystem is true.");
256+
expect(err.message).to.equal("Cannot set skipLoadingLibFiles to true when libFolderPath is provided.");
229257
}
230258
});
231259
});
260+
261+
describe(nameof<ProjectOptions>(o => o.libFolderPath), () => {
262+
it("should support specifying a different folder for the lib files", async () => {
263+
const fileSystem = new InMemoryFileSystemHost();
264+
for (const file of getLibFiles())
265+
fileSystem.writeFileSync(`/other/${file.fileName}`, file.text);
266+
const project = await create({ fileSystem, libFolderPath: "/other" });
267+
const sourceFile = project.createSourceFile("test.ts", "const t: String = '';");
268+
const program = project.createProgram();
269+
expect(ts.getPreEmitDiagnostics(program).length).to.equal(0);
270+
271+
const typeChecker = program.getTypeChecker();
272+
const varDecl = (sourceFile.statements[0] as ts.VariableStatement).declarationList.declarations[0];
273+
const varDeclType = typeChecker.getTypeAtLocation(varDecl.type!);
274+
const stringDec = varDeclType.getSymbol()!.declarations[0];
275+
expect(stringDec.getSourceFile().fileName).to.equal("/other/lib.es5.d.ts");
276+
});
277+
});
232278
}
233279
});
234280

@@ -376,14 +422,14 @@ describe(nameof(Project), () => {
376422
});
377423

378424
it("should add the files from tsconfig.json", async () => {
379-
const fileSystem = new InMemoryFileSystemHost({ skipLoadingLibFiles: true });
425+
const fileSystem = new InMemoryFileSystemHost();
380426
fileSystem.writeFileSync("tsconfig.json",
381427
`{ "compilerOptions": { "rootDir": "test", "target": "ES5" }, "include": ["test"], "exclude": ["/test/exclude"] }`);
382428
fileSystem.writeFileSync("/otherFile.ts", "");
383429
fileSystem.writeFileSync("/test/file.ts", "");
384430
fileSystem.writeFileSync("/test/test2/file2.ts", "");
385431
fileSystem.writeFileSync("/test/exclude/file.ts", "");
386-
const project = await createProject({ fileSystem });
432+
const project = await createProject({ fileSystem, skipLoadingLibFiles: true });
387433
expect(project.getSourceFiles()).to.deep.equal([]);
388434
const returnedFiles = await action(project, "tsconfig.json");
389435
const expectedFiles = ["/test/file.ts", "/test/test2/file2.ts"].sort();
@@ -401,12 +447,12 @@ describe(nameof(Project), () => {
401447

402448
function doTestsForMethod(action: (project: Project, globs: string | readonly string[]) => Promise<ts.SourceFile[]>) {
403449
it("should add the source files based on a file glob", async () => {
404-
const fileSystem = new InMemoryFileSystemHost({ skipLoadingLibFiles: true });
450+
const fileSystem = new InMemoryFileSystemHost();
405451
fileSystem.writeFileSync("/otherFile.ts", "");
406452
fileSystem.writeFileSync("/test/file.ts", "");
407453
fileSystem.writeFileSync("/test/test2/file2.ts", "");
408454
fileSystem.writeFileSync("/test/other/file.ts", "");
409-
const project = await createProject({ fileSystem });
455+
const project = await createProject({ fileSystem, skipLoadingLibFiles: true });
410456
expect(project.getSourceFiles()).to.deep.equal([]);
411457
const returnedFiles = await action(project, "/test/**/*.ts");
412458
const expectedFiles = ["/test/file.ts", "/test/test2/file2.ts", "/test/other/file.ts"].sort();
@@ -427,7 +473,7 @@ describe(nameof(Project), () => {
427473
});
428474

429475
async function fileDependencyResolutionSetup(options: ProjectOptions = {}, create: (options: ProjectOptions) => Promise<Project>) {
430-
const fileSystem = new InMemoryFileSystemHost({ skipLoadingLibFiles: true });
476+
const fileSystem = new InMemoryFileSystemHost();
431477

432478
fileSystem.writeFileSync("/package.json", `{ "name": "testing", "version": "0.0.1" }`);
433479
fileSystem.writeFileSync("/node_modules/library/package.json",
@@ -443,7 +489,7 @@ describe(nameof(Project), () => {
443489
fileSystem.writeFileSync("/other/referenced-file.d.ts", "declare function nameof(): void;");
444490
fileSystem.writeFileSync("/tsconfig.json", `{ "files": ["src/main.ts"] }`);
445491

446-
const project = await create({ tsConfigFilePath: "tsconfig.json", fileSystem, ...options });
492+
const project = await create({ tsConfigFilePath: "tsconfig.json", fileSystem, ...options, skipLoadingLibFiles: true });
447493
return {
448494
project,
449495
initialFiles: ["/src/main.ts"],
@@ -560,7 +606,6 @@ describe(nameof(Project), () => {
560606
expect(moduleResolutionHost.getDirectories!("/")).to.deep.equal([
561607
"/dir1",
562608
"/dir2",
563-
"/node_modules",
564609
]);
565610
});
566611

@@ -573,7 +618,6 @@ describe(nameof(Project), () => {
573618
"/dir1",
574619
"/dir2",
575620
"/dir3",
576-
"/node_modules",
577621
]);
578622
});
579623

0 commit comments

Comments
 (0)