-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Process package.json exports with auto-import provider #47092
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
Merged
andrewbranch
merged 12 commits into
microsoft:main
from
andrewbranch:auto-import-provider-resolution
Jan 11, 2022
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
3ef80b6
Have auto-import provider pull in `exports`
andrewbranch 32b5f7d
Revert filtering of node_modules relative paths, to do in separate PR
andrewbranch eb4ec8b
Do @types and JS prioritization correctly
andrewbranch 6a78118
Cache entrypoints on PackageJsonInfo
andrewbranch f3be5ea
Add one more test
andrewbranch b0c649f
Delete unused function
andrewbranch 78db89f
Fix other tests - dependencies need package.json files
andrewbranch 86eb070
Do two passes of exports resolution
andrewbranch f18e95d
Fix missed refactor
andrewbranch f021d8e
Apply suggestions from code review
andrewbranch 766cfaf
Uncomment rest of test
andrewbranch 55ba7c9
Handle array targets
andrewbranch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -340,10 +340,7 @@ namespace ts { | |
} | ||
|
||
const failedLookupLocations: string[] = []; | ||
const features = | ||
getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node12 ? NodeResolutionFeatures.Node12Default : | ||
getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext ? NodeResolutionFeatures.NodeNextDefault : | ||
NodeResolutionFeatures.None; | ||
const features = getDefaultNodeResolutionFeatures(options); | ||
const moduleResolutionState: ModuleResolutionState = { compilerOptions: options, host, traceEnabled, failedLookupLocations, packageJsonInfoCache: cache, features, conditions: ["node", "require", "types"] }; | ||
let resolved = primaryLookup(); | ||
let primary = true; | ||
|
@@ -433,6 +430,42 @@ namespace ts { | |
} | ||
} | ||
|
||
function getDefaultNodeResolutionFeatures(options: CompilerOptions) { | ||
return getEmitModuleResolutionKind(options) === ModuleResolutionKind.Node12 ? NodeResolutionFeatures.Node12Default : | ||
getEmitModuleResolutionKind(options) === ModuleResolutionKind.NodeNext ? NodeResolutionFeatures.NodeNextDefault : | ||
NodeResolutionFeatures.None; | ||
} | ||
|
||
/** | ||
* @internal | ||
* Does not try `@types/${packageName}` - use a second pass if needed. | ||
*/ | ||
export function resolvePackageNameToPackageJson( | ||
packageName: string, | ||
containingDirectory: string, | ||
options: CompilerOptions, | ||
host: ModuleResolutionHost, | ||
cache: ModuleResolutionCache | undefined, | ||
): PackageJsonInfo | undefined { | ||
const moduleResolutionState: ModuleResolutionState = { | ||
compilerOptions: options, | ||
host, | ||
traceEnabled: isTraceEnabled(options, host), | ||
failedLookupLocations: [], | ||
packageJsonInfoCache: cache?.getPackageJsonInfoCache(), | ||
conditions: emptyArray, | ||
features: NodeResolutionFeatures.None, | ||
}; | ||
|
||
return forEachAncestorDirectory(containingDirectory, ancestorDirectory => { | ||
if (getBaseFileName(ancestorDirectory) !== "node_modules") { | ||
const nodeModulesFolder = combinePaths(ancestorDirectory, "node_modules"); | ||
const candidate = combinePaths(nodeModulesFolder, packageName); | ||
return getPackageJsonInfo(candidate, /*onlyRecordFailures*/ false, moduleResolutionState); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Given a set of options, returns the set of type directive names | ||
* that should be included for this program automatically. | ||
|
@@ -1171,11 +1204,6 @@ namespace ts { | |
return resolvedModule.resolvedFileName; | ||
} | ||
|
||
/* @internal */ | ||
export function tryResolveJSModule(moduleName: string, initialDir: string, host: ModuleResolutionHost) { | ||
return tryResolveJSModuleWorker(moduleName, initialDir, host).resolvedModule; | ||
} | ||
|
||
/* @internal */ | ||
enum NodeResolutionFeatures { | ||
None = 0, | ||
|
@@ -1536,11 +1564,124 @@ namespace ts { | |
return withPackageId(packageInfo, loadNodeModuleFromDirectoryWorker(extensions, candidate, onlyRecordFailures, state, packageJsonContent, versionPaths)); | ||
} | ||
|
||
/* @internal */ | ||
export function getEntrypointsFromPackageJsonInfo( | ||
packageJsonInfo: PackageJsonInfo, | ||
options: CompilerOptions, | ||
host: ModuleResolutionHost, | ||
cache: ModuleResolutionCache | undefined, | ||
resolveJs?: boolean, | ||
): string[] | false { | ||
if (!resolveJs && packageJsonInfo.resolvedEntrypoints !== undefined) { | ||
// Cached value excludes resolutions to JS files - those could be | ||
// cached separately, but they're used rarely. | ||
return packageJsonInfo.resolvedEntrypoints; | ||
} | ||
|
||
let entrypoints: string[] | undefined; | ||
const extensions = resolveJs ? Extensions.JavaScript : Extensions.TypeScript; | ||
const features = getDefaultNodeResolutionFeatures(options); | ||
const requireState: ModuleResolutionState = { | ||
compilerOptions: options, | ||
host, | ||
traceEnabled: isTraceEnabled(options, host), | ||
failedLookupLocations: [], | ||
packageJsonInfoCache: cache?.getPackageJsonInfoCache(), | ||
conditions: ["node", "require", "types"], | ||
features, | ||
}; | ||
const requireResolution = loadNodeModuleFromDirectoryWorker( | ||
extensions, | ||
packageJsonInfo.packageDirectory, | ||
/*onlyRecordFailures*/ false, | ||
requireState, | ||
packageJsonInfo.packageJsonContent, | ||
packageJsonInfo.versionPaths); | ||
entrypoints = append(entrypoints, requireResolution?.path); | ||
|
||
if (features & NodeResolutionFeatures.Exports && packageJsonInfo.packageJsonContent.exports) { | ||
for (const conditions of [["node", "import", "types"], ["node", "require", "types"]]) { | ||
const exportState = { ...requireState, failedLookupLocations: [], conditions }; | ||
const exportResolutions = loadEntrypointsFromExportMap( | ||
packageJsonInfo, | ||
packageJsonInfo.packageJsonContent.exports, | ||
exportState, | ||
extensions); | ||
if (exportResolutions) { | ||
for (const resolution of exportResolutions) { | ||
entrypoints = appendIfUnique(entrypoints, resolution.path); | ||
} | ||
} | ||
} | ||
} | ||
|
||
return packageJsonInfo.resolvedEntrypoints = entrypoints || false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Calling this function caches its results on the |
||
} | ||
|
||
function loadEntrypointsFromExportMap( | ||
scope: PackageJsonInfo, | ||
exports: object, | ||
state: ModuleResolutionState, | ||
extensions: Extensions, | ||
): PathAndExtension[] | undefined { | ||
let entrypoints: PathAndExtension[] | undefined; | ||
if (isArray(exports)) { | ||
for (const target of exports) { | ||
loadEntrypointsFromTargetExports(target); | ||
} | ||
} | ||
// eslint-disable-next-line no-null/no-null | ||
else if (typeof exports === "object" && exports !== null && allKeysStartWithDot(exports as MapLike<unknown>)) { | ||
andrewbranch marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for (const key in exports) { | ||
loadEntrypointsFromTargetExports((exports as MapLike<unknown>)[key]); | ||
} | ||
} | ||
else { | ||
loadEntrypointsFromTargetExports(exports); | ||
} | ||
return entrypoints; | ||
|
||
function loadEntrypointsFromTargetExports(target: unknown): boolean | undefined { | ||
if (typeof target === "string" && startsWith(target, "./") && target.indexOf("*") === -1) { | ||
const partsAfterFirst = getPathComponents(target).slice(2); | ||
if (partsAfterFirst.indexOf("..") >= 0 || partsAfterFirst.indexOf(".") >= 0 || partsAfterFirst.indexOf("node_modules") >= 0) { | ||
return false; | ||
} | ||
const resolvedTarget = combinePaths(scope.packageDirectory, target); | ||
const finalPath = getNormalizedAbsolutePath(resolvedTarget, state.host.getCurrentDirectory?.()); | ||
const result = loadJSOrExactTSFileName(extensions, finalPath, /*recordOnlyFailures*/ false, state); | ||
if (result) { | ||
entrypoints = appendIfUnique(entrypoints, result, (a, b) => a.path === b.path); | ||
return true; | ||
} | ||
} | ||
else if (Array.isArray(target)) { | ||
for (const t of target) { | ||
const success = loadEntrypointsFromTargetExports(t); | ||
if (success) { | ||
return true; | ||
} | ||
} | ||
} | ||
// eslint-disable-next-line no-null/no-null | ||
else if (typeof target === "object" && target !== null) { | ||
return forEach(getOwnKeys(target as MapLike<unknown>), key => { | ||
if (key === "default" || contains(state.conditions, key) || isApplicableVersionedTypesKey(state.conditions, key)) { | ||
loadEntrypointsFromTargetExports((target as MapLike<unknown>)[key]); | ||
return true; | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
|
||
/*@internal*/ | ||
interface PackageJsonInfo { | ||
packageDirectory: string; | ||
packageJsonContent: PackageJsonPathFields; | ||
versionPaths: VersionPaths | undefined; | ||
/** false: resolved to nothing. undefined: not yet resolved */ | ||
resolvedEntrypoints: string[] | false | undefined; | ||
} | ||
|
||
/** | ||
|
@@ -1606,7 +1747,7 @@ namespace ts { | |
trace(host, Diagnostics.Found_package_json_at_0, packageJsonPath); | ||
} | ||
const versionPaths = readPackageJsonTypesVersionPaths(packageJsonContent, state); | ||
const result = { packageDirectory, packageJsonContent, versionPaths }; | ||
const result = { packageDirectory, packageJsonContent, versionPaths, resolvedEntrypoints: undefined }; | ||
state.packageJsonInfoCache?.setPackageJsonInfo(packageJsonPath, result); | ||
return result; | ||
} | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.