diff options
Diffstat (limited to 'cli/tools/registry/graph.rs')
-rw-r--r-- | cli/tools/registry/graph.rs | 290 |
1 files changed, 173 insertions, 117 deletions
diff --git a/cli/tools/registry/graph.rs b/cli/tools/registry/graph.rs index 7e3239ced..d1356df9e 100644 --- a/cli/tools/registry/graph.rs +++ b/cli/tools/registry/graph.rs @@ -3,7 +3,11 @@ use std::collections::HashSet; use std::sync::Arc; +use deno_ast::swc::common::comments::CommentKind; +use deno_ast::ParsedSource; +use deno_ast::SourceRangedForSpanned; use deno_ast::SourceTextInfo; +use deno_core::error::AnyError; use deno_graph::ModuleEntryRef; use deno_graph::ModuleGraph; use deno_graph::ResolutionResolved; @@ -12,137 +16,189 @@ use deno_semver::jsr::JsrPackageReqReference; use deno_semver::npm::NpmPackageReqReference; use lsp_types::Url; +use crate::cache::ParsedSourceCache; + use super::diagnostics::PublishDiagnostic; use super::diagnostics::PublishDiagnosticsCollector; -pub fn collect_invalid_external_imports( - graph: &ModuleGraph, - diagnostics_collector: &PublishDiagnosticsCollector, -) { - let mut visited = HashSet::new(); - let mut skip_specifiers: HashSet<Url> = HashSet::new(); - - let mut collect_if_invalid = - |skip_specifiers: &mut HashSet<Url>, - source_text: &Arc<str>, - specifier_text: &str, - resolution: &ResolutionResolved| { - if visited.insert(resolution.specifier.clone()) { - match resolution.specifier.scheme() { - "file" | "data" | "node" => {} - "jsr" => { - skip_specifiers.insert(resolution.specifier.clone()); - - // check for a missing version constraint - if let Ok(jsr_req_ref) = - JsrPackageReqReference::from_specifier(&resolution.specifier) - { - if jsr_req_ref.req().version_req.version_text() == "*" { - let maybe_version = graph - .packages - .mappings() - .find(|(req, _)| *req == jsr_req_ref.req()) - .map(|(_, nv)| nv.version.clone()); - diagnostics_collector.push( - PublishDiagnostic::MissingConstraint { - specifier: resolution.specifier.clone(), - specifier_text: specifier_text.to_string(), - resolved_version: maybe_version, - text_info: SourceTextInfo::new(source_text.clone()), - referrer: resolution.range.clone(), - }, - ); +pub struct GraphDiagnosticsCollector { + parsed_source_cache: Arc<ParsedSourceCache>, +} + +impl GraphDiagnosticsCollector { + pub fn new(parsed_source_cache: Arc<ParsedSourceCache>) -> Self { + Self { + parsed_source_cache, + } + } + + pub fn collect_diagnostics_for_graph( + &self, + graph: &ModuleGraph, + diagnostics_collector: &PublishDiagnosticsCollector, + ) -> Result<(), AnyError> { + let mut visited = HashSet::new(); + let mut skip_specifiers: HashSet<Url> = HashSet::new(); + + let mut collect_if_invalid = + |skip_specifiers: &mut HashSet<Url>, + source_text: &Arc<str>, + specifier_text: &str, + resolution: &ResolutionResolved| { + if visited.insert(resolution.specifier.clone()) { + match resolution.specifier.scheme() { + "file" | "data" | "node" => {} + "jsr" => { + skip_specifiers.insert(resolution.specifier.clone()); + + // check for a missing version constraint + if let Ok(jsr_req_ref) = + JsrPackageReqReference::from_specifier(&resolution.specifier) + { + if jsr_req_ref.req().version_req.version_text() == "*" { + let maybe_version = graph + .packages + .mappings() + .find(|(req, _)| *req == jsr_req_ref.req()) + .map(|(_, nv)| nv.version.clone()); + diagnostics_collector.push( + PublishDiagnostic::MissingConstraint { + specifier: resolution.specifier.clone(), + specifier_text: specifier_text.to_string(), + resolved_version: maybe_version, + text_info: SourceTextInfo::new(source_text.clone()), + referrer: resolution.range.clone(), + }, + ); + } } } - } - "npm" => { - skip_specifiers.insert(resolution.specifier.clone()); - - // check for a missing version constraint - if let Ok(jsr_req_ref) = - NpmPackageReqReference::from_specifier(&resolution.specifier) - { - if jsr_req_ref.req().version_req.version_text() == "*" { - let maybe_version = graph - .get(&resolution.specifier) - .and_then(|m| m.npm()) - .map(|n| n.nv_reference.nv().version.clone()); - diagnostics_collector.push( - PublishDiagnostic::MissingConstraint { - specifier: resolution.specifier.clone(), - specifier_text: specifier_text.to_string(), - resolved_version: maybe_version, - text_info: SourceTextInfo::new(source_text.clone()), - referrer: resolution.range.clone(), - }, - ); + "npm" => { + skip_specifiers.insert(resolution.specifier.clone()); + + // check for a missing version constraint + if let Ok(jsr_req_ref) = + NpmPackageReqReference::from_specifier(&resolution.specifier) + { + if jsr_req_ref.req().version_req.version_text() == "*" { + let maybe_version = graph + .get(&resolution.specifier) + .and_then(|m| m.npm()) + .map(|n| n.nv_reference.nv().version.clone()); + diagnostics_collector.push( + PublishDiagnostic::MissingConstraint { + specifier: resolution.specifier.clone(), + specifier_text: specifier_text.to_string(), + resolved_version: maybe_version, + text_info: SourceTextInfo::new(source_text.clone()), + referrer: resolution.range.clone(), + }, + ); + } } } - } - "http" | "https" => { - skip_specifiers.insert(resolution.specifier.clone()); - diagnostics_collector.push( - PublishDiagnostic::InvalidExternalImport { - kind: format!("non-JSR '{}'", resolution.specifier.scheme()), - text_info: SourceTextInfo::new(source_text.clone()), - imported: resolution.specifier.clone(), - referrer: resolution.range.clone(), - }, - ); - } - _ => { - skip_specifiers.insert(resolution.specifier.clone()); - diagnostics_collector.push( - PublishDiagnostic::InvalidExternalImport { - kind: format!("'{}'", resolution.specifier.scheme()), - text_info: SourceTextInfo::new(source_text.clone()), - imported: resolution.specifier.clone(), - referrer: resolution.range.clone(), - }, - ); + "http" | "https" => { + skip_specifiers.insert(resolution.specifier.clone()); + diagnostics_collector.push( + PublishDiagnostic::InvalidExternalImport { + kind: format!("non-JSR '{}'", resolution.specifier.scheme()), + text_info: SourceTextInfo::new(source_text.clone()), + imported: resolution.specifier.clone(), + referrer: resolution.range.clone(), + }, + ); + } + _ => { + skip_specifiers.insert(resolution.specifier.clone()); + diagnostics_collector.push( + PublishDiagnostic::InvalidExternalImport { + kind: format!("'{}'", resolution.specifier.scheme()), + text_info: SourceTextInfo::new(source_text.clone()), + imported: resolution.specifier.clone(), + referrer: resolution.range.clone(), + }, + ); + } } } - } + }; + + let options = WalkOptions { + check_js: true, + follow_dynamic: true, + // search the entire graph and not just the fast check subset + prefer_fast_check_graph: false, + follow_type_only: true, }; + let mut iter = graph.walk(&graph.roots, options); + while let Some((specifier, entry)) = iter.next() { + if skip_specifiers.contains(specifier) { + iter.skip_previous_dependencies(); + continue; + } - let options = WalkOptions { - check_js: true, - follow_dynamic: true, - // this being disabled will cause it to follow everything in the graph - prefer_fast_check_graph: false, - follow_type_only: true, - }; - let mut iter = graph.walk(&graph.roots, options); - while let Some((specifier, entry)) = iter.next() { - if skip_specifiers.contains(specifier) { - iter.skip_previous_dependencies(); - continue; - } + let ModuleEntryRef::Module(module) = entry else { + continue; + }; + let Some(module) = module.js() else { + continue; + }; - let ModuleEntryRef::Module(module) = entry else { - continue; - }; - let Some(module) = module.js() else { - continue; - }; + let parsed_source = self + .parsed_source_cache + .get_parsed_source_from_js_module(module)?; + check_for_banned_triple_slash_directives( + &parsed_source, + diagnostics_collector, + ); - for (specifier_text, dep) in &module.dependencies { - if let Some(resolved) = dep.maybe_code.ok() { - collect_if_invalid( - &mut skip_specifiers, - &module.source, - specifier_text, - resolved, - ); - } - if let Some(resolved) = dep.maybe_type.ok() { - collect_if_invalid( - &mut skip_specifiers, - &module.source, - specifier_text, - resolved, - ); + for (specifier_text, dep) in &module.dependencies { + if let Some(resolved) = dep.maybe_code.ok() { + collect_if_invalid( + &mut skip_specifiers, + &module.source, + specifier_text, + resolved, + ); + } + if let Some(resolved) = dep.maybe_type.ok() { + collect_if_invalid( + &mut skip_specifiers, + &module.source, + specifier_text, + resolved, + ); + } } } + + Ok(()) + } +} + +fn check_for_banned_triple_slash_directives( + parsed_source: &ParsedSource, + diagnostics_collector: &PublishDiagnosticsCollector, +) { + let triple_slash_re = lazy_regex::regex!( + r#"^/\s+<reference\s+(no-default-lib\s*=\s*"true"|lib\s*=\s*("[^"]+"|'[^']+'))\s*/>\s*$"# + ); + + let Some(comments) = parsed_source.get_leading_comments() else { + return; + }; + for comment in comments { + if comment.kind != CommentKind::Line { + continue; + } + if triple_slash_re.is_match(&comment.text) { + diagnostics_collector.push( + PublishDiagnostic::BannedTripleSlashDirectives { + specifier: parsed_source.specifier().clone(), + range: comment.range(), + text_info: parsed_source.text_info().clone(), + }, + ); + } } } |