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:: 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.getDiagnosticsProducingTypeChecker / 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.getDiagnosticsProducingTypeChecker(); 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.getDiagnosticsProducingTypeChecker(); 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