summaryrefslogtreecommitdiff
path: root/cli/tools/registry/graph.rs
blob: 001f85e7657aafa66d3f206fabbbd3aceaaef953 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use std::collections::HashSet;
use std::sync::Arc;

use deno_ast::SourceTextInfo;
use deno_graph::ModuleEntryRef;
use deno_graph::ModuleGraph;
use deno_graph::ResolutionResolved;
use deno_graph::WalkOptions;
use lsp_types::Url;

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>,
     text: &Arc<str>,
     resolution: &ResolutionResolved| {
      if visited.insert(resolution.specifier.clone()) {
        match resolution.specifier.scheme() {
          "file" | "data" | "node" => {}
          "jsr" | "npm" => {
            skip_specifiers.insert(resolution.specifier.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(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(text.clone()),
                imported: resolution.specifier.clone(),
                referrer: resolution.range.clone(),
              },
            );
          }
        }
      }
    };

  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;
    };

    for (_, dep) in &module.dependencies {
      if let Some(resolved) = dep.maybe_code.ok() {
        collect_if_invalid(&mut skip_specifiers, &module.source, resolved);
      }
      if let Some(resolved) = dep.maybe_type.ok() {
        collect_if_invalid(&mut skip_specifiers, &module.source, resolved);
      }
    }
  }
}