2024-01-10 17:40:30 -05:00
|
|
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
|
|
|
|
|
|
use std::collections::HashSet;
|
2024-02-08 20:40:26 -05:00
|
|
|
use std::sync::Arc;
|
2024-01-10 17:40:30 -05:00
|
|
|
|
2024-05-27 21:35:08 -04:00
|
|
|
use deno_ast::swc::common::comments::CommentKind;
|
|
|
|
use deno_ast::ParsedSource;
|
|
|
|
use deno_ast::SourceRangedForSpanned;
|
2024-02-08 20:40:26 -05:00
|
|
|
use deno_ast::SourceTextInfo;
|
2024-05-27 21:35:08 -04:00
|
|
|
use deno_core::error::AnyError;
|
2024-08-23 20:21:21 -04:00
|
|
|
use deno_core::url::Url;
|
2024-01-24 16:59:18 -05:00
|
|
|
use deno_graph::ModuleEntryRef;
|
2024-01-10 17:40:30 -05:00
|
|
|
use deno_graph::ModuleGraph;
|
2024-01-24 16:59:18 -05:00
|
|
|
use deno_graph::ResolutionResolved;
|
|
|
|
use deno_graph::WalkOptions;
|
2024-05-14 10:30:09 -04:00
|
|
|
use deno_semver::jsr::JsrPackageReqReference;
|
|
|
|
use deno_semver::npm::NpmPackageReqReference;
|
2024-01-10 17:40:30 -05:00
|
|
|
|
2024-05-27 21:35:08 -04:00
|
|
|
use crate::cache::ParsedSourceCache;
|
|
|
|
|
2024-01-23 10:37:43 -05:00
|
|
|
use super::diagnostics::PublishDiagnostic;
|
|
|
|
use super::diagnostics::PublishDiagnosticsCollector;
|
|
|
|
|
2024-05-27 21:35:08 -04:00
|
|
|
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()
|
2024-05-28 14:58:43 -04:00
|
|
|
.get(jsr_req_ref.req())
|
|
|
|
.map(|nv| nv.version.clone());
|
2024-05-27 21:35:08 -04:00
|
|
|
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(),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
2024-05-14 10:30:09 -04:00
|
|
|
}
|
|
|
|
}
|
2024-05-27 21:35:08 -04:00
|
|
|
"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(),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
2024-05-14 10:30:09 -04:00
|
|
|
}
|
|
|
|
}
|
2024-05-27 21:35:08 -04:00
|
|
|
"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(),
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
2024-01-24 16:59:18 -05:00
|
|
|
}
|
|
|
|
}
|
2024-05-27 21:35:08 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
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,
|
2024-09-25 09:04:57 -04:00
|
|
|
kind: deno_graph::GraphKind::All,
|
2024-01-24 16:59:18 -05:00
|
|
|
};
|
2024-06-05 11:04:16 -04:00
|
|
|
let mut iter = graph.walk(graph.roots.iter(), options);
|
2024-05-27 21:35:08 -04:00
|
|
|
while let Some((specifier, entry)) = iter.next() {
|
|
|
|
if skip_specifiers.contains(specifier) {
|
|
|
|
iter.skip_previous_dependencies();
|
|
|
|
continue;
|
|
|
|
}
|
2024-01-24 16:59:18 -05:00
|
|
|
|
2024-05-27 21:35:08 -04:00
|
|
|
let ModuleEntryRef::Module(module) = entry else {
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
let Some(module) = module.js() else {
|
|
|
|
continue;
|
|
|
|
};
|
2024-01-24 16:59:18 -05:00
|
|
|
|
2024-05-27 21:35:08 -04:00
|
|
|
let parsed_source = self
|
|
|
|
.parsed_source_cache
|
|
|
|
.get_parsed_source_from_js_module(module)?;
|
2024-07-17 23:06:30 -04:00
|
|
|
|
|
|
|
// surface syntax errors
|
|
|
|
for diagnostic in parsed_source.diagnostics() {
|
|
|
|
diagnostics_collector
|
|
|
|
.push(PublishDiagnostic::SyntaxError(diagnostic.clone()));
|
|
|
|
}
|
|
|
|
|
2024-05-27 21:35:08 -04:00
|
|
|
check_for_banned_triple_slash_directives(
|
|
|
|
&parsed_source,
|
|
|
|
diagnostics_collector,
|
|
|
|
);
|
2024-01-24 16:59:18 -05:00
|
|
|
|
2024-05-27 21:35:08 -04:00
|
|
|
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,
|
|
|
|
);
|
|
|
|
}
|
2024-01-24 16:59:18 -05:00
|
|
|
}
|
|
|
|
}
|
2024-05-27 21:35:08 -04:00
|
|
|
|
|
|
|
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(),
|
2024-06-05 11:04:16 -04:00
|
|
|
text_info: parsed_source.text_info_lazy().clone(),
|
2024-05-27 21:35:08 -04:00
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
2024-01-24 16:59:18 -05:00
|
|
|
}
|
|
|
|
}
|