Skip to content

Commit f9923ef

Browse files
Merge pull request #9095 from RyanCavanaugh/implicitTypeReferences
Implicit type inclusion changes
2 parents 3dca09b + 90b319f commit f9923ef

File tree

100 files changed

+603
-523
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+603
-523
lines changed

src/compiler/commandLineParser.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,13 @@ namespace ts {
337337
}
338338
},
339339
{
340-
name: "typesRoot",
341-
type: "string"
340+
name: "typeRoots",
341+
type: "list",
342+
element: {
343+
name: "typeRoots",
344+
type: "string",
345+
isFilePath: true
346+
}
342347
},
343348
{
344349
name: "types",

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -1931,6 +1931,10 @@
19311931
"category": "Error",
19321932
"code": 2687
19331933
},
1934+
"Cannot find type definition file for '{0}'.": {
1935+
"category": "Error",
1936+
"code": 2688
1937+
},
19341938
"Import declaration '{0}' is using private name '{1}'.": {
19351939
"category": "Error",
19361940
"code": 4000

src/compiler/program.ts

+49-53
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,11 @@ namespace ts {
99
/* @internal */ export let ioWriteTime = 0;
1010

1111
/** The version of the TypeScript compiler release */
12+
export const version = "1.9.0";
1213

1314
const emptyArray: any[] = [];
1415

15-
const defaultLibrarySearchPaths = [
16-
"types/",
17-
"node_modules/",
18-
"node_modules/@types/",
19-
];
20-
21-
export const version = "1.9.0";
16+
const defaultTypeRoots = ["node_modules/@types"];
2217

2318
export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean): string {
2419
while (true) {
@@ -183,6 +178,11 @@ namespace ts {
183178

184179
const typeReferenceExtensions = [".d.ts"];
185180

181+
function getEffectiveTypeRoots(options: CompilerOptions, host: ModuleResolutionHost) {
182+
return options.typeRoots ||
183+
defaultTypeRoots.map(d => combinePaths(options.configFilePath ? getDirectoryPath(options.configFilePath) : host.getCurrentDirectory(), d));
184+
}
185+
186186
/**
187187
* @param {string | undefined} containingFile - file that contains type reference directive, can be undefined if containing file is unknown.
188188
* This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups
@@ -197,39 +197,36 @@ namespace ts {
197197
traceEnabled
198198
};
199199

200-
// use typesRoot and fallback to directory that contains tsconfig or current directory if typesRoot is not set
201-
const rootDir = options.typesRoot || (options.configFilePath ? getDirectoryPath(options.configFilePath) : (host.getCurrentDirectory && host.getCurrentDirectory()));
202-
200+
const typeRoots = getEffectiveTypeRoots(options, host);
203201
if (traceEnabled) {
204202
if (containingFile === undefined) {
205-
if (rootDir === undefined) {
203+
if (typeRoots === undefined) {
206204
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_not_set, typeReferenceDirectiveName);
207205
}
208206
else {
209-
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_1, typeReferenceDirectiveName, rootDir);
207+
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_not_set_root_directory_1, typeReferenceDirectiveName, typeRoots);
210208
}
211209
}
212210
else {
213-
if (rootDir === undefined) {
211+
if (typeRoots === undefined) {
214212
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_not_set, typeReferenceDirectiveName, containingFile);
215213
}
216214
else {
217-
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_2, typeReferenceDirectiveName, containingFile, rootDir);
215+
trace(host, Diagnostics.Resolving_type_reference_directive_0_containing_file_1_root_directory_2, typeReferenceDirectiveName, containingFile, typeRoots);
218216
}
219217
}
220218
}
221219

222220
const failedLookupLocations: string[] = [];
223221

224222
// Check primary library paths
225-
if (rootDir !== undefined) {
226-
const effectivePrimarySearchPaths = options.typesSearchPaths || defaultLibrarySearchPaths;
227-
for (const searchPath of effectivePrimarySearchPaths) {
228-
const primaryPath = combinePaths(rootDir, searchPath);
229-
if (traceEnabled) {
230-
trace(host, Diagnostics.Resolving_with_primary_search_path_0, primaryPath);
231-
}
232-
const candidate = combinePaths(primaryPath, typeReferenceDirectiveName);
223+
if (typeRoots.length) {
224+
if (traceEnabled) {
225+
trace(host, Diagnostics.Resolving_with_primary_search_path_0, typeRoots.join(", "));
226+
}
227+
const primarySearchPaths = typeRoots;
228+
for (const typeRoot of primarySearchPaths) {
229+
const candidate = combinePaths(typeRoot, typeReferenceDirectiveName);
233230
const candidateDirectory = getDirectoryPath(candidate);
234231
const resolvedFile = loadNodeModuleFromDirectory(typeReferenceExtensions, candidate, failedLookupLocations,
235232
!directoryProbablyExists(candidateDirectory, host), moduleResolutionState);
@@ -256,9 +253,6 @@ namespace ts {
256253
if (containingFile) {
257254
initialLocationForSecondaryLookup = getDirectoryPath(containingFile);
258255
}
259-
else {
260-
initialLocationForSecondaryLookup = rootDir;
261-
}
262256

263257
if (initialLocationForSecondaryLookup !== undefined) {
264258
// check secondary locations
@@ -937,19 +931,6 @@ namespace ts {
937931
}
938932
}
939933

940-
function getDefaultTypeDirectiveNames(rootPath: string): string[] {
941-
const localTypes = combinePaths(rootPath, "types");
942-
const npmTypes = combinePaths(rootPath, "node_modules/@types");
943-
let result: string[] = [];
944-
if (sys.directoryExists(localTypes)) {
945-
result = result.concat(sys.getDirectories(localTypes));
946-
}
947-
if (sys.directoryExists(npmTypes)) {
948-
result = result.concat(sys.getDirectories(npmTypes));
949-
}
950-
return result;
951-
}
952-
953934
function getDefaultLibLocation(): string {
954935
return getDirectoryPath(normalizePath(sys.getExecutingFilePath()));
955936
}
@@ -958,7 +939,6 @@ namespace ts {
958939
const realpath = sys.realpath && ((path: string) => sys.realpath(path));
959940

960941
return {
961-
getDefaultTypeDirectiveNames,
962942
getSourceFile,
963943
getDefaultLibLocation,
964944
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
@@ -971,6 +951,7 @@ namespace ts {
971951
readFile: fileName => sys.readFile(fileName),
972952
trace: (s: string) => sys.write(s + newLine),
973953
directoryExists: directoryName => sys.directoryExists(directoryName),
954+
getDirectories: (path: string) => sys.getDirectories(path),
974955
realpath
975956
};
976957
}
@@ -1034,21 +1015,35 @@ namespace ts {
10341015
return resolutions;
10351016
}
10361017

1037-
export function getDefaultTypeDirectiveNames(options: CompilerOptions, rootFiles: string[], host: CompilerHost): string[] {
1018+
function getInferredTypesRoot(options: CompilerOptions, rootFiles: string[], host: CompilerHost) {
1019+
return computeCommonSourceDirectoryOfFilenames(rootFiles, host.getCurrentDirectory(), f => host.getCanonicalFileName(f));
1020+
}
1021+
1022+
/**
1023+
* Given a set of options and a set of root files, returns the set of type directive names
1024+
* that should be included for this program automatically.
1025+
* This list could either come from the config file,
1026+
* or from enumerating the types root + initial secondary types lookup location.
1027+
* More type directives might appear in the program later as a result of loading actual source files;
1028+
* this list is only the set of defaults that are implicitly included.
1029+
*/
1030+
export function getAutomaticTypeDirectiveNames(options: CompilerOptions, rootFiles: string[], host: CompilerHost): string[] {
10381031
// Use explicit type list from tsconfig.json
10391032
if (options.types) {
10401033
return options.types;
10411034
}
10421035

1043-
// or load all types from the automatic type import fields
1044-
if (host && host.getDefaultTypeDirectiveNames) {
1045-
const commonRoot = computeCommonSourceDirectoryOfFilenames(rootFiles, host.getCurrentDirectory(), f => host.getCanonicalFileName(f));
1046-
if (commonRoot) {
1047-
return host.getDefaultTypeDirectiveNames(commonRoot);
1036+
// Walk the primary type lookup locations
1037+
let result: string[] = [];
1038+
if (host.directoryExists && host.getDirectories) {
1039+
const typeRoots = getEffectiveTypeRoots(options, host);
1040+
for (const root of typeRoots) {
1041+
if (host.directoryExists(root)) {
1042+
result = result.concat(host.getDirectories(root));
1043+
}
10481044
}
10491045
}
1050-
1051-
return undefined;
1046+
return result;
10521047
}
10531048

10541049
export function createProgram(rootNames: string[], options: CompilerOptions, host?: CompilerHost, oldProgram?: Program): Program {
@@ -1100,11 +1095,13 @@ namespace ts {
11001095
if (!tryReuseStructureFromOldProgram()) {
11011096
forEach(rootNames, name => processRootFile(name, /*isDefaultLib*/ false));
11021097

1103-
// load type declarations specified via 'types' argument
1104-
const typeReferences: string[] = getDefaultTypeDirectiveNames(options, rootNames, host);
1098+
// load type declarations specified via 'types' argument or implicitly from types/ and node_modules/@types folders
1099+
const typeReferences: string[] = getAutomaticTypeDirectiveNames(options, rootNames, host);
11051100

11061101
if (typeReferences) {
1107-
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeReferences, /*containingFile*/ undefined);
1102+
const inferredRoot = getInferredTypesRoot(options, rootNames, host);
1103+
const containingFilename = combinePaths(inferredRoot, "__inferred type names__.ts");
1104+
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeReferences, containingFilename);
11081105
for (let i = 0; i < typeReferences.length; i++) {
11091106
processTypeReferenceDirective(typeReferences[i], resolutions[i]);
11101107
}
@@ -1212,10 +1209,9 @@ namespace ts {
12121209
(oldOptions.jsx !== options.jsx) ||
12131210
(oldOptions.allowJs !== options.allowJs) ||
12141211
(oldOptions.rootDir !== options.rootDir) ||
1215-
(oldOptions.typesSearchPaths !== options.typesSearchPaths) ||
12161212
(oldOptions.configFilePath !== options.configFilePath) ||
12171213
(oldOptions.baseUrl !== options.baseUrl) ||
1218-
(oldOptions.typesRoot !== options.typesRoot) ||
1214+
!arrayIsEqualTo(oldOptions.typeRoots, oldOptions.typeRoots) ||
12191215
!arrayIsEqualTo(oldOptions.rootDirs, options.rootDirs) ||
12201216
!mapIsEqualTo(oldOptions.paths, options.paths)) {
12211217
return false;
@@ -1970,7 +1966,7 @@ namespace ts {
19701966
}
19711967
}
19721968
else {
1973-
fileProcessingDiagnostics.add(createDiagnostic(refFile, refPos, refEnd, Diagnostics.Cannot_find_name_0, typeReferenceDirective));
1969+
fileProcessingDiagnostics.add(createDiagnostic(refFile, refPos, refEnd, Diagnostics.Cannot_find_type_definition_file_for_0, typeReferenceDirective));
19741970
}
19751971

19761972
if (saveResolution) {

src/compiler/types.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -2562,7 +2562,8 @@ namespace ts {
25622562
target?: ScriptTarget;
25632563
traceResolution?: boolean;
25642564
types?: string[];
2565-
/* @internal */ typesRoot?: string;
2565+
/** Paths used to used to compute primary types search locations */
2566+
typeRoots?: string[];
25662567
typesSearchPaths?: string[];
25672568
/*@internal*/ version?: boolean;
25682569
/*@internal*/ watch?: boolean;
@@ -2871,6 +2872,7 @@ namespace ts {
28712872
getDefaultTypeDirectiveNames?(rootPath: string): string[];
28722873
writeFile: WriteFileCallback;
28732874
getCurrentDirectory(): string;
2875+
getDirectories(path: string): string[];
28742876
getCanonicalFileName(fileName: string): string;
28752877
useCaseSensitiveFileNames(): boolean;
28762878
getNewLine(): string;

src/harness/fourslash.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,8 @@ namespace FourSlash {
246246
// Create a new Services Adapter
247247
this.cancellationToken = new TestCancellationToken();
248248
const compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions);
249-
if (compilationOptions.typesRoot) {
250-
compilationOptions.typesRoot = ts.getNormalizedAbsolutePath(compilationOptions.typesRoot, this.basePath);
249+
if (compilationOptions.typeRoots) {
250+
compilationOptions.typeRoots = compilationOptions.typeRoots.map(p => ts.getNormalizedAbsolutePath(p, this.basePath));
251251
}
252252

253253
const languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions);

src/harness/harness.ts

+39-20
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ namespace Harness {
432432
readFile(path: string): string;
433433
writeFile(path: string, contents: string): void;
434434
directoryName(path: string): string;
435+
getDirectories(path: string): string[];
435436
createDirectory(path: string): void;
436437
fileExists(fileName: string): boolean;
437438
directoryExists(path: string): boolean;
@@ -477,6 +478,7 @@ namespace Harness {
477478
export const readFile: typeof IO.readFile = path => ts.sys.readFile(path);
478479
export const writeFile: typeof IO.writeFile = (path, content) => ts.sys.writeFile(path, content);
479480
export const directoryName: typeof IO.directoryName = fso.GetParentFolderName;
481+
export const getDirectories: typeof IO.getDirectories = dir => ts.sys.getDirectories(dir);
480482
export const directoryExists: typeof IO.directoryExists = fso.FolderExists;
481483
export const fileExists: typeof IO.fileExists = fso.FileExists;
482484
export const log: typeof IO.log = global.WScript && global.WScript.StdOut.WriteLine;
@@ -543,6 +545,7 @@ namespace Harness {
543545
export const args = () => ts.sys.args;
544546
export const getExecutingFilePath = () => ts.sys.getExecutingFilePath();
545547
export const exit = (exitCode: number) => ts.sys.exit(exitCode);
548+
export const getDirectories: typeof IO.getDirectories = path => ts.sys.getDirectories(path);
546549

547550
export const readFile: typeof IO.readFile = path => ts.sys.readFile(path);
548551
export const writeFile: typeof IO.writeFile = (path, content) => ts.sys.writeFile(path, content);
@@ -616,6 +619,7 @@ namespace Harness {
616619
export const args = () => <string[]>[];
617620
export const getExecutingFilePath = () => "";
618621
export const exit = (exitCode: number) => { };
622+
export const getDirectories = () => <string[]>[];
619623

620624
export let log = (s: string) => console.log(s);
621625

@@ -861,7 +865,7 @@ namespace Harness {
861865
// Local get canonical file name function, that depends on passed in parameter for useCaseSensitiveFileNames
862866
const getCanonicalFileName = ts.createGetCanonicalFileName(useCaseSensitiveFileNames);
863867

864-
let realPathMap: ts.FileMap<string>;
868+
const realPathMap: ts.FileMap<string> = ts.createFileMap<string>();
865869
const fileMap: ts.FileMap<() => ts.SourceFile> = ts.createFileMap<() => ts.SourceFile>();
866870
for (const file of inputFiles) {
867871
if (file.content !== undefined) {
@@ -870,9 +874,6 @@ namespace Harness {
870874
if (file.fileOptions && file.fileOptions["symlink"]) {
871875
const link = file.fileOptions["symlink"];
872876
const linkPath = ts.toPath(link, currentDirectory, getCanonicalFileName);
873-
if (!realPathMap) {
874-
realPathMap = ts.createFileMap<string>();
875-
}
876877
realPathMap.set(linkPath, fileName);
877878
fileMap.set(path, (): ts.SourceFile => { throw new Error("Symlinks should always be resolved to a realpath first"); });
878879
}
@@ -906,20 +907,6 @@ namespace Harness {
906907

907908

908909
return {
909-
getDefaultTypeDirectiveNames: (path: string) => {
910-
const results: string[] = [];
911-
fileMap.forEachValue((key, value) => {
912-
const rx = /node_modules\/@types\/(\w+)/;
913-
const typeNameResult = rx.exec(key);
914-
if (typeNameResult) {
915-
const typeName = typeNameResult[1];
916-
if (results.indexOf(typeName) < 0) {
917-
results.push(typeName);
918-
}
919-
}
920-
});
921-
return results;
922-
},
923910
getCurrentDirectory: () => currentDirectory,
924911
getSourceFile,
925912
getDefaultLibFileName,
@@ -937,7 +924,37 @@ namespace Harness {
937924
realpath: realPathMap && ((f: string) => {
938925
const path = ts.toPath(f, currentDirectory, getCanonicalFileName);
939926
return realPathMap.contains(path) ? realPathMap.get(path) : path;
940-
})
927+
}),
928+
directoryExists: dir => {
929+
let path = ts.toPath(dir, currentDirectory, getCanonicalFileName);
930+
// Strip trailing /, which may exist if the path is a drive root
931+
if (path[path.length - 1] === "/") {
932+
path = <ts.Path>path.substr(0, path.length - 1);
933+
}
934+
let exists = false;
935+
fileMap.forEachValue(key => {
936+
if (key.indexOf(path) === 0 && key[path.length] === "/") {
937+
exists = true;
938+
}
939+
});
940+
return exists;
941+
},
942+
getDirectories: d => {
943+
const path = ts.toPath(d, currentDirectory, getCanonicalFileName);
944+
const result: string[] = [];
945+
fileMap.forEachValue((key, value) => {
946+
if (key.indexOf(path) === 0 && key.lastIndexOf("/") > path.length) {
947+
let dirName = key.substr(path.length, key.indexOf("/", path.length + 1) - path.length);
948+
if (dirName[0] === "/") {
949+
dirName = dirName.substr(1);
950+
}
951+
if (result.indexOf(dirName) < 0) {
952+
result.push(dirName);
953+
}
954+
}
955+
});
956+
return result;
957+
}
941958
};
942959
}
943960

@@ -1036,7 +1053,9 @@ namespace Harness {
10361053
options.noErrorTruncation = true;
10371054
options.skipDefaultLibCheck = true;
10381055

1039-
currentDirectory = currentDirectory || Harness.IO.getCurrentDirectory();
1056+
if (typeof currentDirectory === "undefined") {
1057+
currentDirectory = Harness.IO.getCurrentDirectory();
1058+
}
10401059

10411060
// Parse settings
10421061
if (harnessSettings) {

0 commit comments

Comments
 (0)