@@ -8,8 +8,10 @@ use std::{
88
99use anyhow:: { Result , bail} ;
1010use auto_hash_map:: AutoSet ;
11+ use once_cell:: sync:: Lazy ;
1112use rustc_hash:: { FxHashMap , FxHashSet } ;
1213use serde:: { Deserialize , Serialize } ;
14+ use smallvec:: SmallVec ;
1315use tracing:: { Instrument , Level } ;
1416use turbo_rcstr:: { RcStr , rcstr} ;
1517use turbo_tasks:: {
@@ -2246,17 +2248,43 @@ async fn resolve_relative_request(
22462248
22472249 let mut new_path = path_pattern. clone ( ) ;
22482250
2251+ #[ derive( Eq , PartialEq , Clone , Hash ) ]
2252+ enum PatternModification {
2253+ AddedFragment ,
2254+ AddedExtension { ext : RcStr } ,
2255+ ReplacedExtension { ext : RcStr } ,
2256+ }
2257+
2258+ let mut modifications = Vec :: new ( ) ;
2259+ modifications. push ( SmallVec :: < [ PatternModification ; 3 ] > :: new ( ) ) ;
2260+
2261+ // If we parsed the pattern with a fragment, it is possible the user was actually attempting to
2262+ // match a literal `#` in a filename
2263+ // So handle that as an alternative here.
22492264 if !fragment. is_empty ( ) {
2250- // 'fragments' should not be part of the result, however it could be that the user is
2251- // attempting to match a file with a '#' character in it. In that case we need
2252- // to look for it which is what this alternation does
2265+ modifications. push ( SmallVec :: from_vec ( vec ! [ PatternModification :: AddedFragment ] ) ) ;
22532266 new_path. push ( Pattern :: Alternatives ( vec ! [
22542267 Pattern :: Constant ( RcStr :: default ( ) ) ,
22552268 Pattern :: Constant ( fragment. clone( ) ) ,
22562269 ] ) ) ;
22572270 }
22582271
22592272 if !options_value. fully_specified {
2273+ // For each current set of modifications append an extension modification
2274+ modifications = options_value
2275+ . extensions
2276+ . iter ( )
2277+ . map ( |ext| Some ( ext. clone ( ) ) )
2278+ . chain ( once ( None ) )
2279+ . flat_map ( |ext| {
2280+ modifications. iter ( ) . cloned ( ) . map ( move |mut mods| {
2281+ if let Some ( ext) = & ext {
2282+ mods. push ( PatternModification :: AddedExtension { ext : ext. clone ( ) } ) ;
2283+ }
2284+ mods
2285+ } )
2286+ } )
2287+ . collect ( ) ;
22602288 // Add the extensions as alternatives to the path
22612289 // read_matches keeps the order of alternatives intact
22622290 new_path. push ( Pattern :: Alternatives (
@@ -2272,48 +2300,100 @@ async fn resolve_relative_request(
22722300 new_path. normalize ( ) ;
22732301 } ;
22742302
2303+ struct ExtensionReplacements {
2304+ forward : FxHashMap < RcStr , SmallVec < [ RcStr ; 3 ] > > ,
2305+ reverse : FxHashMap < RcStr , RcStr > ,
2306+ }
2307+ static TS_EXTENSION_REPLACEMENTS : Lazy < ExtensionReplacements > = Lazy :: new ( || {
2308+ let mut forward = FxHashMap :: default ( ) ;
2309+ let mut reverse = FxHashMap :: default ( ) ;
2310+ forward. insert (
2311+ rcstr ! ( ".js" ) ,
2312+ SmallVec :: from_vec ( vec ! [ rcstr!( ".ts" ) , rcstr!( ".tsx" ) , rcstr!( ".js" ) ] ) ,
2313+ ) ;
2314+ reverse. insert ( rcstr ! ( ".ts" ) , rcstr ! ( ".js" ) ) ;
2315+ reverse. insert ( rcstr ! ( ".tsx" ) , rcstr ! ( ".js" ) ) ;
2316+
2317+ forward. insert (
2318+ rcstr ! ( ".mjs" ) ,
2319+ SmallVec :: from_vec ( vec ! [ rcstr!( ".mts" ) , rcstr!( ".mjs" ) ] ) ,
2320+ ) ;
2321+
2322+ reverse. insert ( rcstr ! ( ".mts" ) , rcstr ! ( ".mjs" ) ) ;
2323+ forward. insert (
2324+ rcstr ! ( ".cjs" ) ,
2325+ SmallVec :: from_vec ( vec ! [ rcstr!( ".cts" ) , rcstr!( ".cjs" ) ] ) ,
2326+ ) ;
2327+ reverse. insert ( rcstr ! ( ".cts" ) , rcstr ! ( ".cjs" ) ) ;
2328+ ExtensionReplacements { forward, reverse }
2329+ } ) ;
2330+
22752331 if options_value. enable_typescript_with_output_extension {
2276- new_path. replace_final_constants ( & |c : & RcStr | -> Option < Pattern > {
2277- let ( base, replacement) = match c. rsplit_once ( "." ) {
2278- Some ( ( base, "js" ) ) => (
2279- base,
2280- vec ! [
2281- Pattern :: Constant ( rcstr!( ".ts" ) ) ,
2282- Pattern :: Constant ( rcstr!( ".tsx" ) ) ,
2283- Pattern :: Constant ( rcstr!( ".js" ) ) ,
2284- ] ,
2285- ) ,
2286- Some ( ( base, "mjs" ) ) => (
2287- base,
2288- vec ! [
2289- Pattern :: Constant ( rcstr!( ".mts" ) ) ,
2290- Pattern :: Constant ( rcstr!( ".mjs" ) ) ,
2291- ] ,
2292- ) ,
2293- Some ( ( base, "cjs" ) ) => (
2294- base,
2295- vec ! [
2296- Pattern :: Constant ( rcstr!( ".cts" ) ) ,
2297- Pattern :: Constant ( rcstr!( ".cjs" ) ) ,
2298- ] ,
2299- ) ,
2300- _ => {
2301- return None ;
2302- }
2332+ // there are at most 4 possible replacements (the size of the reverse map)
2333+ let mut replaced_extensions = SmallVec :: < [ RcStr ; 4 ] > :: new ( ) ;
2334+ let replaced = new_path. replace_final_constants ( & mut |c : & RcStr | -> Option < Pattern > {
2335+ let Some ( dot) = c. rfind ( '.' ) else {
2336+ return None ;
2337+ } ;
2338+ let ( base, ext) = c. split_at ( dot) ;
2339+
2340+ let Some ( ( ext, replacements) ) = TS_EXTENSION_REPLACEMENTS . forward . get_key_value ( ext)
2341+ else {
2342+ return None ;
23032343 } ;
2344+ for replacement in replacements {
2345+ if replacement != ext && !replaced_extensions. contains ( replacement) {
2346+ replaced_extensions. push ( replacement. clone ( ) ) ;
2347+ debug_assert ! ( replaced_extensions. len( ) <= replaced_extensions. inline_size( ) ) ;
2348+ }
2349+ }
2350+
2351+ let replacements = replacements
2352+ . iter ( )
2353+ . cloned ( )
2354+ . map ( Pattern :: Constant )
2355+ . collect ( ) ;
2356+
23042357 if base. is_empty ( ) {
2305- Some ( Pattern :: Alternatives ( replacement ) )
2358+ Some ( Pattern :: Alternatives ( replacements ) )
23062359 } else {
23072360 Some ( Pattern :: Concatenation ( vec ! [
23082361 Pattern :: Constant ( base. into( ) ) ,
2309- Pattern :: Alternatives ( replacement ) ,
2362+ Pattern :: Alternatives ( replacements ) ,
23102363 ] ) )
23112364 }
23122365 } ) ;
2313- new_path. normalize ( ) ;
2366+ if replaced {
2367+ // For each current set of modifications append an extension replacement modification
2368+ modifications = replaced_extensions
2369+ . into_iter ( )
2370+ . map ( |ext| Some ( ext) )
2371+ . chain ( once ( None ) )
2372+ . flat_map ( |ext| {
2373+ modifications. iter ( ) . cloned ( ) . map ( {
2374+ let ext = ext. clone ( ) ;
2375+ move |mut mods| {
2376+ if let Some ( ext) = & ext {
2377+ mods. push ( PatternModification :: ReplacedExtension {
2378+ ext : ext. clone ( ) ,
2379+ } ) ;
2380+ }
2381+ mods
2382+ }
2383+ } )
2384+ } )
2385+ . collect ( ) ;
2386+ new_path. normalize ( ) ;
2387+ }
23142388 }
23152389
2316- let mut results = Vec :: new ( ) ;
2390+ // The pattern expansions above can lead to duplicates
2391+ let modifications = modifications
2392+ . into_iter ( )
2393+ . collect :: < FxIndexSet < _ > > ( )
2394+ . into_iter ( )
2395+ . collect :: < Vec < _ > > ( ) ;
2396+
23172397 let matches = read_matches (
23182398 lookup_path. clone ( ) ,
23192399 rcstr ! ( "" ) ,
@@ -2324,11 +2404,15 @@ async fn resolve_relative_request(
23242404
23252405 // This loop is necessary to 'undo' the modifications to 'new_path' that were performed above.
23262406 // e.g. we added extensions but these shouldn't be part of the request key so remove them
2327- // TODO: this logic is not completely correct because it fails to account for the extension
23282407 // replacement logic that we perform conditionally.
23292408
2409+ // For each match we need to consider if it was produced by one of the pattern modifications
2410+ // below and if so we will need to reverse the transform on the request key. Then alternate
2411+ let mut results = Vec :: new ( ) ;
2412+
23302413 for m in matches. iter ( ) {
2331- if let PatternMatch :: File ( matched_pattern, path) = m {
2414+ // for mods in modifications {}
2415+
23322416 let mut pushed = false ;
23332417 if !options_value. fully_specified {
23342418 for ext in options_value. extensions . iter ( ) {
@@ -2338,7 +2422,7 @@ async fn resolve_relative_request(
23382422
23392423 if !fragment. is_empty ( ) {
23402424 // If the fragment is not empty, we need to strip it from the matched
2341- // pattern so it matches path_pattern
2425+ // pattern
23422426 if let Some ( matched_pattern) =
23432427 matched_pattern. strip_suffix ( fragment. as_str ( ) )
23442428 && path_pattern. is_match ( matched_pattern)
0 commit comments