diff options
author | Kitson Kelly <me@kitsonkelly.com> | 2020-10-16 10:34:55 +1100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-16 10:34:55 +1100 |
commit | bbe4474d39aecfabed52bd080e73d34978b6481b (patch) | |
tree | 51a1681d6d37cde45b2241cc5772d39700a3573b /cli/module_graph2.rs | |
parent | bd0c64b9aeeb75eea25402b0ebd5aecc2cec8e3a (diff) |
fix(cli): ModuleGraph2 properly handles redirects (#7981)
Diffstat (limited to 'cli/module_graph2.rs')
-rw-r--r-- | cli/module_graph2.rs | 223 |
1 files changed, 130 insertions, 93 deletions
diff --git a/cli/module_graph2.rs b/cli/module_graph2.rs index de9e3a5db..2b43cc65d 100644 --- a/cli/module_graph2.rs +++ b/cli/module_graph2.rs @@ -161,7 +161,6 @@ fn get_version(source: &str, version: &str, config: &[u8]) -> String { struct Module { dependencies: DependencyMap, is_dirty: bool, - is_hydrated: bool, is_parsed: bool, maybe_emit: Option<Emit>, maybe_emit_path: Option<(PathBuf, Option<PathBuf>)>, @@ -180,7 +179,6 @@ impl Default for Module { Module { dependencies: HashMap::new(), is_dirty: false, - is_hydrated: false, is_parsed: false, maybe_emit: None, maybe_emit_path: None, @@ -189,7 +187,7 @@ impl Default for Module { maybe_types: None, maybe_version: None, media_type: MediaType::Unknown, - specifier: ModuleSpecifier::resolve_url("https://deno.land/x/").unwrap(), + specifier: ModuleSpecifier::resolve_url("file:///example.js").unwrap(), source: "".to_string(), source_path: PathBuf::new(), } @@ -198,51 +196,49 @@ impl Default for Module { impl Module { pub fn new( - specifier: ModuleSpecifier, + cached_module: CachedModule, maybe_import_map: Option<Rc<RefCell<ImportMap>>>, ) -> Self { - Module { - specifier, + let mut module = Module { + specifier: cached_module.specifier, maybe_import_map, - ..Module::default() - } - } - - /// Return `true` if the current hash of the module matches the stored - /// version. - pub fn emit_valid(&self, config: &[u8]) -> bool { - if let Some(version) = self.maybe_version.clone() { - version == get_version(&self.source, version::DENO, config) - } else { - false - } - } - - pub fn hydrate(&mut self, cached_module: CachedModule) { - self.media_type = cached_module.media_type; - self.source = cached_module.source; - self.source_path = cached_module.source_path; - self.maybe_emit = cached_module.maybe_emit; - self.maybe_emit_path = cached_module.maybe_emit_path; - if self.maybe_import_map.is_none() { + media_type: cached_module.media_type, + source: cached_module.source, + source_path: cached_module.source_path, + maybe_emit: cached_module.maybe_emit, + maybe_emit_path: cached_module.maybe_emit_path, + maybe_version: cached_module.maybe_version, + is_dirty: false, + ..Self::default() + }; + if module.maybe_import_map.is_none() { if let Some(dependencies) = cached_module.maybe_dependencies { - self.dependencies = dependencies; - self.is_parsed = true; + module.dependencies = dependencies; + module.is_parsed = true; } } - self.maybe_types = if let Some(ref specifier) = cached_module.maybe_types { + module.maybe_types = if let Some(ref specifier) = cached_module.maybe_types + { Some(( specifier.clone(), - self + module .resolve_import(&specifier, None) .expect("could not resolve module"), )) } else { None }; - self.maybe_version = cached_module.maybe_version; - self.is_dirty = false; - self.is_hydrated = true; + module + } + + /// Return `true` if the current hash of the module matches the stored + /// version. + pub fn is_emit_valid(&self, config: &[u8]) -> bool { + if let Some(version) = self.maybe_version.clone() { + version == get_version(&self.source, version::DENO, config) + } else { + false + } } pub fn parse(&mut self) -> Result<(), AnyError> { @@ -415,6 +411,7 @@ pub struct Graph2 { handler: Rc<RefCell<dyn SpecifierHandler>>, maybe_ts_build_info: Option<String>, modules: HashMap<ModuleSpecifier, Module>, + redirects: HashMap<ModuleSpecifier, ModuleSpecifier>, roots: Vec<ModuleSpecifier>, } @@ -429,10 +426,40 @@ impl Graph2 { handler, maybe_ts_build_info: None, modules: HashMap::new(), + redirects: HashMap::new(), roots: Vec::new(), } } + fn contains_module(&self, specifier: &ModuleSpecifier) -> bool { + let s = self.resolve_specifier(specifier); + self.modules.contains_key(s) + } + + /// Update the handler with any modules that are marked as _dirty_ and update + /// any build info if present. + fn flush(&mut self) -> Result<(), AnyError> { + let mut handler = self.handler.borrow_mut(); + for (_, module) in self.modules.iter_mut() { + if module.is_dirty { + if let Some(emit) = &module.maybe_emit { + handler.set_cache(&module.specifier, emit)?; + } + if let Some(version) = &module.maybe_version { + handler.set_version(&module.specifier, version.clone())?; + } + module.is_dirty = false; + } + } + for root_specifier in self.roots.iter() { + if let Some(ts_build_info) = &self.maybe_ts_build_info { + handler.set_ts_build_info(root_specifier, ts_build_info.to_owned())?; + } + } + + Ok(()) + } + fn get_info( &self, specifier: &ModuleSpecifier, @@ -440,7 +467,7 @@ impl Graph2 { totals: &mut HashMap<ModuleSpecifier, usize>, ) -> ModuleInfo { let not_seen = seen.insert(specifier.clone()); - let module = self.modules.get(specifier).unwrap(); + let module = self.get_module(specifier).unwrap(); let mut deps = Vec::new(); let mut total_size = None; @@ -511,6 +538,32 @@ impl Graph2 { ModuleInfoMap::new(map) } + pub fn get_media_type( + &self, + specifier: &ModuleSpecifier, + ) -> Option<MediaType> { + if let Some(module) = self.get_module(specifier) { + Some(module.media_type) + } else { + None + } + } + + fn get_module(&self, specifier: &ModuleSpecifier) -> Option<&Module> { + let s = self.resolve_specifier(specifier); + self.modules.get(s) + } + + /// Get the source for a given module specifier. If the module is not part + /// of the graph, the result will be `None`. + pub fn get_source(&self, specifier: &ModuleSpecifier) -> Option<String> { + if let Some(module) = self.get_module(specifier) { + Some(module.source.clone()) + } else { + None + } + } + /// Return a structure which provides information about the module graph and /// the relationship of the modules in the graph. This structure is used to /// provide information for the `info` subcommand. @@ -520,7 +573,7 @@ impl Graph2 { } let module = self.roots[0].clone(); - let m = self.modules.get(&module).unwrap(); + let m = self.get_module(&module).unwrap(); let mut seen = HashSet::new(); let mut totals = HashMap::new(); @@ -548,51 +601,6 @@ impl Graph2 { }) } - /// Update the handler with any modules that are marked as _dirty_ and update - /// any build info if present. - fn flush(&mut self) -> Result<(), AnyError> { - let mut handler = self.handler.borrow_mut(); - for (_, module) in self.modules.iter_mut() { - if module.is_dirty { - if let Some(emit) = &module.maybe_emit { - handler.set_cache(&module.specifier, emit)?; - } - if let Some(version) = &module.maybe_version { - handler.set_version(&module.specifier, version.clone())?; - } - module.is_dirty = false; - } - } - for root_specifier in self.roots.iter() { - if let Some(ts_build_info) = &self.maybe_ts_build_info { - handler.set_ts_build_info(root_specifier, ts_build_info.to_owned())?; - } - } - - Ok(()) - } - - pub fn get_media_type( - &self, - specifier: &ModuleSpecifier, - ) -> Option<MediaType> { - if let Some(module) = self.modules.get(specifier) { - Some(module.media_type) - } else { - None - } - } - - /// Get the source for a given module specifier. If the module is not part - /// of the graph, the result will be `None`. - pub fn get_source(&self, specifier: &ModuleSpecifier) -> Option<String> { - if let Some(module) = self.modules.get(specifier) { - Some(module.source.clone()) - } else { - None - } - } - /// Verify the subresource integrity of the graph based upon the optional /// lockfile, updating the lockfile with any missing resources. This will /// error if any of the resources do not match their lock status. @@ -624,10 +632,10 @@ impl Graph2 { specifier: &str, referrer: &ModuleSpecifier, ) -> Result<ModuleSpecifier, AnyError> { - if !self.modules.contains_key(referrer) { + if !self.contains_module(referrer) { return Err(MissingSpecifier(referrer.to_owned()).into()); } - let module = self.modules.get(referrer).unwrap(); + let module = self.get_module(referrer).unwrap(); if !module.dependencies.contains_key(specifier) { return Err( MissingDependency(referrer.to_owned(), specifier.to_owned()).into(), @@ -647,13 +655,13 @@ impl Graph2 { MissingDependency(referrer.to_owned(), specifier.to_owned()).into(), ); }; - if !self.modules.contains_key(&resolved_specifier) { + if !self.contains_module(&resolved_specifier) { return Err( MissingDependency(referrer.to_owned(), resolved_specifier.to_string()) .into(), ); } - let dep_module = self.modules.get(&resolved_specifier).unwrap(); + let dep_module = self.get_module(&resolved_specifier).unwrap(); // In the case that there is a X-TypeScript-Types or a triple-slash types, // then the `maybe_types` specifier will be populated and we should use that // instead. @@ -666,6 +674,29 @@ impl Graph2 { Ok(result) } + /// Takes a module specifier and returns the "final" specifier, accounting for + /// any redirects that may have occurred. + fn resolve_specifier<'a>( + &'a self, + specifier: &'a ModuleSpecifier, + ) -> &'a ModuleSpecifier { + let mut s = specifier; + let mut seen = HashSet::new(); + seen.insert(s.clone()); + while let Some(redirect) = self.redirects.get(s) { + if !seen.insert(redirect.clone()) { + eprintln!("An infinite loop of module redirections detected.\n Original specifier: {}", specifier); + break; + } + s = redirect; + if seen.len() > 5 { + eprintln!("An excessive number of module redirections detected.\n Original specifier: {}", specifier); + break; + } + } + s + } + /// Transpile (only transform) the graph, updating any emitted modules /// with the specifier handler. The result contains any performance stats /// from the compiler and optionally any user provided configuration compiler @@ -724,7 +755,7 @@ impl Graph2 { } let config = ts_config.as_bytes(); // skip modules that already have a valid emit - if module.maybe_emit.is_some() && module.emit_valid(&config) { + if module.maybe_emit.is_some() && module.is_emit_valid(&config) { continue; } if module.maybe_parsed_module.is_none() { @@ -794,9 +825,8 @@ impl GraphBuilder2 { /// module into the graph. fn visit(&mut self, cached_module: CachedModule) -> Result<(), AnyError> { let specifier = cached_module.specifier.clone(); - let mut module = - Module::new(specifier.clone(), self.maybe_import_map.clone()); - module.hydrate(cached_module); + let requested_specifier = cached_module.requested_specifier.clone(); + let mut module = Module::new(cached_module, self.maybe_import_map.clone()); if !module.is_parsed { let has_types = module.maybe_types.is_some(); module.parse()?; @@ -821,6 +851,12 @@ impl GraphBuilder2 { if let Some((_, specifier)) = module.maybe_types.as_ref() { self.fetch(specifier)?; } + if specifier != requested_specifier { + self + .graph + .redirects + .insert(requested_specifier, specifier.clone()); + } self.graph.modules.insert(specifier, module); Ok(()) @@ -921,6 +957,7 @@ pub mod tests { Ok(CachedModule { source, + requested_specifier: specifier.clone(), source_path, specifier, media_type, @@ -1014,7 +1051,7 @@ pub mod tests { maybe_version, ..Module::default() }; - assert!(module.emit_valid(b"")); + assert!(module.is_emit_valid(b"")); let source = "console.log(42);".to_string(); let old_source = "console.log(43);"; @@ -1024,7 +1061,7 @@ pub mod tests { maybe_version, ..Module::default() }; - assert!(!module.emit_valid(b"")); + assert!(!module.is_emit_valid(b"")); let source = "console.log(42);".to_string(); let maybe_version = Some(get_version(&source, "0.0.0", b"")); @@ -1033,14 +1070,14 @@ pub mod tests { maybe_version, ..Module::default() }; - assert!(!module.emit_valid(b"")); + assert!(!module.is_emit_valid(b"")); let source = "console.log(42);".to_string(); let module = Module { source, ..Module::default() }; - assert!(!module.emit_valid(b"")); + assert!(!module.is_emit_valid(b"")); } #[test] |