Skip to content

Refactor module resolution logic to have configurable goal extensions #9430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,8 @@ namespace ts {

// Rather than requery this for each file and filespec, we query the supported extensions
// once and store it on the expansion context.
const supportedExtensions = getSupportedExtensions(options);
const resolvedExtensions = getExtensionCollectionForCompilerOptions(options);
const supportedExtensions = getExtensionsForCollection(resolvedExtensions);

// Literal files are always included verbatim. An "include" or "exclude" specification cannot
// remove a literal file.
Expand All @@ -999,7 +1000,7 @@ namespace ts {
// This handles cases where we may encounter both <file>.ts and
// <file>.d.ts (or <file>.js if "allowJs" is enabled) in the same
// directory when they are compilation outputs.
if (hasFileWithHigherPriorityExtension(file, literalFileMap, wildcardFileMap, supportedExtensions, keyMapper)) {
if (hasFileWithHigherPriorityExtension(file, literalFileMap, wildcardFileMap, resolvedExtensions, keyMapper)) {
continue;
}

Expand All @@ -1011,7 +1012,7 @@ namespace ts {
// extension due to the user-defined order of entries in the
// "include" array. If there is a lower priority extension in the
// same directory, we should remove it.
removeWildcardFilesWithLowerPriorityExtension(file, wildcardFileMap, supportedExtensions, keyMapper);
removeWildcardFilesWithLowerPriorityExtension(file, wildcardFileMap, resolvedExtensions, keyMapper);

const key = keyMapper(file);
if (!hasProperty(literalFileMap, key) && !hasProperty(wildcardFileMap, key)) {
Expand Down Expand Up @@ -1112,11 +1113,11 @@ namespace ts {
* @param extensionPriority The priority of the extension.
* @param context The expansion context.
*/
function hasFileWithHigherPriorityExtension(file: string, literalFiles: Map<string>, wildcardFiles: Map<string>, extensions: string[], keyMapper: (value: string) => string) {
function hasFileWithHigherPriorityExtension(file: string, literalFiles: Map<string>, wildcardFiles: Map<string>, extensions: PrioritizedExtensionCollection, keyMapper: (value: string) => string) {
const extensionPriority = getExtensionPriority(file, extensions);
const adjustedExtensionPriority = adjustExtensionPriority(extensionPriority);
for (let i = ExtensionPriority.Highest; i < adjustedExtensionPriority; i++) {
const higherPriorityExtension = extensions[i];
for (let i = PrioritizedExtensionCollection.HighestPriority; i < adjustedExtensionPriority; i <<= 1) {
const higherPriorityExtension = extensionMap[i];
const higherPriorityPath = keyMapper(changeExtension(file, higherPriorityExtension));
if (hasProperty(literalFiles, higherPriorityPath) || hasProperty(wildcardFiles, higherPriorityPath)) {
return true;
Expand All @@ -1134,11 +1135,11 @@ namespace ts {
* @param extensionPriority The priority of the extension.
* @param context The expansion context.
*/
function removeWildcardFilesWithLowerPriorityExtension(file: string, wildcardFiles: Map<string>, extensions: string[], keyMapper: (value: string) => string) {
function removeWildcardFilesWithLowerPriorityExtension(file: string, wildcardFiles: Map<string>, extensions: PrioritizedExtensionCollection, keyMapper: (value: string) => string) {
const extensionPriority = getExtensionPriority(file, extensions);
const nextExtensionPriority = getNextLowestExtensionPriority(extensionPriority);
for (let i = nextExtensionPriority; i < extensions.length; i++) {
const lowerPriorityExtension = extensions[i];
for (let i = nextExtensionPriority; i < PrioritizedExtensionCollection.LowestPriority; i <<= 1) {
const lowerPriorityExtension = extensionMap[i];
const lowerPriorityPath = keyMapper(changeExtension(file, lowerPriorityExtension));
delete wildcardFiles[lowerPriorityPath];
}
Expand Down
95 changes: 64 additions & 31 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1136,10 +1136,56 @@ namespace ts {
*/
export const supportedTypeScriptExtensions = [".ts", ".tsx", ".d.ts"];
export const supportedJavascriptExtensions = [".js", ".jsx"];
const allSupportedExtensions = supportedTypeScriptExtensions.concat(supportedJavascriptExtensions);

/* @internal */
export const extensionMap = {
[PrioritizedExtensionCollection.TS]: ".ts",
[PrioritizedExtensionCollection.TSX]: ".tsx",
[PrioritizedExtensionCollection.DTS]: ".d.ts",
[PrioritizedExtensionCollection.JS]: ".js",
[PrioritizedExtensionCollection.JSX]: ".jsx",
};

/* @internal */
export const reverseExtensionMap: { [index: string]: PrioritizedExtensionCollection } = {
".ts": PrioritizedExtensionCollection.TS,
".tsx": PrioritizedExtensionCollection.TSX,
".d.ts": PrioritizedExtensionCollection.DTS,
".js": PrioritizedExtensionCollection.JS,
".jsx": PrioritizedExtensionCollection.JSX,
};

/* @internal */
const extensionPowerset: { [index: number]: string[] } = {
[PrioritizedExtensionCollection.None]: []
};

function createPowerset(base: PrioritizedExtensionCollection, powerset: { [index: number]: string[] }): void {
const newBases: number[] = [];
for (const key in extensionMap) {
const newKey = base | parseInt(key);
if (!powerset[newKey]) {
newBases.push(newKey);
powerset[newKey] = concatenate(powerset[base], [extensionMap[key]]);
}
}
for (const newBase of newBases) {
createPowerset(newBase, powerset);
}
}

createPowerset(PrioritizedExtensionCollection.None, extensionPowerset);

export function getExtensionsForCollection(goal: PrioritizedExtensionCollection): string[] {
return extensionPowerset[goal];
}

export function getExtensionCollectionForCompilerOptions(options?: CompilerOptions) {
return (options && options.allowJs) ? PrioritizedExtensionCollection.Any : PrioritizedExtensionCollection.TypeScript;
}

export function getSupportedExtensions(options?: CompilerOptions): string[] {
return options && options.allowJs ? allSupportedExtensions : supportedTypeScriptExtensions;
return getExtensionsForCollection(getExtensionCollectionForCompilerOptions(options));
}

export function isSupportedSourceFileName(fileName: string, compilerOptions?: CompilerOptions) {
Expand All @@ -1153,56 +1199,43 @@ namespace ts {
return false;
}

/**
* Extension boundaries by priority. Lower numbers indicate higher priorities, and are
* aligned to the offset of the highest priority extension in the
* allSupportedExtensions array.
*/
export const enum ExtensionPriority {
TypeScriptFiles = 0,
DeclarationAndJavaScriptFiles = 2,
Limit = 5,

Highest = TypeScriptFiles,
Lowest = DeclarationAndJavaScriptFiles,
}

export function getExtensionPriority(path: string, supportedExtensions: string[]): ExtensionPriority {
for (let i = supportedExtensions.length - 1; i >= 0; i--) {
if (fileExtensionIs(path, supportedExtensions[i])) {
return adjustExtensionPriority(<ExtensionPriority>i);
export function getExtensionPriority(path: string, supportedExtensions: PrioritizedExtensionCollection): PrioritizedExtensionCollection {
const extensions = getExtensionsForCollection(supportedExtensions);
for (let i = extensions.length - 1; i >= 0; i--) {
if (fileExtensionIs(path, extensions[i])) {
return adjustExtensionPriority(reverseExtensionMap[extensions[i]]);
}
}

// If its not in the list of supported extensions, this is likely a
// TypeScript file with a non-ts extension
return ExtensionPriority.Highest;
return PrioritizedExtensionCollection.HighestPriority;
}

/**
* Adjusts an extension priority to be the highest priority within the same range.
*/
export function adjustExtensionPriority(extensionPriority: ExtensionPriority): ExtensionPriority {
if (extensionPriority < ExtensionPriority.DeclarationAndJavaScriptFiles) {
return ExtensionPriority.TypeScriptFiles;
export function adjustExtensionPriority(prioritizedExtension: PrioritizedExtensionCollection): PrioritizedExtensionCollection {
if (prioritizedExtension < PrioritizedExtensionCollection.SecondPriority) {
return PrioritizedExtensionCollection.FirstPriority;
}
else if (extensionPriority < ExtensionPriority.Limit) {
return ExtensionPriority.DeclarationAndJavaScriptFiles;
else if (prioritizedExtension < PrioritizedExtensionCollection.LowestPriority) {
return PrioritizedExtensionCollection.SecondPriority;
}
else {
return ExtensionPriority.Limit;
return PrioritizedExtensionCollection.LowestPriority;
}
}

/**
* Gets the next lowest extension priority for a given priority.
*/
export function getNextLowestExtensionPriority(extensionPriority: ExtensionPriority): ExtensionPriority {
if (extensionPriority < ExtensionPriority.DeclarationAndJavaScriptFiles) {
return ExtensionPriority.DeclarationAndJavaScriptFiles;
export function getNextLowestExtensionPriority(prioritizedExtension: PrioritizedExtensionCollection): PrioritizedExtensionCollection {
if (prioritizedExtension < PrioritizedExtensionCollection.SecondPriority) {
return PrioritizedExtensionCollection.SecondPriority;
}
else {
return ExtensionPriority.Limit;
return PrioritizedExtensionCollection.LowestPriority;
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -2664,7 +2664,7 @@
"category": "Message",
"code": 6099
},
"'package.json' does not have 'types' field.": {
"'package.json' does not have '{0}' field.": {
"category": "Message",
"code": 6100
},
Expand All @@ -2684,7 +2684,7 @@
"category": "Message",
"code": 6104
},
"Expected type of '{0}' field in 'package.json' to be 'string', got '{1}'.": {
"Expected type of '{0}' field in 'package.json' to be '{1}', got '{2}'.": {
"category": "Message",
"code": 6105
},
Expand Down
Loading