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