fix(check): support types@ export conditions (#28450)

This commit is contained in:
David Sherret 2025-03-10 13:20:48 -04:00 committed by GitHub
parent 64f810d45c
commit 9ea4f82643
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 94 additions and 9 deletions

1
Cargo.lock generated
View file

@ -5589,6 +5589,7 @@ dependencies = [
"deno_media_type",
"deno_package_json",
"deno_path_util",
"deno_semver",
"futures",
"lazy-regex",
"once_cell",

View file

@ -49,6 +49,7 @@ use deno_runtime::inspector_server::InspectorServer;
use deno_runtime::permissions::RuntimePermissionDescriptorParser;
use node_resolver::analyze::NodeCodeTranslator;
use node_resolver::cache::NodeResolutionThreadLocalCache;
use node_resolver::NodeResolverOptions;
use once_cell::sync::OnceCell;
use sys_traits::EnvCurrentDir;
@ -697,7 +698,15 @@ impl CliFactory {
Ok(Arc::new(CliResolverFactory::new(
self.workspace_factory()?.clone(),
ResolverFactoryOptions {
conditions_from_resolution_mode: Default::default(),
node_resolver_options: NodeResolverOptions {
conditions_from_resolution_mode: Default::default(),
typescript_version: Some(
deno_semver::Version::parse_standard(
deno_lib::version::DENO_VERSION_INFO.typescript,
)
.unwrap(),
),
},
node_resolution_cache: Some(Arc::new(NodeResolutionThreadLocalCache)),
npm_system_info: self.flags.subcommand.npm_system_info(),
specified_import_map: Some(Box::new(CliSpecifiedImportMapProvider {

View file

@ -42,6 +42,7 @@ use node_resolver::cache::NodeResolutionSys;
use node_resolver::cache::NodeResolutionThreadLocalCache;
use node_resolver::DenoIsBuiltInNodeModuleChecker;
use node_resolver::NodeResolutionKind;
use node_resolver::NodeResolverOptions;
use node_resolver::PackageJsonThreadLocalCache;
use node_resolver::ResolutionMode;
@ -930,7 +931,15 @@ impl<'a> ResolverFactory<'a> {
npm_resolver.clone(),
self.pkg_json_resolver.clone(),
self.node_resolution_sys.clone(),
node_resolver::ConditionsFromResolutionMode::default(),
NodeResolverOptions {
conditions_from_resolution_mode: Default::default(),
typescript_version: Some(
deno_semver::Version::parse_standard(
deno_lib::version::DENO_VERSION_INFO.typescript,
)
.unwrap(),
),
},
)))
})
.as_ref()

View file

@ -786,7 +786,7 @@ pub async fn run(
npm_resolver.clone(),
pkg_json_resolver.clone(),
node_resolution_sys,
node_resolver::ConditionsFromResolutionMode::default(),
node_resolver::NodeResolverOptions::default(),
));
let cjs_tracker = Arc::new(CjsTracker::new(
in_npm_pkg_checker.clone(),

View file

@ -23,9 +23,9 @@ use deno_path_util::fs::canonicalize_path_maybe_not_exists;
use deno_path_util::normalize_path;
use futures::future::FutureExt;
use node_resolver::cache::NodeResolutionSys;
use node_resolver::ConditionsFromResolutionMode;
use node_resolver::DenoIsBuiltInNodeModuleChecker;
use node_resolver::NodeResolver;
use node_resolver::NodeResolverOptions;
use node_resolver::NodeResolverRc;
use node_resolver::PackageJsonResolver;
use node_resolver::PackageJsonResolverRc;
@ -559,8 +559,8 @@ impl<TSys: WorkspaceFactorySys> WorkspaceFactory<TSys> {
#[derive(Debug, Default)]
pub struct ResolverFactoryOptions {
pub conditions_from_resolution_mode: ConditionsFromResolutionMode,
pub npm_system_info: NpmSystemInfo,
pub node_resolver_options: NodeResolverOptions,
pub node_resolution_cache: Option<node_resolver::NodeResolutionCacheRc>,
pub package_json_cache: Option<node_resolver::PackageJsonCacheRc>,
pub package_json_dep_resolution: Option<PackageJsonDepResolution>,
@ -691,7 +691,7 @@ impl<TSys: WorkspaceFactorySys> ResolverFactory<TSys> {
self.npm_resolver()?.clone(),
self.pkg_json_resolver().clone(),
self.sys.clone(),
self.options.conditions_from_resolution_mode.clone(),
self.options.node_resolver_options.clone(),
)))
})
}

View file

@ -26,6 +26,7 @@ deno_error.workspace = true
deno_media_type.workspace = true
deno_package_json.workspace = true
deno_path_util.workspace = true
deno_semver.workspace = true
futures.workspace = true
lazy-regex.workspace = true
once_cell.workspace = true

View file

@ -36,6 +36,7 @@ pub use resolution::ConditionsFromResolutionMode;
pub use resolution::NodeResolution;
pub use resolution::NodeResolutionKind;
pub use resolution::NodeResolver;
pub use resolution::NodeResolverOptions;
pub use resolution::NodeResolverRc;
pub use resolution::ResolutionMode;
pub use resolution::DEFAULT_CONDITIONS;

View file

@ -10,6 +10,8 @@ use anyhow::Error as AnyError;
use deno_media_type::MediaType;
use deno_package_json::PackageJson;
use deno_path_util::url_to_file_path;
use deno_semver::Version;
use deno_semver::VersionReq;
use serde_json::Map;
use serde_json::Value;
use sys_traits::FileType;
@ -168,6 +170,14 @@ enum ResolvedMethod {
PackageSubPath,
}
#[derive(Debug, Default, Clone)]
pub struct NodeResolverOptions {
pub conditions_from_resolution_mode: ConditionsFromResolutionMode,
/// TypeScript version to use for typesVersions resolution and
/// `types@req` exports resolution.
pub typescript_version: Option<Version>,
}
#[allow(clippy::disallowed_types)]
pub type NodeResolverRc<
TInNpmPackageChecker,
@ -196,6 +206,7 @@ pub struct NodeResolver<
pkg_json_resolver: PackageJsonResolverRc<TSys>,
sys: NodeResolutionSys<TSys>,
conditions_from_resolution_mode: ConditionsFromResolutionMode,
typescript_version: Option<Version>,
}
impl<
@ -217,7 +228,7 @@ impl<
npm_pkg_folder_resolver: TNpmPackageFolderResolver,
pkg_json_resolver: PackageJsonResolverRc<TSys>,
sys: NodeResolutionSys<TSys>,
conditions_from_resolution_mode: ConditionsFromResolutionMode,
options: NodeResolverOptions,
) -> Self {
Self {
in_npm_pkg_checker,
@ -225,7 +236,8 @@ impl<
npm_pkg_folder_resolver,
pkg_json_resolver,
sys,
conditions_from_resolution_mode,
conditions_from_resolution_mode: options.conditions_from_resolution_mode,
typescript_version: options.typescript_version,
}
}
@ -1160,7 +1172,7 @@ impl<
if key == "default"
|| conditions.contains(&key.as_str())
|| resolution_kind.is_types() && key.as_str() == "types"
|| resolution_kind.is_types() && self.matches_types_key(key)
{
let resolved = self.resolve_package_target(
package_json_path,
@ -1198,6 +1210,22 @@ impl<
)
}
fn matches_types_key(&self, key: &str) -> bool {
if key == "types" {
return true;
}
let Some(ts_version) = &self.typescript_version else {
return false;
};
let Some(constraint) = key.strip_prefix("types@") else {
return false;
};
let Ok(version_req) = VersionReq::parse_from_npm(constraint) else {
return false;
};
version_req.matches(ts_version)
}
#[allow(clippy::too_many_arguments)]
pub fn package_exports_resolve(
&self,

View file

@ -0,0 +1,5 @@
{
"args": "check main.ts",
"output": "check.out",
"exitCode": 1
}

View file

@ -0,0 +1,7 @@
Check file:///[WILDLINE]/main.ts
TS2322 [ERROR]: Type '"expected"' is not assignable to type '"not"'.
const local: "not" = value;
~~~~~
at file:///[WILDLINE]/main.ts:4:7
error: Type checking failed.

View file

@ -0,0 +1,5 @@
import { value } from "package";
// should cause a type error where the type of value is "expected"
const local: "not" = value;
console.log(local);

View file

@ -0,0 +1 @@
module.exports.value = 5;

View file

@ -0,0 +1,10 @@
{
"exports": {
".": {
"types@<4.8": "./types-4.8.d.ts",
"types@>5.0": "./types-expected.d.ts",
"types": "./types-fallback.d.ts",
"import": "./index.js"
}
}
}

View file

@ -0,0 +1 @@
export const value: "4.8";

View file

@ -0,0 +1 @@
export const value: "expected";

View file

@ -0,0 +1 @@
export const value: "fallback";

View file

@ -0,0 +1,5 @@
{
"dependencies": {
"package": "*"
}
}