Skip to content

Commit ded2f14

Browse files
Merge branch 'master' into release-3.4
2 parents 6ff9112 + 7ab2ad7 commit ded2f14

37 files changed

+803
-140
lines changed

src/compiler/checker.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ namespace ts {
388388
const intersectionTypes = createMap<IntersectionType>();
389389
const literalTypes = createMap<LiteralType>();
390390
const indexedAccessTypes = createMap<IndexedAccessType>();
391-
const conditionalTypes = createMap<Type>();
391+
const conditionalTypes = createMap<Type | undefined>();
392392
const evolvingArrayTypes: EvolvingArrayType[] = [];
393393
const undefinedProperties = createMap<Symbol>() as UnderscoreEscapedMap<Symbol>;
394394

@@ -10138,11 +10138,24 @@ namespace ts {
1013810138
const trueType = instantiateType(root.trueType, mapper);
1013910139
const falseType = instantiateType(root.falseType, mapper);
1014010140
const instantiationId = `${root.isDistributive ? "d" : ""}${getTypeId(checkType)}>${getTypeId(extendsType)}?${getTypeId(trueType)}:${getTypeId(falseType)}`;
10141-
const result = conditionalTypes.get(instantiationId);
10142-
if (result) {
10143-
return result;
10141+
if (conditionalTypes.has(instantiationId)) {
10142+
const result = conditionalTypes.get(instantiationId);
10143+
if (result !== undefined) {
10144+
return result;
10145+
}
10146+
// Somehow the conditional type depends on itself - usually via `infer` types in the `extends` clause
10147+
// paired with a (potentially deferred) circularly constrained type.
10148+
// The conditional _must_ be deferred.
10149+
const deferred = getDeferredConditionalType(root, mapper, /*combinedMapper*/ undefined, checkType, extendsType, trueType, falseType);
10150+
conditionalTypes.set(instantiationId, deferred);
10151+
return deferred;
1014410152
}
10153+
conditionalTypes.set(instantiationId, undefined);
1014510154
const newResult = getConditionalTypeWorker(root, mapper, checkType, extendsType, trueType, falseType);
10155+
const cachedRecursiveResult = conditionalTypes.get(instantiationId);
10156+
if (cachedRecursiveResult) {
10157+
return cachedRecursiveResult;
10158+
}
1014610159
conditionalTypes.set(instantiationId, newResult);
1014710160
return newResult;
1014810161
}
@@ -10206,6 +10219,10 @@ namespace ts {
1020610219
}
1020710220
}
1020810221
// Return a deferred type for a check that is neither definitely true nor definitely false
10222+
return getDeferredConditionalType(root, mapper, combinedMapper, checkType, extendsType, trueType, falseType);
10223+
}
10224+
10225+
function getDeferredConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, combinedMapper: TypeMapper | undefined, checkType: Type, extendsType: Type, trueType: Type, falseType: Type) {
1020910226
const erasedCheckType = getActualTypeVariable(checkType);
1021010227
const result = <ConditionalType>createType(TypeFlags.Conditional);
1021110228
result.root = root;

src/compiler/emitter.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,81 @@ namespace ts {
131131
return Extension.Js;
132132
}
133133

134+
function rootDirOfOptions(configFile: ParsedCommandLine) {
135+
return configFile.options.rootDir || getDirectoryPath(Debug.assertDefined(configFile.options.configFilePath));
136+
}
137+
138+
/* @internal */
139+
export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) {
140+
Debug.assert(!fileExtensionIs(inputFileName, Extension.Dts) && hasTSFileExtension(inputFileName));
141+
const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile), inputFileName, ignoreCase);
142+
const outputPath = resolvePath(configFile.options.declarationDir || configFile.options.outDir || getDirectoryPath(Debug.assertDefined(configFile.options.configFilePath)), relativePath);
143+
return changeExtension(outputPath, Extension.Dts);
144+
}
145+
146+
function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean) {
147+
const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile), inputFileName, ignoreCase);
148+
const outputPath = resolvePath(configFile.options.outDir || getDirectoryPath(Debug.assertDefined(configFile.options.configFilePath)), relativePath);
149+
const isJsonFile = fileExtensionIs(inputFileName, Extension.Json);
150+
const outputFileName = changeExtension(outputPath, isJsonFile ?
151+
Extension.Json :
152+
fileExtensionIs(inputFileName, Extension.Tsx) && configFile.options.jsx === JsxEmit.Preserve ?
153+
Extension.Jsx :
154+
Extension.Js);
155+
return !isJsonFile || comparePaths(inputFileName, outputFileName, Debug.assertDefined(configFile.options.configFilePath), ignoreCase) !== Comparison.EqualTo ?
156+
outputFileName :
157+
undefined;
158+
}
159+
160+
/*@internal*/
161+
export function getAllProjectOutputs(configFile: ParsedCommandLine, ignoreCase: boolean): ReadonlyArray<string> {
162+
let outputs: string[] | undefined;
163+
const addOutput = (path: string | undefined) => path && (outputs || (outputs = [])).push(path);
164+
if (configFile.options.outFile || configFile.options.out) {
165+
const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false);
166+
addOutput(jsFilePath);
167+
addOutput(sourceMapFilePath);
168+
addOutput(declarationFilePath);
169+
addOutput(declarationMapPath);
170+
addOutput(buildInfoPath);
171+
}
172+
else {
173+
for (const inputFileName of configFile.fileNames) {
174+
if (fileExtensionIs(inputFileName, Extension.Dts)) continue;
175+
const js = getOutputJSFileName(inputFileName, configFile, ignoreCase);
176+
addOutput(js);
177+
if (fileExtensionIs(inputFileName, Extension.Json)) continue;
178+
if (configFile.options.sourceMap) {
179+
addOutput(`${js}.map`);
180+
}
181+
if (getEmitDeclarations(configFile.options) && hasTSFileExtension(inputFileName)) {
182+
const dts = getOutputDeclarationFileName(inputFileName, configFile, ignoreCase);
183+
addOutput(dts);
184+
if (configFile.options.declarationMap) {
185+
addOutput(`${dts}.map`);
186+
}
187+
}
188+
}
189+
addOutput(getOutputPathForBuildInfo(configFile.options));
190+
}
191+
return outputs || emptyArray;
192+
}
193+
194+
/*@internal*/
195+
export function getFirstProjectOutput(configFile: ParsedCommandLine, ignoreCase: boolean): string {
196+
if (configFile.options.outFile || configFile.options.out) {
197+
const { jsFilePath } = getOutputPathsForBundle(configFile.options, /*forceDtsPaths*/ false);
198+
return Debug.assertDefined(jsFilePath, `project ${configFile.options.configFilePath} expected to have at least one output`);
199+
}
200+
201+
for (const inputFileName of configFile.fileNames) {
202+
if (fileExtensionIs(inputFileName, Extension.Dts)) continue;
203+
const jsFilePath = getOutputJSFileName(inputFileName, configFile, ignoreCase);
204+
if (jsFilePath) return jsFilePath;
205+
}
206+
return Debug.fail(`project ${configFile.options.configFilePath} expected to have at least one output`);
207+
}
208+
134209
/*@internal*/
135210
// targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature
136211
export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile | undefined, emitOnlyDtsFiles?: boolean, transformers?: TransformerFactory<Bundle | SourceFile>[], declarationTransformers?: TransformerFactory<Bundle | SourceFile>[], onlyBuildInfo?: boolean): EmitResult {

src/compiler/program.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,7 @@ namespace ts {
833833
else if (getEmitModuleKind(parsedRef.commandLine.options) === ModuleKind.None) {
834834
for (const fileName of parsedRef.commandLine.fileNames) {
835835
if (!fileExtensionIs(fileName, Extension.Dts) && hasTSFileExtension(fileName)) {
836-
processSourceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
836+
processSourceFile(getOutputDeclarationFileName(fileName, parsedRef.commandLine, !host.useCaseSensitiveFileNames()), /*isDefaultLib*/ false, /*ignoreNoDefaultLib*/ false, /*packageId*/ undefined);
837837
}
838838
}
839839
}
@@ -2378,7 +2378,7 @@ namespace ts {
23782378
const out = referencedProject.commandLine.options.outFile || referencedProject.commandLine.options.out;
23792379
return out ?
23802380
changeExtension(out, Extension.Dts) :
2381-
getOutputDeclarationFileName(fileName, referencedProject.commandLine);
2381+
getOutputDeclarationFileName(fileName, referencedProject.commandLine, !host.useCaseSensitiveFileNames());
23822382
}
23832383

23842384
/**

src/compiler/sys.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1148,7 +1148,7 @@ namespace ts {
11481148
}
11491149

11501150
function readDirectory(path: string, extensions?: ReadonlyArray<string>, excludes?: ReadonlyArray<string>, includes?: ReadonlyArray<string>, depth?: number): string[] {
1151-
return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries);
1151+
return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), depth, getAccessibleFileSystemEntries, realpath);
11521152
}
11531153

11541154
function fileSystemEntryExists(path: string, entryKind: FileSystemEntryKind): boolean {

src/compiler/tsbuild.ts

Lines changed: 5 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -276,60 +276,6 @@ namespace ts {
276276
return getOrCreateValueFromConfigFileMap<Map<T>>(configFileMap, resolved, createMap);
277277
}
278278

279-
export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine) {
280-
const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile.options, configFile.options.configFilePath!), inputFileName, /*ignoreCase*/ true);
281-
const outputPath = resolvePath(configFile.options.declarationDir || configFile.options.outDir || getDirectoryPath(configFile.options.configFilePath!), relativePath);
282-
return changeExtension(outputPath, Extension.Dts);
283-
}
284-
285-
function getOutputJSFileName(inputFileName: string, configFile: ParsedCommandLine) {
286-
const relativePath = getRelativePathFromDirectory(rootDirOfOptions(configFile.options, configFile.options.configFilePath!), inputFileName, /*ignoreCase*/ true);
287-
const outputPath = resolvePath(configFile.options.outDir || getDirectoryPath(configFile.options.configFilePath!), relativePath);
288-
const newExtension = fileExtensionIs(inputFileName, Extension.Json) ? Extension.Json :
289-
fileExtensionIs(inputFileName, Extension.Tsx) && configFile.options.jsx === JsxEmit.Preserve ? Extension.Jsx : Extension.Js;
290-
return changeExtension(outputPath, newExtension);
291-
}
292-
293-
function getOutputFileNames(inputFileName: string, configFile: ParsedCommandLine): ReadonlyArray<string> {
294-
// outFile is handled elsewhere; .d.ts files don't generate outputs
295-
if (configFile.options.outFile || configFile.options.out || fileExtensionIs(inputFileName, Extension.Dts)) {
296-
return emptyArray;
297-
}
298-
299-
const outputs: string[] = [];
300-
const js = getOutputJSFileName(inputFileName, configFile);
301-
outputs.push(js);
302-
if (configFile.options.sourceMap) {
303-
outputs.push(`${js}.map`);
304-
}
305-
if (getEmitDeclarations(configFile.options) && !fileExtensionIs(inputFileName, Extension.Json)) {
306-
const dts = getOutputDeclarationFileName(inputFileName, configFile);
307-
outputs.push(dts);
308-
if (configFile.options.declarationMap) {
309-
outputs.push(`${dts}.map`);
310-
}
311-
}
312-
return outputs;
313-
}
314-
315-
function getOutFileOutputs(project: ParsedCommandLine, ignoreBuildInfo?: boolean): ReadonlyArray<string> {
316-
Debug.assert(!!project.options.outFile || !!project.options.out, "outFile must be set");
317-
const { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } = getOutputPathsForBundle(project.options, /*forceDtsPaths*/ false);
318-
319-
let outputs: string[] | undefined = [];
320-
const addOutput = (path: string | undefined) => path && (outputs || (outputs = [])).push(path);
321-
addOutput(jsFilePath);
322-
addOutput(sourceMapFilePath);
323-
addOutput(declarationFilePath);
324-
addOutput(declarationMapPath);
325-
if (!ignoreBuildInfo) addOutput(buildInfoPath);
326-
return outputs || emptyArray;
327-
}
328-
329-
function rootDirOfOptions(opts: CompilerOptions, configFileName: string) {
330-
return opts.rootDir || getDirectoryPath(configFileName);
331-
}
332-
333279
function newer(date1: Date, date2: Date): Date {
334280
return date2 > date1 ? date2 : date1;
335281
}
@@ -715,7 +661,7 @@ namespace ts {
715661
}
716662

717663
// Collect the expected outputs of this project
718-
const outputs = getAllProjectOutputs(project);
664+
const outputs = getAllProjectOutputs(project, !host.useCaseSensitiveFileNames());
719665

720666
if (outputs.length === 0) {
721667
return {
@@ -1202,7 +1148,7 @@ namespace ts {
12021148
const status: Status.UpToDate = {
12031149
type: UpToDateStatusType.UpToDate,
12041150
newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime,
1205-
oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(configFile)
1151+
oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(configFile, !host.useCaseSensitiveFileNames())
12061152
};
12071153
diagnostics.removeKey(proj);
12081154
projectStatus.setValue(proj, status);
@@ -1295,13 +1241,13 @@ namespace ts {
12951241
const status: Status.UpToDate = {
12961242
type: UpToDateStatusType.UpToDate,
12971243
newestDeclarationFileContentChangedTime: priorNewestUpdateTime,
1298-
oldestOutputFileName: getFirstProjectOutput(proj)
1244+
oldestOutputFileName: getFirstProjectOutput(proj, !host.useCaseSensitiveFileNames())
12991245
};
13001246
projectStatus.setValue(proj.options.configFilePath as ResolvedConfigFilePath, status);
13011247
}
13021248

13031249
function updateOutputTimestampsWorker(proj: ParsedCommandLine, priorNewestUpdateTime: Date, verboseMessage: DiagnosticMessage, skipOutputs?: FileMap<true>) {
1304-
const outputs = getAllProjectOutputs(proj);
1250+
const outputs = getAllProjectOutputs(proj, !host.useCaseSensitiveFileNames());
13051251
if (!skipOutputs || outputs.length !== skipOutputs.getSize()) {
13061252
if (options.verbose) {
13071253
reportStatus(verboseMessage, proj.options.configFilePath!);
@@ -1337,7 +1283,7 @@ namespace ts {
13371283
reportParseConfigFileDiagnostic(proj);
13381284
continue;
13391285
}
1340-
const outputs = getAllProjectOutputs(parsed);
1286+
const outputs = getAllProjectOutputs(parsed, !host.useCaseSensitiveFileNames());
13411287
for (const output of outputs) {
13421288
if (host.fileExists(output)) {
13431289
filesToDelete.push(output);
@@ -1499,35 +1445,6 @@ namespace ts {
14991445
return combinePaths(project, "tsconfig.json") as ResolvedConfigFileName;
15001446
}
15011447

1502-
export function getAllProjectOutputs(project: ParsedCommandLine): ReadonlyArray<string> {
1503-
if (project.options.outFile || project.options.out) {
1504-
return getOutFileOutputs(project);
1505-
}
1506-
else {
1507-
const outputs: string[] = [];
1508-
for (const inputFile of project.fileNames) {
1509-
outputs.push(...getOutputFileNames(inputFile, project));
1510-
}
1511-
const buildInfoPath = getOutputPathForBuildInfo(project.options);
1512-
if (buildInfoPath) outputs.push(buildInfoPath);
1513-
return outputs;
1514-
}
1515-
}
1516-
1517-
function getFirstProjectOutput(project: ParsedCommandLine): string {
1518-
if (project.options.outFile || project.options.out) {
1519-
return first(getOutFileOutputs(project));
1520-
}
1521-
1522-
for (const inputFile of project.fileNames) {
1523-
const outputs = getOutputFileNames(inputFile, project);
1524-
if (outputs.length) {
1525-
return first(outputs);
1526-
}
1527-
}
1528-
return Debug.fail(`project ${project.options.configFilePath} expected to have at least one output`);
1529-
}
1530-
15311448
export function formatUpToDateStatus<T>(configFileName: string, status: UpToDateStatus, relName: (fileName: string) => string, formatMessage: (message: DiagnosticMessage, ...args: string[]) => T) {
15321449
switch (status.type) {
15331450
case UpToDateStatusType.OutOfDateWithSelf:

src/compiler/utilities.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8101,7 +8101,7 @@ namespace ts {
81018101
}
81028102

81038103
/** @param path directory of the tsconfig.json */
8104-
export function matchFiles(path: string, extensions: ReadonlyArray<string> | undefined, excludes: ReadonlyArray<string> | undefined, includes: ReadonlyArray<string> | undefined, useCaseSensitiveFileNames: boolean, currentDirectory: string, depth: number | undefined, getFileSystemEntries: (path: string) => FileSystemEntries): string[] {
8104+
export function matchFiles(path: string, extensions: ReadonlyArray<string> | undefined, excludes: ReadonlyArray<string> | undefined, includes: ReadonlyArray<string> | undefined, useCaseSensitiveFileNames: boolean, currentDirectory: string, depth: number | undefined, getFileSystemEntries: (path: string) => FileSystemEntries, realpath: (path: string) => string): string[] {
81058105
path = normalizePath(path);
81068106
currentDirectory = normalizePath(currentDirectory);
81078107

@@ -8114,14 +8114,18 @@ namespace ts {
81148114
// Associate an array of results with each include regex. This keeps results in order of the "include" order.
81158115
// If there are no "includes", then just put everything in results[0].
81168116
const results: string[][] = includeFileRegexes ? includeFileRegexes.map(() => []) : [[]];
8117-
8117+
const visited = createMap<true>();
8118+
const toCanonical = createGetCanonicalFileName(useCaseSensitiveFileNames);
81188119
for (const basePath of patterns.basePaths) {
81198120
visitDirectory(basePath, combinePaths(currentDirectory, basePath), depth);
81208121
}
81218122

81228123
return flatten<string>(results);
81238124

81248125
function visitDirectory(path: string, absolutePath: string, depth: number | undefined) {
8126+
const canonicalPath = toCanonical(realpath(absolutePath));
8127+
if (visited.has(canonicalPath)) return;
8128+
visited.set(canonicalPath, true);
81258129
const { files, directories } = getFileSystemEntries(path);
81268130

81278131
for (const current of sort<string>(files, compareStringsCaseSensitive)) {

0 commit comments

Comments
 (0)