Skip to content

Commit 60b9aa3

Browse files
author
andy-ms
committed
For duplicate source files of the same package, make one redirect to the other
1 parent 8ace7b8 commit 60b9aa3

15 files changed

+568
-85
lines changed

src/compiler/core.ts

+18-15
Original file line numberDiff line numberDiff line change
@@ -953,7 +953,7 @@ namespace ts {
953953
* @param map A map-like.
954954
* @param key A property key.
955955
*/
956-
export function hasProperty<T>(map: MapLike<T>, key: string): boolean {
956+
export function hasProperty(map: MapLike<any>, key: string): boolean {
957957
return hasOwnProperty.call(map, key);
958958
}
959959

@@ -2478,21 +2478,24 @@ namespace ts {
24782478
}
24792479
Debug.fail(`File ${path} has unknown extension.`);
24802480
}
2481+
2482+
export function isAnySupportedFileExtension(path: string): boolean {
2483+
return tryGetExtensionFromPath(path) !== undefined;
2484+
}
2485+
2486+
const allExtensions = [Extension.Dts, Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx];
2487+
24812488
export function tryGetExtensionFromPath(path: string): Extension | undefined {
2482-
if (fileExtensionIs(path, ".d.ts")) {
2483-
return Extension.Dts;
2484-
}
2485-
if (fileExtensionIs(path, ".ts")) {
2486-
return Extension.Ts;
2487-
}
2488-
if (fileExtensionIs(path, ".tsx")) {
2489-
return Extension.Tsx;
2490-
}
2491-
if (fileExtensionIs(path, ".js")) {
2492-
return Extension.Js;
2493-
}
2494-
if (fileExtensionIs(path, ".jsx")) {
2495-
return Extension.Jsx;
2489+
return ts.find(allExtensions, ext => fileExtensionIs(path, extensionText(ext)));
2490+
}
2491+
2492+
export function extensionText(ext: Extension): string {
2493+
switch (ext) {
2494+
case Extension.Dts: return ".d.ts";
2495+
case Extension.Ts: return ".ts";
2496+
case Extension.Tsx: return ".tsx";
2497+
case Extension.Js: return ".js";
2498+
case Extension.Jsx: return ".jsx";
24962499
}
24972500
}
24982501

src/compiler/moduleNameResolver.ts

+66-37
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,26 @@ namespace ts {
1919
push(value: T): void;
2020
}
2121

22-
/**
23-
* Result of trying to resolve a module.
24-
* At least one of `ts` and `js` should be defined, or the whole thing should be `undefined`.
25-
*/
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. */
2631
interface Resolved {
2732
path: string;
2833
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;
2942
}
3043

3144
/**
@@ -49,7 +62,7 @@ namespace ts {
4962

5063
function createResolvedModuleWithFailedLookupLocations(resolved: Resolved | undefined, isExternalLibraryImport: boolean, failedLookupLocations: string[]): ResolvedModuleWithFailedLookupLocations {
5164
return {
52-
resolvedModule: resolved && { resolvedFileName: resolved.path, extension: resolved.extension, isExternalLibraryImport },
65+
resolvedModule: resolved && { resolvedFileName: resolved.path, extension: resolved.extension, isExternalLibraryImport, packageId: resolved.packageId },
5366
failedLookupLocations
5467
};
5568
}
@@ -65,8 +78,7 @@ namespace ts {
6578
}
6679

6780
/** Reads from "main" or "types"/"typings" depending on `extensions`. */
68-
function tryReadPackageJsonFields(readTypes: boolean, packageJsonPath: string, baseDirectory: string, state: ModuleResolutionState): string | undefined {
69-
const jsonContent = readJson(packageJsonPath, state.host);
81+
function tryReadPackageJsonFields(readTypes: boolean, jsonContent: PackageJson, baseDirectory: string, state: ModuleResolutionState): string | undefined {
7082
return readTypes ? tryReadFromField("typings") || tryReadFromField("types") : tryReadFromField("main");
7183

7284
function tryReadFromField(fieldName: "typings" | "types" | "main"): string | undefined {
@@ -93,7 +105,8 @@ namespace ts {
93105
}
94106
}
95107

96-
function readJson(path: string, host: ModuleResolutionHost): { typings?: string, types?: string, main?: string } {
108+
interface PackageJson { name?: string; version?: string; typings?: string; types?: string; main?: string; }
109+
function readJson(path: string, host: ModuleResolutionHost): PackageJson {
97110
try {
98111
const jsonText = host.readFile(path);
99112
return jsonText ? JSON.parse(jsonText) : {};
@@ -658,7 +671,7 @@ namespace ts {
658671
if (extension !== undefined) {
659672
const path = tryFile(candidate, failedLookupLocations, /*onlyRecordFailures*/ false, state);
660673
if (path !== undefined) {
661-
return { path, extension };
674+
return { path, extension, packageId: undefined };
662675
}
663676
}
664677

@@ -721,7 +734,7 @@ namespace ts {
721734
}
722735
const resolved = loadModuleFromNodeModules(extensions, moduleName, containingDirectory, failedLookupLocations, state, cache);
723736
// For node_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files.
724-
return resolved && { value: resolved.value && { resolved: { path: realpath(resolved.value.path, host, traceEnabled), extension: resolved.value.extension }, isExternalLibraryImport: true } };
737+
return resolved && { value: resolved.value && { resolved: { ...resolved.value, path: realpath(resolved.value.path, host, traceEnabled) }, isExternalLibraryImport: true } };
725738
}
726739
else {
727740
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
@@ -759,7 +772,7 @@ namespace ts {
759772
}
760773
const resolvedFromFile = loadModuleFromFile(extensions, candidate, failedLookupLocations, onlyRecordFailures, state);
761774
if (resolvedFromFile) {
762-
return resolvedFromFile;
775+
return noPackageId(resolvedFromFile);
763776
}
764777
}
765778
if (!onlyRecordFailures) {
@@ -780,11 +793,15 @@ namespace ts {
780793
return !host.directoryExists || host.directoryExists(directoryName);
781794
}
782795

796+
function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved {
797+
return noPackageId(loadModuleFromFile(extensions, candidate, failedLookupLocations, onlyRecordFailures, state));
798+
}
799+
783800
/**
784801
* @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
785802
* 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.
786803
*/
787-
function loadModuleFromFile(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined {
804+
function loadModuleFromFile(extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
788805
// First, try adding an extension. An import of "foo" could be matched by a file "foo.ts", or "foo.js" by "foo.js.ts"
789806
const resolvedByAddingExtension = tryAddingExtensions(candidate, extensions, failedLookupLocations, onlyRecordFailures, state);
790807
if (resolvedByAddingExtension) {
@@ -804,7 +821,7 @@ namespace ts {
804821
}
805822

806823
/** Try to return an existing file that adds one of the `extensions` to `candidate`. */
807-
function tryAddingExtensions(candidate: string, extensions: Extensions, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined {
824+
function tryAddingExtensions(candidate: string, extensions: Extensions, failedLookupLocations: Push<string>, onlyRecordFailures: boolean, state: ModuleResolutionState): PathAndExtension | undefined {
808825
if (!onlyRecordFailures) {
809826
// check if containing folder exists - if it doesn't then just record failures for all supported extensions without disk probing
810827
const directory = getDirectoryPath(candidate);
@@ -815,16 +832,16 @@ namespace ts {
815832

816833
switch (extensions) {
817834
case Extensions.DtsOnly:
818-
return tryExtension(".d.ts", Extension.Dts);
835+
return tryExtension(Extension.Dts);
819836
case Extensions.TypeScript:
820-
return tryExtension(".ts", Extension.Ts) || tryExtension(".tsx", Extension.Tsx) || tryExtension(".d.ts", Extension.Dts);
837+
return tryExtension(Extension.Ts) || tryExtension(Extension.Tsx) || tryExtension(Extension.Dts);
821838
case Extensions.JavaScript:
822-
return tryExtension(".js", Extension.Js) || tryExtension(".jsx", Extension.Jsx);
839+
return tryExtension(Extension.Js) || tryExtension(Extension.Jsx);
823840
}
824841

825-
function tryExtension(ext: string, extension: Extension): Resolved | undefined {
826-
const path = tryFile(candidate + ext, failedLookupLocations, onlyRecordFailures, state);
827-
return path && { path, extension };
842+
function tryExtension(ext: Extension): PathAndExtension | undefined {
843+
const path = tryFile(candidate + extensionText(ext), failedLookupLocations, onlyRecordFailures, state);
844+
return path && { path, ext };
828845
}
829846
}
830847

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

870+
let packageId: PackageId | undefined;
871+
853872
if (considerPackageJson) {
854873
const packageJsonPath = pathToPackageJson(candidate);
855874
if (directoryExists && state.host.fileExists(packageJsonPath)) {
856-
const fromPackageJson = loadModuleFromPackageJson(packageJsonPath, extensions, candidate, failedLookupLocations, state);
875+
if (state.traceEnabled) {
876+
trace(state.host, Diagnostics.Found_package_json_at_0, packageJsonPath);
877+
}
878+
const jsonContent = readJson(packageJsonPath, state.host);
879+
880+
if (typeof jsonContent.name === "string" && typeof jsonContent.version === "string") {
881+
packageId = { name: jsonContent.name, version: jsonContent.version };
882+
}
883+
884+
const fromPackageJson = loadModuleFromPackageJson(jsonContent, extensions, candidate, failedLookupLocations, state);
857885
if (fromPackageJson) {
858-
return fromPackageJson;
886+
return withPackageId(packageId, fromPackageJson);
859887
}
860888
}
861889
else {
@@ -867,15 +895,11 @@ namespace ts {
867895
}
868896
}
869897

870-
return loadModuleFromFile(extensions, combinePaths(candidate, "index"), failedLookupLocations, !directoryExists, state);
898+
return withPackageId(packageId, loadModuleFromFile(extensions, combinePaths(candidate, "index"), failedLookupLocations, !directoryExists, state));
871899
}
872900

873-
function loadModuleFromPackageJson(packageJsonPath: string, extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
874-
if (state.traceEnabled) {
875-
trace(state.host, Diagnostics.Found_package_json_at_0, packageJsonPath);
876-
}
877-
878-
const file = tryReadPackageJsonFields(extensions !== Extensions.JavaScript, packageJsonPath, candidate, state);
901+
function loadModuleFromPackageJson(jsonContent: PackageJson, extensions: Extensions, candidate: string, failedLookupLocations: Push<string>, state: ModuleResolutionState): PathAndExtension | undefined {
902+
const file = tryReadPackageJsonFields(extensions !== Extensions.JavaScript, jsonContent, candidate, state);
879903
if (!file) {
880904
return undefined;
881905
}
@@ -895,13 +919,18 @@ namespace ts {
895919
// Even if extensions is DtsOnly, we can still look up a .ts file as a result of package.json "types"
896920
const nextExtensions = extensions === Extensions.DtsOnly ? Extensions.TypeScript : extensions;
897921
// Don't do package.json lookup recursively, because Node.js' package lookup doesn't.
898-
return nodeLoadModuleByRelativeName(nextExtensions, file, failedLookupLocations, onlyRecordFailures, state, /*considerPackageJson*/ false);
922+
const result = nodeLoadModuleByRelativeName(nextExtensions, file, failedLookupLocations, onlyRecordFailures, state, /*considerPackageJson*/ false);
923+
if (result) {
924+
// It won't have a `packageId` set, because we disabled `considerPackageJson`.
925+
Debug.assert(result.packageId === undefined);
926+
return { path: result.path, ext: result.extension };
927+
}
899928
}
900929

901930
/** Resolve from an arbitrarily specified file. Return `undefined` if it has an unsupported extension. */
902-
function resolvedIfExtensionMatches(extensions: Extensions, path: string): Resolved | undefined {
903-
const extension = tryGetExtensionFromPath(path);
904-
return extension !== undefined && extensionIsOk(extensions, extension) ? { path, extension } : undefined;
931+
function resolvedIfExtensionMatches(extensions: Extensions, path: string): PathAndExtension | undefined {
932+
const ext = tryGetExtensionFromPath(path);
933+
return ext !== undefined && extensionIsOk(extensions, ext) ? { path, ext } : undefined;
905934
}
906935

907936
/** True if `extension` is one of the supported `extensions`. */
@@ -923,7 +952,7 @@ namespace ts {
923952
function loadModuleFromNodeModulesFolder(extensions: Extensions, moduleName: string, nodeModulesFolder: string, nodeModulesFolderExists: boolean, failedLookupLocations: Push<string>, state: ModuleResolutionState): Resolved | undefined {
924953
const candidate = normalizePath(combinePaths(nodeModulesFolder, moduleName));
925954

926-
return loadModuleFromFile(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state) ||
955+
return loadModuleFromFileNoPackageId(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state) ||
927956
loadNodeModuleFromDirectory(extensions, candidate, failedLookupLocations, !nodeModulesFolderExists, state);
928957
}
929958

@@ -1008,7 +1037,7 @@ namespace ts {
10081037
if (traceEnabled) {
10091038
trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache, moduleName);
10101039
}
1011-
return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, extension: result.resolvedModule.extension } };
1040+
return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } };
10121041
}
10131042
}
10141043

@@ -1022,7 +1051,7 @@ namespace ts {
10221051
return createResolvedModuleWithFailedLookupLocations(resolved && resolved.value, /*isExternalLibraryImport*/ false, failedLookupLocations);
10231052

10241053
function tryResolve(extensions: Extensions): SearchResult<Resolved> {
1025-
const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFile, failedLookupLocations, state);
1054+
const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, failedLookupLocations, state);
10261055
if (resolvedUsingSettings) {
10271056
return { value: resolvedUsingSettings };
10281057
}
@@ -1036,7 +1065,7 @@ namespace ts {
10361065
return resolutionFromCache;
10371066
}
10381067
const searchName = normalizePath(combinePaths(directory, moduleName));
1039-
return toSearchResult(loadModuleFromFile(extensions, searchName, failedLookupLocations, /*onlyRecordFailures*/ false, state));
1068+
return toSearchResult(loadModuleFromFileNoPackageId(extensions, searchName, failedLookupLocations, /*onlyRecordFailures*/ false, state));
10401069
});
10411070
if (resolved) {
10421071
return resolved;
@@ -1048,7 +1077,7 @@ namespace ts {
10481077
}
10491078
else {
10501079
const candidate = normalizePath(combinePaths(containingDirectory, moduleName));
1051-
return toSearchResult(loadModuleFromFile(extensions, candidate, failedLookupLocations, /*onlyRecordFailures*/ false, state));
1080+
return toSearchResult(loadModuleFromFileNoPackageId(extensions, candidate, failedLookupLocations, /*onlyRecordFailures*/ false, state));
10521081
}
10531082
}
10541083
}

0 commit comments

Comments
 (0)