summaryrefslogtreecommitdiff
path: root/cli/tools/registry/graph.rs
blob: 29c825242c325be518f9b935c581e3aebe6bf338 (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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use std::collections::HashSet;
use std::collections::VecDeque;

use deno_ast::ModuleSpecifier;
use deno_config::ConfigFile;
use deno_config::WorkspaceConfig;
use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
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,
  pub dir_url: ModuleSpecifier,
  pub exports: Vec<ModuleSpecifier>,
}

pub fn get_workspace_member_roots(
  config: &WorkspaceConfig,
) -> Result<Vec<MemberRoots>, AnyError> {
  let mut members = Vec::with_capacity(config.members.len());
  let mut seen_names = HashSet::with_capacity(config.members.len());
  for member in &config.members {
    if !seen_names.insert(&member.package_name) {
      bail!(
        "Cannot have two workspace packages with the same name ('{}' at {})",
        member.package_name,
        member.path.display(),
      );
    }
    members.push(MemberRoots {
      name: member.package_name.clone(),
      dir_url: member.config_file.specifier.join("./").unwrap().clone(),
      exports: resolve_config_file_roots_from_exports(&member.config_file)?,
    });
  }
  Ok(members)
}

pub fn resolve_config_file_roots_from_exports(
  config_file: &ConfigFile,
) -> Result<Vec<ModuleSpecifier>, AnyError> {
  let exports_config = config_file
    .to_exports_config()
    .with_context(|| {
      format!("Failed to parse exports at {}", config_file.specifier)
    })?
    .into_map();
  let mut exports = Vec::with_capacity(exports_config.len());
  for (_, value) in exports_config {
    let entry_point =
      config_file.specifier.join(&value).with_context(|| {
        format!("Failed to join {} with {}", config_file.specifier, value)
      })?;
    exports.push(entry_point);
  }
  Ok(exports)
}

/// 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],
  diagnostics_collector: &PublishDiagnosticsCollector,
) -> bool {
  let mut seen_diagnostics = HashSet::new();
  let mut seen_modules = HashSet::with_capacity(graph.specifiers_count());
  for package in packages {
    let mut pending = VecDeque::new();
    for export in &package.exports {
      if seen_modules.insert(export.clone()) {
        pending.push_back(export.clone());
      }
    }

    'analyze_package: while let Some(specifier) = pending.pop_front() {
      let Ok(Some(module)) = graph.try_get_prefer_types(&specifier) else {
        continue;
      };
      let Some(esm_module) = module.esm() else {
        continue;
      };
      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 { .. }
          ) {
            break 'analyze_package; // no need to keep analyzing this package
          }
        }
      }

      // analyze the next dependencies
      for dep in esm_module.dependencies_prefer_fast_check().values() {
        let Some(specifier) = graph.resolve_dependency_from_dep(dep, true)
        else {
          continue;
        };

        let dep_in_same_package =
          specifier.as_str().starts_with(package.dir_url.as_str());
        if dep_in_same_package {
          let is_new = seen_modules.insert(specifier.clone());
          if is_new {
            pending.push_back(specifier.clone());
          }
        }
      }
    }
  }

  !seen_diagnostics.is_empty()
}