Skip to content
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

fix: support sloppy resolution to file where directory exists #22800

Merged
merged 1 commit into from
Mar 8, 2024
Merged
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
333 changes: 209 additions & 124 deletions cli/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1015,15 +1015,19 @@ impl SloppyImportsResolver {
fn media_types_to_paths(
path_no_ext: &str,
probe_media_type_types: Vec<MediaType>,
) -> Vec<PathBuf> {
reason: SloppyImportsResolutionReason,
) -> Vec<(PathBuf, SloppyImportsResolutionReason)> {
probe_media_type_types
.into_iter()
.map(|media_type| {
PathBuf::from(format!(
"{}{}",
path_no_ext,
media_type.as_ts_extension()
))
(
PathBuf::from(format!(
"{}{}",
path_no_ext,
media_type.as_ts_extension()
)),
reason,
)
})
.collect::<Vec<_>>()
}
Expand All @@ -1035,138 +1039,205 @@ impl SloppyImportsResolver {
let Ok(path) = specifier_to_file_path(specifier) else {
return SloppyImportsResolution::None(specifier);
};
let mut is_dir_resolution = false;
let mut is_no_ext_resolution = false;
let probe_paths = match (stat_sync)(&path) {
Some(SloppyImportsFsEntry::File) => {
if mode.is_types() {

#[derive(Clone, Copy)]
enum SloppyImportsResolutionReason {
JsToTs,
NoExtension,
Directory,
}

let probe_paths: Vec<(PathBuf, SloppyImportsResolutionReason)> =
match (stat_sync)(&path) {
Some(SloppyImportsFsEntry::File) => {
if mode.is_types() {
let media_type = MediaType::from_specifier(specifier);
// attempt to resolve the .d.ts file before the .js file
let probe_media_type_types = match media_type {
MediaType::JavaScript => {
vec![(MediaType::Dts), MediaType::JavaScript]
}
MediaType::Mjs => {
vec![MediaType::Dmts, MediaType::Dts, MediaType::Mjs]
}
MediaType::Cjs => {
vec![MediaType::Dcts, MediaType::Dts, MediaType::Cjs]
}
_ => return SloppyImportsResolution::None(specifier),
};
let Some(path_no_ext) = path_without_ext(&path, media_type) else {
return SloppyImportsResolution::None(specifier);
};
media_types_to_paths(
&path_no_ext,
probe_media_type_types,
SloppyImportsResolutionReason::JsToTs,
)
} else {
return SloppyImportsResolution::None(specifier);
}
}
entry @ None | entry @ Some(SloppyImportsFsEntry::Dir) => {
let media_type = MediaType::from_specifier(specifier);
// attempt to resolve the .d.ts file before the .js file
let probe_media_type_types = match media_type {
MediaType::JavaScript => {
vec![MediaType::Dts, MediaType::JavaScript]
}
MediaType::Mjs => {
vec![MediaType::Dmts, MediaType::Dts, MediaType::Mjs]
MediaType::JavaScript => (
if mode.is_types() {
vec![MediaType::TypeScript, MediaType::Tsx, MediaType::Dts]
} else {
vec![MediaType::TypeScript, MediaType::Tsx]
},
SloppyImportsResolutionReason::JsToTs,
),
MediaType::Jsx => {
(vec![MediaType::Tsx], SloppyImportsResolutionReason::JsToTs)
}
MediaType::Cjs => {
vec![MediaType::Dcts, MediaType::Dts, MediaType::Cjs]
MediaType::Mjs => (
if mode.is_types() {
vec![MediaType::Mts, MediaType::Dmts, MediaType::Dts]
} else {
vec![MediaType::Mts]
},
SloppyImportsResolutionReason::JsToTs,
),
MediaType::Cjs => (
if mode.is_types() {
vec![MediaType::Cts, MediaType::Dcts, MediaType::Dts]
} else {
vec![MediaType::Cts]
},
SloppyImportsResolutionReason::JsToTs,
),
MediaType::TypeScript
| MediaType::Mts
| MediaType::Cts
| MediaType::Dts
| MediaType::Dmts
| MediaType::Dcts
| MediaType::Tsx
| MediaType::Json
| MediaType::Wasm
| MediaType::TsBuildInfo
| MediaType::SourceMap => {
return SloppyImportsResolution::None(specifier)
}
_ => return SloppyImportsResolution::None(specifier),
MediaType::Unknown => (
if mode.is_types() {
vec![
MediaType::TypeScript,
MediaType::Tsx,
MediaType::Mts,
MediaType::Dts,
MediaType::Dmts,
MediaType::Dcts,
MediaType::JavaScript,
MediaType::Jsx,
MediaType::Mjs,
]
} else {
vec![
MediaType::TypeScript,
MediaType::JavaScript,
MediaType::Tsx,
MediaType::Jsx,
MediaType::Mts,
MediaType::Mjs,
]
},
SloppyImportsResolutionReason::NoExtension,
),
};
let Some(path_no_ext) = path_without_ext(&path, media_type) else {
return SloppyImportsResolution::None(specifier);
let mut probe_paths = match path_without_ext(&path, media_type) {
Some(path_no_ext) => media_types_to_paths(
&path_no_ext,
probe_media_type_types.0,
probe_media_type_types.1,
),
None => vec![],
};
media_types_to_paths(&path_no_ext, probe_media_type_types)
} else {
return SloppyImportsResolution::None(specifier);
}
}
Some(SloppyImportsFsEntry::Dir) => {
is_dir_resolution = true;
// try to resolve at the index file
if mode.is_types() {
vec![
path.join("index.ts"),
path.join("index.mts"),
path.join("index.d.ts"),
path.join("index.d.mts"),
path.join("index.js"),
path.join("index.mjs"),
path.join("index.tsx"),
path.join("index.jsx"),
]
} else {
vec![
path.join("index.ts"),
path.join("index.mts"),
path.join("index.tsx"),
path.join("index.js"),
path.join("index.mjs"),
path.join("index.jsx"),
]
}
}
None => {
let media_type = MediaType::from_specifier(specifier);
let probe_media_type_types = match media_type {
MediaType::JavaScript => {
if mode.is_types() {
vec![MediaType::TypeScript, MediaType::Tsx, MediaType::Dts]
} else {
vec![MediaType::TypeScript, MediaType::Tsx]
}
}
MediaType::Jsx => vec![MediaType::Tsx],
MediaType::Mjs => {
if mode.is_types() {
vec![MediaType::Mts, MediaType::Dmts, MediaType::Dts]
} else {
vec![MediaType::Mts]
}
}
MediaType::Cjs => {

if matches!(entry, Some(SloppyImportsFsEntry::Dir)) {
// try to resolve at the index file
if mode.is_types() {
vec![MediaType::Cts, MediaType::Dcts, MediaType::Dts]
probe_paths.push((
path.join("index.ts"),
SloppyImportsResolutionReason::Directory,
));

probe_paths.push((
path.join("index.mts"),
SloppyImportsResolutionReason::Directory,
));
probe_paths.push((
path.join("index.d.ts"),
SloppyImportsResolutionReason::Directory,
));
probe_paths.push((
path.join("index.d.mts"),
SloppyImportsResolutionReason::Directory,
));
probe_paths.push((
path.join("index.js"),
SloppyImportsResolutionReason::Directory,
));
probe_paths.push((
path.join("index.mjs"),
SloppyImportsResolutionReason::Directory,
));
probe_paths.push((
path.join("index.tsx"),
SloppyImportsResolutionReason::Directory,
));
probe_paths.push((
path.join("index.jsx"),
SloppyImportsResolutionReason::Directory,
));
} else {
vec![MediaType::Cts]
probe_paths.push((
path.join("index.ts"),
SloppyImportsResolutionReason::Directory,
));
probe_paths.push((
path.join("index.mts"),
SloppyImportsResolutionReason::Directory,
));
probe_paths.push((
path.join("index.tsx"),
SloppyImportsResolutionReason::Directory,
));
probe_paths.push((
path.join("index.js"),
SloppyImportsResolutionReason::Directory,
));
probe_paths.push((
path.join("index.mjs"),
SloppyImportsResolutionReason::Directory,
));
probe_paths.push((
path.join("index.jsx"),
SloppyImportsResolutionReason::Directory,
));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably eventually switch this to an iterator.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup - this could all use a cleanup I agree

}
}
MediaType::TypeScript
| MediaType::Mts
| MediaType::Cts
| MediaType::Dts
| MediaType::Dmts
| MediaType::Dcts
| MediaType::Tsx
| MediaType::Json
| MediaType::Wasm
| MediaType::TsBuildInfo
| MediaType::SourceMap => {
return SloppyImportsResolution::None(specifier)
}
MediaType::Unknown => {
is_no_ext_resolution = true;
if mode.is_types() {
vec![
MediaType::TypeScript,
MediaType::Tsx,
MediaType::Mts,
MediaType::Dts,
MediaType::Dmts,
MediaType::Dcts,
MediaType::JavaScript,
MediaType::Jsx,
MediaType::Mjs,
]
} else {
vec![
MediaType::TypeScript,
MediaType::JavaScript,
MediaType::Tsx,
MediaType::Jsx,
MediaType::Mts,
MediaType::Mjs,
]
}
if probe_paths.is_empty() {
return SloppyImportsResolution::None(specifier);
}
};
let Some(path_no_ext) = path_without_ext(&path, media_type) else {
return SloppyImportsResolution::None(specifier);
};
media_types_to_paths(&path_no_ext, probe_media_type_types)
}
};
probe_paths
}
};

for probe_path in probe_paths {
for (probe_path, reason) in probe_paths {
if (stat_sync)(&probe_path) == Some(SloppyImportsFsEntry::File) {
if let Ok(specifier) = ModuleSpecifier::from_file_path(probe_path) {
if is_dir_resolution {
return SloppyImportsResolution::Directory(specifier);
} else if is_no_ext_resolution {
return SloppyImportsResolution::NoExtension(specifier);
} else {
return SloppyImportsResolution::JsToTs(specifier);
match reason {
SloppyImportsResolutionReason::JsToTs => {
return SloppyImportsResolution::JsToTs(specifier)
}
SloppyImportsResolutionReason::NoExtension => {
return SloppyImportsResolution::NoExtension(specifier)
}
SloppyImportsResolutionReason::Directory => {
return SloppyImportsResolution::Directory(specifier)
}
}
}
}
Expand Down Expand Up @@ -1339,6 +1410,20 @@ mod test {
SloppyImportsResolution::Directory(index_file.uri_file()),
);
}

// both a directory and a file with specifier is present
{
let api_dir = temp_dir.join("api");
api_dir.create_dir_all();
let bar_file = api_dir.join("bar.ts");
bar_file.write("");
let api_file = temp_dir.join("api.ts");
api_file.write("");
assert_eq!(
resolve(&api_dir.uri_file()),
SloppyImportsResolution::NoExtension(api_file.uri_file()),
);
}
}

#[test]
Expand Down