• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1namespace ts {
2    function verifyMissingFilePaths(missingPaths: readonly Path[], expected: readonly string[]) {
3        assert.isDefined(missingPaths);
4        const map = new Set(expected);
5        for (const missing of missingPaths) {
6            const value = map.has(missing);
7            assert.isTrue(value, `${missing} to be ${value === undefined ? "not present" : "present only once"}, in actual: ${missingPaths} expected: ${expected}`);
8            map.delete(missing);
9        }
10        const notFound = arrayFrom(mapDefinedIterator(map.keys(), k => map.has(k) ? k : undefined));
11        assert.equal(notFound.length, 0, `Not found ${notFound} in actual: ${missingPaths} expected: ${expected}`);
12    }
13
14    describe("unittests:: programApi:: Program.getMissingFilePaths", () => {
15
16        const options: CompilerOptions = {
17            noLib: true,
18        };
19
20        const emptyFileName = "empty.ts";
21        const emptyFileRelativePath = "./" + emptyFileName;
22
23        const emptyFile = new documents.TextDocument(emptyFileName, "");
24
25        const referenceFileName = "reference.ts";
26        const referenceFileRelativePath = "./" + referenceFileName;
27
28        const referenceFile = new documents.TextDocument(referenceFileName,
29            "/// <reference path=\"d:/imaginary/nonexistent1.ts\"/>\n" + // Absolute
30            "/// <reference path=\"./nonexistent2.ts\"/>\n" + // Relative
31            "/// <reference path=\"nonexistent3.ts\"/>\n" + // Unqualified
32            "/// <reference path=\"nonexistent4\"/>\n"   // No extension
33        );
34
35        const testCompilerHost = new fakes.CompilerHost(
36            vfs.createFromFileSystem(
37                Harness.IO,
38                /*ignoreCase*/ true,
39                { documents: [emptyFile, referenceFile], cwd: "d:\\pretend\\" }),
40            { newLine: NewLineKind.LineFeed });
41
42        it("handles no missing root files", () => {
43            const program = createProgram([emptyFileRelativePath], options, testCompilerHost);
44            const missing = program.getMissingFilePaths();
45            verifyMissingFilePaths(missing, []);
46        });
47
48        it("handles missing root file", () => {
49            const program = createProgram(["./nonexistent.ts"], options, testCompilerHost);
50            const missing = program.getMissingFilePaths();
51            verifyMissingFilePaths(missing, ["d:/pretend/nonexistent.ts"]); // Absolute path
52        });
53
54        it("handles multiple missing root files", () => {
55            const program = createProgram(["./nonexistent0.ts", "./nonexistent1.ts"], options, testCompilerHost);
56            const missing = program.getMissingFilePaths();
57            verifyMissingFilePaths(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]);
58        });
59
60        it("handles a mix of present and missing root files", () => {
61            const program = createProgram(["./nonexistent0.ts", emptyFileRelativePath, "./nonexistent1.ts"], options, testCompilerHost);
62            const missing = program.getMissingFilePaths();
63            verifyMissingFilePaths(missing, ["d:/pretend/nonexistent0.ts", "d:/pretend/nonexistent1.ts"]);
64        });
65
66        it("handles repeatedly specified root files", () => {
67            const program = createProgram(["./nonexistent.ts", "./nonexistent.ts"], options, testCompilerHost);
68            const missing = program.getMissingFilePaths();
69            verifyMissingFilePaths(missing, ["d:/pretend/nonexistent.ts"]);
70        });
71
72        it("normalizes file paths", () => {
73            const program0 = createProgram(["./nonexistent.ts", "./NONEXISTENT.ts"], options, testCompilerHost);
74            const program1 = createProgram(["./NONEXISTENT.ts", "./nonexistent.ts"], options, testCompilerHost);
75            const missing0 = program0.getMissingFilePaths();
76            const missing1 = program1.getMissingFilePaths();
77            assert.equal(missing0.length, 1);
78            assert.deepEqual(missing0, missing1);
79        });
80
81        it("handles missing triple slash references", () => {
82            const program = createProgram([referenceFileRelativePath], options, testCompilerHost);
83            const missing = program.getMissingFilePaths();
84            verifyMissingFilePaths(missing, [
85                // From absolute reference
86                "d:/imaginary/nonexistent1.ts",
87
88                // From relative reference
89                "d:/pretend/nonexistent2.ts",
90
91                // From unqualified reference
92                "d:/pretend/nonexistent3.ts",
93
94                // From no-extension reference
95                "d:/pretend/nonexistent4.d.ts",
96                "d:/pretend/nonexistent4.ts",
97                "d:/pretend/nonexistent4.tsx",
98                "d:/pretend/nonexistent4.ets",
99                "d:/pretend/nonexistent4.d.ets"
100            ]);
101        });
102
103        it("should not have missing file paths", () => {
104            const testSource = `
105            class Foo extends HTMLElement {
106                bar: string = 'baz';
107            }`;
108
109            const host: CompilerHost = {
110                getSourceFile: (fileName: string, languageVersion: ScriptTarget, _onError?: (message: string) => void) => {
111                    return fileName === "test.ts" ? createSourceFile(fileName, testSource, languageVersion) : undefined;
112                },
113                getDefaultLibFileName: () => "",
114                writeFile: (_fileName, _content) => { throw new Error("unsupported"); },
115                getCurrentDirectory: () => sys.getCurrentDirectory(),
116                getCanonicalFileName: fileName => sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase(),
117                getNewLine: () => sys.newLine,
118                useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames,
119                fileExists: fileName => fileName === "test.ts",
120                readFile: fileName => fileName === "test.ts" ? testSource : undefined,
121                resolveModuleNames: (_moduleNames: string[], _containingFile: string) => { throw new Error("unsupported"); },
122                getDirectories: _path => { throw new Error("unsupported"); },
123            };
124
125            const program = createProgram(["test.ts"], { module: ModuleKind.ES2015 }, host);
126            assert(program.getSourceFiles().length === 1, "expected 'getSourceFiles' length to be 1");
127            assert(program.getMissingFilePaths().length === 0, "expected 'getMissingFilePaths' length to be 0");
128            assert((program.getFileProcessingDiagnostics()?.length || 0) === 0, "expected 'getFileProcessingDiagnostics' length to be 0");
129        });
130    });
131
132    describe("unittests:: Program.isSourceFileFromExternalLibrary", () => {
133        it("works on redirect files", () => {
134            // In this example '/node_modules/foo/index.d.ts' will redirect to '/node_modules/bar/node_modules/foo/index.d.ts'.
135            const a = new documents.TextDocument("/a.ts", 'import * as bar from "bar"; import * as foo from "foo";');
136            const bar = new documents.TextDocument("/node_modules/bar/index.d.ts", 'import * as foo from "foo";');
137            const fooPackageJsonText = '{ "name": "foo", "version": "1.2.3" }';
138            const fooIndexText = "export const x: number;";
139            const barFooPackage = new documents.TextDocument("/node_modules/bar/node_modules/foo/package.json", fooPackageJsonText);
140            const barFooIndex = new documents.TextDocument("/node_modules/bar/node_modules/foo/index.d.ts", fooIndexText);
141            const fooPackage = new documents.TextDocument("/node_modules/foo/package.json", fooPackageJsonText);
142            const fooIndex = new documents.TextDocument("/node_modules/foo/index.d.ts", fooIndexText);
143
144            const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [a, bar, barFooPackage, barFooIndex, fooPackage, fooIndex], cwd: "/" });
145            const program = createProgram(["/a.ts"], emptyOptions, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed }));
146            assertIsExternal(program, [a, bar, barFooIndex, fooIndex], f => f !== a);
147        });
148
149        it('works on `/// <reference types="" />`', () => {
150            const a = new documents.TextDocument("/a.ts", '/// <reference types="foo" />');
151            const fooIndex = new documents.TextDocument("/node_modules/foo/index.d.ts", "declare const foo: number;");
152            const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [a, fooIndex], cwd: "/" });
153            const program = createProgram(["/a.ts"], emptyOptions, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed }));
154            assertIsExternal(program, [a, fooIndex], f => f !== a);
155        });
156
157        function assertIsExternal(program: Program, files: readonly documents.TextDocument[], isExternalExpected: (file: documents.TextDocument) => boolean): void {
158            for (const file of files) {
159                const actual = program.isSourceFileFromExternalLibrary(program.getSourceFile(file.file)!);
160                const expected = isExternalExpected(file);
161                assert.equal(actual, expected, `Expected ${file.file} isSourceFileFromExternalLibrary to be ${expected}, got ${actual}`);
162            }
163        }
164    });
165
166    describe("unittests:: Program.getNodeCount / Program.getIdentifierCount", () => {
167        it("works on projects that have .json files", () => {
168            const main = new documents.TextDocument("/main.ts", 'export { version } from "./package.json";');
169            const pkg = new documents.TextDocument("/package.json", '{"version": "1.0.0"}');
170
171            const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, pkg], cwd: "/" });
172            const program = createProgram(["/main.ts"], { resolveJsonModule: true }, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed }));
173
174            const json = program.getSourceFile("/package.json")!;
175            assert.equal(json.scriptKind, ScriptKind.JSON);
176            assert.isNumber(json.nodeCount);
177            assert.isNumber(json.identifierCount);
178
179            assert.isNotNaN(program.getNodeCount());
180            assert.isNotNaN(program.getIdentifierCount());
181        });
182    });
183
184    describe("unittests:: programApi:: Program.getTypeChecker / Program.getSemanticDiagnostics", () => {
185        it("does not produce errors on `as const` it would not normally produce on the command line", () => {
186            const main = new documents.TextDocument("/main.ts", "0 as const");
187
188            const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main], cwd: "/" });
189            const program = createProgram(["/main.ts"], {}, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed }));
190            const typeChecker = program.getTypeChecker();
191            const sourceFile = program.getSourceFile("main.ts")!;
192            typeChecker.getTypeAtLocation(((sourceFile.statements[0] as ExpressionStatement).expression as AsExpression).type);
193            const diag = program.getSemanticDiagnostics();
194            assert.isEmpty(diag);
195        });
196        it("getSymbolAtLocation does not cause additional error to be added on module resolution failure", () => {
197            const main = new documents.TextDocument("/main.ts", "import \"./module\";");
198            const mod = new documents.TextDocument("/module.d.ts", "declare const foo: any;");
199
200            const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, mod], cwd: "/" });
201            const program = createProgram(["/main.ts"], {}, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed }));
202
203            const sourceFile = program.getSourceFile("main.ts")!;
204            const typeChecker = program.getTypeChecker();
205            typeChecker.getSymbolAtLocation((sourceFile.statements[0] as ImportDeclaration).moduleSpecifier);
206            assert.isEmpty(program.getSemanticDiagnostics());
207        });
208    });
209
210    describe("unittests:: programApi:: CompilerOptions relative paths", () => {
211        it("resolves relative paths by getCurrentDirectory", () => {
212            const main = new documents.TextDocument("/main.ts", "import \"module\";");
213            const mod = new documents.TextDocument("/lib/module.ts", "declare const foo: any;");
214
215            const fs = vfs.createFromFileSystem(Harness.IO, /*ignoreCase*/ false, { documents: [main, mod], cwd: "/" });
216            const program = createProgram(["./main.ts"], {
217                paths: { "*": ["./lib/*"] }
218            }, new fakes.CompilerHost(fs, { newLine: NewLineKind.LineFeed }));
219
220            assert.isEmpty(program.getConfigFileParsingDiagnostics());
221            assert.isEmpty(program.getGlobalDiagnostics());
222            assert.isEmpty(program.getSemanticDiagnostics());
223        });
224    });
225}
226