diff options
Diffstat (limited to 'cli/tools/registry')
-rw-r--r-- | cli/tools/registry/diagnostics.rs | 152 | ||||
-rw-r--r-- | cli/tools/registry/graph.rs | 57 | ||||
-rw-r--r-- | cli/tools/registry/mod.rs | 110 |
3 files changed, 209 insertions, 110 deletions
diff --git a/cli/tools/registry/diagnostics.rs b/cli/tools/registry/diagnostics.rs new file mode 100644 index 000000000..f6d81f5a8 --- /dev/null +++ b/cli/tools/registry/diagnostics.rs @@ -0,0 +1,152 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use std::borrow::Cow; +use std::fmt::Display; +use std::sync::Arc; +use std::sync::Mutex; + +use deno_ast::swc::common::util::take::Take; +use deno_core::anyhow::anyhow; +use deno_core::error::AnyError; +use deno_graph::FastCheckDiagnostic; +use deno_graph::ParsedSourceStore; + +use crate::diagnostics::Diagnostic; +use crate::diagnostics::DiagnosticLevel; +use crate::diagnostics::DiagnosticLocation; +use crate::diagnostics::DiagnosticSnippet; +use crate::diagnostics::DiagnosticSnippetHighlight; +use crate::diagnostics::DiagnosticSnippetHighlightStyle; +use crate::diagnostics::DiagnosticSnippetSource; +use crate::diagnostics::DiagnosticSourcePos; +use crate::diagnostics::DiagnosticSourceRange; +use crate::diagnostics::SourceTextParsedSourceStore; + +#[derive(Clone, Default)] +pub struct PublishDiagnosticsCollector { + diagnostics: Arc<Mutex<Vec<PublishDiagnostic>>>, +} + +impl PublishDiagnosticsCollector { + pub fn print_and_error( + &self, + sources: &dyn ParsedSourceStore, + ) -> Result<(), AnyError> { + let mut errors = 0; + let diagnostics = self.diagnostics.lock().unwrap().take(); + let sources = SourceTextParsedSourceStore(sources); + for diagnostic in diagnostics { + eprintln!("{}", diagnostic.display(&sources)); + if matches!(diagnostic.level(), DiagnosticLevel::Error) { + errors += 1; + } + } + if errors > 0 { + Err(anyhow!( + "Found {} problem{}", + errors, + if errors == 1 { "" } else { "s" } + )) + } else { + Ok(()) + } + } + + pub fn push(&self, diagnostic: PublishDiagnostic) { + self.diagnostics.lock().unwrap().push(diagnostic); + } +} + +pub enum PublishDiagnostic { + FastCheck { diagnostic: FastCheckDiagnostic }, +} + +impl Diagnostic for PublishDiagnostic { + fn level(&self) -> DiagnosticLevel { + match self { + PublishDiagnostic::FastCheck { + diagnostic: FastCheckDiagnostic::UnsupportedJavaScriptEntrypoint { .. }, + } => DiagnosticLevel::Warning, + PublishDiagnostic::FastCheck { .. } => DiagnosticLevel::Error, + } + } + + fn code(&self) -> impl Display + '_ { + match &self { + PublishDiagnostic::FastCheck { diagnostic, .. } => diagnostic.code(), + } + } + + fn message(&self) -> impl Display + '_ { + match &self { + PublishDiagnostic::FastCheck { diagnostic, .. } => diagnostic.to_string(), // todo + } + } + + fn location(&self) -> DiagnosticLocation { + match &self { + PublishDiagnostic::FastCheck { diagnostic } => match diagnostic.range() { + Some(range) => DiagnosticLocation::PositionInFile { + specifier: Cow::Borrowed(diagnostic.specifier()), + source_pos: DiagnosticSourcePos::SourcePos(range.range.start), + }, + None => DiagnosticLocation::File { + specifier: Cow::Borrowed(diagnostic.specifier()), + }, + }, + } + } + + fn snippet(&self) -> Option<DiagnosticSnippet<'_>> { + match &self { + PublishDiagnostic::FastCheck { diagnostic } => { + diagnostic.range().map(|range| DiagnosticSnippet { + source: DiagnosticSnippetSource::Specifier(Cow::Borrowed( + diagnostic.specifier(), + )), + highlight: DiagnosticSnippetHighlight { + style: DiagnosticSnippetHighlightStyle::Error, + range: DiagnosticSourceRange { + start: DiagnosticSourcePos::SourcePos(range.range.start), + end: DiagnosticSourcePos::SourcePos(range.range.end), + }, + description: diagnostic.range_description().map(Cow::Borrowed), + }, + }) + } + } + } + + fn hint(&self) -> Option<impl Display + '_> { + match &self { + PublishDiagnostic::FastCheck { diagnostic } => { + Some(diagnostic.fix_hint()) + } + } + } + + fn snippet_fixed(&self) -> Option<DiagnosticSnippet<'_>> { + None + } + + fn info(&self) -> Cow<'_, [Cow<'_, str>]> { + match &self { + PublishDiagnostic::FastCheck { diagnostic } => { + let infos = diagnostic + .additional_info() + .iter() + .map(|s| Cow::Borrowed(*s)) + .collect(); + Cow::Owned(infos) + } + } + } + + fn docs_url(&self) -> Option<impl Display + '_> { + match &self { + PublishDiagnostic::FastCheck { diagnostic } => { + Some(format!("https://jsr.io/go/{}", diagnostic.code())) + } + } + } +} diff --git a/cli/tools/registry/graph.rs b/cli/tools/registry/graph.rs index b64350887..29c825242 100644 --- a/cli/tools/registry/graph.rs +++ b/cli/tools/registry/graph.rs @@ -12,6 +12,9 @@ use deno_core::error::AnyError; use deno_graph::FastCheckDiagnostic; use deno_graph::ModuleGraph; +use super::diagnostics::PublishDiagnostic; +use super::diagnostics::PublishDiagnosticsCollector; + #[derive(Debug)] pub struct MemberRoots { pub name: String, @@ -61,11 +64,13 @@ pub fn resolve_config_file_roots_from_exports( Ok(exports) } -pub fn surface_fast_check_type_graph_errors( +/// Collects diagnostics from the module graph for the given packages. +/// Returns true if any diagnostics were collected. +pub fn collect_fast_check_type_graph_diagnostics( graph: &ModuleGraph, packages: &[MemberRoots], -) -> Result<(), AnyError> { - let mut diagnostic_count = 0; + diagnostics_collector: &PublishDiagnosticsCollector, +) -> bool { let mut seen_diagnostics = HashSet::new(); let mut seen_modules = HashSet::with_capacity(graph.specifiers_count()); for package in packages { @@ -85,31 +90,18 @@ pub fn surface_fast_check_type_graph_errors( }; if let Some(diagnostic) = esm_module.fast_check_diagnostic() { for diagnostic in diagnostic.flatten_multiple() { + if !seen_diagnostics.insert(diagnostic.message_with_range_for_test()) + { + continue; + } + diagnostics_collector.push(PublishDiagnostic::FastCheck { + diagnostic: diagnostic.clone(), + }); if matches!( diagnostic, FastCheckDiagnostic::UnsupportedJavaScriptEntrypoint { .. } ) { - // ignore JS packages for fast check - log::warn!( - concat!( - "{} Package '{}' is a JavaScript package without a corresponding ", - "declaration file. This may lead to a non-optimal experience for ", - "users of your package. For performance reasons, it's recommended ", - "to ship a corresponding TypeScript declaration file or to ", - "convert to TypeScript.", - ), - deno_runtime::colors::yellow("Warning"), - package.name, - ); break 'analyze_package; // no need to keep analyzing this package - } else { - let message = diagnostic.message_with_range_for_test(); - if !seen_diagnostics.insert(message.clone()) { - continue; - } - - log::error!("\n{}", message); - diagnostic_count += 1; } } } @@ -133,22 +125,5 @@ pub fn surface_fast_check_type_graph_errors( } } - if diagnostic_count > 0 { - // for the time being, tell the user why we have these errors and the benefit they bring - log::error!( - concat!( - "\nFixing these fast check errors is required to make the code fast check compatible ", - "which enables type checking your package's TypeScript code with the same ", - "performance as if you had distributed declaration files. Do any of these ", - "errors seem too restrictive or incorrect? Please open an issue if so to ", - "help us improve: https://github.com/denoland/deno/issues\n", - ) - ); - bail!( - "Had {} fast check error{}.", - diagnostic_count, - if diagnostic_count == 1 { "" } else { "s" } - ) - } - Ok(()) + !seen_diagnostics.is_empty() } diff --git a/cli/tools/registry/mod.rs b/cli/tools/registry/mod.rs index 3b8ffcd04..8eaf61258 100644 --- a/cli/tools/registry/mod.rs +++ b/cli/tools/registry/mod.rs @@ -32,15 +32,17 @@ use crate::factory::CliFactory; use crate::graph_util::ModuleGraphBuilder; use crate::http_util::HttpClient; use crate::tools::check::CheckOptions; +use crate::tools::registry::diagnostics::PublishDiagnosticsCollector; +use crate::tools::registry::graph::collect_fast_check_type_graph_diagnostics; use crate::tools::registry::graph::get_workspace_member_roots; use crate::tools::registry::graph::resolve_config_file_roots_from_exports; -use crate::tools::registry::graph::surface_fast_check_type_graph_errors; use crate::tools::registry::graph::MemberRoots; use crate::util::display::human_size; use crate::util::import_map::ImportMapUnfurler; mod api; mod auth; +mod diagnostics; mod graph; mod publish_order; mod tar; @@ -166,47 +168,6 @@ pub enum Permission<'s> { }, } -/// Prints diagnostics like so: -/// ``` -/// -/// Warning -/// ├╌ Dynamic import was not analyzable... -/// ├╌╌ at file:///dev/foo/bar/foo.ts:4:5 -/// | -/// ├╌ Dynamic import was not analyzable... -/// ├╌╌ at file:///dev/foo/bar/foo.ts:4:5 -/// | -/// ├╌ Dynamic import was not analyzable... -/// └╌╌ at file:///dev/foo/bar/foo.ts:4:5 -/// -/// ``` -fn print_diagnostics(diagnostics: &[String]) { - if !diagnostics.is_empty() { - let len = diagnostics.len(); - log::warn!(""); - log::warn!("{}", crate::colors::yellow("Warning")); - for (i, diagnostic) in diagnostics.iter().enumerate() { - let last_diagnostic = i == len - 1; - let lines = diagnostic.split('\n').collect::<Vec<_>>(); - let lines_len = lines.len(); - if i != 0 { - log::warn!("|"); - } - for (j, line) in lines.iter().enumerate() { - let last_line = j == lines_len - 1; - if j == 0 { - log::warn!("├╌ {}", line); - } else if last_line && last_diagnostic { - log::warn!("└╌╌ {}", line); - } else { - log::warn!("├╌╌ {}", line); - } - } - } - log::warn!(""); - } -} - async fn get_auth_headers( client: &reqwest::Client, registry_url: String, @@ -484,11 +445,6 @@ async fn perform_publish( .values() .cloned() .collect::<Vec<_>>(); - let diagnostics = packages - .iter() - .flat_map(|p| p.tarball.diagnostics.iter().cloned()) - .collect::<Vec<_>>(); - print_diagnostics(&diagnostics); ensure_scopes_and_packages_exist( client, @@ -679,6 +635,7 @@ async fn publish_package( async fn prepare_packages_for_publishing( cli_factory: &CliFactory, + diagnostics_collector: &PublishDiagnosticsCollector, deno_json: ConfigFile, import_map: Arc<ImportMap>, ) -> Result< @@ -700,6 +657,7 @@ async fn prepare_packages_for_publishing( module_graph_builder, type_checker, cli_options, + diagnostics_collector, &[MemberRoots { name: get_deno_json_package_name(&deno_json)?, dir_url: deno_json.specifier.join("./").unwrap().clone(), @@ -724,6 +682,7 @@ async fn prepare_packages_for_publishing( module_graph_builder, type_checker, cli_options, + diagnostics_collector, &roots, ) .await?; @@ -765,6 +724,7 @@ async fn build_and_check_graph_for_publish( module_graph_builder: &ModuleGraphBuilder, type_checker: &TypeChecker, cli_options: &CliOptions, + diagnostics_collector: &PublishDiagnosticsCollector, packages: &[MemberRoots], ) -> Result<Arc<deno_graph::ModuleGraph>, deno_core::anyhow::Error> { let graph = Arc::new( @@ -784,28 +744,34 @@ async fn build_and_check_graph_for_publish( ); graph.valid()?; log::info!("Checking fast check type graph for errors..."); - surface_fast_check_type_graph_errors(&graph, packages)?; - log::info!("Ensuring type checks..."); - let diagnostics = type_checker - .check_diagnostics( - graph.clone(), - CheckOptions { - lib: cli_options.ts_type_lib_window(), - log_ignored_options: false, - reload: cli_options.reload_flag(), - }, - ) - .await?; - if !diagnostics.is_empty() { - bail!( - concat!( - "{:#}\n\n", - "You may have discovered a bug in Deno's fast check implementation. ", - "Fast check is still early days and we would appreciate if you log a ", - "bug if you believe this is one: https://github.com/denoland/deno/issues/" - ), - diagnostics - ); + let has_fast_check_diagnostics = collect_fast_check_type_graph_diagnostics( + &graph, + packages, + diagnostics_collector, + ); + if !has_fast_check_diagnostics { + log::info!("Ensuring type checks..."); + let diagnostics = type_checker + .check_diagnostics( + graph.clone(), + CheckOptions { + lib: cli_options.ts_type_lib_window(), + log_ignored_options: false, + reload: cli_options.reload_flag(), + }, + ) + .await?; + if !diagnostics.is_empty() { + bail!( + concat!( + "{:#}\n\n", + "You may have discovered a bug in Deno's fast check implementation. ", + "Fast check is still early days and we would appreciate if you log a ", + "bug if you believe this is one: https://github.com/denoland/deno/issues/" + ), + diagnostics + ); + } } Ok(graph) } @@ -836,14 +802,20 @@ pub async fn publish( ); }; + let diagnostics_collector = PublishDiagnosticsCollector::default(); + let (publish_order_graph, prepared_package_by_name) = prepare_packages_for_publishing( &cli_factory, + &diagnostics_collector, config_file.clone(), import_map, ) .await?; + diagnostics_collector + .print_and_error(&**cli_factory.parsed_source_cache())?; + if prepared_package_by_name.is_empty() { bail!("No packages to publish"); } |