Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 37b20ee

Browse filesBrowse files
author
Andy
authored
For duplicate source files of the same package, make one redirect to the other (microsoft#16274)
* For duplicate source files of the same package, make one redirect to the other * Add reuseProgramStructure tests * Copy `sourceFileToPackageId` and `isSourceFileTargetOfRedirect` only if we completely reuse old structure * Use fallthrough instead of early exit from loop * Use a set to efficiently detect duplicate package names * Move map setting outside of createRedirectSourceFile * Correctly handle seenPackageNames set * sourceFileToPackageId -> sourceFileToPackageName * Renames * Respond to PR comments * Fix bug where `oldSourceFile !== newSourceFile` because oldSourceFile was a redirect * Clean up redirectInfo * Respond to PR comments
1 parent 17a6f7b commit 37b20ee
Copy full SHA for 37b20ee

16 files changed

+696-106Lines changed: 696 additions & 106 deletions
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/compiler/core.ts‎

Copy file name to clipboardExpand all lines: src/compiler/core.ts
+7-8Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2210,20 +2210,14 @@ namespace ts {
22102210
/** Must have ".d.ts" first because if ".ts" goes first, that will be detected as the extension instead of ".d.ts". */
22112211
export const supportedTypescriptExtensionsForExtractExtension: ReadonlyArray<Extension> = [Extension.Dts, Extension.Ts, Extension.Tsx];
22122212
export const supportedJavascriptExtensions: ReadonlyArray<Extension> = [Extension.Js, Extension.Jsx];
2213-
const allSupportedExtensions = [...supportedTypeScriptExtensions, ...supportedJavascriptExtensions];
2213+
const allSupportedExtensions: ReadonlyArray<Extension> = [...supportedTypeScriptExtensions, ...supportedJavascriptExtensions];
22142214

22152215
export function getSupportedExtensions(options?: CompilerOptions, extraFileExtensions?: ReadonlyArray<JsFileExtensionInfo>): ReadonlyArray<string> {
22162216
const needAllExtensions = options && options.allowJs;
22172217
if (!extraFileExtensions || extraFileExtensions.length === 0 || !needAllExtensions) {
22182218
return needAllExtensions ? allSupportedExtensions : supportedTypeScriptExtensions;
22192219
}
2220-
const extensions: string[] = allSupportedExtensions.slice(0);
2221-
for (const extInfo of extraFileExtensions) {
2222-
if (extensions.indexOf(extInfo.extension) === -1) {
2223-
extensions.push(extInfo.extension);
2224-
}
2225-
}
2226-
return extensions;
2220+
return deduplicate([...allSupportedExtensions, ...extraFileExtensions.map(e => e.extension)]);
22272221
}
22282222

22292223
export function hasJavaScriptFileExtension(fileName: string) {
@@ -2590,6 +2584,11 @@ namespace ts {
25902584
}
25912585
Debug.fail(`File ${path} has unknown extension.`);
25922586
}
2587+
2588+
export function isAnySupportedFileExtension(path: string): boolean {
2589+
return tryGetExtensionFromPath(path) !== undefined;
2590+
}
2591+
25932592
export function tryGetExtensionFromPath(path: string): Extension | undefined {
25942593
return find<Extension>(supportedTypescriptExtensionsForExtractExtension, e => fileExtensionIs(path, e)) || find(supportedJavascriptExtensions, e => fileExtensionIs(path, e));
25952594
}
Collapse file

‎src/compiler/moduleNameResolver.ts‎

Copy file name to clipboardExpand all lines: src/compiler/moduleNameResolver.ts
+76-34Lines changed: 76 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,32 @@ namespace ts {
1313
return compilerOptions.traceResolution && host.trace !== undefined;
1414
}
1515

16-
/**
17-
* Result of trying to resolve a module.
18-
* At least one of `ts` and `js` should be defined, or the whole thing should be `undefined`.
19-
*/
16+
/** Array that is only intended to be pushed to, never read. */
17+
/* @internal */
18+
export interface Push<T> {
19+
push(value: T): void;
20+
}
21+
22+
function withPackageId(packageId: PackageId | undefined, r: PathAndExtension | undefined): Resolved {
23+
return r && { path: r.path, extension: r.ext, packageId };
24+
}
25+
26+
function noPackageId(r: PathAndExtension | undefined): Resolved {
27+
return withPackageId(/*packageId*/ undefined, r);
28+
}
29+
30+
/** Result of trying to resolve a module. */
2031
interface Resolved {
2132
path: string;
2233
extension: Extension;
34+
packageId: PackageId | undefined;
35+
}
36+
37+
/** Result of trying to resolve a module at a file. Needs to have 'packageId' added later. */
38+
interface PathAndExtension {
39+
path: string;
40+
// (Use a different name than `extension` to make sure Resolved isn't assignable to PathAndExtension.)
41+
ext: Extension;
2342
}
2443

2544
/**
@@ -43,7 +62,7 @@ namespace ts {
4362

4463
function createResolvedModuleWithFailedLookupLocations(resolved: Resolved | undefined, isExternalLibraryImport: boolean, failedLookupLocations: string[]): ResolvedModuleWithFailedLookupLocations {
4564
return {
46-
resolvedModule: resolved && { resolvedFileName: resolved.path, extension: resolved.extension, isExternalLibraryImport },
65+
resolvedModule: resolved && { resolvedFileName: resolved.path, extension: resolved.extension, isExternalLibraryImport, packageId: resolved.packageId },
4766
failedLookupLocations
4867
};
4968
}
@@ -54,9 +73,16 @@ namespace ts {
5473
traceEnabled: boolean;
5574
}
5675

76+
interface PackageJson {
77+
name?: string;
78+
version?: string;
79+
typings?: string;
80+
types?: string;
81+
main?: string;
82+
}
83+
5784
/** Reads from "main" or "types"/"typings" depending on `extensions`. */
58-
function tryReadPackageJsonFields(readTypes: boolean, packageJsonPath: string, baseDirectory: string, state: ModuleResolutionState): string | undefined {
59-
const jsonContent = readJson(packageJsonPath, state.host);
85+
function tryReadPackageJsonFields(readTypes: boolean, jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState): string | undefined {
6086
return readTypes ? tryReadFromField("typings") || tryReadFromField("types") : tryReadFromField("main");
6187

6288
function tryReadFromField(fieldName: "typings" | "types" | "main"): string | undefined {
@@ -83,7 +109,7 @@ namespace ts {
83109
}
84110
}
85111

86-
function readJson(path: string, host: ModuleResolutionHost): { typings?: string, types?: string, main?: string } {
112+
function readJson(path: string, host: ModuleResolutionHost): PackageJson {
87113
try {
88114
const jsonText = host.readFile(path);
89115
return jsonText ? JSON.parse(jsonText) : {};
@@ -646,7 +672,7 @@ namespace ts {
646672
if (extension !== undefined) {
647673
const path = tryFile(candidate, failedLookupLocations, /*onlyRecordFailures*/ false, state);
648674
if (path !== undefined) {
649-
return { path, extension };
675+
return { path, extension, packageId: undefined };
650676
}
651677
}
652678

@@ -709,7 +735,7 @@ namespace ts {
709735
}
710736
const resolved = loadModuleFromNodeModules(extensions, moduleName, containingDirectory, failedLookupLocations, state, cache);
711737
// For node_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files.
712-
return resolved && { value: resolved.value && { resolved: { path: realpath(resolved.value.path, host, traceEnabled), extension: resolved.value.extension }, isExternalLibraryImport: true } };
738+
return resolved && { value: resolved.value && { resolved: { ...resolved.value, path: realpath(resolved.value.path, host, traceEnabled) }, isExternalLibraryImport: true } };
713739
}
714740
else {
715741
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
@@ -747,7 +773,7 @@ namespace ts {
747773
}
748774
const resolvedFromFile = loadModuleFromFile(extensions, candidate, failedLookupLocations, onlyRecordFailures, state);
749775
if (resolvedFromFile) {
750-
return resolvedFromFile;
776+
return noPackageId(resolvedFromFile);
751777
}
752778
}
753779
if (!onlyRecordFailures) {
@@ -768,11 +794,15 @@ namespace ts {
768794
return !host.directoryExists || host.directoryExists(directoryName);
769795
}
770796

797+
function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved {
798+
return noPackageId(loadModuleFromFile(extensions, candidate, failedLookupLocations, onlyRecordFailures, state));
799+
}
800+
771801
/**
772802
* @param {boolean} onlyRecordFailures - if true then function won't try to actually load files but instead record all attempts as failures. This flag is necessary
773803
* in cases when we know upfront that all load attempts will fail (because containing folder does not exists) however we still need to record all failed lookup locations.
774804
*/
775-
function loadModuleFromFile(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined {
805+
function loadModuleFromFile(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
776806
// First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts"
777807
const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, failedLookupLocations, onlyRecordFailures, state);
778808
if (resolvedByAddingExtension) {
@@ -792,7 +822,7 @@ namespace ts {
792822
}
793823

794824
/** Try to return an existing file that adds one of the `extensions` to `candidate`. */
795-
function tryAddingExtensions(candidate: string, extensions: Extensions, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined {
825+
function tryAddingExtensions(candidate: string, extensions: Extensions, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
796826
if (!onlyRecordFailures) {
797827
// check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing
798828
const directory = getDirectoryPath(candidate);
@@ -810,9 +840,9 @@ namespace ts {
810840
return tryExtension(Extension.Js) || tryExtension(Extension.Jsx);
811841
}
812842

813-
function tryExtension(extension: Extension): Resolved | undefined {
814-
const path = tryFile(candidate + extension, failedLookupLocations, onlyRecordFailures, state);
815-
return path && { path, extension };
843+
function tryExtension(ext: Extension): PathAndExtension | undefined {
844+
const path = tryFile(candidate + ext, failedLookupLocations, onlyRecordFailures, state);
845+
return path && { path, ext };
816846
}
817847
}
818848

@@ -838,12 +868,23 @@ namespace ts {
838868
function loadNodeModuleFromDirectory(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState, considerPackageJson = true): Resolved | undefined {
839869
const directoryExists = !onlyRecordFailures && directoryProbablyExists(candidate, state.host);
840870

871+
let packageId: PackageId | undefined;
872+
841873
if (considerPackageJson) {
842874
const packageJsonPath = pathToPackageJson(candidate);
843875
if (directoryExists && state.host.fileExists(packageJsonPath)) {
844-
const fromPackageJson = loadModuleFromPackageJson(packageJsonPath, extensions, candidate, failedLookupLocations, state);
876+
if (state.traceEnabled) {
877+
trace(state.host, Diagnostics.Found_package_json_at_0, packageJsonPath);
878+
}
879+
const jsonContent = readJson(packageJsonPath, state.host);
880+
881+
if (typeof jsonContent.name === "string" && typeof jsonContent.version === "string") {
882+
packageId = { name: jsonContent.name, version: jsonContent.version };
883+
}
884+
885+
const fromPackageJson = loadModuleFromPackageJson(jsonContent, extensions, candidate, failedLookupLocations, state);
845886
if (fromPackageJson) {
846-
return fromPackageJson;
887+
return withPackageId(packageId, fromPackageJson);
847888
}
848889
}
849890
else {
@@ -855,15 +896,11 @@ namespace ts {
855896
}
856897
}
857898

858-
return loadModuleFromFile(extensions, combinePaths(candidate, "index"), failedLookupLocations, !directoryExists, state);
899+
return withPackageId(packageId, loadModuleFromFile(extensions, combinePaths(candidate, "index"), failedLookupLocations, !directoryExists, state));
859900
}
860901

861-
function loadModuleFromPackageJson(packageJsonPath: string, extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
862-
if (state.traceEnabled) {
863-
trace(state.host, Diagnostics.Found_package_json_at_0, packageJsonPath);
864-
}
865-
866-
const file = tryReadPackageJsonFields(extensions !== Extensions.JavaScript, packageJsonPath, candidate, state);
902+
function loadModuleFromPackageJson(jsonContent: PackageJson, extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, state: ModuleResolutionState): PathAndExtension | undefined {
903+
const file = tryReadPackageJsonFields(extensions !== Extensions.JavaScript, jsonContent, candidate, state);
867904
if (!file) {
868905
return undefined;
869906
}
@@ -883,13 +920,18 @@ namespace ts {
883920
// Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types"
884921
const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions;
885922
// Don't do package.json lookup recursively, because Node.js' package lookup doesn't.
886-
return nodeLoadModuleByRelativeName(nextExtensions, file, failedLookupLocations, onlyRecordFailures, state, /*considerPackageJson*/ false);
923+
const result = nodeLoadModuleByRelativeName(nextExtensions, file, failedLookupLocations, onlyRecordFailures, state, /*considerPackageJson*/ false);
924+
if (result) {
925+
// It won't have a `packageId` set, because we disabled `considerPackageJson`.
926+
Debug.assert(result.packageId === undefined);
927+
return { path: result.path, ext: result.extension };
928+
}
887929
}
888930

889931
/** Resolve from an arbitrarily specified file. Return `undefined` if it has an unsupported extension. */
890-
function resolvedIfExtensionMatches(extensions: Extensions, path: string): Resolved | undefined {
891-
const extension = tryGetExtensionFromPath(path);
892-
return extension !== undefined && extensionIsOk(extensions, extension) ? { path, extension } : undefined;
932+
function resolvedIfExtensionMatches(extensions: Extensions, path: string): PathAndExtension | undefined {
933+
const ext = tryGetExtensionFromPath(path);
934+
return ext !== undefined && extensionIsOk(extensions, ext) ? { path, ext } : undefined;
893935
}
894936

895937
/** True if `extension` is one of the supported `extensions`. */
@@ -911,7 +953,7 @@ namespace ts {
911953
function loadModuleFromNodeModulesFolder(extensions: Extensions, moduleName: string, nodeModulesFolder: string, nodeModulesFolderExists: boolean, failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
912954
const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName));
913955

914-
return loadModuleFromFile(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state) ||
956+
return loadModuleFromFileNoPackageId(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state) ||
915957
loadNodeModuleFromDirectory(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state);
916958
}
917959

@@ -996,7 +1038,7 @@ namespace ts {
9961038
if (traceEnabled) {
9971039
trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache, moduleName);
9981040
}
999-
return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, extension: result.resolvedModule.extension } };
1041+
return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } };
10001042
}
10011043
}
10021044

@@ -1010,7 +1052,7 @@ namespace ts {
10101052
return createResolvedModuleWithFailedLookupLocations(resolved && resolved.value, /*isExternalLibraryImport*/ false, failedLookupLocations);
10111053

10121054
function tryResolve(extensions: Extensions): SearchResult<Resolved> {
1013-
const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFile, failedLookupLocations, state);
1055+
const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, failedLookupLocations, state);
10141056
if (resolvedUsingSettings) {
10151057
return { value: resolvedUsingSettings };
10161058
}
@@ -1024,7 +1066,7 @@ namespace ts {
10241066
return resolutionFromCache;
10251067
}
10261068
const searchName = normalizePath(combinePaths(directory, moduleName));
1027-
return toSearchResult(loadModuleFromFile(extensions, searchName, failedLookupLocations, /*onlyRecordFailures*/ false, state));
1069+
return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, failedLookupLocations, /*onlyRecordFailures*/ false, state));
10281070
});
10291071
if (resolved) {
10301072
return resolved;
@@ -1036,7 +1078,7 @@ namespace ts {
10361078
}
10371079
else {
10381080
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
1039-
return toSearchResult(loadModuleFromFile(extensions, candidate, failedLookupLocations, /*onlyRecordFailures*/ false, state));
1081+
return toSearchResult(loadModuleFromFileNoPackageId(extensions, candidate, failedLookupLocations, /*onlyRecordFailures*/ false, state));
10401082
}
10411083
}
10421084
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.