use crate::ReferrerImports;
use crate::analysis::DependencyDescriptor;
use crate::analysis::DynamicArgument;
use crate::analysis::DynamicDependencyKind;
use crate::analysis::DynamicTemplatePart;
use crate::analysis::ImportAttributes;
use crate::analysis::ModuleAnalyzer;
use crate::analysis::ModuleInfo;
use crate::analysis::SpecifierWithRange;
use crate::analysis::StaticDependencyKind;
use crate::analysis::TypeScriptReference;
use crate::analysis::TypeScriptTypesResolutionMode;
use crate::collections::SeenPendingCollection;
use crate::jsr::JsrMetadataStore;
use crate::jsr::JsrMetadataStoreServices;
use crate::jsr::PendingJsrPackageVersionInfoLoadItem;
use crate::jsr::PendingResult;
use crate::packages::JsrVersionResolver;
use crate::packages::NewestDependencyDate;
use crate::module_specifier::ModuleSpecifier;
use crate::module_specifier::SpecifierError;
use crate::module_specifier::is_fs_root_specifier;
use crate::module_specifier::resolve_import;
use crate::packages::JsrPackageInfo;
use crate::packages::JsrPackageVersionInfo;
use crate::packages::PackageSpecifiers;
use crate::rt::Executor;
use crate::source::*;
use crate::MediaType;
use boxed_error::Boxed;
use deno_error::JsError;
use deno_error::JsErrorBox;
use deno_error::JsErrorClass;
use deno_media_type::encoding::BOM_CHAR;
use deno_media_type::encoding::DecodedArcSourceDetailKind;
use deno_semver::RangeSetOrTag;
use deno_semver::SmallStackString;
use deno_semver::StackString;
use deno_semver::Version;
use deno_semver::VersionReq;
use deno_semver::jsr::JsrDepPackageReq;
use deno_semver::jsr::JsrPackageNvReference;
use deno_semver::jsr::JsrPackageReqReference;
use deno_semver::npm::NpmPackageReqReference;
use deno_semver::package::PackageNv;
use deno_semver::package::PackageNvReference;
use deno_semver::package::PackageReq;
use deno_semver::package::PackageReqReferenceParseError;
use futures::FutureExt;
use futures::future::LocalBoxFuture;
use futures::stream::FuturesOrdered;
use futures::stream::FuturesUnordered;
use futures::stream::StreamExt;
use indexmap::IndexMap;
use indexmap::IndexSet;
use serde::Deserialize;
use serde::Serialize;
use serde::Serializer;
use serde::ser::SerializeSeq;
use serde::ser::SerializeStruct;
use serde::ser::SerializeTuple;
use std::borrow::Cow;
use std::cell::RefCell;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::collections::HashMap;
use std::collections::HashSet;
use std::collections::VecDeque;
use std::fmt;
use std::path::Path;
use std::rc::Rc;
use std::sync::Arc;
use std::time::SystemTime;
use sys_traits::FileType;
use sys_traits::FsDirEntry;
use thiserror::Error;
use url::Url;
use wasm::wasm_module_to_dts;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub struct Position {
pub line: usize,
pub character: usize,
}
impl std::fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.line + 1, self.character + 1)
}
}
impl PartialOrd for Position {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Position {
fn cmp(&self, other: &Self) -> Ordering {
match self.line.cmp(&other.line) {
Ordering::Equal => self.character.cmp(&other.character),
Ordering::Greater => Ordering::Greater,
Ordering::Less => Ordering::Less,
}
}
}
impl Position {
pub fn new(line: usize, character: usize) -> Self {
Self { line, character }
}
pub fn zeroed() -> Self {
Self {
line: 0,
character: 0,
}
}
#[cfg(feature = "swc")]
pub fn from_source_pos(
pos: deno_ast::SourcePos,
text_info: &deno_ast::SourceTextInfo,
) -> Self {
let line_and_column_index = text_info.line_and_column_index(pos);
Self {
line: line_and_column_index.line_index,
character: line_and_column_index.column_index,
}
}
#[cfg(feature = "swc")]
pub fn as_source_pos(
&self,
text_info: &deno_ast::SourceTextInfo,
) -> deno_ast::SourcePos {
text_info.loc_to_source_pos(deno_ast::LineAndColumnIndex {
line_index: self.line,
column_index: self.character,
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Hash)]
pub struct PositionRange {
#[serde(default = "Position::zeroed")]
pub start: Position,
#[serde(default = "Position::zeroed")]
pub end: Position,
}
impl PositionRange {
pub fn zeroed() -> Self {
Self {
start: Position::zeroed(),
end: Position::zeroed(),
}
}
pub fn includes(&self, position: Position) -> bool {
(position >= self.start) && (position <= self.end)
}
#[cfg(feature = "swc")]
pub fn from_source_range(
range: deno_ast::SourceRange,
text_info: &deno_ast::SourceTextInfo,
) -> Self {
Self {
start: Position::from_source_pos(range.start, text_info),
end: Position::from_source_pos(range.end, text_info),
}
}
#[cfg(feature = "swc")]
pub fn as_source_range(
&self,
text_info: &deno_ast::SourceTextInfo,
) -> deno_ast::SourceRange {
deno_ast::SourceRange::new(
self.start.as_source_pos(text_info),
self.end.as_source_pos(text_info),
)
}
}
impl Serialize for PositionRange {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
struct PositionSerializer<'a>(&'a Position);
impl Serialize for PositionSerializer<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_tuple(2)?;
seq.serialize_element(&self.0.line)?;
seq.serialize_element(&self.0.character)?;
seq.end()
}
}
let mut seq = serializer.serialize_tuple(2)?;
seq.serialize_element(&PositionSerializer(&self.start))?;
seq.serialize_element(&PositionSerializer(&self.end))?;
seq.end()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
pub struct Range {
#[serde(skip_serializing)]
pub specifier: ModuleSpecifier,
#[serde(flatten, serialize_with = "serialize_position")]
pub range: PositionRange,
#[serde(default, skip_serializing)]
pub resolution_mode: Option<ResolutionMode>,
}
fn serialize_position<S: Serializer>(
range: &PositionRange,
serializer: S,
) -> Result<S::Ok, S::Error> {
let mut seq = serializer.serialize_struct("PositionRange", 2)?;
seq.serialize_field("start", &range.start)?;
seq.serialize_field("end", &range.end)?;
seq.end()
}
impl fmt::Display for Range {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.specifier, self.range.start)
}
}
impl Range {
pub fn includes(&self, position: Position) -> bool {
self.range.includes(position)
}
}
#[derive(Debug, Clone, Error, JsError)]
pub enum JsrLoadError {
#[class(type)]
#[error(
"Unsupported checksum in JSR package manifest. Maybe try upgrading deno?"
)]
UnsupportedManifestChecksum,
#[class(inherit)]
#[error(transparent)]
ContentChecksumIntegrity(ChecksumIntegrityError),
#[class(generic)]
#[error(
"Loader should never return an external specifier for a jsr: specifier content load."
)]
ContentLoadExternalSpecifier,
#[class(inherit)]
#[error(transparent)]
ContentLoad(Arc<LoadError>),
#[class(inherit)]
#[error("JSR package manifest for '{}' failed to load. {:#}", .0, .1)]
PackageManifestLoad(String, #[inherit] Arc<LoadError>),
#[class("NotFound")]
#[error("JSR package not found: {}", .0)]
PackageNotFound(String),
#[class("NotFound")]
#[error("JSR package version not found: {}", .0)]
PackageVersionNotFound(Box<PackageNv>),
#[class(inherit)]
#[error("JSR package version manifest for '{}' failed to load: {:#}", .0, .1)]
PackageVersionManifestLoad(Box<PackageNv>, #[inherit] Arc<dyn JsErrorClass>),
#[class(inherit)]
#[error("JSR package version manifest for '{}' failed to load: {:#}", .0, .1)]
PackageVersionManifestChecksumIntegrity(
Box<PackageNv>,
#[inherit] ChecksumIntegrityError,
),
#[class(inherit)]
#[error(transparent)]
PackageFormat(JsrPackageFormatError),
#[class(inherit)]
#[error(transparent)]
PackageReqNotFound(JsrPackageReqNotFoundError),
#[class(generic)]
#[error("Redirects in the JSR registry are not supported (redirected to '{}')", .0)]
RedirectInPackage(ModuleSpecifier),
#[class("NotFound")]
#[error("Unknown export '{}' for '{}'.\n Package exports:\n{}", export_name, .nv, .exports.iter().map(|e| format!(" * {}", e)).collect::<Vec<_>>().join("\n"))]
UnknownExport {
nv: Box<PackageNv>,
export_name: String,
exports: Vec<String>,
},
}
#[derive(Error, Debug, Clone, JsError)]
#[class("NotFound")]
#[error("Could not find version of '{}' that matches specified version constraint '{}'{}", req.name, req.version_req, newest_dependency_date.map(|v| format!("\n\nA newer matching version was found, but it was not used because it was newer than the specified minimum dependency date of {}", v)).unwrap_or_else(String::new))]
pub struct JsrPackageReqNotFoundError {
pub req: PackageReq,
pub newest_dependency_date: Option<NewestDependencyDate>,
}
#[derive(Error, Debug, Clone, JsError)]
#[class(type)]
pub enum JsrPackageFormatError {
#[error(transparent)]
JsrPackageParseError(PackageReqReferenceParseError),
#[error("Version tag not supported in jsr specifiers ('{}').{}",
.tag,
match .tag.strip_prefix('v').and_then(|v| VersionReq::parse_from_specifier(v).ok().map(|s| s.tag().is_none())).unwrap_or(false) {
true => " Remove leading 'v' before version.",
false => ""
}
)]
VersionTagNotSupported { tag: SmallStackString },
}
#[derive(Debug, Clone, Error, JsError)]
pub enum NpmLoadError {
#[class(type)]
#[error("npm specifiers are not supported in this environment")]
NotSupportedEnvironment,
#[class(inherit)]
#[error(transparent)]
PackageReqResolution(Arc<dyn JsErrorClass>),
#[class(inherit)]
#[error(transparent)]
PackageReqReferenceParse(PackageReqReferenceParseError),
#[class(inherit)]
#[error(transparent)]
RegistryInfo(Arc<dyn JsErrorClass>),
}
#[derive(Debug, Error, Clone, JsError)]
pub enum ModuleLoadError {
#[class(inherit)]
#[error(transparent)]
HttpsChecksumIntegrity(ChecksumIntegrityError),
#[class(inherit)]
#[error(transparent)]
Decode(Arc<DecodeError>),
#[class(inherit)]
#[error(transparent)]
Loader(Arc<LoadError>),
#[class(inherit)]
#[error(transparent)]
Jsr(#[from] JsrLoadError),
#[class(inherit)]
#[error(transparent)]
Npm(#[from] NpmLoadError),
#[class(generic)]
#[error("Too many redirects.")]
TooManyRedirects,
}
#[derive(Debug, JsError)]
#[class(inherit)]
pub struct DecodeError {
pub mtime: Option<SystemTime>,
#[inherit]
pub err: std::io::Error,
}
impl std::error::Error for DecodeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.err.source()
}
}
impl std::fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.err.fmt(f)
}
}
#[derive(Debug, Clone, JsError, Boxed)]
pub struct ModuleError(pub Box<ModuleErrorKind>);
impl ModuleError {
pub fn specifier(&self) -> &ModuleSpecifier {
self.as_kind().specifier()
}
pub fn maybe_referrer(&self) -> Option<&Range> {
self.as_kind().maybe_referrer()
}
pub fn mtime(&self) -> Option<SystemTime> {
self.as_kind().mtime()
}
pub fn to_string_with_range(&self) -> String {
self.as_kind().to_string_with_range()
}
}
#[derive(Debug, Clone, JsError)]
pub enum ModuleErrorKind {
#[class(inherit)]
Load {
specifier: ModuleSpecifier,
maybe_referrer: Option<Range>,
#[inherit]
err: ModuleLoadError,
},
#[class("NotFound")]
Missing {
specifier: ModuleSpecifier,
maybe_referrer: Option<Range>,
},
#[class("NotFound")]
MissingDynamic {
specifier: ModuleSpecifier,
referrer: Range,
},
#[class(inherit)]
Parse {
specifier: ModuleSpecifier,
mtime: Option<SystemTime>,
#[inherit]
diagnostic: Arc<JsErrorBox>,
},
#[class(inherit)]
WasmParse {
specifier: ModuleSpecifier,
mtime: Option<SystemTime>,
#[inherit]
err: wasm_dep_analyzer::ParseError,
},
#[class(type)]
UnsupportedMediaType {
specifier: ModuleSpecifier,
media_type: MediaType,
maybe_referrer: Option<Range>,
},
#[class(syntax)]
InvalidTypeAssertion {
specifier: ModuleSpecifier,
referrer: Range,
actual_media_type: MediaType,
expected_media_type: MediaType,
},
#[class(type)]
UnsupportedImportAttributeType {
specifier: ModuleSpecifier,
referrer: Range,
kind: String,
},
#[class(type)]
UnsupportedModuleTypeForSourcePhaseImport {
specifier: ModuleSpecifier,
referrer: Range,
actual_media_type: MediaType,
actual_attribute_type: Option<String>,
},
}
impl ModuleErrorKind {
pub fn specifier(&self) -> &ModuleSpecifier {
match self {
Self::Load { specifier, .. }
| Self::Parse { specifier, .. }
| Self::WasmParse { specifier, .. }
| Self::UnsupportedMediaType { specifier, .. }
| Self::Missing { specifier, .. }
| Self::MissingDynamic { specifier, .. }
| Self::InvalidTypeAssertion { specifier, .. }
| Self::UnsupportedImportAttributeType { specifier, .. } => specifier,
Self::UnsupportedModuleTypeForSourcePhaseImport { specifier, .. } => {
specifier
}
}
}
pub fn maybe_referrer(&self) -> Option<&Range> {
match self {
Self::Load { maybe_referrer, .. }
| Self::Missing { maybe_referrer, .. } => maybe_referrer.as_ref(),
Self::UnsupportedMediaType { maybe_referrer, .. } => {
maybe_referrer.as_ref()
}
Self::Parse { .. } => None,
Self::WasmParse { .. } => None,
Self::MissingDynamic { referrer, .. }
| Self::InvalidTypeAssertion { referrer, .. }
| Self::UnsupportedImportAttributeType { referrer, .. }
| Self::UnsupportedModuleTypeForSourcePhaseImport { referrer, .. } => {
Some(referrer)
}
}
}
pub fn mtime(&self) -> Option<SystemTime> {
match self {
Self::Parse { mtime, .. } | Self::WasmParse { mtime, .. } => *mtime,
Self::Load { err, .. } => match err {
ModuleLoadError::Decode(decode_error) => decode_error.mtime,
ModuleLoadError::HttpsChecksumIntegrity { .. }
| ModuleLoadError::Loader { .. }
| ModuleLoadError::Jsr { .. }
| ModuleLoadError::Npm { .. }
| ModuleLoadError::TooManyRedirects => None,
},
Self::Missing { .. }
| Self::MissingDynamic { .. }
| Self::UnsupportedMediaType { .. }
| Self::InvalidTypeAssertion { .. }
| Self::UnsupportedImportAttributeType { .. }
| Self::UnsupportedModuleTypeForSourcePhaseImport { .. } => None,
}
}
pub fn to_string_with_range(&self) -> String {
if let Some(range) = self.maybe_referrer() {
format!("{self:#}\n at {range}")
} else {
format!("{self:#}")
}
}
}
impl std::error::Error for ModuleErrorKind {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Load { err, .. } => Some(err),
Self::Missing { .. }
| Self::MissingDynamic { .. }
| Self::Parse { .. }
| Self::WasmParse { .. }
| Self::UnsupportedMediaType { .. }
| Self::InvalidTypeAssertion { .. }
| Self::UnsupportedImportAttributeType { .. }
| Self::UnsupportedModuleTypeForSourcePhaseImport { .. } => None,
}
}
}
impl fmt::Display for ModuleErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Load { err, .. } => err.fmt(f),
Self::Parse { diagnostic, .. } => {
write!(f, "{diagnostic}")
}
Self::WasmParse { specifier, err, .. } => write!(
f,
"The Wasm module could not be parsed: {err}\n Specifier: {specifier}"
),
Self::UnsupportedMediaType {
specifier,
media_type: MediaType::Json,
..
} => write!(
f,
"Expected a JavaScript or TypeScript module, but identified a Json module. Consider importing Json modules with an import attribute with the type of \"json\".\n Specifier: {specifier}"
),
Self::UnsupportedMediaType {
specifier,
media_type: MediaType::Cjs | MediaType::Cts,
..
} if specifier.scheme() != "file" => write!(
f,
"Remote CJS modules are not supported.\n Specifier: {specifier}"
),
Self::UnsupportedMediaType {
specifier,
media_type,
..
} => write!(
f,
"Expected a JavaScript or TypeScript module, but identified a {media_type} module. Importing these types of modules is currently not supported.\n Specifier: {specifier}"
),
Self::Missing { specifier, .. } => {
write!(f, "Module not found \"{specifier}\".")
}
Self::MissingDynamic { specifier, .. } => {
write!(f, "Dynamic import not found \"{specifier}\".")
}
Self::InvalidTypeAssertion {
specifier,
actual_media_type: MediaType::Json,
expected_media_type,
..
} => write!(
f,
"Expected a {expected_media_type} module, but identified a Json module. Consider importing Json modules with an import attribute with the type of \"json\".\n Specifier: {specifier}"
),
Self::InvalidTypeAssertion {
specifier,
actual_media_type,
expected_media_type,
..
} => write!(
f,
"Expected a {expected_media_type} module, but identified a {actual_media_type} module.\n Specifier: {specifier}"
),
Self::UnsupportedImportAttributeType {
specifier, kind, ..
} => write!(
f,
"The import attribute type of \"{kind}\" is unsupported.\n Specifier: {specifier}"
),
Self::UnsupportedModuleTypeForSourcePhaseImport {
specifier,
actual_media_type,
actual_attribute_type: None,
..
} => write!(
f,
"Importing {actual_media_type} modules at source phase is unsupported.\n Specifier: {specifier}"
),
Self::UnsupportedModuleTypeForSourcePhaseImport {
specifier,
actual_media_type,
actual_attribute_type: Some(actual_attribute_type),
..
} => write!(
f,
"Importing {actual_media_type} modules with {{ type: \"{actual_attribute_type}\" }} at source phase is unsupported.\n Specifier: {specifier}"
),
}
}
}
#[derive(Debug, Clone, JsError)]
pub enum ModuleGraphError {
#[class(inherit)]
ModuleError(ModuleError),
#[class(inherit)]
ResolutionError(ResolutionError),
#[class(inherit)]
TypesResolutionError(ResolutionError),
}
impl ModuleGraphError {
pub fn as_module_error_kind(&self) -> Option<&ModuleErrorKind> {
match self {
Self::ModuleError(err) => Some(err.as_kind()),
_ => None,
}
}
fn for_resolution_kind(kind: ResolutionKind, error: ResolutionError) -> Self {
match kind {
ResolutionKind::Execution => Self::ResolutionError(error),
ResolutionKind::Types => Self::TypesResolutionError(error),
}
}
pub fn to_string_with_range(&self) -> String {
match self {
ModuleGraphError::ModuleError(err) => err.to_string_with_range(),
ModuleGraphError::ResolutionError(err)
| ModuleGraphError::TypesResolutionError(err) => {
err.to_string_with_range()
}
}
}
pub fn maybe_range(&self) -> Option<&Range> {
match self {
Self::ModuleError(err) => err.maybe_referrer(),
Self::ResolutionError(err) | Self::TypesResolutionError(err) => {
Some(err.range())
}
}
}
}
impl std::error::Error for ModuleGraphError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::ModuleError(err) => Some(err),
Self::ResolutionError(err) | Self::TypesResolutionError(err) => Some(err),
}
}
}
impl fmt::Display for ModuleGraphError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ModuleError(err) => err.fmt(f),
Self::ResolutionError(err) => err.fmt(f),
Self::TypesResolutionError(err) => {
f.write_str("Failed resolving types. ")?;
err.fmt(f)
}
}
}
}
#[derive(Debug, Clone, JsError)]
#[class(type)]
pub enum ResolutionError {
InvalidDowngrade {
specifier: ModuleSpecifier,
range: Range,
},
InvalidJsrHttpsTypesImport {
specifier: ModuleSpecifier,
range: Range,
},
InvalidLocalImport {
specifier: ModuleSpecifier,
range: Range,
},
InvalidSpecifier {
error: SpecifierError,
range: Range,
},
ResolverError {
error: Arc<ResolveError>,
specifier: String,
range: Range,
},
}
impl ResolutionError {
pub fn range(&self) -> &Range {
match self {
Self::InvalidDowngrade { range, .. }
| Self::InvalidJsrHttpsTypesImport { range, .. }
| Self::InvalidLocalImport { range, .. }
| Self::InvalidSpecifier { range, .. }
| Self::ResolverError { range, .. } => range,
}
}
pub fn to_string_with_range(&self) -> String {
format!("{}\n at {}", self, self.range())
}
}
impl std::error::Error for ResolutionError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::InvalidDowngrade { .. }
| Self::InvalidJsrHttpsTypesImport { .. }
| Self::InvalidLocalImport { .. } => None,
Self::InvalidSpecifier { error, .. } => Some(error),
Self::ResolverError { error, .. } => Some(error.as_ref()),
}
}
}
impl PartialEq for ResolutionError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
Self::ResolverError {
specifier: a,
range: a_range,
..
},
Self::ResolverError {
specifier: b,
range: b_range,
..
},
) => a == b && a_range == b_range,
(
Self::InvalidDowngrade {
specifier: a,
range: a_range,
..
},
Self::InvalidDowngrade {
specifier: b,
range: b_range,
..
},
)
| (
Self::InvalidJsrHttpsTypesImport {
specifier: a,
range: a_range,
..
},
Self::InvalidJsrHttpsTypesImport {
specifier: b,
range: b_range,
..
},
)
| (
Self::InvalidLocalImport {
specifier: a,
range: a_range,
..
},
Self::InvalidLocalImport {
specifier: b,
range: b_range,
..
},
) => a == b && a_range == b_range,
(
Self::InvalidSpecifier {
error: a,
range: a_range,
..
},
Self::InvalidSpecifier {
error: b,
range: b_range,
..
},
) => a == b && a_range == b_range,
_ => false,
}
}
}
impl Eq for ResolutionError {}
impl fmt::Display for ResolutionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidDowngrade { specifier, .. } => write!(
f,
"Modules imported via https are not allowed to import http modules.\n Importing: {specifier}"
),
Self::InvalidJsrHttpsTypesImport { specifier, .. } => write!(
f,
"Importing JSR packages via HTTPS specifiers for type checking is not supported for performance reasons. If you would like types, import via a `jsr:` specifier instead or else use a non-statically analyzable dynamic import.\n Importing: {specifier}"
),
Self::InvalidLocalImport { specifier, .. } => write!(
f,
"Remote modules are not allowed to import local modules. Consider using a dynamic import instead.\n Importing: {specifier}"
),
Self::ResolverError { error, .. } => error.fmt(f),
Self::InvalidSpecifier { error, .. } => error.fmt(f),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolutionResolved {
pub specifier: ModuleSpecifier,
pub range: Range,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Resolution {
None,
Ok(Box<ResolutionResolved>),
Err(Box<ResolutionError>),
}
impl Resolution {
pub fn from_resolve_result(
result: Result<ModuleSpecifier, ResolveError>,
specifier_text: &str,
range: Range,
) -> Self {
match result {
Ok(specifier) => {
Resolution::Ok(Box::new(ResolutionResolved { specifier, range }))
}
Err(err) => {
let resolution_error =
if let ResolveError::Specifier(specifier_error) = err {
ResolutionError::InvalidSpecifier {
error: specifier_error.clone(),
range,
}
} else {
ResolutionError::ResolverError {
error: Arc::new(err),
specifier: specifier_text.to_string(),
range,
}
};
Self::Err(Box::new(resolution_error))
}
}
}
pub fn includes(&self, position: Position) -> Option<&Range> {
match self {
Self::Ok(resolution) if resolution.range.includes(position) => {
Some(&resolution.range)
}
Self::Err(err) => {
let range = err.range();
if range.includes(position) {
Some(range)
} else {
None
}
}
_ => None,
}
}
pub fn is_none(&self) -> bool {
matches!(self, Self::None)
}
pub fn maybe_specifier(&self) -> Option<&ModuleSpecifier> {
self.ok().map(|r| &r.specifier)
}
pub fn maybe_range(&self) -> Option<&Range> {
match self {
Resolution::None => None,
Resolution::Ok(r) => Some(&r.range),
Resolution::Err(e) => Some(e.range()),
}
}
pub fn ok(&self) -> Option<&ResolutionResolved> {
if let Resolution::Ok(resolved) = self {
Some(&**resolved)
} else {
None
}
}
pub fn err(&self) -> Option<&ResolutionError> {
if let Resolution::Err(err) = self {
Some(&**err)
} else {
None
}
}
}
impl Default for Resolution {
fn default() -> Self {
Self::None
}
}
fn is_false(v: &bool) -> bool {
!v
}
#[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum ImportKind {
Es,
EsSource,
Require,
TsType,
TsModuleAugmentation,
TsReferencePath,
TsReferenceTypes,
JsxImportSource,
JsDoc,
}
impl ImportKind {
pub fn is_runtime(&self) -> bool {
match self {
ImportKind::Es
| ImportKind::EsSource
| ImportKind::Require
| ImportKind::JsxImportSource => true,
ImportKind::TsType
| ImportKind::TsModuleAugmentation
| ImportKind::TsReferencePath
| ImportKind::TsReferenceTypes
| ImportKind::JsDoc => false,
}
}
pub fn is_source_phase(&self) -> bool {
match self {
ImportKind::EsSource => true,
ImportKind::Es
| ImportKind::Require
| ImportKind::JsxImportSource
| ImportKind::TsType
| ImportKind::TsModuleAugmentation
| ImportKind::TsReferencePath
| ImportKind::TsReferenceTypes
| ImportKind::JsDoc => false,
}
}
fn is_es(&self) -> bool {
matches!(self, ImportKind::Es)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Import {
pub specifier: String,
#[serde(skip_serializing_if = "ImportKind::is_es")]
pub kind: ImportKind,
#[serde(rename = "range")]
pub specifier_range: Range,
#[serde(skip_serializing_if = "is_false")]
pub is_dynamic: bool,
#[serde(skip_serializing)]
pub is_side_effect: bool,
#[serde(skip_serializing)]
pub attributes: ImportAttributes,
}
#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Dependency {
#[serde(rename = "code", skip_serializing_if = "Resolution::is_none")]
pub maybe_code: Resolution,
#[serde(rename = "type", skip_serializing_if = "Resolution::is_none")]
pub maybe_type: Resolution,
#[serde(skip_serializing)]
pub maybe_deno_types_specifier: Option<String>,
#[serde(skip_serializing_if = "is_false")]
pub is_dynamic: bool,
#[serde(rename = "assertionType", skip_serializing_if = "Option::is_none")]
pub maybe_attribute_type: Option<String>,
#[serde(skip_serializing)]
pub imports: Vec<Import>,
}
impl Dependency {
pub fn get_code(&self) -> Option<&ModuleSpecifier> {
self.maybe_code.maybe_specifier()
}
pub fn get_type(&self) -> Option<&ModuleSpecifier> {
self.maybe_type.maybe_specifier()
}
pub fn includes(&self, position: Position) -> Option<&Range> {
for import in &self.imports {
if import.specifier_range.includes(position) {
return Some(&import.specifier_range);
}
}
if let Some(range) = self.maybe_type.includes(position) {
return Some(range);
}
None
}
pub fn with_new_resolver(
&self,
specifier: &str,
jsr_url_provider: &dyn JsrUrlProvider,
maybe_resolver: Option<&dyn Resolver>,
) -> Self {
let maybe_code = self
.maybe_code
.maybe_range()
.map(|r| {
resolve(
specifier,
r.clone(),
ResolutionKind::Execution,
jsr_url_provider,
maybe_resolver,
)
})
.unwrap_or_default();
let maybe_type = self
.maybe_type
.maybe_range()
.map(|r| {
resolve(
self
.maybe_deno_types_specifier
.as_deref()
.unwrap_or(specifier),
r.clone(),
ResolutionKind::Types,
jsr_url_provider,
maybe_resolver,
)
})
.unwrap_or_default();
Self {
maybe_code,
maybe_type,
maybe_deno_types_specifier: self.maybe_deno_types_specifier.clone(),
is_dynamic: self.is_dynamic,
maybe_attribute_type: self.maybe_attribute_type.clone(),
imports: self.imports.clone(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TypesDependency {
pub specifier: String,
pub dependency: Resolution,
}
impl TypesDependency {
pub fn with_new_resolver(
&self,
jsr_url_provider: &dyn JsrUrlProvider,
maybe_resolver: Option<&dyn Resolver>,
) -> Self {
let dependency = self
.dependency
.maybe_range()
.map(|r| {
resolve(
&self.specifier,
r.clone(),
ResolutionKind::Types,
jsr_url_provider,
maybe_resolver,
)
})
.unwrap_or_default();
Self {
specifier: self.specifier.clone(),
dependency,
}
}
}
fn is_media_type_unknown(media_type: &MediaType) -> bool {
matches!(media_type, MediaType::Unknown)
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WorkspaceMember {
pub base: Url,
pub name: StackString,
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<Version>,
pub exports: IndexMap<String, String>,
}
impl WorkspaceMember {
pub fn as_nv(&self) -> PackageNv {
PackageNv {
name: self.name.clone(),
version: self
.version
.clone()
.unwrap_or_else(|| Version::parse_standard("0.0.0").unwrap()),
}
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
#[serde(tag = "kind")]
pub enum Module {
#[serde(rename = "esm")]
Js(JsModule),
#[serde(rename = "asserted")]
Json(JsonModule),
Wasm(WasmModule),
Npm(NpmModule),
Node(BuiltInNodeModule),
External(ExternalModule),
}
impl Module {
pub fn specifier(&self) -> &ModuleSpecifier {
match self {
Module::Js(module) => &module.specifier,
Module::Json(module) => &module.specifier,
Module::Wasm(module) => &module.specifier,
Module::Npm(module) => &module.specifier,
Module::Node(module) => &module.specifier,
Module::External(module) => &module.specifier,
}
}
pub fn media_type(&self) -> MediaType {
match self {
Module::Js(module) => module.media_type,
Module::Json(module) => module.media_type,
Module::Wasm(_) => MediaType::Wasm,
Module::Node(_) => MediaType::JavaScript,
Module::Npm(_) | Module::External(_) => MediaType::Unknown,
}
}
pub fn mtime(&self) -> Option<SystemTime> {
match self {
Module::Js(m) => m.mtime,
Module::Json(m) => m.mtime,
Module::Wasm(m) => m.mtime,
Module::Npm(_) | Module::Node(_) | Module::External(_) => None,
}
}
pub fn json(&self) -> Option<&JsonModule> {
if let Module::Json(module) = &self {
Some(module)
} else {
None
}
}
pub fn js(&self) -> Option<&JsModule> {
if let Module::Js(module) = &self {
Some(module)
} else {
None
}
}
pub fn npm(&self) -> Option<&NpmModule> {
if let Module::Npm(module) = &self {
Some(module)
} else {
None
}
}
pub fn node(&self) -> Option<&BuiltInNodeModule> {
if let Module::Node(module) = &self {
Some(module)
} else {
None
}
}
pub fn external(&self) -> Option<&ExternalModule> {
if let Module::External(module) = &self {
Some(module)
} else {
None
}
}
pub fn source(&self) -> Option<&Arc<str>> {
match self {
crate::Module::Js(m) => Some(&m.source.text),
crate::Module::Json(m) => Some(&m.source.text),
crate::Module::Wasm(_)
| crate::Module::Npm(_)
| crate::Module::Node(_)
| crate::Module::External(_) => None,
}
}
pub fn maybe_types_dependency(&self) -> Option<&TypesDependency> {
match self {
Module::Js(js_module) => js_module.maybe_types_dependency.as_ref(),
Module::Wasm(_)
| Module::Json(_)
| Module::Npm(_)
| Module::Node(_)
| Module::External(_) => None,
}
}
pub fn dependencies(&self) -> &IndexMap<String, Dependency> {
match self {
Module::Js(js_module) => &js_module.dependencies,
Module::Wasm(wasm_module) => &wasm_module.dependencies,
Module::Npm(_)
| Module::Node(_)
| Module::External(_)
| Module::Json(_) => EMPTY_DEPS.get_or_init(Default::default),
}
}
pub fn dependencies_prefer_fast_check(
&self,
) -> &IndexMap<String, Dependency> {
match self {
Module::Js(js_module) => js_module.dependencies_prefer_fast_check(),
Module::Wasm(wasm_module) => &wasm_module.dependencies,
Module::Npm(_)
| Module::Node(_)
| Module::External(_)
| Module::Json(_) => EMPTY_DEPS.get_or_init(Default::default),
}
}
}
static EMPTY_DEPS: std::sync::OnceLock<IndexMap<String, Dependency>> =
std::sync::OnceLock::new();
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct NpmModule {
pub specifier: ModuleSpecifier,
#[serde(skip_serializing)]
pub pkg_req_ref: NpmPackageReqReference,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ExternalModule {
pub specifier: ModuleSpecifier,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub maybe_cache_info: Option<CacheInfo>,
#[serde(skip_serializing)]
pub was_asset_load: bool,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BuiltInNodeModule {
pub specifier: ModuleSpecifier,
pub module_name: String,
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JsonModule {
pub specifier: ModuleSpecifier,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub maybe_cache_info: Option<CacheInfo>,
#[serde(rename = "size", serialize_with = "serialize_source")]
pub source: ModuleTextSource,
#[serde(skip_serializing)]
pub mtime: Option<SystemTime>,
pub media_type: MediaType,
}
impl JsonModule {
pub fn size(&self) -> usize {
self.source.text.len()
}
}
#[cfg(feature = "fast_check")]
#[derive(Debug, Clone)]
pub enum FastCheckTypeModuleSlot {
Module(Box<FastCheckTypeModule>),
Error(Vec<crate::fast_check::FastCheckDiagnostic>),
}
#[cfg(feature = "fast_check")]
#[derive(Debug, Clone)]
pub struct FastCheckTypeModule {
pub dependencies: IndexMap<String, Dependency>,
pub source: Arc<str>,
pub source_map: Arc<str>,
pub dts: Option<crate::fast_check::FastCheckDtsModule>,
}
#[derive(Debug, Clone)]
pub struct ModuleTextSource {
pub text: Arc<str>,
pub decoded_kind: DecodedArcSourceDetailKind,
}
impl ModuleTextSource {
pub fn new_unknown(text: Arc<str>) -> Self {
Self {
text,
decoded_kind: DecodedArcSourceDetailKind::Changed,
}
}
pub fn try_get_original_bytes(&self) -> Option<Arc<[u8]>> {
match self.decoded_kind {
DecodedArcSourceDetailKind::Unchanged => unsafe {
let raw_ptr = Arc::into_raw(self.text.clone());
Some(Arc::from_raw(
std::mem::transmute::<*const str, *const [u8]>(raw_ptr),
))
},
DecodedArcSourceDetailKind::Changed => None,
DecodedArcSourceDetailKind::OnlyUtf8Bom => {
let mut bytes =
Vec::with_capacity(self.text.len() + BOM_CHAR.len_utf8());
bytes.extend([0xEF, 0xBB, 0xBF]);
bytes.extend(self.text.as_bytes());
Some(Arc::from(bytes))
}
}
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct JsModule {
#[serde(skip_serializing)]
pub is_script: bool,
#[serde(
skip_serializing_if = "IndexMap::is_empty",
serialize_with = "serialize_dependencies"
)]
pub dependencies: IndexMap<String, Dependency>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub maybe_cache_info: Option<CacheInfo>,
#[serde(skip_serializing)]
pub mtime: Option<SystemTime>,
#[serde(rename = "size", serialize_with = "serialize_source")]
pub source: ModuleTextSource,
#[serde(rename = "typesDependency", skip_serializing_if = "Option::is_none")]
pub maybe_types_dependency: Option<TypesDependency>,
#[serde(skip_serializing_if = "is_media_type_unknown")]
pub media_type: MediaType,
pub specifier: ModuleSpecifier,
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_source_map_dependency: Option<TypesDependency>,
#[cfg(feature = "fast_check")]
#[serde(skip_serializing)]
pub fast_check: Option<FastCheckTypeModuleSlot>,
}
impl JsModule {
pub fn size(&self) -> usize {
self.source.text.len()
}
#[cfg(feature = "fast_check")]
pub fn fast_check_diagnostics(
&self,
) -> Option<&Vec<crate::fast_check::FastCheckDiagnostic>> {
let module_slot = self.fast_check.as_ref()?;
match module_slot {
FastCheckTypeModuleSlot::Module(_) => None,
FastCheckTypeModuleSlot::Error(d) => Some(d),
}
}
#[cfg(feature = "fast_check")]
pub fn fast_check_module(&self) -> Option<&FastCheckTypeModule> {
let module_slot = self.fast_check.as_ref()?;
match module_slot {
FastCheckTypeModuleSlot::Module(m) => Some(m),
FastCheckTypeModuleSlot::Error(_) => None,
}
}
pub fn dependencies_prefer_fast_check(
&self,
) -> &IndexMap<String, Dependency> {
#[cfg(feature = "fast_check")]
{
match self.fast_check_module() {
Some(fast_check) => &fast_check.dependencies,
None => &self.dependencies,
}
}
#[cfg(not(feature = "fast_check"))]
&self.dependencies
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WasmModule {
pub specifier: ModuleSpecifier,
#[serde(skip_serializing)]
pub mtime: Option<SystemTime>,
#[serde(
skip_serializing_if = "IndexMap::is_empty",
serialize_with = "serialize_dependencies"
)]
pub dependencies: IndexMap<String, Dependency>,
#[serde(rename = "size", serialize_with = "serialize_source_bytes")]
pub source: Arc<[u8]>,
#[serde(skip_serializing)]
pub source_dts: Arc<str>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub maybe_cache_info: Option<CacheInfo>,
}
impl WasmModule {
pub fn size(&self) -> usize {
self.source.len()
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone)]
pub(crate) enum ModuleSlot {
Module(Module),
Err(ModuleError),
Pending {
is_asset: bool,
},
}
impl ModuleSlot {
#[cfg(test)]
pub fn module(&self) -> Option<&Module> {
if let ModuleSlot::Module(module) = self {
Some(module)
} else {
None
}
}
pub fn is_pending(&self) -> bool {
matches!(self, ModuleSlot::Pending { .. })
}
pub fn was_external_asset_load(&self) -> bool {
matches!(
self,
ModuleSlot::Module(Module::External(ExternalModule {
was_asset_load: true,
..
}))
)
}
pub fn is_pending_asset_load(&self) -> bool {
matches!(self, ModuleSlot::Pending { is_asset: true })
}
fn as_err_kind(&self) -> Option<&ModuleErrorKind> {
match self {
ModuleSlot::Err(err) => Some(err.as_kind()),
_ => None,
}
}
}
type ModuleResult<'a> =
(&'a ModuleSpecifier, Result<&'a Module, &'a ModuleError>);
fn to_result<'a>(
(specifier, module_slot): (&'a ModuleSpecifier, &'a ModuleSlot),
) -> Option<ModuleResult<'a>> {
match module_slot {
ModuleSlot::Err(err) => Some((specifier, Err(err))),
ModuleSlot::Module(module) => Some((specifier, Ok(module))),
ModuleSlot::Pending { .. } => None,
}
}
#[derive(Debug, Clone, Serialize)]
pub struct GraphImport {
#[serde(serialize_with = "serialize_dependencies")]
pub dependencies: IndexMap<String, Dependency>,
}
impl GraphImport {
pub fn new(
referrer: &ModuleSpecifier,
imports: Vec<String>,
jsr_url_provider: &dyn JsrUrlProvider,
maybe_resolver: Option<&dyn Resolver>,
) -> Self {
let dependencies = imports
.into_iter()
.map(|import| {
let referrer_range = Range {
specifier: referrer.clone(),
range: PositionRange::zeroed(),
resolution_mode: None,
};
let maybe_type = resolve(
&import,
referrer_range,
ResolutionKind::Types,
jsr_url_provider,
maybe_resolver,
);
(
import,
Dependency {
is_dynamic: false,
maybe_code: Resolution::None,
maybe_type,
maybe_deno_types_specifier: None,
maybe_attribute_type: None,
imports: vec![],
},
)
})
.collect();
Self { dependencies }
}
}
#[cfg(feature = "fast_check")]
#[derive(Debug, Default)]
pub enum WorkspaceFastCheckOption<'a> {
#[default]
Disabled,
Enabled(&'a [WorkspaceMember]),
}
#[cfg(feature = "fast_check")]
#[derive(Default)]
pub struct BuildFastCheckTypeGraphOptions<'a> {
pub fast_check_cache: Option<&'a dyn crate::fast_check::FastCheckCache>,
pub fast_check_dts: bool,
pub jsr_url_provider: &'a dyn JsrUrlProvider,
pub es_parser: Option<&'a dyn crate::ast::EsParser>,
pub resolver: Option<&'a dyn Resolver>,
pub workspace_fast_check: WorkspaceFastCheckOption<'a>,
}
pub struct BuildOptions<'a> {
pub is_dynamic: bool,
pub skip_dynamic_deps: bool,
pub unstable_bytes_imports: bool,
pub unstable_text_imports: bool,
pub executor: &'a dyn Executor,
pub locker: Option<&'a mut dyn Locker>,
pub file_system: &'a FileSystem,
pub jsr_url_provider: &'a dyn JsrUrlProvider,
pub jsr_version_resolver: Cow<'a, JsrVersionResolver>,
pub passthrough_jsr_specifiers: bool,
pub module_analyzer: &'a dyn ModuleAnalyzer,
pub module_info_cacher: &'a dyn ModuleInfoCacher,
pub npm_resolver: Option<&'a dyn NpmResolver>,
pub reporter: Option<&'a dyn Reporter>,
pub resolver: Option<&'a dyn Resolver>,
pub jsr_metadata_store: Option<Rc<JsrMetadataStore>>,
}
impl Default for BuildOptions<'_> {
fn default() -> Self {
Self {
is_dynamic: false,
skip_dynamic_deps: false,
unstable_bytes_imports: false,
unstable_text_imports: false,
executor: Default::default(),
locker: None,
file_system: &NullFileSystem,
jsr_url_provider: Default::default(),
jsr_version_resolver: Default::default(),
passthrough_jsr_specifiers: false,
module_analyzer: Default::default(),
module_info_cacher: Default::default(),
npm_resolver: None,
reporter: None,
resolver: None,
jsr_metadata_store: None,
}
}
}
#[derive(Debug, Copy, Clone)]
pub enum ModuleEntryRef<'a> {
Module(&'a Module),
Err(&'a ModuleError),
Redirect(&'a ModuleSpecifier),
}
pub trait CheckJsResolver: std::fmt::Debug {
fn resolve(&self, specifier: &ModuleSpecifier) -> bool;
}
#[derive(Debug, Clone, Copy)]
pub enum CheckJsOption<'a> {
True,
False,
Custom(&'a dyn CheckJsResolver),
}
impl CheckJsOption<'_> {
pub fn resolve(&self, specifier: &ModuleSpecifier) -> bool {
match self {
CheckJsOption::True => true,
CheckJsOption::False => false,
CheckJsOption::Custom(check_js_resolver) => {
check_js_resolver.resolve(specifier)
}
}
}
}
#[derive(Debug, Clone)]
pub struct WalkOptions<'a> {
pub check_js: CheckJsOption<'a>,
pub follow_dynamic: bool,
pub kind: GraphKind,
pub prefer_fast_check_graph: bool,
}
pub struct FillFromLockfileOptions<
'a,
TRedirectIter: Iterator<Item = (&'a str, &'a str)>,
TPackageSpecifiersIter: Iterator<Item = (&'a JsrDepPackageReq, &'a str)>,
> {
pub redirects: TRedirectIter,
pub package_specifiers: TPackageSpecifiersIter,
}
pub struct ModuleEntryIterator<'a, 'options> {
graph: &'a ModuleGraph,
seen: HashSet<&'a ModuleSpecifier>,
visiting: VecDeque<&'a ModuleSpecifier>,
follow_dynamic: bool,
kind: GraphKind,
check_js: CheckJsOption<'options>,
prefer_fast_check_graph: bool,
previous_module: Option<ModuleEntryRef<'a>>,
}
impl<'a, 'options> ModuleEntryIterator<'a, 'options> {
fn new(
graph: &'a ModuleGraph,
roots: impl Iterator<Item = &'a ModuleSpecifier>,
options: WalkOptions<'options>,
) -> Self {
let mut seen =
HashSet::<&'a ModuleSpecifier>::with_capacity(graph.specifiers_count());
let mut visiting = VecDeque::<&'a ModuleSpecifier>::new();
for root in roots {
seen.insert(root);
visiting.push_back(root);
}
for (_, dep) in graph.imports.values().flat_map(|i| &i.dependencies) {
let mut resolutions = Vec::with_capacity(2);
resolutions.push(&dep.maybe_code);
if options.kind.include_types() {
resolutions.push(&dep.maybe_type);
}
#[allow(clippy::manual_flatten)]
for resolution in resolutions {
if let Resolution::Ok(resolved) = resolution {
let specifier = &resolved.specifier;
if seen.insert(specifier) {
visiting.push_front(specifier);
}
}
}
}
Self {
graph,
seen,
visiting,
follow_dynamic: options.follow_dynamic,
kind: options.kind,
check_js: options.check_js,
prefer_fast_check_graph: options.prefer_fast_check_graph,
previous_module: None,
}
}
pub fn skip_previous_dependencies(&mut self) {
self.previous_module = None;
}
pub fn errors(self) -> ModuleGraphErrorIterator<'a, 'options> {
ModuleGraphErrorIterator::new(self)
}
#[allow(clippy::result_large_err)]
pub fn validate(self) -> Result<(), ModuleGraphError> {
match self.errors().next() {
Some(err) => Err(err),
_ => Ok(()),
}
}
fn is_checkable(
&self,
specifier: &ModuleSpecifier,
media_type: MediaType,
) -> bool {
match media_type {
MediaType::TypeScript
| MediaType::Mts
| MediaType::Cts
| MediaType::Dts
| MediaType::Dmts
| MediaType::Dcts
| MediaType::Tsx
| MediaType::Json
| MediaType::Wasm => true,
MediaType::Css
| MediaType::SourceMap
| MediaType::Html
| MediaType::Markdown
| MediaType::Sql
| MediaType::Jsonc
| MediaType::Json5
| MediaType::Unknown => false,
MediaType::JavaScript
| MediaType::Jsx
| MediaType::Mjs
| MediaType::Cjs => self.check_js.resolve(specifier),
}
}
fn analyze_module_deps(
&mut self,
module_deps: &'a IndexMap<String, Dependency>,
) {
for dep in module_deps.values().rev() {
if !dep.is_dynamic || self.follow_dynamic {
let mut resolutions = Vec::with_capacity(2);
resolutions.push(&dep.maybe_code);
if self.kind.include_types() {
resolutions.push(&dep.maybe_type);
}
#[allow(clippy::manual_flatten)]
for resolution in resolutions {
if let Resolution::Ok(resolved) = resolution {
let specifier = &resolved.specifier;
if self.seen.insert(specifier) {
self.visiting.push_front(specifier);
}
}
}
}
}
}
}
impl<'a> Iterator for ModuleEntryIterator<'a, '_> {
type Item = (&'a ModuleSpecifier, ModuleEntryRef<'a>);
fn next(&mut self) -> Option<Self::Item> {
match self.previous_module.take() {
Some(ModuleEntryRef::Module(module)) => {
let check_types = self.kind.include_types()
&& self.is_checkable(module.specifier(), module.media_type());
let module_deps = if check_types && self.prefer_fast_check_graph {
module.dependencies_prefer_fast_check()
} else {
module.dependencies()
};
self.analyze_module_deps(module_deps);
}
Some(ModuleEntryRef::Redirect(specifier)) => {
if self.seen.insert(specifier) {
self.visiting.push_front(specifier);
}
}
Some(ModuleEntryRef::Err(_)) | None => {}
}
let (specifier, module_entry) = loop {
let specifier = self.visiting.pop_front()?;
match self.graph.module_slots.get_key_value(specifier) {
Some((specifier, module_slot)) => {
match module_slot {
ModuleSlot::Pending { .. } => {
}
ModuleSlot::Module(module) => {
if let Module::Js(module) = &module
&& self.kind.include_types()
{
if let Some(Resolution::Ok(resolved)) = module
.maybe_types_dependency
.as_ref()
.map(|d| &d.dependency)
{
let specifier = &resolved.specifier;
if self.seen.insert(specifier) {
self.visiting.push_front(specifier);
}
if self.kind == GraphKind::TypesOnly {
continue; }
} else if self.kind == GraphKind::TypesOnly
&& !self.is_checkable(&module.specifier, module.media_type)
{
continue; }
}
break (specifier, ModuleEntryRef::Module(module));
}
ModuleSlot::Err(err) => {
break (specifier, ModuleEntryRef::Err(err));
}
}
}
None => {
if let Some((specifier, to)) =
self.graph.redirects.get_key_value(specifier)
{
break (specifier, ModuleEntryRef::Redirect(to));
}
}
}
};
self.previous_module = Some(module_entry);
Some((specifier, module_entry))
}
}
pub struct ModuleGraphErrorIterator<'a, 'options> {
iterator: ModuleEntryIterator<'a, 'options>,
next_errors: Vec<ModuleGraphError>,
}
impl<'a, 'options> ModuleGraphErrorIterator<'a, 'options> {
pub fn new(iterator: ModuleEntryIterator<'a, 'options>) -> Self {
Self {
iterator,
next_errors: Default::default(),
}
}
fn check_resolution(
&self,
module: &Module,
kind: ResolutionKind,
specifier_text: &str,
resolution: &Resolution,
is_dynamic: bool,
) -> Option<ModuleGraphError> {
match resolution {
Resolution::Ok(resolved) => {
let referrer_scheme = module.specifier().scheme();
let specifier_scheme = resolved.specifier.scheme();
if referrer_scheme == "https" && specifier_scheme == "http" {
Some(ModuleGraphError::for_resolution_kind(
kind,
ResolutionError::InvalidDowngrade {
specifier: resolved.specifier.clone(),
range: resolved.range.clone(),
},
))
} else if matches!(referrer_scheme, "https" | "http")
&& matches!(specifier_scheme, "file")
&& specifier_text.to_lowercase().starts_with("file://")
{
Some(ModuleGraphError::for_resolution_kind(
kind,
ResolutionError::InvalidLocalImport {
specifier: resolved.specifier.clone(),
range: resolved.range.clone(),
},
))
} else if self.iterator.follow_dynamic {
let resolved_specifier =
self.iterator.graph.resolve(&resolved.specifier);
let module_slot =
self.iterator.graph.module_slots.get(resolved_specifier);
if let Some(ModuleErrorKind::Missing {
specifier,
maybe_referrer,
}) = module_slot.and_then(|m| m.as_err_kind())
{
if is_dynamic {
Some(ModuleGraphError::ModuleError(
ModuleErrorKind::MissingDynamic {
specifier: specifier.clone(),
referrer: resolved.range.clone(),
}
.into_box(),
))
} else {
Some(ModuleGraphError::ModuleError(
ModuleErrorKind::Missing {
specifier: specifier.clone(),
maybe_referrer: maybe_referrer.clone(),
}
.into_box(),
))
}
} else {
None
}
} else {
None
}
}
Resolution::Err(err) => {
Some(ModuleGraphError::for_resolution_kind(kind, *err.clone()))
}
Resolution::None => None,
}
}
}
impl Iterator for ModuleGraphErrorIterator<'_, '_> {
type Item = ModuleGraphError;
fn next(&mut self) -> Option<Self::Item> {
while self.next_errors.is_empty() {
let kind = self.iterator.kind;
let follow_dynamic = self.iterator.follow_dynamic;
let prefer_fast_check_graph = self.iterator.prefer_fast_check_graph;
if let Some((_, module_entry)) = self.iterator.next() {
match module_entry {
ModuleEntryRef::Module(module) => {
if kind.include_types()
&& let Some(dep) = module.maybe_types_dependency().as_ref()
&& let Some(err) = self.check_resolution(
module,
ResolutionKind::Types,
&dep.specifier,
&dep.dependency,
false,
)
{
self.next_errors.push(err);
}
let check_types = kind.include_types()
&& self
.iterator
.is_checkable(module.specifier(), module.media_type());
let module_deps = if check_types && prefer_fast_check_graph {
module.dependencies_prefer_fast_check()
} else {
module.dependencies()
};
for (specifier_text, dep) in module_deps {
if follow_dynamic || !dep.is_dynamic {
if let Some(err) = self.check_resolution(
module,
ResolutionKind::Execution,
specifier_text,
&dep.maybe_code,
dep.is_dynamic,
) {
self.next_errors.push(err);
}
if check_types
&& let Some(err) = self.check_resolution(
module,
ResolutionKind::Types,
specifier_text,
&dep.maybe_type,
dep.is_dynamic,
)
{
self.next_errors.push(err);
}
}
}
}
ModuleEntryRef::Err(error) => {
let should_ignore = follow_dynamic
&& matches!(error.as_kind(), ModuleErrorKind::Missing { .. });
if !should_ignore {
self
.next_errors
.push(ModuleGraphError::ModuleError(error.clone()));
}
}
ModuleEntryRef::Redirect(_) => {
}
}
} else {
break; }
}
self.next_errors.pop()
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ModuleGraph {
#[serde(skip_serializing)]
graph_kind: GraphKind,
pub roots: IndexSet<ModuleSpecifier>,
#[serde(rename = "modules")]
#[serde(serialize_with = "serialize_module_slots")]
pub(crate) module_slots: BTreeMap<ModuleSpecifier, ModuleSlot>,
#[serde(skip_serializing_if = "IndexMap::is_empty")]
#[serde(serialize_with = "serialize_graph_imports")]
pub imports: IndexMap<ModuleSpecifier, GraphImport>,
pub redirects: BTreeMap<ModuleSpecifier, ModuleSpecifier>,
#[serde(skip_serializing)]
pub has_node_specifier: bool,
#[serde(rename = "packages")]
#[serde(skip_serializing_if = "PackageSpecifiers::is_empty")]
pub packages: PackageSpecifiers,
#[serde(skip_serializing)]
pub npm_dep_graph_result: Result<(), Arc<dyn JsErrorClass>>,
}
impl ModuleGraph {
pub fn new(graph_kind: GraphKind) -> Self {
Self {
graph_kind,
roots: Default::default(),
module_slots: Default::default(),
imports: Default::default(),
redirects: Default::default(),
has_node_specifier: false,
packages: Default::default(),
npm_dep_graph_result: Ok(()),
}
}
pub fn graph_kind(&self) -> GraphKind {
self.graph_kind
}
pub fn fill_from_lockfile<
'a,
TRedirectIter: Iterator<Item = (&'a str, &'a str)>,
TPackageSpecifiersIter: Iterator<Item = (&'a JsrDepPackageReq, &'a str)>,
>(
&mut self,
options: FillFromLockfileOptions<'a, TRedirectIter, TPackageSpecifiersIter>,
) {
for (from, to) in options.redirects {
if let Ok(from) = ModuleSpecifier::parse(from)
&& let Ok(to) = ModuleSpecifier::parse(to)
&& !matches!(from.scheme(), "file" | "npm" | "jsr")
{
self.redirects.insert(from, to);
}
}
for (req_dep, value) in options.package_specifiers {
match req_dep.kind {
deno_semver::package::PackageKind::Jsr => {
if let Ok(version) = Version::parse_standard(value) {
self.packages.add_nv(
req_dep.req.clone(),
PackageNv {
name: req_dep.req.name.clone(),
version,
},
);
}
}
deno_semver::package::PackageKind::Npm => {
}
}
}
}
pub async fn build<'a>(
&mut self,
roots: Vec<ModuleSpecifier>,
imports: Vec<ReferrerImports>,
loader: &'a dyn Loader,
options: BuildOptions<'a>,
) {
Builder::new(self, loader, options)
.build(roots, imports)
.await
}
pub async fn reload<'a>(
&mut self,
specifiers: Vec<ModuleSpecifier>,
loader: &'a dyn Loader,
options: BuildOptions<'a>,
) {
Builder::new(self, loader, options).reload(specifiers).await
}
#[cfg(feature = "fast_check")]
pub fn build_fast_check_type_graph(
&mut self,
options: BuildFastCheckTypeGraphOptions,
) {
if !self.graph_kind().include_types() {
return;
}
let mut pending_nvs = self
.packages
.top_level_packages()
.iter()
.cloned()
.collect::<VecDeque<_>>();
if let WorkspaceFastCheckOption::Enabled(workspace_members) =
options.workspace_fast_check
{
pending_nvs.extend(workspace_members.iter().map(|n| n.as_nv()));
}
if pending_nvs.is_empty() {
return;
}
let default_es_parser = crate::ast::CapturingModuleAnalyzer::default();
let root_symbol = crate::symbols::RootSymbol::new(
self,
options.es_parser.unwrap_or(&default_es_parser),
);
let modules = crate::fast_check::build_fast_check_type_graph(
options.fast_check_cache,
options.jsr_url_provider,
self,
&root_symbol,
pending_nvs,
&crate::fast_check::TransformOptions {
workspace_members: match options.workspace_fast_check {
WorkspaceFastCheckOption::Disabled => &[],
WorkspaceFastCheckOption::Enabled(members) => members,
},
should_error_on_first_diagnostic: match options.workspace_fast_check {
WorkspaceFastCheckOption::Disabled => true,
WorkspaceFastCheckOption::Enabled(_) => false,
},
dts: options.fast_check_dts,
},
);
for (specifier, fast_check_module_result) in modules {
let module_slot = self.module_slots.get_mut(&specifier).unwrap();
let module = match module_slot {
ModuleSlot::Module(m) => match m {
Module::Js(m) => m,
_ => continue,
},
ModuleSlot::Err(_) | ModuleSlot::Pending { .. } => continue,
};
module.fast_check = Some(match fast_check_module_result {
Ok(fast_check_module) => {
let mut dependencies: IndexMap<String, Dependency> =
Default::default();
fill_module_dependencies(
GraphKind::TypesOnly,
module.media_type,
match Arc::try_unwrap(fast_check_module.module_info) {
Ok(module_info) => module_info.dependencies,
Err(module_info) => module_info.dependencies.clone(),
},
&module.specifier,
&mut dependencies,
&NullFileSystem,
options.jsr_url_provider,
options.resolver,
);
FastCheckTypeModuleSlot::Module(Box::new(FastCheckTypeModule {
dependencies,
source: fast_check_module.text,
source_map: fast_check_module.source_map,
dts: fast_check_module.dts,
}))
}
Err(diagnostic) => FastCheckTypeModuleSlot::Error(diagnostic),
});
}
}
pub fn segment(&self, roots: &[ModuleSpecifier]) -> Self {
let roots = roots.iter().collect::<IndexSet<_>>();
if roots.iter().all(|r| self.roots.contains(*r)) {
return self.clone();
}
let mut new_graph = ModuleGraph::new(self.graph_kind);
let entries = self.walk(
roots.iter().copied(),
WalkOptions {
follow_dynamic: true,
kind: self.graph_kind,
check_js: CheckJsOption::True,
prefer_fast_check_graph: false,
},
);
for (specifier, module_entry) in entries {
match module_entry {
ModuleEntryRef::Module(module) => {
new_graph
.module_slots
.insert(specifier.clone(), ModuleSlot::Module(module.clone()));
}
ModuleEntryRef::Err(err) => {
new_graph
.module_slots
.insert(specifier.clone(), ModuleSlot::Err(err.clone()));
}
ModuleEntryRef::Redirect(specifier_to) => {
new_graph
.redirects
.insert(specifier.clone(), specifier_to.clone());
}
}
}
new_graph.imports.clone_from(&self.imports);
new_graph.roots = roots.iter().map(|r| (*r).to_owned()).collect();
new_graph.packages.clone_from(&self.packages);
new_graph.has_node_specifier = self.has_node_specifier;
new_graph
}
pub fn prune_types(&mut self) {
if !self.graph_kind.include_types() {
return; }
self.graph_kind = GraphKind::CodeOnly;
let specifiers_count = self.specifiers_count();
let mut seen_pending =
SeenPendingCollection::with_capacity(specifiers_count);
seen_pending.extend(self.roots.iter().cloned());
let mut has_node_specifier = false;
let handle_dependencies =
|seen_pending: &mut SeenPendingCollection<Url>,
dependencies: &mut IndexMap<String, Dependency>| {
for dependency in dependencies.values_mut() {
dependency.maybe_deno_types_specifier = None;
dependency.maybe_type = Resolution::None;
if let Some(url) = dependency.get_code() {
seen_pending.add(url.clone());
}
if let Some(url) = dependency.get_type() {
seen_pending.add(url.clone());
}
}
};
self.imports.clear();
while let Some(specifier) = seen_pending.next_pending() {
let specifier = match self.redirects.get(&specifier) {
Some(redirected_specifier) => {
seen_pending.add(redirected_specifier.clone());
continue;
}
None => specifier,
};
let Some(module) = self.module_slots.get_mut(&specifier) else {
continue;
};
let module = match module {
ModuleSlot::Module(module) => module,
ModuleSlot::Err(_) | ModuleSlot::Pending { .. } => {
continue;
}
};
match module {
Module::Js(js_module) => {
#[cfg(feature = "fast_check")]
{
js_module.fast_check = None;
}
js_module.maybe_types_dependency = None;
handle_dependencies(&mut seen_pending, &mut js_module.dependencies);
}
Module::Wasm(wasm_module) => {
wasm_module.source_dts = Default::default();
handle_dependencies(&mut seen_pending, &mut wasm_module.dependencies);
}
Module::Npm(_) => {}
Module::Node(_) => {
has_node_specifier = true;
}
Module::Json(_) | Module::External(_) => {
}
}
}
self
.module_slots
.retain(|specifier, _| seen_pending.has_seen(specifier));
self
.redirects
.retain(|redirect, _| seen_pending.has_seen(redirect));
self.has_node_specifier = has_node_specifier;
}
pub async fn build_npm_packages<'a>(
&mut self,
npm_root_dir: &ModuleSpecifier,
resolution_kind: ResolutionKind,
loader: &'a dyn Loader,
mut options: BuildOptions<'a>,
) {
let resolver = options.resolver;
let jsr_url_provider = options.jsr_url_provider;
let mut new_specifiers: Vec<NewSpecifier> = Vec::new();
for slot in self.module_slots.values_mut() {
let ModuleSlot::Module(module) = slot else {
continue;
};
match module {
Module::Js(m) => {
if resolution_kind.is_types()
&& let Some(types_dep) = &mut m.maybe_types_dependency
&& let Resolution::Ok(resolved) = &types_dep.dependency
&& resolved.specifier.scheme() == "npm"
{
let range = resolved.range.clone();
types_dep.dependency = resolve(
&types_dep.specifier,
range.clone(),
ResolutionKind::Types,
jsr_url_provider,
resolver,
);
if let Some(s) = types_dep.dependency.maybe_specifier() {
new_specifiers.push(NewSpecifier {
specifier: s.clone(),
maybe_range: Some(range),
is_root: false,
is_asset: false,
in_dynamic_branch: false,
maybe_attribute_type: None,
maybe_source_phase_referrer: None,
});
}
}
resolve_npm_deps(
&mut m.dependencies,
resolution_kind,
jsr_url_provider,
resolver,
&mut new_specifiers,
);
}
Module::Wasm(m) => {
resolve_npm_deps(
&mut m.dependencies,
resolution_kind,
jsr_url_provider,
resolver,
&mut new_specifiers,
);
}
_ => {}
}
}
for graph_import in self.imports.values_mut() {
resolve_npm_deps(
&mut graph_import.dependencies,
resolution_kind,
jsr_url_provider,
resolver,
&mut new_specifiers,
);
}
let base_range = Range {
specifier: npm_root_dir.clone(),
range: PositionRange::zeroed(),
resolution_mode: None,
};
let npm_roots: Vec<_> = self
.roots
.iter()
.filter(|r| r.scheme() == "npm")
.cloned()
.collect();
for old_root in npm_roots {
let new_resolution = resolve(
old_root.as_str(),
base_range.clone(),
resolution_kind,
jsr_url_provider,
resolver,
);
if let Some(new_spec) = new_resolution.maybe_specifier() {
self.roots.swap_remove(&old_root);
self.roots.insert(new_spec.clone());
new_specifiers.push(NewSpecifier {
specifier: new_spec.clone(),
maybe_range: None,
is_root: true,
is_asset: false,
in_dynamic_branch: false,
maybe_attribute_type: None,
maybe_source_phase_referrer: None,
});
}
}
self
.module_slots
.retain(|_, slot| !matches!(slot, ModuleSlot::Module(Module::Npm(_))));
self.redirects.retain(|_, to| to.scheme() != "npm");
options.npm_resolver.take();
let mut builder = Builder::new(self, loader, options);
for new in &new_specifiers {
builder.load(LoadOptionsRef {
specifier: &new.specifier,
maybe_range: new.maybe_range.as_ref(),
maybe_source_phase_referrer: new.maybe_source_phase_referrer.as_ref(),
is_asset: new.is_asset,
in_dynamic_branch: new.in_dynamic_branch,
is_root: new.is_root,
maybe_attribute_type: new.maybe_attribute_type.clone(),
maybe_version_info: None,
});
}
builder.resolve_pending().await;
}
pub fn walk<'a, 'options>(
&'a self,
roots: impl Iterator<Item = &'a ModuleSpecifier>,
options: WalkOptions<'options>,
) -> ModuleEntryIterator<'a, 'options> {
ModuleEntryIterator::new(self, roots, options)
}
pub fn contains(&self, specifier: &ModuleSpecifier) -> bool {
let specifier = self.resolve(specifier);
self
.module_slots
.get(specifier)
.is_some_and(|ms| matches!(ms, ModuleSlot::Module(_)))
}
pub fn module_errors(&self) -> impl Iterator<Item = &ModuleError> {
self.module_slots.values().filter_map(|ms| match ms {
ModuleSlot::Err(err) => Some(err),
ModuleSlot::Module(_) | ModuleSlot::Pending { .. } => None,
})
}
pub fn get(&self, specifier: &ModuleSpecifier) -> Option<&Module> {
let specifier = self.resolve(specifier);
match self.module_slots.get(specifier) {
Some(ModuleSlot::Module(module)) => Some(module),
_ => None,
}
}
pub fn asset_module_urls(&self) -> IndexSet<&Url> {
let mut result = IndexSet::with_capacity(self.module_slots.len());
for module in self.module_slots.values() {
match module {
ModuleSlot::Module(module) => {
for dep in module.dependencies().values() {
let is_asset = dep.imports.iter().all(|i| i.attributes.has_asset());
if is_asset && let Some(url) = dep.get_code() {
let url = self.resolve(url);
result.insert(url);
}
}
}
ModuleSlot::Pending { .. } | ModuleSlot::Err { .. } => {}
}
}
result
}
pub fn modules(&self) -> impl Iterator<Item = &Module> {
self.module_slots.values().filter_map(|ms| match ms {
ModuleSlot::Module(m) => Some(m),
_ => None,
})
}
pub fn resolve<'a>(
&'a self,
specifier: &'a ModuleSpecifier,
) -> &'a ModuleSpecifier {
const MAX_REDIRECTS: usize = 10;
let mut redirected_specifier = specifier;
if let Some(specifier) = self.redirects.get(specifier) {
let mut seen = HashSet::with_capacity(MAX_REDIRECTS);
seen.insert(redirected_specifier);
seen.insert(specifier);
redirected_specifier = specifier;
while let Some(specifier) = self.redirects.get(redirected_specifier) {
if !seen.insert(specifier) {
log::warn!(
"An infinite loop of redirections detected.\n Original specifier: {specifier}"
);
break;
}
redirected_specifier = specifier;
if seen.len() >= MAX_REDIRECTS {
log::warn!(
"An excessive number of redirections detected.\n Original specifier: {specifier}"
);
break;
}
}
}
redirected_specifier
}
pub fn resolve_dependency<'a>(
&'a self,
specifier: &str,
referrer: &ModuleSpecifier,
prefer_types: bool,
) -> Option<&'a ModuleSpecifier> {
let referrer = self.resolve(referrer);
if let Some(ModuleSlot::Module(referring_module)) =
self.module_slots.get(referrer)
{
self.resolve_dependency_from_module(
specifier,
referring_module,
prefer_types,
)
} else if let Some(graph_import) = self.imports.get(referrer) {
let dependency = graph_import.dependencies.get(specifier)?;
self.resolve_dependency_from_dep(dependency, prefer_types)
} else {
None
}
}
pub fn resolve_dependency_from_module<'a>(
&'a self,
specifier: &str,
referring_module: &'a Module,
prefer_types: bool,
) -> Option<&'a ModuleSpecifier> {
match referring_module {
Module::Js(referring_module) => {
let dependency = referring_module.dependencies.get(specifier)?;
self.resolve_dependency_from_dep(dependency, prefer_types)
}
Module::Wasm(referring_module) => {
let dependency = referring_module.dependencies.get(specifier)?;
self.resolve_dependency_from_dep(dependency, prefer_types)
}
Module::Json(_)
| Module::Npm(_)
| Module::Node(_)
| Module::External(_) => None,
}
}
pub fn resolve_dependency_from_dep<'a>(
&'a self,
dependency: &'a Dependency,
prefer_types: bool,
) -> Option<&'a ModuleSpecifier> {
let (maybe_first, maybe_second) = if prefer_types {
(&dependency.maybe_type, &dependency.maybe_code)
} else {
(&dependency.maybe_code, &dependency.maybe_type)
};
let unresolved_specifier = maybe_first
.maybe_specifier()
.or_else(|| maybe_second.maybe_specifier())?;
let resolved_specifier = self.resolve(unresolved_specifier);
match self.module_slots.get(resolved_specifier) {
Some(ModuleSlot::Module(Module::Js(module))) if prefer_types => {
if let Some(Resolution::Ok(resolved)) = module
.maybe_types_dependency
.as_ref()
.map(|d| &d.dependency)
{
let resolved_specifier = self.resolve(&resolved.specifier);
if matches!(
self.module_slots.get(resolved_specifier),
Some(ModuleSlot::Module(_))
) {
return Some(resolved_specifier);
}
}
Some(resolved_specifier)
}
Some(ModuleSlot::Module(_)) => Some(resolved_specifier),
_ => None,
}
}
pub fn specifiers(
&self,
) -> impl Iterator<Item = (&ModuleSpecifier, Result<&Module, &ModuleError>)>
{
self.module_slots.iter().filter_map(to_result).chain(
self.redirects.iter().filter_map(|(specifier, found)| {
let module_slot = self.module_slots.get(found)?;
to_result((specifier, module_slot))
}),
)
}
pub fn try_get(
&self,
specifier: &ModuleSpecifier,
) -> Result<Option<&Module>, &ModuleError> {
let specifier = self.resolve(specifier);
match self.module_slots.get(specifier) {
Some(ModuleSlot::Module(module)) => Ok(Some(module)),
Some(ModuleSlot::Err(err)) => Err(err),
_ => Ok(None),
}
}
pub fn try_get_prefer_types(
&self,
specifier: &ModuleSpecifier,
) -> Result<Option<&Module>, &ModuleError> {
let Some(module) = self.try_get(specifier)? else {
return Ok(None);
};
if let Some(specifier) = module.js().and_then(|m| {
m.maybe_types_dependency
.as_ref()
.and_then(|d| d.dependency.ok())
.map(|r| &r.specifier)
}) {
self.try_get(specifier)
} else {
Ok(Some(module))
}
}
#[allow(clippy::result_large_err)]
pub fn valid(&self) -> Result<(), ModuleGraphError> {
self
.walk(
self.roots.iter(),
WalkOptions {
check_js: CheckJsOption::True,
kind: GraphKind::CodeOnly,
follow_dynamic: false,
prefer_fast_check_graph: false,
},
)
.validate()
}
pub fn specifiers_count(&self) -> usize {
self.module_slots.len() + self.redirects.len() + self.imports.len()
}
}
fn resolve(
specifier_text: &str,
referrer_range: Range,
resolution_kind: ResolutionKind,
jsr_url_provider: &dyn JsrUrlProvider,
maybe_resolver: Option<&dyn Resolver>,
) -> Resolution {
let response = if let Some(resolver) = maybe_resolver {
resolver.resolve(specifier_text, &referrer_range, resolution_kind)
} else {
resolve_import(specifier_text, &referrer_range.specifier)
.map_err(|err| err.into())
};
if resolution_kind.is_types()
&& let Ok(resolved_url) = &response
&& let Some(package_nv) = jsr_url_provider.package_url_to_nv(resolved_url)
&& Some(package_nv)
!= jsr_url_provider.package_url_to_nv(&referrer_range.specifier)
{
return Resolution::Err(Box::new(
ResolutionError::InvalidJsrHttpsTypesImport {
specifier: resolved_url.clone(),
range: referrer_range.clone(),
},
));
}
Resolution::from_resolve_result(response, specifier_text, referrer_range)
}
struct NewSpecifier {
specifier: ModuleSpecifier,
maybe_range: Option<Range>,
is_root: bool,
is_asset: bool,
in_dynamic_branch: bool,
maybe_attribute_type: Option<AttributeTypeWithRange>,
maybe_source_phase_referrer: Option<Range>,
}
fn resolve_npm_deps(
deps: &mut IndexMap<String, Dependency>,
resolution_kind: ResolutionKind,
jsr_url_provider: &dyn JsrUrlProvider,
resolver: Option<&dyn Resolver>,
new_specifiers: &mut Vec<NewSpecifier>,
) {
for (specifier_text, dep) in deps.iter_mut() {
if !resolution_kind.is_types()
&& let Resolution::Ok(resolved) = &dep.maybe_code
&& resolved.specifier.scheme() == "npm"
{
let range = resolved.range.clone();
dep.maybe_code = resolve(
specifier_text,
range.clone(),
ResolutionKind::Execution,
jsr_url_provider,
resolver,
);
if let Some(s) = dep.maybe_code.maybe_specifier() {
let is_asset = dep.imports.iter().all(|i| i.attributes.has_asset());
let maybe_attribute_type =
dep.maybe_attribute_type.as_ref().map(|kind| {
AttributeTypeWithRange {
range: range.clone(),
kind: kind.clone(),
}
});
let maybe_source_phase_referrer = dep.imports.iter().find_map(|i| {
i.kind.is_source_phase().then(|| i.specifier_range.clone())
});
new_specifiers.push(NewSpecifier {
specifier: s.clone(),
maybe_range: Some(range),
is_root: false,
is_asset,
in_dynamic_branch: dep.is_dynamic,
maybe_attribute_type,
maybe_source_phase_referrer,
});
}
}
if resolution_kind.is_types() {
let maybe_range = if let Resolution::Ok(resolved) = &dep.maybe_type
&& resolved.specifier.scheme() == "npm"
{
Some(resolved.range.clone())
} else if let Resolution::Ok(resolved) = &dep.maybe_code
&& resolved.specifier.scheme() == "npm"
{
Some(resolved.range.clone())
} else {
None
};
if let Some(range) = maybe_range {
let text = dep
.maybe_deno_types_specifier
.as_deref()
.unwrap_or(specifier_text);
dep.maybe_type = resolve(
text,
range.clone(),
ResolutionKind::Types,
jsr_url_provider,
resolver,
);
if let Some(s) = dep.maybe_type.maybe_specifier() {
let is_asset = dep.imports.iter().all(|i| i.attributes.has_asset());
let maybe_attribute_type =
dep.maybe_attribute_type.as_ref().map(|kind| {
AttributeTypeWithRange {
range: range.clone(),
kind: kind.clone(),
}
});
let maybe_source_phase_referrer = dep.imports.iter().find_map(|i| {
i.kind.is_source_phase().then(|| i.specifier_range.clone())
});
new_specifiers.push(NewSpecifier {
specifier: s.clone(),
maybe_range: Some(range),
is_root: false,
is_asset,
in_dynamic_branch: dep.is_dynamic,
maybe_attribute_type,
maybe_source_phase_referrer,
});
}
}
}
}
}
fn serialize_module_slots<S>(
module_slots: &BTreeMap<ModuleSpecifier, ModuleSlot>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(module_slots.len()))?;
for (specifier, slot) in module_slots.iter() {
match slot {
ModuleSlot::Module(module) => seq.serialize_element(module)?,
ModuleSlot::Err(err) => seq.serialize_element(&serde_json::json!({
"specifier": specifier,
"error": err.to_string(),
}))?,
ModuleSlot::Pending { .. } => {
seq.serialize_element(&serde_json::json!({
"specifier": specifier,
"error": "[INTERNAL ERROR] A pending module load never completed.",
}))?
}
};
}
seq.end()
}
fn serialize_graph_imports<S>(
graph_imports: &IndexMap<ModuleSpecifier, GraphImport>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
struct GraphImportWithReferrer<'a> {
referrer: &'a ModuleSpecifier,
#[serde(flatten)]
graph_import: &'a GraphImport,
}
let mut seq = serializer.serialize_seq(Some(graph_imports.len()))?;
for (referrer, graph_import) in graph_imports {
seq.serialize_element(&GraphImportWithReferrer {
referrer,
graph_import,
})?
}
seq.end()
}
#[derive(Clone)]
pub(crate) enum ModuleSourceAndInfo {
Json {
specifier: ModuleSpecifier,
mtime: Option<SystemTime>,
source: ModuleTextSource,
},
Js {
specifier: ModuleSpecifier,
media_type: MediaType,
maybe_headers: Option<HashMap<String, String>>,
module_info: Box<ModuleInfo>,
mtime: Option<SystemTime>,
source: ModuleTextSource,
},
Wasm {
specifier: ModuleSpecifier,
module_info: Box<ModuleInfo>,
mtime: Option<SystemTime>,
source: Arc<[u8]>,
source_dts: Arc<str>,
},
}
impl ModuleSourceAndInfo {
pub fn specifier(&self) -> &ModuleSpecifier {
match self {
Self::Json { specifier, .. } => specifier,
Self::Js { specifier, .. } => specifier,
Self::Wasm { specifier, .. } => specifier,
}
}
pub fn media_type(&self) -> MediaType {
match self {
Self::Json { .. } => MediaType::Json,
Self::Js { media_type, .. } => *media_type,
Self::Wasm { .. } => MediaType::Wasm,
}
}
pub fn source_bytes(&self) -> &[u8] {
match self {
Self::Json { source, .. } => source.text.as_bytes(),
Self::Js { source, .. } => source.text.as_bytes(),
Self::Wasm { source, .. } => source,
}
}
}
pub(crate) struct ParseModuleAndSourceInfoOptions<'a> {
pub specifier: ModuleSpecifier,
pub maybe_headers: Option<HashMap<String, String>>,
pub mtime: Option<SystemTime>,
pub content: Arc<[u8]>,
pub maybe_attribute_type: Option<&'a AttributeTypeWithRange>,
pub maybe_referrer: Option<&'a Range>,
pub maybe_source_phase_referrer: Option<&'a Range>,
pub is_root: bool,
pub is_dynamic_branch: bool,
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::result_large_err)]
pub(crate) async fn parse_module_source_and_info(
module_analyzer: &dyn ModuleAnalyzer,
opts: ParseModuleAndSourceInfoOptions<'_>,
) -> Result<ModuleSourceAndInfo, ModuleError> {
let (mut media_type, maybe_charset) =
resolve_media_type_and_charset_from_headers(
&opts.specifier,
opts.maybe_headers.as_ref(),
);
if opts.is_root && media_type == MediaType::Unknown {
media_type = MediaType::JavaScript;
}
if let Some(source_phase_referrer) = opts.maybe_source_phase_referrer
&& !(media_type == MediaType::Wasm && opts.maybe_attribute_type.is_none())
{
return Err(
ModuleErrorKind::UnsupportedModuleTypeForSourcePhaseImport {
specifier: opts.specifier,
referrer: source_phase_referrer.clone(),
actual_media_type: media_type,
actual_attribute_type: opts
.maybe_attribute_type
.map(|t| t.kind.clone()),
}
.into_box(),
);
}
if let Some(attribute_type) = opts.maybe_attribute_type
&& !matches!(attribute_type.kind.as_str(), "json" | "text" | "bytes")
{
return Err(
ModuleErrorKind::UnsupportedImportAttributeType {
specifier: opts.specifier,
referrer: attribute_type.range.clone(),
kind: attribute_type.kind.clone(),
}
.into_box(),
);
}
if media_type == MediaType::Json
&& (opts.is_root
|| opts.is_dynamic_branch
|| matches!(
opts.maybe_attribute_type.map(|t| t.kind.as_str()),
Some("json")
))
{
return new_source_with_text(
&opts.specifier,
opts.content,
maybe_charset,
opts.mtime,
)
.map(|source| ModuleSourceAndInfo::Json {
specifier: opts.specifier,
mtime: opts.mtime,
source,
});
}
if let Some(attribute_type) = opts.maybe_attribute_type
&& attribute_type.kind == "json"
{
return Err(
ModuleErrorKind::InvalidTypeAssertion {
specifier: opts.specifier.clone(),
referrer: attribute_type.range.clone(),
actual_media_type: media_type,
expected_media_type: MediaType::Json,
}
.into_box(),
);
}
if matches!(media_type, MediaType::Cjs | MediaType::Cts)
&& opts.specifier.scheme() != "file"
{
return Err(
ModuleErrorKind::UnsupportedMediaType {
specifier: opts.specifier,
media_type,
maybe_referrer: opts.maybe_referrer.map(|r| r.to_owned()),
}
.into_box(),
);
}
match media_type {
MediaType::JavaScript
| MediaType::Mjs
| MediaType::Jsx
| MediaType::TypeScript
| MediaType::Mts
| MediaType::Tsx
| MediaType::Cjs
| MediaType::Cts
| MediaType::Dts
| MediaType::Dmts
| MediaType::Dcts => {
let source = new_source_with_text(
&opts.specifier,
opts.content,
maybe_charset,
opts.mtime,
)?;
match module_analyzer
.analyze(&opts.specifier, source.text.clone(), media_type)
.await
{
Ok(module_info) => {
Ok(ModuleSourceAndInfo::Js {
specifier: opts.specifier,
media_type,
mtime: opts.mtime,
source,
maybe_headers: opts.maybe_headers,
module_info: Box::new(module_info),
})
}
Err(diagnostic) => Err(
ModuleErrorKind::Parse {
specifier: opts.specifier,
mtime: opts.mtime,
diagnostic: Arc::new(diagnostic),
}
.into_box(),
),
}
}
MediaType::Wasm => {
let source_dts_result = wasm_module_to_dts(&opts.content);
match source_dts_result {
Ok(source_dts) => {
let source_dts: Arc<str> = source_dts.into();
match module_analyzer
.analyze(&opts.specifier, source_dts.clone(), MediaType::Dmts)
.await
{
Ok(module_info) => {
Ok(ModuleSourceAndInfo::Wasm {
specifier: opts.specifier,
module_info: Box::new(module_info),
mtime: opts.mtime,
source: opts.content,
source_dts,
})
}
Err(diagnostic) => Err(
ModuleErrorKind::Parse {
specifier: opts.specifier,
mtime: opts.mtime,
diagnostic: Arc::new(diagnostic),
}
.into_box(),
),
}
}
Err(err) => Err(
ModuleErrorKind::WasmParse {
specifier: opts.specifier,
mtime: opts.mtime,
err,
}
.into_box(),
),
}
}
MediaType::Css
| MediaType::Json
| MediaType::SourceMap
| MediaType::Html
| MediaType::Sql
| MediaType::Jsonc
| MediaType::Json5
| MediaType::Markdown
| MediaType::Unknown => Err(
ModuleErrorKind::UnsupportedMediaType {
specifier: opts.specifier,
media_type,
maybe_referrer: opts.maybe_referrer.map(|r| r.to_owned()),
}
.into_box(),
),
}
}
pub(crate) struct ParseModuleOptions {
pub graph_kind: GraphKind,
pub module_source_and_info: ModuleSourceAndInfo,
}
#[allow(clippy::result_large_err)]
pub(crate) fn parse_module(
file_system: &FileSystem,
jsr_url_provider: &dyn JsrUrlProvider,
maybe_resolver: Option<&dyn Resolver>,
options: ParseModuleOptions,
) -> Module {
match options.module_source_and_info {
ModuleSourceAndInfo::Json {
specifier,
mtime,
source,
} => Module::Json(JsonModule {
maybe_cache_info: None,
source,
mtime,
media_type: MediaType::Json,
specifier,
}),
ModuleSourceAndInfo::Js {
specifier,
media_type,
mtime,
maybe_headers,
module_info,
source,
} => Module::Js(parse_js_module_from_module_info(
options.graph_kind,
specifier,
media_type,
maybe_headers.as_ref(),
*module_info,
mtime,
source,
file_system,
jsr_url_provider,
maybe_resolver,
)),
ModuleSourceAndInfo::Wasm {
specifier,
mtime,
source,
source_dts,
module_info,
} => Module::Wasm(parse_wasm_module_from_module_info(
options.graph_kind,
specifier,
*module_info,
mtime,
source,
source_dts,
file_system,
jsr_url_provider,
maybe_resolver,
)),
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn parse_js_module_from_module_info(
graph_kind: GraphKind,
specifier: ModuleSpecifier,
media_type: MediaType,
maybe_headers: Option<&HashMap<String, String>>,
module_info: ModuleInfo,
mtime: Option<SystemTime>,
source: ModuleTextSource,
file_system: &FileSystem,
jsr_url_provider: &dyn JsrUrlProvider,
maybe_resolver: Option<&dyn Resolver>,
) -> JsModule {
let mut module = JsModule {
is_script: module_info.is_script,
dependencies: Default::default(),
maybe_cache_info: None,
mtime,
source,
maybe_types_dependency: None,
media_type,
specifier,
maybe_source_map_dependency: None,
#[cfg(feature = "fast_check")]
fast_check: None,
};
if let Some(source_map_specifier) = module_info.source_map_url.as_ref() {
let range = Range {
specifier: module.specifier.clone(),
range: source_map_specifier.range,
resolution_mode: None,
};
module.maybe_source_map_dependency = Some(TypesDependency {
specifier: source_map_specifier.text.clone(),
dependency: resolve(
&source_map_specifier.text,
range.clone(),
ResolutionKind::Execution,
jsr_url_provider,
maybe_resolver,
),
});
}
if graph_kind.include_types() {
if let Some(specifier) = module_info.self_types_specifier.as_ref() {
let range = Range {
specifier: module.specifier.clone(),
range: specifier.range,
resolution_mode: None,
};
module.maybe_types_dependency = Some(TypesDependency {
specifier: specifier.text.clone(),
dependency: resolve(
&specifier.text,
range.clone(),
ResolutionKind::Types,
jsr_url_provider,
maybe_resolver,
),
});
}
for reference in module_info.ts_references {
match reference {
TypeScriptReference::Path(specifier) => {
let dep = module
.dependencies
.entry(specifier.text.clone())
.or_default();
let range = Range {
specifier: module.specifier.clone(),
range: specifier.range,
resolution_mode: None,
};
if dep.maybe_type.is_none() {
dep.maybe_type = resolve(
&specifier.text,
range.clone(),
ResolutionKind::Types,
jsr_url_provider,
maybe_resolver,
);
}
dep.imports.push(Import {
specifier: specifier.text,
kind: ImportKind::TsReferencePath,
specifier_range: range,
is_dynamic: false,
attributes: Default::default(),
is_side_effect: false,
});
}
TypeScriptReference::Types {
specifier,
resolution_mode: mode,
} => {
let is_untyped = !module.media_type.is_typed();
if is_untyped && module.maybe_types_dependency.is_some() {
continue; }
let range = Range {
specifier: module.specifier.clone(),
range: specifier.range,
resolution_mode: mode.map(|mode| mode.as_deno_graph()),
};
let dep_resolution = resolve(
&specifier.text,
range.clone(),
ResolutionKind::Types,
jsr_url_provider,
maybe_resolver,
);
if is_untyped {
module.maybe_types_dependency = Some(TypesDependency {
specifier: specifier.text.clone(),
dependency: dep_resolution,
});
} else {
let dep = module
.dependencies
.entry(specifier.text.clone())
.or_default();
if dep.maybe_type.is_none() {
dep.maybe_type = dep_resolution;
}
dep.imports.push(Import {
specifier: specifier.text,
kind: ImportKind::TsReferenceTypes,
specifier_range: range,
is_dynamic: false,
attributes: Default::default(),
is_side_effect: false,
});
}
}
}
}
}
if media_type.is_jsx() {
let has_jsx_import_source_pragma = module_info.jsx_import_source.is_some();
let res = module_info.jsx_import_source.or_else(|| {
maybe_resolver.and_then(|r| {
r.default_jsx_import_source(&module.specifier)
.map(|import_source| SpecifierWithRange {
text: import_source,
range: PositionRange {
start: Position::zeroed(),
end: Position::zeroed(),
},
})
})
});
if let Some(import_source) = res {
let jsx_import_source_module = maybe_resolver
.map(|r| r.jsx_import_source_module(&module.specifier))
.unwrap_or(DEFAULT_JSX_IMPORT_SOURCE_MODULE);
let specifier_text =
format!("{}/{}", import_source.text, jsx_import_source_module);
let dep = module
.dependencies
.entry(specifier_text.clone())
.or_default();
let range = Range {
specifier: module.specifier.clone(),
range: import_source.range,
resolution_mode: None,
};
if dep.maybe_code.is_none() {
dep.maybe_code = resolve(
&specifier_text,
range.clone(),
ResolutionKind::Execution,
jsr_url_provider,
maybe_resolver,
);
}
if graph_kind.include_types() && dep.maybe_type.is_none() {
let mut types_res = module_info.jsx_import_source_types;
if types_res.is_none() && !has_jsx_import_source_pragma {
types_res = maybe_resolver.and_then(|r| {
r.default_jsx_import_source_types(&module.specifier).map(
|import_source| SpecifierWithRange {
text: import_source,
range: PositionRange {
start: Position::zeroed(),
end: Position::zeroed(),
},
},
)
});
}
if let Some(import_source_types) = types_res {
let specifier_text = format!(
"{}/{}",
import_source_types.text, jsx_import_source_module
);
let range = Range {
specifier: module.specifier.clone(),
range: import_source_types.range,
resolution_mode: None,
};
dep.maybe_type = resolve(
&specifier_text,
range,
ResolutionKind::Types,
jsr_url_provider,
maybe_resolver,
);
dep.maybe_deno_types_specifier = Some(specifier_text);
} else {
let types_resolution = resolve(
&specifier_text,
range.clone(),
ResolutionKind::Types,
jsr_url_provider,
maybe_resolver,
);
if types_resolution.maybe_specifier()
!= dep.maybe_code.maybe_specifier()
{
dep.maybe_type = types_resolution;
}
}
}
dep.imports.push(Import {
specifier: specifier_text,
kind: ImportKind::JsxImportSource,
specifier_range: range,
is_dynamic: false,
attributes: Default::default(),
is_side_effect: false,
});
}
}
if graph_kind.include_types() {
for jsdoc_import in module_info.jsdoc_imports {
let specifier = jsdoc_import.specifier;
let dep = module
.dependencies
.entry(specifier.text.clone())
.or_default();
let specifier_range = Range {
specifier: module.specifier.clone(),
range: specifier.range,
resolution_mode: jsdoc_import
.resolution_mode
.map(|mode| mode.as_deno_graph()),
};
if dep.maybe_type.is_none() {
dep.maybe_type = resolve(
&specifier.text,
specifier_range.clone(),
ResolutionKind::Types,
jsr_url_provider,
maybe_resolver,
);
}
dep.imports.push(Import {
specifier: specifier.text,
kind: ImportKind::JsDoc,
specifier_range,
is_dynamic: false,
attributes: Default::default(),
is_side_effect: false,
});
}
}
if graph_kind.include_types()
&& module.maybe_types_dependency.is_none()
&& let Some(headers) = maybe_headers
&& let Some(types_header) = headers.get("x-typescript-types")
{
let range = Range {
specifier: module.specifier.clone(),
range: PositionRange::zeroed(),
resolution_mode: None,
};
module.maybe_types_dependency = Some(TypesDependency {
specifier: types_header.to_string(),
dependency: resolve(
types_header,
range,
ResolutionKind::Types,
jsr_url_provider,
maybe_resolver,
),
});
}
if let Some(resolver) = maybe_resolver {
if graph_kind.include_types()
&& module.maybe_types_dependency.is_none()
&& !module.media_type.is_typed()
{
module.maybe_types_dependency =
match resolver.resolve_types(&module.specifier) {
Ok(Some((specifier, maybe_range))) => {
let specifier_text = module.specifier.to_string();
Some(TypesDependency {
specifier: specifier_text,
dependency: Resolution::Ok(Box::new(ResolutionResolved {
specifier: specifier.clone(),
range: maybe_range.unwrap_or_else(|| Range {
specifier,
range: PositionRange::zeroed(),
resolution_mode: None,
}),
})),
})
}
Ok(None) => None,
Err(err) => Some(TypesDependency {
specifier: module.specifier.to_string(),
dependency: Resolution::Err(Box::new(
ResolutionError::ResolverError {
error: Arc::new(err),
specifier: module.specifier.to_string(),
range: Range {
specifier: module.specifier.clone(),
range: PositionRange::zeroed(),
resolution_mode: None,
},
},
)),
}),
};
}
}
fill_module_dependencies(
graph_kind,
module.media_type,
module_info.dependencies,
&module.specifier,
&mut module.dependencies,
file_system,
jsr_url_provider,
maybe_resolver,
);
module
}
#[allow(clippy::too_many_arguments)]
fn parse_wasm_module_from_module_info(
graph_kind: GraphKind,
specifier: Url,
module_info: ModuleInfo,
mtime: Option<SystemTime>,
source: Arc<[u8]>,
source_dts: Arc<str>,
file_system: &FileSystem,
jsr_url_provider: &dyn JsrUrlProvider,
maybe_resolver: Option<&dyn Resolver>,
) -> WasmModule {
let mut module = WasmModule {
specifier,
dependencies: Default::default(),
mtime,
source,
source_dts,
maybe_cache_info: None,
};
fill_module_dependencies(
graph_kind,
MediaType::Wasm,
module_info.dependencies,
&module.specifier,
&mut module.dependencies,
file_system,
jsr_url_provider,
maybe_resolver,
);
module
}
#[allow(clippy::too_many_arguments)]
fn fill_module_dependencies(
graph_kind: GraphKind,
media_type: MediaType,
dependencies: Vec<DependencyDescriptor>,
module_specifier: &ModuleSpecifier,
module_dependencies: &mut IndexMap<String, Dependency>,
file_system: &FileSystem,
jsr_url_provider: &dyn JsrUrlProvider,
maybe_resolver: Option<&dyn Resolver>,
) {
for desc in dependencies {
let (imports, types_specifier) = match desc {
DependencyDescriptor::Static(desc)
if desc.kind == StaticDependencyKind::MaybeTsModuleAugmentation =>
{
if !graph_kind.include_types() {
continue;
}
(
vec![Import {
specifier: desc.specifier,
kind: ImportKind::TsModuleAugmentation,
specifier_range: Range {
specifier: module_specifier.clone(),
range: desc.specifier_range,
resolution_mode: Some(ResolutionMode::Import),
},
is_dynamic: false,
attributes: desc.import_attributes,
is_side_effect: desc.is_side_effect,
}],
desc.types_specifier,
)
}
DependencyDescriptor::Static(desc) => {
let kind = match desc.kind {
StaticDependencyKind::ImportType
| StaticDependencyKind::ExportType => {
if !graph_kind.include_types() {
continue;
}
ImportKind::TsType
}
StaticDependencyKind::ImportSource => ImportKind::EsSource,
StaticDependencyKind::Import
| StaticDependencyKind::ImportDefer
| StaticDependencyKind::ImportEquals
| StaticDependencyKind::Export
| StaticDependencyKind::ExportEquals => ImportKind::Es,
StaticDependencyKind::MaybeTsModuleAugmentation => unreachable!(),
};
let is_types =
kind == ImportKind::TsType || media_type.is_declaration();
let specifier_range = Range {
specifier: module_specifier.clone(),
range: desc.specifier_range,
resolution_mode: match desc.kind {
StaticDependencyKind::Import
| StaticDependencyKind::ImportDefer
| StaticDependencyKind::ImportSource
| StaticDependencyKind::Export
| StaticDependencyKind::ImportType
| StaticDependencyKind::ExportType => is_types
.then(|| {
desc
.import_attributes
.get("resolution-mode")
.and_then(TypeScriptTypesResolutionMode::from_str)
.map(|m| m.as_deno_graph())
})
.flatten()
.or_else(|| {
if media_type.is_declaration() {
None
} else {
Some(ResolutionMode::Import)
}
}),
StaticDependencyKind::ImportEquals
| StaticDependencyKind::ExportEquals => {
Some(ResolutionMode::Require)
}
StaticDependencyKind::MaybeTsModuleAugmentation => unreachable!(),
},
};
(
vec![Import {
specifier: desc.specifier,
kind,
specifier_range,
is_dynamic: false,
attributes: desc.import_attributes,
is_side_effect: desc.is_side_effect,
}],
desc.types_specifier,
)
}
DependencyDescriptor::Dynamic(desc) => {
let import_attributes = desc.import_attributes;
let specifiers = match desc.argument {
DynamicArgument::String(text) => {
vec![text]
}
DynamicArgument::Template(parts)
if module_specifier.scheme() == "file" =>
{
let mut parts = analyze_dynamic_arg_template_parts(
&parts,
module_specifier,
&desc.argument_range,
&import_attributes,
file_system,
);
parts.sort();
parts
}
_ => continue,
};
let specifier_range = Range {
specifier: module_specifier.clone(),
range: desc.argument_range,
resolution_mode: match desc.kind {
DynamicDependencyKind::Import
| DynamicDependencyKind::ImportDefer
| DynamicDependencyKind::ImportSource => {
if media_type.is_declaration() {
None
} else {
Some(ResolutionMode::Import)
}
}
DynamicDependencyKind::Require => Some(ResolutionMode::Require),
},
};
(
specifiers
.into_iter()
.map(|specifier| Import {
specifier,
kind: match desc.kind {
DynamicDependencyKind::Import
| DynamicDependencyKind::ImportDefer => ImportKind::Es,
DynamicDependencyKind::ImportSource => ImportKind::EsSource,
DynamicDependencyKind::Require => ImportKind::Require,
},
specifier_range: specifier_range.clone(),
is_dynamic: true,
attributes: import_attributes.clone(),
is_side_effect: false,
})
.collect::<Vec<_>>(),
desc.types_specifier,
)
}
};
for import in imports {
let dep = module_dependencies
.entry(import.specifier.clone())
.or_default();
if dep.maybe_attribute_type.is_none() {
dep.maybe_attribute_type =
import.attributes.get("type").map(|s| s.to_string());
}
if let Some(types_specifier) = &types_specifier
&& graph_kind.include_types()
&& dep.maybe_type.is_none()
{
dep.maybe_deno_types_specifier = Some(types_specifier.text.clone());
dep.maybe_type = resolve(
&types_specifier.text,
Range {
specifier: module_specifier.clone(),
range: types_specifier.range,
resolution_mode: import.specifier_range.resolution_mode,
},
ResolutionKind::Types,
jsr_url_provider,
maybe_resolver,
);
}
if matches!(
import.kind,
ImportKind::TsType | ImportKind::TsModuleAugmentation
) {
if dep.maybe_type.is_none() {
dep.maybe_type = resolve(
&import.specifier,
import.specifier_range.clone(),
ResolutionKind::Types,
jsr_url_provider,
maybe_resolver,
);
}
} else if !media_type.is_declaration() {
if dep.maybe_code.is_none() {
dep.maybe_code = resolve(
&import.specifier,
import.specifier_range.clone(),
ResolutionKind::Execution,
jsr_url_provider,
maybe_resolver,
);
dep.is_dynamic = import.is_dynamic;
} else {
dep.is_dynamic = dep.is_dynamic && import.is_dynamic;
}
}
if graph_kind.include_types() && dep.maybe_type.is_none() {
let maybe_type = resolve(
&import.specifier,
import.specifier_range.clone(),
ResolutionKind::Types,
jsr_url_provider,
maybe_resolver,
);
let is_side_effect_import_error =
import.is_side_effect && maybe_type.err().is_some();
if !is_side_effect_import_error
&& maybe_type.maybe_specifier() != dep.maybe_code.maybe_specifier()
{
dep.maybe_type = maybe_type
}
}
dep.imports.push(import);
}
}
if media_type.is_typed() {
module_dependencies.retain(|_, dep| {
if dep.get_type().is_some() {
return true;
}
dep
.imports
.retain(|i| i.kind != ImportKind::TsModuleAugmentation);
!dep.imports.is_empty()
});
}
}
fn analyze_dynamic_arg_template_parts(
parts: &[DynamicTemplatePart],
referrer: &Url,
referrer_range: &PositionRange,
import_attributes: &ImportAttributes,
file_system: &FileSystem,
) -> Vec<String> {
fn resolve_initial_dir_path(
parts: &[DynamicTemplatePart],
referrer: &Url,
) -> Option<ModuleSpecifier> {
match parts.first()? {
DynamicTemplatePart::String { value } => {
if value.starts_with("./") {
referrer.join(value).ok()
} else if value.starts_with("file://") {
ModuleSpecifier::parse(value).ok()
} else {
None
}
}
DynamicTemplatePart::Expr => None,
}
}
fn validate_string_parts(
string_parts: &[&String],
is_last_string: bool,
) -> bool {
fn validate_part(
index: usize,
part: &str,
path_parts: &[&String],
is_last_string: bool,
) -> bool {
!part.contains("/../")
&& if index == 0 {
let valid_start = part.starts_with("./") || part.starts_with('/');
let valid_end = part.ends_with('/');
valid_start && valid_end
} else if is_last_string && index == path_parts.len() - 1 {
part.starts_with('/') || !part.contains('/')
} else {
part.starts_with('/') && part.ends_with('/')
}
}
string_parts.iter().enumerate().all(|(index, part)| {
validate_part(index, part, string_parts, is_last_string)
})
}
let Some(dir_path) = resolve_initial_dir_path(parts, referrer) else {
return Vec::new();
};
let string_parts = parts
.iter()
.enumerate()
.filter_map(|(i, p)| match p {
DynamicTemplatePart::String { value } => {
if i == 0 && value.starts_with("file://") {
None } else {
Some(value)
}
}
DynamicTemplatePart::Expr => None,
})
.collect::<Vec<_>>();
let is_last_string =
matches!(parts.last(), Some(DynamicTemplatePart::String { .. }));
if !validate_string_parts(&string_parts, is_last_string) {
return Vec::new(); }
let matching_media_types = if import_attributes.get("type") == Some("json") {
vec![MediaType::Json]
} else {
vec![
MediaType::JavaScript,
MediaType::TypeScript,
MediaType::Jsx,
MediaType::Tsx,
MediaType::Mjs,
MediaType::Mts,
]
};
let mut specifiers = Vec::new();
if is_fs_root_specifier(&dir_path) {
return specifiers;
}
let Ok(dir_path) = deno_path_util::url_to_file_path(&dir_path) else {
return specifiers;
};
let mut pending_dirs = VecDeque::from([dir_path]);
let handle_err = |path: &Path, err: &std::io::Error| {
if matches!(
err.kind(),
std::io::ErrorKind::PermissionDenied | std::io::ErrorKind::NotFound
) {
return;
}
log::warn!(
"Graph failed resolving '{}'. {:#}\n at {}:{}:{}",
path.display(),
err,
referrer,
referrer_range.start.line + 1,
referrer_range.start.character + 1,
);
};
while let Some(dir_path) = pending_dirs.pop_front() {
let entries = match file_system.fs_read_dir_boxed(&dir_path) {
Ok(entries) => entries,
Err(err) => {
handle_err(&dir_path, &err);
continue;
}
};
for entry in entries {
let entry = match entry {
Ok(entry) => entry,
Err(err) => {
handle_err(&dir_path, &err);
continue;
}
};
let path = entry.path();
match entry.file_type() {
Ok(FileType::File) => {
let Ok(url) = deno_path_util::url_from_file_path(&path) else {
continue;
};
if matching_media_types.contains(&MediaType::from_specifier(&url)) {
if url == *referrer {
continue; }
if let Some(specifier) = referrer.make_relative(&url) {
let specifier = if !specifier.starts_with("../") {
format!("./{}", specifier)
} else {
specifier
};
let mut valid = true;
let mut last_index = 0;
for part in &string_parts {
if let Some(index) = &specifier[last_index..].find(*part) {
last_index += index + part.len();
} else {
valid = false;
break;
}
}
if valid {
if let Some(DynamicTemplatePart::String { value }) =
parts.last()
{
if specifier.ends_with(value) {
specifiers.push(specifier);
}
} else {
specifiers.push(specifier);
}
}
}
}
}
Ok(FileType::Dir) => {
let is_allowed_dir = path
.file_name()
.map(|c| {
!c.to_string_lossy().starts_with('.')
&& c != "node_modules"
&& c != "vendor"
})
.unwrap_or(true);
if is_allowed_dir {
pending_dirs.push_back(path.into_owned());
}
}
Ok(_) => {
}
Err(err) => {
handle_err(&path, &err);
}
};
}
}
specifiers
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GraphKind {
All,
CodeOnly,
TypesOnly,
}
impl Default for GraphKind {
fn default() -> Self {
Self::All
}
}
impl GraphKind {
pub fn include_types(&self) -> bool {
matches!(self, Self::All | Self::TypesOnly)
}
pub fn include_code(&self) -> bool {
matches!(self, Self::All | Self::CodeOnly)
}
}
enum PendingInfoResponse {
External {
specifier: ModuleSpecifier,
is_root: bool,
is_asset: bool,
},
Module {
specifier: ModuleSpecifier,
module_source_and_info: ModuleSourceAndInfo,
pending_load: Option<Box<(LoaderChecksum, ModuleInfo)>>,
is_root: bool,
},
Redirect {
count: usize,
specifier: ModuleSpecifier,
maybe_attribute_type: Option<AttributeTypeWithRange>,
is_asset: bool,
is_dynamic: bool,
is_root: bool,
},
}
impl PendingInfoResponse {
fn specifier(&self) -> &ModuleSpecifier {
match self {
Self::External { specifier, .. } => specifier,
Self::Module {
module_source_and_info,
..
} => module_source_and_info.specifier(),
Self::Redirect { specifier, .. } => specifier,
}
}
}
#[derive(Debug, Clone)]
struct JsrPackageVersionInfoExt {
base_url: Url,
inner: Arc<JsrPackageVersionInfo>,
}
impl JsrPackageVersionInfoExt {
pub fn get_subpath<'a>(&self, specifier: &'a Url) -> Option<&'a str> {
let base_url = self.base_url.as_str();
let base_url = base_url.strip_suffix('/').unwrap_or(base_url);
specifier.as_str().strip_prefix(base_url)
}
pub fn get_checksum(&self, sub_path: &str) -> Result<&str, ModuleLoadError> {
match self.inner.manifest.get(sub_path) {
Some(manifest_entry) => {
match manifest_entry.checksum.strip_prefix("sha256-") {
Some(checksum) => Ok(checksum),
None => Err(ModuleLoadError::Jsr(
JsrLoadError::UnsupportedManifestChecksum,
)),
}
}
None => Ok("package-manifest-missing-checksum"),
}
}
}
enum LoadSpecifierKind {
Jsr(JsrPackageReqReference),
Npm(NpmPackageReqReference),
Node(String),
Url,
}
struct PendingInfo {
requested_specifier: ModuleSpecifier,
maybe_range: Option<Range>,
maybe_source_phase_referrer: Option<Range>,
result: Result<PendingInfoResponse, ModuleError>,
maybe_version_info: Option<JsrPackageVersionInfoExt>,
loaded_package_via_https_url: Option<LoadedJsrPackageViaHttpsUrl>,
}
struct PendingModuleLoadItem {
redirect_count: usize,
requested_specifier: Url,
maybe_attribute_type: Option<AttributeTypeWithRange>,
maybe_range: Option<Range>,
maybe_source_phase_referrer: Option<Range>,
load_specifier: Url,
in_dynamic_branch: bool,
is_asset: bool,
is_root: bool,
maybe_checksum: Option<LoaderChecksum>,
maybe_version_info: Option<JsrPackageVersionInfoExt>,
}
struct LoadedJsrPackageViaHttpsUrl {
nv: PackageNv,
manifest_checksum_for_locker: Option<LoaderChecksum>,
}
type PendingInfoFuture<'a> = LocalBoxFuture<'a, PendingInfo>;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct AttributeTypeWithRange {
range: Range,
kind: String,
}
#[derive(Debug, Default)]
struct PendingNpmState {
requested_registry_info_loads: HashSet<StackString>,
pending_resolutions: Vec<PendingNpmResolutionItem>,
}
#[derive(Debug)]
struct PendingJsrReqResolutionItem {
specifier: ModuleSpecifier,
package_ref: JsrPackageReqReference,
maybe_attribute_type: Option<AttributeTypeWithRange>,
maybe_range: Option<Range>,
in_dynamic_branch: bool,
is_asset: bool,
is_root: bool,
}
#[derive(Debug)]
struct PendingJsrNvResolutionItem {
specifier: ModuleSpecifier,
nv_ref: JsrPackageNvReference,
maybe_attribute_type: Option<AttributeTypeWithRange>,
maybe_range: Option<Range>,
is_asset: bool,
is_dynamic: bool,
is_root: bool,
}
#[derive(Debug)]
struct PendingContentLoadItem {
specifier: ModuleSpecifier,
maybe_range: Option<Range>,
result: LoadResult,
module_info: ModuleInfo,
}
#[derive(Debug, Default)]
struct PendingJsrState {
pending_resolutions: VecDeque<PendingJsrReqResolutionItem>,
pending_content_loads:
FuturesUnordered<LocalBoxFuture<'static, PendingContentLoadItem>>,
metadata: Rc<JsrMetadataStore>,
}
#[derive(Debug)]
struct PendingDynamicBranch {
range: Range,
maybe_source_phase_referrer: Option<Range>,
is_asset: bool,
maybe_attribute_type: Option<AttributeTypeWithRange>,
maybe_version_info: Option<JsrPackageVersionInfoExt>,
}
#[derive(Debug)]
struct DeferredLoad {
maybe_range: Option<Range>,
maybe_source_phase_referrer: Option<Range>,
in_dynamic_branch: bool,
is_root: bool,
maybe_attribute_type: Option<AttributeTypeWithRange>,
maybe_version_info: Option<JsrPackageVersionInfoExt>,
}
struct LoadOptionsRef<'a> {
specifier: &'a ModuleSpecifier,
maybe_range: Option<&'a Range>,
maybe_source_phase_referrer: Option<&'a Range>,
is_asset: bool,
in_dynamic_branch: bool,
is_root: bool,
maybe_attribute_type: Option<AttributeTypeWithRange>,
maybe_version_info: Option<&'a JsrPackageVersionInfoExt>,
}
#[derive(Debug, Default)]
struct PendingState<'a> {
deferred: HashMap<ModuleSpecifier, DeferredLoad>,
pending: FuturesOrdered<PendingInfoFuture<'a>>,
jsr: PendingJsrState,
npm: PendingNpmState,
dynamic_branches: HashMap<ModuleSpecifier, PendingDynamicBranch>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum FillPassMode {
AllowRestart,
NoRestart,
CacheBusting,
}
impl FillPassMode {
fn to_cache_setting(self) -> CacheSetting {
if self == FillPassMode::CacheBusting {
CacheSetting::Reload
} else {
CacheSetting::Use
}
}
}
struct Builder<'a, 'graph> {
in_dynamic_branch: bool,
skip_dynamic_deps: bool,
was_dynamic_root: bool,
unstable_bytes_imports: bool,
unstable_text_imports: bool,
file_system: &'a FileSystem,
jsr_url_provider: &'a dyn JsrUrlProvider,
jsr_version_resolver: Cow<'a, JsrVersionResolver>,
passthrough_jsr_specifiers: bool,
loader: &'a dyn Loader,
locker: Option<&'a mut dyn Locker>,
resolver: Option<&'a dyn Resolver>,
module_analyzer: &'a dyn ModuleAnalyzer,
module_info_cacher: &'a dyn ModuleInfoCacher,
npm_resolver: Option<&'a dyn NpmResolver>,
reporter: Option<&'a dyn Reporter>,
graph: &'graph mut ModuleGraph,
state: PendingState<'a>,
fill_pass_mode: FillPassMode,
executor: &'a dyn Executor,
resolved_roots: BTreeSet<ModuleSpecifier>,
}
impl<'a, 'graph> Builder<'a, 'graph> {
pub fn new(
graph: &'graph mut ModuleGraph,
loader: &'a dyn Loader,
options: BuildOptions<'a>,
) -> Self {
let fill_pass_mode = match graph.roots.is_empty() {
true => FillPassMode::AllowRestart,
false => FillPassMode::NoRestart,
};
Self {
in_dynamic_branch: options.is_dynamic,
skip_dynamic_deps: options.skip_dynamic_deps,
was_dynamic_root: options.is_dynamic,
unstable_bytes_imports: options.unstable_bytes_imports,
unstable_text_imports: options.unstable_text_imports,
file_system: options.file_system,
jsr_url_provider: options.jsr_url_provider,
jsr_version_resolver: options.jsr_version_resolver,
passthrough_jsr_specifiers: options.passthrough_jsr_specifiers,
loader,
locker: options.locker,
resolver: options.resolver,
npm_resolver: options.npm_resolver,
module_analyzer: options.module_analyzer,
module_info_cacher: options.module_info_cacher,
reporter: options.reporter,
graph,
state: PendingState {
jsr: PendingJsrState {
metadata: options
.jsr_metadata_store
.unwrap_or(Rc::new(JsrMetadataStore::default())),
..Default::default()
},
..Default::default()
},
fill_pass_mode,
executor: options.executor,
resolved_roots: Default::default(),
}
}
pub async fn build(
&mut self,
roots: Vec<ModuleSpecifier>,
imports: Vec<ReferrerImports>,
) {
let provided_roots = roots;
let provided_imports = imports;
let roots = provided_roots
.iter()
.filter(|r| !self.graph.roots.contains(*r))
.cloned()
.collect::<Vec<_>>();
let imports = provided_imports
.iter()
.filter(|r| !self.graph.imports.contains_key(&r.referrer))
.cloned()
.collect::<Vec<_>>();
self.graph.roots.extend(roots.clone());
for root in roots {
self.load(LoadOptionsRef {
specifier: &root,
maybe_range: None,
maybe_source_phase_referrer: None,
is_asset: false,
in_dynamic_branch: self.in_dynamic_branch,
is_root: true,
maybe_attribute_type: None,
maybe_version_info: None,
});
}
self.handle_provided_imports(imports);
let should_restart = self.resolve_pending().await;
if should_restart {
self.restart(provided_roots, provided_imports).await
}
}
pub async fn reload(&mut self, specifiers: Vec<ModuleSpecifier>) {
let specifiers = specifiers
.into_iter()
.map(|s| {
let resolved = self.graph.resolve(&s);
if *resolved == s {
s } else {
resolved.clone()
}
})
.collect::<Vec<_>>();
for specifier in &specifiers {
self.graph.module_slots.remove(specifier);
self.load(LoadOptionsRef {
specifier,
maybe_range: None,
maybe_source_phase_referrer: None,
is_asset: false,
in_dynamic_branch: self.in_dynamic_branch,
is_root: true,
maybe_attribute_type: None,
maybe_version_info: None,
});
}
self.resolve_pending().await;
}
async fn resolve_pending(&mut self) -> bool {
while !(self.state.pending.is_empty()
&& self.state.jsr.pending_resolutions.is_empty()
&& self.state.dynamic_branches.is_empty()
&& self.state.deferred.is_empty())
{
let specifier = match self.state.pending.next().await {
Some(PendingInfo {
requested_specifier,
maybe_range,
maybe_source_phase_referrer,
result,
maybe_version_info,
loaded_package_via_https_url,
}) => {
if let Some(pkg) = loaded_package_via_https_url {
if let Some(locker) = &mut self.locker
&& let Some(checksum) = pkg.manifest_checksum_for_locker
{
locker.set_pkg_manifest_checksum(&pkg.nv, checksum);
}
self.graph.packages.ensure_package(pkg.nv);
}
match result {
Ok(response) => {
self.check_specifier(&requested_specifier, response.specifier());
self.visit(
response,
maybe_range.clone(),
maybe_source_phase_referrer.clone(),
maybe_version_info.as_ref(),
);
Some(requested_specifier)
}
Err(err) => {
self.check_specifier(&requested_specifier, err.specifier());
self
.graph
.module_slots
.insert(err.specifier().clone(), ModuleSlot::Err(err));
Some(requested_specifier)
}
}
}
None => None,
};
if let (Some(specifier), Some(reporter)) = (specifier, self.reporter) {
let modules_total = self.graph.module_slots.len();
let modules_done = modules_total - self.state.pending.len();
reporter.on_load(&specifier, modules_done, modules_total);
}
if self.state.pending.is_empty() {
if !self.state.deferred.is_empty() {
let items = std::mem::take(&mut self.state.deferred);
for (specifier, item) in items {
self.load(LoadOptionsRef {
specifier: &specifier,
maybe_range: item.maybe_range.as_ref(),
maybe_source_phase_referrer: item
.maybe_source_phase_referrer
.as_ref(),
is_asset: false, in_dynamic_branch: item.in_dynamic_branch,
is_root: item.is_root,
maybe_attribute_type: item.maybe_attribute_type,
maybe_version_info: item.maybe_version_info.as_ref(),
});
}
} else {
let should_restart = self.resolve_pending_jsr_specifiers().await;
if should_restart {
return true;
}
if self.state.pending.is_empty() {
self.resolve_dynamic_branches();
}
}
}
}
self.handle_jsr_registry_pending_content_loads().await;
self.fill_graph_with_cache_info();
NpmSpecifierResolver::fill_builder(self).await;
false
}
fn handle_provided_imports(&mut self, imports: Vec<ReferrerImports>) {
for referrer_imports in imports {
let referrer = referrer_imports.referrer;
let imports = referrer_imports.imports;
let graph_import = GraphImport::new(
&referrer,
imports,
self.jsr_url_provider,
self.resolver,
);
for dep in graph_import.dependencies.values() {
if let Resolution::Ok(resolved) = &dep.maybe_type {
self.load(LoadOptionsRef {
specifier: &resolved.specifier,
maybe_range: Some(&resolved.range),
maybe_source_phase_referrer: None,
is_asset: false,
in_dynamic_branch: self.in_dynamic_branch,
is_root: self.resolved_roots.contains(&resolved.specifier),
maybe_attribute_type: None,
maybe_version_info: None,
});
}
}
self.graph.imports.insert(referrer, graph_import);
}
}
async fn resolve_pending_jsr_specifiers(&mut self) -> bool {
let mut pending_resolutions =
std::mem::take(&mut self.state.jsr.pending_resolutions);
let mut pending_version_resolutions =
Vec::with_capacity(pending_resolutions.len());
let should_collect_top_level_nvs =
self.graph.packages.top_level_packages().is_empty()
&& self.graph.graph_kind.include_types();
let mut restarted_pkgs = HashSet::new();
while let Some(pending_resolution) = pending_resolutions.pop_front() {
let package_name = &pending_resolution.package_ref.req().name;
let fut = self
.state
.jsr
.metadata
.get_package_metadata(package_name)
.unwrap();
match fut.await {
Ok(info) => {
let package_req = pending_resolution.package_ref.req();
match self.resolve_jsr_nv(package_req, &info) {
Ok(package_nv) => {
self.queue_load_package_version_info(&package_nv);
pending_version_resolutions.push(PendingJsrNvResolutionItem {
specifier: pending_resolution.specifier,
nv_ref: JsrPackageNvReference::new(PackageNvReference {
nv: package_nv,
sub_path: pending_resolution
.package_ref
.into_inner()
.sub_path,
}),
maybe_attribute_type: pending_resolution.maybe_attribute_type,
maybe_range: pending_resolution.maybe_range,
is_asset: pending_resolution.is_asset,
is_dynamic: pending_resolution.in_dynamic_branch,
is_root: pending_resolution.is_root,
});
}
Err(package_req_not_found_err) => {
if self.fill_pass_mode == FillPassMode::AllowRestart {
return true; } else if self.fill_pass_mode != FillPassMode::CacheBusting
&& restarted_pkgs.insert(package_name.clone())
{
self
.state
.jsr
.metadata
.remove_package_metadata(package_name);
self.state.jsr.metadata.queue_load_package_info(
package_name,
CacheSetting::Reload, JsrMetadataStoreServices {
executor: self.executor,
jsr_url_provider: self.jsr_url_provider,
loader: self.loader,
},
);
pending_resolutions.push_front(pending_resolution);
} else {
self.graph.module_slots.insert(
pending_resolution.specifier.clone(),
ModuleSlot::Err(
ModuleErrorKind::Load {
specifier: pending_resolution.specifier.clone(),
maybe_referrer: pending_resolution.maybe_range.clone(),
err: JsrLoadError::PackageReqNotFound(
package_req_not_found_err,
)
.into(),
}
.into_box(),
),
);
}
}
}
}
Err(err) => {
self.graph.module_slots.insert(
pending_resolution.specifier.clone(),
ModuleSlot::Err(
ModuleErrorKind::Load {
specifier: pending_resolution.specifier,
maybe_referrer: pending_resolution.maybe_range,
err: err.into(),
}
.into_box(),
),
);
}
}
}
for resolution_item in pending_version_resolutions {
let nv = resolution_item.nv_ref.nv();
let version_info_result = self
.state
.jsr
.metadata
.get_package_version_metadata(nv)
.unwrap()
.await;
match version_info_result {
Ok(version_info_load_item) => {
let version_info = version_info_load_item.info;
self.graph.packages.ensure_package(nv.clone());
if let Some(locker) = &mut self.locker
&& let Some(checksum) = version_info_load_item.checksum_for_locker
{
locker.set_pkg_manifest_checksum(nv, checksum);
}
let base_url = self.jsr_url_provider.package_url(nv);
let export_name = resolution_item.nv_ref.export_name();
match version_info.export(&export_name) {
Some(export_value) => {
self.graph.packages.add_export(
nv,
(
resolution_item.nv_ref.export_name().into_owned(),
export_value.to_string(),
),
);
if should_collect_top_level_nvs {
self.graph.packages.add_top_level_package(nv.clone());
}
let specifier = base_url.join(export_value).unwrap();
self
.graph
.redirects
.insert(resolution_item.specifier, specifier.clone());
if resolution_item.is_root {
self.resolved_roots.insert(specifier.clone());
}
let version_info = JsrPackageVersionInfoExt {
base_url,
inner: version_info,
};
self.load(LoadOptionsRef {
specifier: &specifier,
maybe_range: resolution_item.maybe_range.as_ref(),
maybe_source_phase_referrer: None,
is_asset: resolution_item.is_asset,
in_dynamic_branch: resolution_item.is_dynamic,
is_root: resolution_item.is_root,
maybe_attribute_type: resolution_item.maybe_attribute_type,
maybe_version_info: Some(&version_info),
});
}
None => {
self.graph.module_slots.insert(
resolution_item.specifier.clone(),
ModuleSlot::Err(
ModuleErrorKind::Load {
specifier: resolution_item.specifier,
maybe_referrer: resolution_item.maybe_range,
err: JsrLoadError::UnknownExport {
export_name: export_name.to_string(),
nv: Box::new(resolution_item.nv_ref.into_inner().nv),
exports: version_info
.exports()
.map(|(k, _)| k.to_string())
.collect::<Vec<_>>(),
}
.into(),
}
.into_box(),
),
);
}
}
}
Err(err) => {
self.graph.module_slots.insert(
resolution_item.specifier.clone(),
ModuleSlot::Err(
ModuleErrorKind::Load {
specifier: resolution_item.specifier,
maybe_referrer: resolution_item.maybe_range,
err: err.into(),
}
.into_box(),
),
);
}
}
}
false }
fn resolve_dynamic_branches(&mut self) {
if !self.in_dynamic_branch {
self.in_dynamic_branch = true;
for (specifier, dynamic_branch) in
std::mem::take(&mut self.state.dynamic_branches)
{
self.load(LoadOptionsRef {
specifier: &specifier,
maybe_range: Some(&dynamic_branch.range),
maybe_source_phase_referrer: dynamic_branch
.maybe_source_phase_referrer
.as_ref(),
is_asset: dynamic_branch.is_asset,
in_dynamic_branch: true,
is_root: self.resolved_roots.contains(&specifier),
maybe_attribute_type: dynamic_branch.maybe_attribute_type,
maybe_version_info: dynamic_branch.maybe_version_info.as_ref(),
});
}
}
}
async fn handle_jsr_registry_pending_content_loads(&mut self) {
while let Some(item) = self.state.jsr.pending_content_loads.next().await {
match item.result {
Ok(Some(response)) => {
match response {
LoadResponse::External { .. } => {
self.graph.module_slots.insert(
item.specifier.clone(),
ModuleSlot::Err(
ModuleErrorKind::Load {
specifier: item.specifier,
maybe_referrer: item.maybe_range,
err: JsrLoadError::ContentLoadExternalSpecifier.into(),
}
.into_box(),
),
);
}
LoadResponse::Module {
content,
specifier,
mtime: _,
maybe_headers: _maybe_headers,
} if specifier == item.specifier => {
let slot = self.graph.module_slots.get_mut(&specifier).unwrap();
match slot {
ModuleSlot::Module(module) => {
match module {
Module::Js(module) => {
self.module_info_cacher.cache_module_info(
&specifier,
module.media_type,
&content,
&item.module_info,
);
match new_source_with_text(
&module.specifier,
content,
None, None, ) {
Ok(source) => {
module.source = source;
}
Err(err) => *slot = ModuleSlot::Err(err),
}
}
Module::Json(module) => {
match new_source_with_text(
&module.specifier,
content,
None, None, ) {
Ok(source) => {
module.source = source;
}
Err(err) => *slot = ModuleSlot::Err(err),
}
}
Module::Wasm(module) => {
match wasm_module_to_dts(&content) {
Ok(source_dts) => {
module.source = content.clone();
module.source_dts = source_dts.into();
}
Err(err) => {
*slot = ModuleSlot::Err(
ModuleErrorKind::WasmParse {
specifier: module.specifier.clone(),
mtime: None,
err,
}
.into_box(),
);
}
}
}
Module::Npm(_) | Module::Node(_) | Module::External(_) => {
unreachable!(); }
}
}
ModuleSlot::Err(_) => {
}
ModuleSlot::Pending { .. } => {
unreachable!(); }
}
}
LoadResponse::Redirect { specifier }
| LoadResponse::Module { specifier, .. } => {
self.graph.module_slots.insert(
item.specifier.clone(),
ModuleSlot::Err(
ModuleErrorKind::Load {
specifier: item.specifier,
maybe_referrer: item.maybe_range,
err: JsrLoadError::RedirectInPackage(specifier).into(),
}
.into_box(),
),
);
}
}
}
Ok(None) => {
self.graph.module_slots.insert(
item.specifier.clone(),
ModuleSlot::Err(
ModuleErrorKind::Missing {
specifier: item.specifier,
maybe_referrer: item.maybe_range,
}
.into_box(),
),
);
}
Err(err) => {
self.graph.module_slots.insert(
item.specifier.clone(),
ModuleSlot::Err(
ModuleErrorKind::Load {
specifier: item.specifier,
maybe_referrer: item.maybe_range,
err: JsrLoadError::ContentLoad(Arc::new(err)).into(),
}
.into_box(),
),
);
}
}
}
}
fn fill_graph_with_cache_info(&mut self) {
if !self.loader.cache_info_enabled() {
return;
}
for slot in self.graph.module_slots.values_mut() {
if let ModuleSlot::Module(module) = slot {
match module {
Module::Json(module) => {
module.maybe_cache_info =
self.loader.get_cache_info(&module.specifier);
}
Module::Js(module) => {
module.maybe_cache_info =
self.loader.get_cache_info(&module.specifier);
}
Module::Wasm(module) => {
module.maybe_cache_info =
self.loader.get_cache_info(&module.specifier);
}
Module::External(module) => {
module.maybe_cache_info =
self.loader.get_cache_info(&module.specifier);
}
Module::Npm(_) | Module::Node(_) => {}
}
}
}
}
fn restart(
&mut self,
roots: Vec<ModuleSpecifier>,
imports: Vec<ReferrerImports>,
) -> LocalBoxFuture<'_, ()> {
*self.graph = ModuleGraph::new(self.graph.graph_kind);
self.state = PendingState::default();
self.fill_pass_mode = FillPassMode::CacheBusting;
async move { self.build(roots, imports).await }.boxed_local()
}
fn resolve_jsr_nv(
&mut self,
package_req: &PackageReq,
package_info: &JsrPackageInfo,
) -> Result<PackageNv, JsrPackageReqNotFoundError> {
let version_resolver = self
.jsr_version_resolver
.get_for_package(&package_req.name, package_info);
let resolved_version = version_resolver.resolve_version(
package_req,
self
.graph
.packages
.versions_by_name(&package_req.name)
.into_iter()
.flatten()
.map(|nv| &nv.version),
)?;
let version = resolved_version.version.clone();
if resolved_version.is_yanked {
self.graph.packages.add_used_yanked_package(PackageNv {
name: package_req.name.clone(),
version: version.clone(),
});
}
let package_nv = PackageNv {
name: package_req.name.clone(),
version,
};
if let Some(reporter) = &self.reporter {
reporter.on_resolve(package_req, &package_nv);
}
self
.graph
.packages
.add_nv(package_req.clone(), package_nv.clone());
Ok(package_nv)
}
fn check_specifier(
&mut self,
requested_specifier: &ModuleSpecifier,
specifier: &ModuleSpecifier,
) {
if requested_specifier != specifier {
self.add_redirect(requested_specifier.clone(), specifier.clone());
}
}
fn add_redirect(
&mut self,
requested_specifier: ModuleSpecifier,
specifier: ModuleSpecifier,
) {
debug_assert_ne!(requested_specifier, specifier);
if let Some(slot) = self.graph.module_slots.get(&requested_specifier)
&& matches!(slot, ModuleSlot::Pending { .. })
{
self.graph.module_slots.remove(&requested_specifier);
}
self
.graph
.redirects
.entry(requested_specifier)
.or_insert(specifier);
}
fn load(&mut self, options: LoadOptionsRef) {
self.load_with_redirect_count(0, options)
}
#[allow(clippy::too_many_arguments)]
fn load_with_redirect_count(
&mut self,
redirect_count: usize,
options: LoadOptionsRef,
) {
let specifier = options.specifier;
let maybe_range = options.maybe_range;
let maybe_source_phase_referrer = options.maybe_source_phase_referrer;
let original_specifier = specifier;
let specifier = self.graph.redirects.get(specifier).unwrap_or(specifier);
if options.is_asset {
let inferred_media_type = MediaType::from_specifier(specifier);
if let Some(source_phase_referrer) = maybe_source_phase_referrer
&& !(inferred_media_type == MediaType::Wasm
&& options.maybe_attribute_type.as_ref().is_none())
{
self.graph.module_slots.insert(
specifier.clone(),
ModuleSlot::Err(
ModuleErrorKind::UnsupportedModuleTypeForSourcePhaseImport {
specifier: specifier.clone(),
referrer: source_phase_referrer.clone(),
actual_media_type: inferred_media_type,
actual_attribute_type: options
.maybe_attribute_type
.as_ref()
.map(|t| t.kind.clone()),
}
.into_box(),
),
);
return;
}
if let Some(attribute) = &options.maybe_attribute_type {
let is_allowed = match attribute.kind.as_str() {
"bytes" => self.unstable_bytes_imports,
"text" => self.unstable_text_imports,
_ => false,
};
if !is_allowed {
self.graph.module_slots.insert(
specifier.clone(),
ModuleSlot::Err(
ModuleErrorKind::UnsupportedImportAttributeType {
specifier: specifier.clone(),
referrer: attribute.range.clone(),
kind: attribute.kind.clone(),
}
.into_box(),
),
);
return;
}
}
}
if let Some(module_slot) = self.graph.module_slots.get(specifier) {
let should_reload_immediately =
module_slot.was_external_asset_load() && !options.is_asset;
if !should_reload_immediately {
let should_defer_reload =
module_slot.is_pending_asset_load() && !options.is_asset;
if should_defer_reload {
self
.state
.deferred
.entry(specifier.clone())
.or_insert_with(|| DeferredLoad {
maybe_range: maybe_range.cloned(),
maybe_source_phase_referrer: maybe_source_phase_referrer.cloned(),
in_dynamic_branch: options.in_dynamic_branch,
is_root: options.is_root,
maybe_attribute_type: options.maybe_attribute_type,
maybe_version_info: options.maybe_version_info.cloned(),
});
}
if matches!(original_specifier.scheme(), "jsr" | "npm")
&& let Ok(load_specifier) =
self.parse_load_specifier_kind(original_specifier, maybe_range)
{
self.maybe_mark_dep(&load_specifier, maybe_range);
}
return;
}
}
if let Some(version_info) = options.maybe_version_info {
let specifier = specifier.clone();
if let Some(sub_path) = version_info.get_subpath(&specifier) {
self.load_jsr_subpath(
redirect_count,
&specifier,
version_info,
sub_path,
options,
);
return;
}
}
let specifier = specifier.clone();
let build_load_url_item = |specifier: Url,
maybe_attribute_type: Option<
AttributeTypeWithRange,
>| PendingModuleLoadItem {
redirect_count,
requested_specifier: specifier.clone(),
maybe_attribute_type,
maybe_range: maybe_range.cloned(),
maybe_source_phase_referrer: maybe_source_phase_referrer.cloned(),
load_specifier: specifier,
is_asset: options.is_asset,
in_dynamic_branch: options.in_dynamic_branch,
is_root: options.is_root,
maybe_checksum: None,
maybe_version_info: None,
};
match self.parse_load_specifier_kind(&specifier, maybe_range) {
Ok(LoadSpecifierKind::Jsr(package_req_ref)) => {
self.mark_jsr_dep(&package_req_ref, maybe_range);
if self.passthrough_jsr_specifiers {
self.graph.module_slots.insert(
specifier.clone(),
ModuleSlot::Module(Module::External(ExternalModule {
specifier,
maybe_cache_info: None,
was_asset_load: false,
})),
);
} else {
self.load_jsr_specifier(
specifier,
package_req_ref,
options.maybe_attribute_type,
maybe_range,
options.is_asset,
options.in_dynamic_branch,
options.is_root,
);
}
}
Ok(LoadSpecifierKind::Npm(package_req_ref)) => {
self.mark_npm_dep(&package_req_ref, maybe_range);
if let Some(npm_resolver) = self.npm_resolver {
self.load_npm_specifier(
npm_resolver,
specifier,
package_req_ref,
maybe_range,
options.in_dynamic_branch,
);
} else {
self.load_pending_module(build_load_url_item(
specifier,
options.maybe_attribute_type,
))
}
}
Ok(LoadSpecifierKind::Node(module_name)) => {
self.graph.has_node_specifier = true;
self.graph.module_slots.insert(
specifier.clone(),
ModuleSlot::Module(Module::Node(BuiltInNodeModule {
specifier,
module_name,
})),
);
}
Ok(LoadSpecifierKind::Url) => self.load_pending_module(
build_load_url_item(specifier, options.maybe_attribute_type),
),
Err(err) => {
self
.graph
.module_slots
.insert(specifier, ModuleSlot::Err(err));
}
}
}
fn load_jsr_subpath(
&mut self,
redirect_count: usize,
specifier: &ModuleSpecifier,
version_info: &JsrPackageVersionInfoExt,
sub_path: &str,
options: LoadOptionsRef,
) {
struct ProvidedModuleAnalyzer(RefCell<Option<ModuleInfo>>);
#[async_trait::async_trait(?Send)]
impl ModuleAnalyzer for ProvidedModuleAnalyzer {
async fn analyze(
&self,
_specifier: &ModuleSpecifier,
_source: Arc<str>,
_media_type: MediaType,
) -> Result<ModuleInfo, JsErrorBox> {
Ok(self.0.borrow_mut().take().unwrap()) }
}
let checksum = match version_info.get_checksum(sub_path) {
Ok(checksum) => checksum,
Err(err) => {
self.graph.module_slots.insert(
specifier.clone(),
ModuleSlot::Err(
ModuleErrorKind::Load {
specifier: specifier.clone(),
maybe_referrer: options.maybe_range.cloned(),
err,
}
.into_box(),
),
);
return;
}
};
let checksum = LoaderChecksum::new(checksum.to_string());
if let Some(module_info) = (!options.is_asset)
.then(|| version_info.inner.module_info(sub_path))
.flatten()
{
let fut = self.loader.load(
specifier,
LoadOptions {
in_dynamic_branch: self.in_dynamic_branch,
was_dynamic_root: self.was_dynamic_root,
cache_setting: CacheSetting::Only,
maybe_checksum: Some(checksum.clone()),
},
);
let is_dynamic_branch = self.in_dynamic_branch;
let module_analyzer = self.module_analyzer;
self.state.pending.push_back({
let requested_specifier = specifier.clone();
let maybe_range = options.maybe_range.cloned();
let maybe_source_phase_referrer =
options.maybe_source_phase_referrer.cloned();
let version_info = version_info.clone();
async move {
let response = fut.await;
let result = match response {
Ok(None) => {
parse_module_source_and_info(
&ProvidedModuleAnalyzer(RefCell::new(Some(
module_info.clone(),
))),
ParseModuleAndSourceInfoOptions {
specifier: requested_specifier.clone(),
maybe_headers: Default::default(),
mtime: None, content: if MediaType::from_specifier(&requested_specifier)
== MediaType::Wasm
{
Arc::new([
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x0A,
0x01, 0x00,
])
} else {
Arc::new([]) as Arc<[u8]>
},
maybe_attribute_type: options.maybe_attribute_type.as_ref(),
maybe_referrer: maybe_range.as_ref(),
maybe_source_phase_referrer: maybe_source_phase_referrer
.as_ref(),
is_root: options.is_root,
is_dynamic_branch,
},
)
.await
.map(|module_source_and_info| {
PendingInfoResponse::Module {
specifier: requested_specifier.clone(),
module_source_and_info,
pending_load: Some(Box::new((checksum, module_info))),
is_root: options.is_root,
}
})
}
Ok(Some(response)) => match response {
LoadResponse::External { specifier } => {
Ok(PendingInfoResponse::External {
specifier,
is_root: options.is_root,
is_asset: false,
})
}
LoadResponse::Redirect { specifier } => Err(
ModuleErrorKind::Load {
specifier: requested_specifier.clone(),
maybe_referrer: maybe_range.clone(),
err: JsrLoadError::RedirectInPackage(specifier).into(),
}
.into_box(),
),
LoadResponse::Module {
content,
specifier,
mtime,
maybe_headers,
} => parse_module_source_and_info(
module_analyzer,
ParseModuleAndSourceInfoOptions {
specifier: specifier.clone(),
maybe_headers,
mtime,
content,
maybe_attribute_type: options.maybe_attribute_type.as_ref(),
maybe_referrer: maybe_range.as_ref(),
maybe_source_phase_referrer: maybe_source_phase_referrer
.as_ref(),
is_root: options.is_root,
is_dynamic_branch,
},
)
.await
.map(|module_source_and_info| {
PendingInfoResponse::Module {
specifier: specifier.clone(),
module_source_and_info,
pending_load: None,
is_root: options.is_root,
}
}),
},
Err(err) => Err(
ModuleErrorKind::Load {
specifier: requested_specifier.clone(),
maybe_referrer: maybe_range.clone(),
err: ModuleLoadError::Loader(Arc::new(err)),
}
.into_box(),
),
};
PendingInfo {
requested_specifier,
maybe_range,
maybe_source_phase_referrer,
result,
maybe_version_info: Some(version_info),
loaded_package_via_https_url: None,
}
}
.boxed_local()
});
self
.graph
.module_slots
.insert(specifier.clone(), ModuleSlot::Pending { is_asset: false });
} else {
self.load_pending_module(PendingModuleLoadItem {
redirect_count,
requested_specifier: specifier.clone(),
maybe_attribute_type: options.maybe_attribute_type,
maybe_range: options.maybe_range.cloned(),
maybe_source_phase_referrer: options
.maybe_source_phase_referrer
.cloned(),
load_specifier: specifier.clone(),
is_asset: options.is_asset,
in_dynamic_branch: options.in_dynamic_branch,
is_root: options.is_root,
maybe_checksum: Some(checksum),
maybe_version_info: Some(version_info.clone()),
});
}
}
#[allow(clippy::too_many_arguments)]
fn load_jsr_specifier(
&mut self,
specifier: Url,
package_ref: JsrPackageReqReference,
maybe_attribute_type: Option<AttributeTypeWithRange>,
maybe_range: Option<&Range>,
is_asset: bool,
in_dynamic_branch: bool,
is_root: bool,
) {
let package_name = &package_ref.req().name;
let specifier = specifier.clone();
self.queue_load_package_info(package_name);
self
.state
.jsr
.pending_resolutions
.push_back(PendingJsrReqResolutionItem {
specifier,
package_ref,
maybe_attribute_type,
maybe_range: maybe_range.cloned(),
in_dynamic_branch,
is_asset,
is_root,
});
}
fn load_npm_specifier(
&mut self,
npm_resolver: &dyn NpmResolver,
specifier: Url,
package_ref: NpmPackageReqReference,
maybe_range: Option<&Range>,
in_dynamic_branch: bool,
) {
if self
.state
.npm
.requested_registry_info_loads
.insert(package_ref.req().name.clone())
{
npm_resolver.load_and_cache_npm_package_info(&package_ref.req().name);
}
self
.state
.npm
.pending_resolutions
.push(PendingNpmResolutionItem {
specifier,
package_ref,
maybe_range: maybe_range.cloned(),
in_dynamic_branch,
});
}
fn parse_load_specifier_kind(
&self,
specifier: &Url,
maybe_range: Option<&Range>,
) -> Result<LoadSpecifierKind, ModuleError> {
match specifier.scheme() {
"jsr" => validate_jsr_specifier(specifier)
.map(LoadSpecifierKind::Jsr)
.map_err(|err| {
ModuleErrorKind::Load {
specifier: specifier.clone(),
maybe_referrer: maybe_range.cloned(),
err: JsrLoadError::PackageFormat(err).into(),
}
.into_box()
}),
"npm" => NpmPackageReqReference::from_specifier(specifier)
.map(LoadSpecifierKind::Npm)
.map_err(|err| {
ModuleErrorKind::Load {
specifier: specifier.clone(),
maybe_referrer: maybe_range.cloned(),
err: NpmLoadError::PackageReqReferenceParse(err).into(),
}
.into_box()
}),
"node" => Ok(LoadSpecifierKind::Node(specifier.path().to_string())),
_ => Ok(LoadSpecifierKind::Url),
}
}
fn maybe_mark_dep(
&mut self,
load_specifier: &LoadSpecifierKind,
maybe_range: Option<&Range>,
) {
match load_specifier {
LoadSpecifierKind::Jsr(package_ref) => {
self.mark_jsr_dep(package_ref, maybe_range);
}
LoadSpecifierKind::Npm(package_ref) => {
self.mark_npm_dep(package_ref, maybe_range);
}
LoadSpecifierKind::Node(_) | LoadSpecifierKind::Url => {
}
}
}
fn mark_jsr_dep(
&mut self,
package_ref: &JsrPackageReqReference,
maybe_range: Option<&Range>,
) {
if let Some(range) = &maybe_range
&& let Some(nv) =
self.jsr_url_provider.package_url_to_nv(&range.specifier)
{
self
.graph
.packages
.add_dependency(&nv, JsrDepPackageReq::jsr(package_ref.req().clone()));
}
}
fn mark_npm_dep(
&mut self,
package_ref: &NpmPackageReqReference,
maybe_range: Option<&Range>,
) {
if let Some(range) = &maybe_range
&& let Some(nv) =
self.jsr_url_provider.package_url_to_nv(&range.specifier)
{
self
.graph
.packages
.add_dependency(&nv, JsrDepPackageReq::npm(package_ref.req().clone()));
}
}
fn load_pending_module(&mut self, item: PendingModuleLoadItem) {
let PendingModuleLoadItem {
redirect_count,
requested_specifier,
maybe_attribute_type,
maybe_range,
maybe_source_phase_referrer,
load_specifier,
in_dynamic_branch,
is_asset,
is_root,
mut maybe_checksum,
mut maybe_version_info,
} = item;
self.graph.module_slots.insert(
requested_specifier.clone(),
ModuleSlot::Pending { is_asset },
);
let loader = self.loader;
let module_analyzer = self.module_analyzer;
let jsr_url_provider = self.jsr_url_provider;
let was_dynamic_root = self.was_dynamic_root;
let maybe_nv_when_no_version_info = if maybe_version_info.is_none() {
self
.jsr_url_provider
.package_url_to_nv(&requested_specifier)
} else {
None
};
let maybe_version_load_fut =
maybe_nv_when_no_version_info.map(|package_nv| {
self.queue_load_package_version_info(&package_nv);
let fut = self
.state
.jsr
.metadata
.get_package_version_metadata(&package_nv)
.unwrap();
(package_nv, fut)
});
if maybe_checksum.is_none() {
maybe_checksum = self
.locker
.as_ref()
.and_then(|l| l.get_remote_checksum(&requested_specifier));
}
let fut = async move {
#[allow(clippy::too_many_arguments)]
async fn try_load(
is_root: bool,
redirect_count: usize,
load_specifier: ModuleSpecifier,
mut maybe_checksum: Option<LoaderChecksum>,
maybe_range: Option<&Range>,
maybe_source_phase_referrer: Option<&Range>,
maybe_version_info: &mut Option<JsrPackageVersionInfoExt>,
maybe_attribute_type: Option<AttributeTypeWithRange>,
loaded_package_via_https_url: &mut Option<LoadedJsrPackageViaHttpsUrl>,
maybe_version_load_fut: Option<(
PackageNv,
PendingResult<PendingJsrPackageVersionInfoLoadItem>,
)>,
is_asset: bool,
in_dynamic_branch: bool,
was_dynamic_root: bool,
loader: &dyn Loader,
jsr_url_provider: &dyn JsrUrlProvider,
module_analyzer: &dyn ModuleAnalyzer,
) -> Result<PendingInfoResponse, ModuleError> {
async fn handle_success(
module_analyzer: &dyn ModuleAnalyzer,
specifier: Url,
options: ParseModuleAndSourceInfoOptions<'_>,
) -> Result<PendingInfoResponse, ModuleError> {
let is_root = options.is_root;
parse_module_source_and_info(module_analyzer, options)
.await
.map(|module_source_and_info| PendingInfoResponse::Module {
specifier: specifier.clone(),
module_source_and_info,
pending_load: None,
is_root,
})
}
if let Some((package_nv, fut)) = maybe_version_load_fut {
let inner = fut.await.map_err(|err| {
ModuleErrorKind::Load {
specifier: jsr_url_provider.package_url(&package_nv),
maybe_referrer: maybe_range.cloned(),
err: err.into(),
}
.into_box()
})?;
let info = JsrPackageVersionInfoExt {
base_url: jsr_url_provider.package_url(&package_nv),
inner: inner.info,
};
if let Some(sub_path) = info.get_subpath(&load_specifier) {
maybe_checksum = Some(LoaderChecksum::new(
info
.get_checksum(sub_path)
.map_err(|err| {
ModuleErrorKind::Load {
specifier: load_specifier.clone(),
maybe_referrer: maybe_range.cloned(),
err,
}
.into_box()
})?
.to_string(),
));
}
maybe_version_info.replace(info);
loaded_package_via_https_url.replace(LoadedJsrPackageViaHttpsUrl {
nv: package_nv,
manifest_checksum_for_locker: inner.checksum_for_locker,
});
}
let load_options = LoadOptions {
in_dynamic_branch,
was_dynamic_root,
cache_setting: CacheSetting::Use,
maybe_checksum: maybe_checksum.clone(),
};
let handle_redirect =
|specifier: Url,
maybe_attribute_type: Option<AttributeTypeWithRange>,
maybe_checksum: Option<LoaderChecksum>| {
if maybe_version_info.is_some() {
Err(
ModuleErrorKind::Load {
specifier: load_specifier.clone(),
maybe_referrer: maybe_range.cloned(),
err: JsrLoadError::RedirectInPackage(specifier.clone())
.into(),
}
.into_box(),
)
} else if let Some(expected_checksum) = maybe_checksum {
Err(
ModuleErrorKind::Load {
specifier: load_specifier.clone(),
maybe_referrer: maybe_range.cloned(),
err: ModuleLoadError::HttpsChecksumIntegrity(
ChecksumIntegrityError {
actual: format!("Redirect to {}", specifier),
expected: expected_checksum.into_string(),
},
),
}
.into_box(),
)
} else if redirect_count >= loader.max_redirects() {
Err(
ModuleErrorKind::Load {
specifier: load_specifier.clone(),
maybe_referrer: maybe_range.cloned(),
err: ModuleLoadError::TooManyRedirects,
}
.into_box(),
)
} else {
Ok(PendingInfoResponse::Redirect {
count: redirect_count + 1,
specifier,
maybe_attribute_type,
is_asset,
is_dynamic: in_dynamic_branch,
is_root,
})
}
};
if is_asset {
let result = loader.ensure_cached(&load_specifier, load_options);
return match result.await {
Ok(Some(CacheResponse::Cached)) => {
Ok(PendingInfoResponse::External {
specifier: load_specifier,
is_root,
is_asset,
})
}
Ok(Some(CacheResponse::Redirect { specifier })) => {
handle_redirect(specifier, maybe_attribute_type, maybe_checksum)
}
Ok(None) => Err(
ModuleErrorKind::Missing {
specifier: load_specifier.clone(),
maybe_referrer: maybe_range.cloned(),
}
.into_box(),
),
Err(LoadError::ChecksumIntegrity(err)) => {
if maybe_version_info.is_none() {
let result = loader
.ensure_cached(
&load_specifier,
LoadOptions {
in_dynamic_branch,
was_dynamic_root,
cache_setting: CacheSetting::Reload,
maybe_checksum: maybe_checksum.clone(),
},
)
.await;
if let Ok(Some(CacheResponse::Cached)) = result {
return Ok(PendingInfoResponse::External {
specifier: load_specifier,
is_root,
is_asset,
});
}
}
Err(
ModuleErrorKind::Load {
specifier: load_specifier.clone(),
maybe_referrer: maybe_range.cloned(),
err: if maybe_version_info.is_some() {
ModuleLoadError::Jsr(
JsrLoadError::ContentChecksumIntegrity(err),
)
} else {
ModuleLoadError::HttpsChecksumIntegrity(err)
},
}
.into_box(),
)
}
Err(err) => Err(
ModuleErrorKind::Load {
specifier: load_specifier.clone(),
maybe_referrer: maybe_range.cloned(),
err: ModuleLoadError::Loader(Arc::new(err)),
}
.into_box(),
),
};
}
let result = loader.load(&load_specifier, load_options);
match result.await {
Ok(Some(response)) => match response {
LoadResponse::Redirect { specifier } => {
handle_redirect(specifier, maybe_attribute_type, maybe_checksum)
}
LoadResponse::External { specifier } => {
Ok(PendingInfoResponse::External {
specifier,
is_root,
is_asset,
})
}
LoadResponse::Module {
content,
mtime,
specifier,
maybe_headers,
} => {
handle_success(
module_analyzer,
specifier.clone(),
ParseModuleAndSourceInfoOptions {
specifier,
maybe_headers,
mtime,
content,
maybe_attribute_type: maybe_attribute_type.as_ref(),
maybe_referrer: maybe_range,
maybe_source_phase_referrer,
is_root,
is_dynamic_branch: in_dynamic_branch,
},
)
.await
}
},
Ok(None) => Err(
ModuleErrorKind::Missing {
specifier: load_specifier.clone(),
maybe_referrer: maybe_range.cloned(),
}
.into_box(),
),
Err(LoadError::ChecksumIntegrity(err)) => {
if maybe_version_info.is_none() {
let result = loader
.load(
&load_specifier,
LoadOptions {
in_dynamic_branch,
was_dynamic_root,
cache_setting: CacheSetting::Reload,
maybe_checksum: maybe_checksum.clone(),
},
)
.await;
if let Ok(Some(LoadResponse::Module {
specifier,
maybe_headers,
mtime,
content,
})) = result
{
return handle_success(
module_analyzer,
specifier.clone(),
ParseModuleAndSourceInfoOptions {
specifier,
maybe_headers,
mtime,
content,
maybe_attribute_type: maybe_attribute_type.as_ref(),
maybe_referrer: maybe_range,
maybe_source_phase_referrer,
is_root,
is_dynamic_branch: in_dynamic_branch,
},
)
.await;
}
}
Err(
ModuleErrorKind::Load {
specifier: load_specifier.clone(),
maybe_referrer: maybe_range.cloned(),
err: if maybe_version_info.is_some() {
ModuleLoadError::Jsr(JsrLoadError::ContentChecksumIntegrity(
err,
))
} else {
ModuleLoadError::HttpsChecksumIntegrity(err)
},
}
.into_box(),
)
}
Err(err) => Err(
ModuleErrorKind::Load {
specifier: load_specifier.clone(),
maybe_referrer: maybe_range.cloned(),
err: ModuleLoadError::Loader(Arc::new(err)),
}
.into_box(),
),
}
}
let mut loaded_package_via_https_url = None;
let result = try_load(
is_root,
redirect_count,
load_specifier,
maybe_checksum,
maybe_range.as_ref(),
maybe_source_phase_referrer.as_ref(),
&mut maybe_version_info,
maybe_attribute_type,
&mut loaded_package_via_https_url,
maybe_version_load_fut,
is_asset,
in_dynamic_branch,
was_dynamic_root,
loader,
jsr_url_provider,
module_analyzer,
)
.await;
PendingInfo {
result,
requested_specifier,
maybe_range,
maybe_source_phase_referrer,
maybe_version_info,
loaded_package_via_https_url,
}
}
.boxed_local();
self.state.pending.push_back(fut);
}
fn queue_load_package_info(&mut self, package_name: &str) {
self.state.jsr.metadata.queue_load_package_info(
package_name,
self.fill_pass_mode.to_cache_setting(),
JsrMetadataStoreServices {
executor: self.executor,
jsr_url_provider: self.jsr_url_provider,
loader: self.loader,
},
);
}
fn queue_load_package_version_info(&mut self, package_nv: &PackageNv) {
self.state.jsr.metadata.queue_load_package_version_info(
package_nv,
self.fill_pass_mode.to_cache_setting(),
self.locker.as_deref(),
JsrMetadataStoreServices {
executor: self.executor,
jsr_url_provider: self.jsr_url_provider,
loader: self.loader,
},
);
}
fn visit(
&mut self,
response: PendingInfoResponse,
maybe_referrer: Option<Range>,
maybe_source_phase_referrer: Option<Range>,
maybe_version_info: Option<&JsrPackageVersionInfoExt>,
) {
match response {
PendingInfoResponse::External {
specifier,
is_root,
is_asset,
} => {
if is_root {
self.resolved_roots.insert(specifier.clone());
}
if let Some(entry) = self.graph.module_slots.get_mut(&specifier) {
if entry.is_pending() {
*entry = ModuleSlot::Module(Module::External(ExternalModule {
maybe_cache_info: None,
specifier,
was_asset_load: is_asset,
}));
}
} else {
self.graph.module_slots.insert(
specifier.clone(),
ModuleSlot::Module(Module::External(ExternalModule {
maybe_cache_info: None,
specifier,
was_asset_load: is_asset,
})),
);
}
}
PendingInfoResponse::Module {
specifier,
pending_load,
module_source_and_info,
is_root,
} => {
debug_assert_eq!(
maybe_version_info.is_none(),
self
.jsr_url_provider
.package_url_to_nv(&specifier)
.is_none(),
"{}",
specifier
);
if is_root {
self.resolved_roots.insert(specifier.clone());
}
if let Some((checksum, module_info)) = pending_load.map(|v| *v) {
self.state.jsr.pending_content_loads.push({
let specifier = specifier.clone();
let maybe_range = maybe_referrer.clone();
let fut = self.loader.load(
&specifier,
LoadOptions {
in_dynamic_branch: self.in_dynamic_branch,
was_dynamic_root: self.was_dynamic_root,
cache_setting: CacheSetting::Use,
maybe_checksum: Some(checksum.clone()),
},
);
async move {
let result = fut.await;
PendingContentLoadItem {
specifier,
maybe_range,
result,
module_info,
}
}
.boxed_local()
});
} else if maybe_version_info.is_none()
&& !module_source_and_info.media_type().is_declaration()
&& matches!(specifier.scheme(), "https" | "http")
&& let Some(locker) = &mut self.locker
&& !locker.has_remote_checksum(&specifier)
{
locker.set_remote_checksum(
&specifier,
LoaderChecksum::new(LoaderChecksum::r#gen(
module_source_and_info.source_bytes(),
)),
);
}
let module_slot =
self.visit_module(module_source_and_info, maybe_version_info);
self.graph.module_slots.insert(specifier, module_slot);
}
PendingInfoResponse::Redirect {
count,
specifier,
is_asset,
is_dynamic,
is_root,
maybe_attribute_type,
} => {
self.load_with_redirect_count(
count,
LoadOptionsRef {
specifier: &specifier,
maybe_range: maybe_referrer.as_ref(),
maybe_source_phase_referrer: maybe_source_phase_referrer.as_ref(),
is_asset,
in_dynamic_branch: is_dynamic,
is_root,
maybe_attribute_type,
maybe_version_info: None,
},
);
}
}
}
fn visit_module(
&mut self,
module_source_and_info: ModuleSourceAndInfo,
maybe_version_info: Option<&JsrPackageVersionInfoExt>,
) -> ModuleSlot {
let module = parse_module(
self.file_system,
self.jsr_url_provider,
self.resolver,
ParseModuleOptions {
graph_kind: self.graph.graph_kind,
module_source_and_info,
},
);
let mut module_slot = ModuleSlot::Module(module);
match &mut module_slot {
ModuleSlot::Module(Module::Js(module)) => {
if matches!(self.graph.graph_kind, GraphKind::All | GraphKind::CodeOnly)
|| module.maybe_types_dependency.is_none()
{
if let Some(Resolution::Ok(resolved)) = module
.maybe_source_map_dependency
.as_ref()
.map(|d| &d.dependency)
{
self.load(LoadOptionsRef {
specifier: &resolved.specifier,
maybe_range: Some(&resolved.range),
maybe_source_phase_referrer: None,
is_asset: true,
in_dynamic_branch: self.in_dynamic_branch,
is_root: self.resolved_roots.contains(&resolved.specifier),
maybe_attribute_type: None,
maybe_version_info,
});
}
self.visit_module_dependencies(
&mut module.dependencies,
maybe_version_info,
);
} else {
module.dependencies.clear();
}
if self.graph.graph_kind.include_types() {
if let Some(Resolution::Ok(resolved)) = module
.maybe_types_dependency
.as_ref()
.map(|d| &d.dependency)
{
self.load(LoadOptionsRef {
specifier: &resolved.specifier,
maybe_range: Some(&resolved.range),
maybe_source_phase_referrer: None,
is_asset: false, in_dynamic_branch: false,
is_root: self.resolved_roots.contains(&resolved.specifier),
maybe_attribute_type: None,
maybe_version_info,
});
}
} else {
module.maybe_types_dependency = None;
}
}
ModuleSlot::Module(Module::Wasm(module)) => {
self.visit_module_dependencies(
&mut module.dependencies,
maybe_version_info,
);
}
_ => {}
}
module_slot
}
fn visit_module_dependencies(
&mut self,
dependencies: &mut IndexMap<String, Dependency>,
maybe_version_info: Option<&JsrPackageVersionInfoExt>,
) {
for dep in dependencies.values_mut() {
if dep.is_dynamic && self.skip_dynamic_deps {
continue;
}
let maybe_source_phase_referrer = dep.imports.iter().find_map(|i| {
i.kind.is_source_phase().then(|| i.specifier_range.clone())
});
let is_asset = dep
.imports
.iter()
.all(|i| i.attributes.has_asset() || i.kind.is_source_phase());
if self.graph.graph_kind.include_code() || dep.maybe_type.is_none() {
if let Resolution::Ok(resolved) = &dep.maybe_code {
let specifier = &resolved.specifier;
let range = &resolved.range;
let maybe_attribute_type =
dep.maybe_attribute_type.as_ref().map(|attribute_type| {
AttributeTypeWithRange {
range: range.clone(),
kind: attribute_type.clone(),
}
});
if dep.is_dynamic && !self.in_dynamic_branch {
let value = self
.state
.dynamic_branches
.entry(specifier.clone())
.or_insert_with(|| PendingDynamicBranch {
range: range.clone(),
maybe_source_phase_referrer: maybe_source_phase_referrer
.clone(),
is_asset,
maybe_attribute_type,
maybe_version_info: maybe_version_info.map(ToOwned::to_owned),
});
if !is_asset {
value.is_asset = false;
}
} else {
self.load(LoadOptionsRef {
specifier,
maybe_range: Some(range),
maybe_source_phase_referrer: maybe_source_phase_referrer.as_ref(),
is_asset,
in_dynamic_branch: self.in_dynamic_branch,
is_root: self.resolved_roots.contains(specifier),
maybe_attribute_type,
maybe_version_info,
});
}
}
} else {
dep.maybe_code = Resolution::None;
}
if self.graph.graph_kind.include_types() {
if let Resolution::Ok(resolved) = &dep.maybe_type {
let specifier = &resolved.specifier;
let range = &resolved.range;
let maybe_attribute_type =
dep.maybe_attribute_type.as_ref().map(|assert_type| {
AttributeTypeWithRange {
range: range.clone(),
kind: assert_type.clone(),
}
});
if dep.is_dynamic && !self.in_dynamic_branch {
self.state.dynamic_branches.insert(
specifier.clone(),
PendingDynamicBranch {
is_asset,
range: range.clone(),
maybe_source_phase_referrer: maybe_source_phase_referrer
.clone(),
maybe_attribute_type,
maybe_version_info: maybe_version_info.map(ToOwned::to_owned),
},
);
} else {
self.load(LoadOptionsRef {
specifier,
maybe_range: Some(range),
maybe_source_phase_referrer: maybe_source_phase_referrer.as_ref(),
is_asset,
in_dynamic_branch: self.in_dynamic_branch,
is_root: self.resolved_roots.contains(specifier),
maybe_attribute_type,
maybe_version_info,
});
}
}
} else {
dep.maybe_type = Resolution::None;
}
}
}
}
fn validate_jsr_specifier(
specifier: &Url,
) -> Result<JsrPackageReqReference, JsrPackageFormatError> {
let package_ref = JsrPackageReqReference::from_specifier(specifier)
.map_err(JsrPackageFormatError::JsrPackageParseError)?;
match package_ref.req().version_req.inner() {
RangeSetOrTag::Tag(tag) => {
Err(JsrPackageFormatError::VersionTagNotSupported { tag: tag.clone() })
}
RangeSetOrTag::RangeSet(_) => Ok(package_ref),
}
}
struct NpmSpecifierBuildPendingInfo {
module_slots: HashMap<ModuleSpecifier, ModuleSlot>,
dependencies_resolution: Option<Result<(), Arc<dyn JsErrorClass>>>,
redirects: HashMap<ModuleSpecifier, ModuleSpecifier>,
}
impl NpmSpecifierBuildPendingInfo {
pub fn with_capacity(capacity: usize) -> Self {
Self {
module_slots: HashMap::with_capacity(capacity),
dependencies_resolution: None,
redirects: HashMap::with_capacity(capacity),
}
}
}
#[derive(Debug)]
struct PendingNpmResolutionItem {
specifier: ModuleSpecifier,
package_ref: NpmPackageReqReference,
maybe_range: Option<Range>,
in_dynamic_branch: bool,
}
struct NpmSpecifierResolver<'a> {
npm_resolver: Option<&'a dyn NpmResolver>,
pending_info: NpmSpecifierBuildPendingInfo,
pending_npm_specifiers: Vec<PendingNpmResolutionItem>,
}
impl<'a> NpmSpecifierResolver<'a> {
pub async fn fill_builder(builder: &mut Builder<'a, '_>) {
let mut npm_specifier_resolver = NpmSpecifierResolver::new(
builder.npm_resolver,
std::mem::take(&mut builder.state.npm.pending_resolutions),
);
npm_specifier_resolver.resolve().await;
npm_specifier_resolver.fill_graph(builder.graph);
}
fn new(
npm_resolver: Option<&'a dyn NpmResolver>,
pending_npm_specifiers: Vec<PendingNpmResolutionItem>,
) -> Self {
Self {
npm_resolver,
pending_info: NpmSpecifierBuildPendingInfo::with_capacity(
pending_npm_specifiers.len(),
),
pending_npm_specifiers,
}
}
async fn resolve(&mut self) {
let Some(npm_resolver) = self.npm_resolver else {
for item in self.pending_npm_specifiers.drain(..) {
self.pending_info.module_slots.insert(
item.specifier.clone(),
ModuleSlot::Err(
ModuleErrorKind::Load {
specifier: item.specifier.clone(),
maybe_referrer: item.maybe_range.clone(),
err: NpmLoadError::NotSupportedEnvironment.into(),
}
.into_box(),
),
);
}
return;
};
let (main_items, dynamic_items) = self
.pending_npm_specifiers
.drain(..)
.partition::<Vec<_>, _>(|item| !item.in_dynamic_branch);
if !main_items.is_empty() || dynamic_items.is_empty() {
let items_by_req: IndexMap<_, Vec<_>> =
IndexMap::with_capacity(main_items.len());
let items_by_req =
main_items
.into_iter()
.fold(items_by_req, |mut items_by_req, item| {
items_by_req
.entry(item.package_ref.req().clone())
.or_default()
.push(item);
items_by_req
});
let all_package_reqs = items_by_req.keys().cloned().collect::<Vec<_>>();
let result = npm_resolver.resolve_pkg_reqs(&all_package_reqs).await;
self.pending_info.dependencies_resolution = Some(result.dep_graph_result);
assert_eq!(all_package_reqs.len(), result.results.len());
for (req, resolution) in
all_package_reqs.into_iter().zip(result.results.into_iter())
{
let items = items_by_req.get(&req).unwrap();
for item in items {
match &resolution {
Ok(_) => {
self.add_req_ref_for_item(
item.specifier.clone(),
item.package_ref.clone(),
);
}
Err(err) => {
self.pending_info.module_slots.insert(
item.specifier.clone(),
ModuleSlot::Err(
ModuleErrorKind::Load {
specifier: item.specifier.clone(),
maybe_referrer: item.maybe_range.clone(),
err: err.clone().into(),
}
.into_box(),
),
);
}
}
}
}
}
for item in dynamic_items {
let mut result = npm_resolver
.resolve_pkg_reqs(&[item.package_ref.req().clone()])
.await;
assert_eq!(result.results.len(), 1);
match result.results.remove(0) {
Ok(_) => {
if let Err(err) = result.dep_graph_result {
self.pending_info.module_slots.insert(
item.specifier.clone(),
ModuleSlot::Err(
ModuleErrorKind::Load {
specifier: item.specifier,
maybe_referrer: item.maybe_range,
err: NpmLoadError::PackageReqResolution(err).into(),
}
.into_box(),
),
);
} else {
self.add_req_ref_for_item(item.specifier, item.package_ref);
}
}
Err(err) => {
self.pending_info.module_slots.insert(
item.specifier.clone(),
ModuleSlot::Err(
ModuleErrorKind::Load {
specifier: item.specifier,
maybe_referrer: item.maybe_range,
err: err.into(),
}
.into_box(),
),
);
}
}
}
}
fn add_req_ref_for_item(
&mut self,
specifier: ModuleSpecifier,
pkg_req_ref: NpmPackageReqReference,
) {
self.pending_info.module_slots.insert(
specifier.clone(),
ModuleSlot::Module(Module::Npm(NpmModule {
specifier,
pkg_req_ref,
})),
);
}
fn fill_graph(self, graph: &mut ModuleGraph) {
let pending_info = self.pending_info;
for (key, value) in pending_info.module_slots {
graph.module_slots.entry(key).or_insert(value);
}
for (key, value) in pending_info.redirects {
graph.redirects.entry(key).or_insert(value);
}
if let Some(result) = pending_info.dependencies_resolution {
graph.npm_dep_graph_result = result;
}
}
}
fn new_source_with_text(
specifier: &ModuleSpecifier,
bytes: Arc<[u8]>,
maybe_charset: Option<&str>,
mtime: Option<SystemTime>,
) -> Result<ModuleTextSource, ModuleError> {
let charset = maybe_charset.unwrap_or_else(|| {
deno_media_type::encoding::detect_charset(specifier, bytes.as_ref())
});
deno_media_type::encoding::decode_arc_source_detail(charset, bytes)
.map(|detail| ModuleTextSource {
text: detail.text,
decoded_kind: detail.kind,
})
.map_err(|err| {
ModuleErrorKind::Load {
specifier: specifier.clone(),
maybe_referrer: None,
err: ModuleLoadError::Decode(Arc::new(DecodeError { mtime, err })),
}
.into_box()
})
}
impl Serialize for Resolution {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Resolution::Ok(resolved) => {
let mut state = serializer.serialize_struct("ResolvedSpecifier", 3)?;
state.serialize_field("specifier", &resolved.specifier)?;
if resolved.range.resolution_mode.is_some() {
state.serialize_field(
"resolutionMode",
&resolved.range.resolution_mode,
)?;
}
state.serialize_field("span", &resolved.range)?;
state.end()
}
Resolution::Err(err) => {
let mut state = serializer.serialize_struct("ResolvedError", 3)?;
state.serialize_field("error", &err.to_string())?;
let range = err.range();
if range.resolution_mode.is_some() {
state.serialize_field("resolutionMode", &range.resolution_mode)?;
}
state.serialize_field("span", range)?;
state.end()
}
Resolution::None => {
Serialize::serialize(&serde_json::Value::Null, serializer)
}
}
}
}
fn serialize_dependencies<S>(
dependencies: &IndexMap<String, Dependency>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
enum ImportPhase {
Evaluation,
Source,
}
impl ImportPhase {
fn is_evaluation(&self) -> bool {
match self {
Self::Evaluation => true,
Self::Source => false,
}
}
}
#[derive(Serialize)]
struct DependencyWithSpecifierAndPhase<'a> {
specifier: &'a str,
#[serde(flatten)]
dependency: &'a Dependency,
#[serde(skip_serializing_if = "ImportPhase::is_evaluation")]
phase: ImportPhase,
}
let mut seq = serializer.serialize_seq(Some(dependencies.len()))?;
for (specifier, dependency) in dependencies {
let phase = if !dependency.imports.is_empty()
&& dependency.imports.iter().all(|i| i.kind.is_source_phase())
{
ImportPhase::Source
} else {
ImportPhase::Evaluation
};
seq.serialize_element(&DependencyWithSpecifierAndPhase {
specifier,
dependency,
phase,
})?
}
seq.end()
}
fn serialize_source<S>(
source: &ModuleTextSource,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u32(source.text.len() as u32)
}
fn serialize_source_bytes<S>(
source: &Arc<[u8]>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u32(source.len() as u32)
}
#[cfg(test)]
mod tests {
use crate::analysis::ImportAttribute;
use crate::packages::JsrPackageInfoVersion;
use deno_ast::EmitOptions;
use deno_ast::SourceMap;
use deno_ast::emit;
use pretty_assertions::assert_eq;
use serde_json::json;
use super::*;
use url::Url;
#[test]
fn test_range_includes() {
let range = PositionRange {
start: Position {
line: 1,
character: 20,
},
end: Position {
line: 1,
character: 30,
},
};
assert!(range.includes(Position {
line: 1,
character: 20
}));
assert!(range.includes(Position {
line: 1,
character: 25
}));
assert!(range.includes(Position {
line: 1,
character: 30
}));
assert!(!range.includes(Position {
line: 0,
character: 25
}));
assert!(!range.includes(Position {
line: 2,
character: 25
}));
}
#[test]
fn test_jsr_import_format() {
assert!(
validate_jsr_specifier(&Url::parse("jsr:@scope/mod@tag").unwrap())
.is_err(),
"jsr import specifier with tag should be an error"
);
assert!(
validate_jsr_specifier(&Url::parse("jsr:@scope/mod@").unwrap()).is_err()
);
assert!(
validate_jsr_specifier(&Url::parse("jsr:@scope/mod@1.2.3").unwrap())
.is_ok()
);
assert!(
validate_jsr_specifier(&Url::parse("jsr:@scope/mod").unwrap()).is_ok()
);
}
#[test]
fn test_module_dependency_includes() {
let specifier = ModuleSpecifier::parse("file:///a.ts").unwrap();
let dependency = Dependency {
maybe_code: Resolution::Ok(Box::new(ResolutionResolved {
specifier: ModuleSpecifier::parse("file:///b.ts").unwrap(),
range: Range {
specifier: specifier.clone(),
range: PositionRange {
start: Position {
line: 0,
character: 19,
},
end: Position {
line: 0,
character: 27,
},
},
resolution_mode: None,
},
})),
imports: vec![
Import {
specifier: "./b.ts".to_string(),
kind: ImportKind::Es,
specifier_range: Range {
specifier: specifier.clone(),
range: PositionRange {
start: Position {
line: 0,
character: 19,
},
end: Position {
line: 0,
character: 27,
},
},
resolution_mode: None,
},
is_dynamic: false,
attributes: Default::default(),
is_side_effect: false,
},
Import {
specifier: "./b.ts".to_string(),
kind: ImportKind::Es,
specifier_range: Range {
specifier: specifier.clone(),
range: PositionRange {
start: Position {
line: 1,
character: 19,
},
end: Position {
line: 1,
character: 27,
},
},
resolution_mode: None,
},
is_dynamic: false,
attributes: Default::default(),
is_side_effect: false,
},
],
..Default::default()
};
assert_eq!(
dependency.includes(Position {
line: 0,
character: 21,
}),
Some(&Range {
specifier: specifier.clone(),
range: PositionRange {
start: Position {
line: 0,
character: 19
},
end: Position {
line: 0,
character: 27
},
},
resolution_mode: None,
})
);
assert_eq!(
dependency.includes(Position {
line: 1,
character: 21,
}),
Some(&Range {
specifier,
range: PositionRange {
start: Position {
line: 1,
character: 19
},
end: Position {
line: 1,
character: 27
},
},
resolution_mode: None,
})
);
assert_eq!(
dependency.includes(Position {
line: 0,
character: 18,
}),
None,
);
}
#[test]
fn dependency_with_new_resolver() {
let referrer = ModuleSpecifier::parse("file:///a/main.ts").unwrap();
let dependency = Dependency {
maybe_code: Resolution::Ok(Box::new(ResolutionResolved {
specifier: ModuleSpecifier::parse("file:///wrong.ts").unwrap(),
range: Range {
specifier: referrer.clone(),
range: PositionRange::zeroed(),
resolution_mode: None,
},
})),
maybe_type: Resolution::Ok(Box::new(ResolutionResolved {
specifier: ModuleSpecifier::parse("file:///wrong.ts").unwrap(),
range: Range {
specifier: referrer.clone(),
range: PositionRange::zeroed(),
resolution_mode: None,
},
})),
maybe_deno_types_specifier: Some("./b.d.ts".to_string()),
..Default::default()
};
let new_dependency =
dependency.with_new_resolver("./b.ts", Default::default(), None);
assert_eq!(
new_dependency,
Dependency {
maybe_code: Resolution::Ok(Box::new(ResolutionResolved {
specifier: ModuleSpecifier::parse("file:///a/b.ts").unwrap(),
range: Range {
specifier: referrer.clone(),
range: PositionRange::zeroed(),
resolution_mode: None,
},
})),
maybe_type: Resolution::Ok(Box::new(ResolutionResolved {
specifier: ModuleSpecifier::parse("file:///a/b.d.ts").unwrap(),
range: Range {
specifier: referrer.clone(),
range: PositionRange::zeroed(),
resolution_mode: None,
},
})),
maybe_deno_types_specifier: Some("./b.d.ts".to_string()),
..Default::default()
}
);
}
#[test]
fn types_dependency_with_new_resolver() {
let referrer = ModuleSpecifier::parse("file:///a/main.ts").unwrap();
let types_dependency = TypesDependency {
specifier: "./main.d.ts".to_string(),
dependency: Resolution::Ok(Box::new(ResolutionResolved {
specifier: ModuleSpecifier::parse("file:///wrong.ts").unwrap(),
range: Range {
specifier: referrer.clone(),
range: PositionRange::zeroed(),
resolution_mode: None,
},
})),
};
let new_types_dependency =
types_dependency.with_new_resolver(Default::default(), None);
assert_eq!(
new_types_dependency,
TypesDependency {
specifier: "./main.d.ts".to_string(),
dependency: Resolution::Ok(Box::new(ResolutionResolved {
specifier: ModuleSpecifier::parse("file:///a/main.d.ts").unwrap(),
range: Range {
specifier: referrer.clone(),
range: PositionRange::zeroed(),
resolution_mode: None,
},
})),
}
);
}
#[tokio::test]
async fn static_dep_of_dynamic_dep_is_dynamic() {
#[derive(Default)]
struct TestLoader {
loaded_foo: RefCell<bool>,
loaded_bar: RefCell<bool>,
loaded_baz: RefCell<bool>,
loaded_dynamic_root: RefCell<bool>,
}
impl Loader for TestLoader {
fn load(
&self,
specifier: &ModuleSpecifier,
options: LoadOptions,
) -> LoadFuture {
let specifier = specifier.clone();
match specifier.as_str() {
"file:///foo.js" => {
assert!(!options.in_dynamic_branch);
assert!(!options.was_dynamic_root);
*self.loaded_foo.borrow_mut() = true;
Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"await import('file:///bar.js')".to_vec().into(),
}))
})
}
"file:///bar.js" => {
assert!(options.in_dynamic_branch);
assert!(!options.was_dynamic_root);
*self.loaded_bar.borrow_mut() = true;
Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"import 'file:///baz.js'".to_vec().into(),
}))
})
}
"file:///baz.js" => {
assert!(options.in_dynamic_branch);
assert!(!options.was_dynamic_root);
*self.loaded_baz.borrow_mut() = true;
Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"console.log('Hello, world!')".to_vec().into(),
}))
})
}
"file:///dynamic_root.js" => {
assert!(options.in_dynamic_branch);
assert!(options.was_dynamic_root);
*self.loaded_dynamic_root.borrow_mut() = true;
Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"console.log('Hello, world!')".to_vec().into(),
}))
})
}
_ => unreachable!(),
}
}
}
let loader = TestLoader::default();
let mut graph = ModuleGraph::new(GraphKind::All);
graph
.build(
vec![Url::parse("file:///foo.js").unwrap()],
Vec::new(),
&loader,
Default::default(),
)
.await;
assert!(*loader.loaded_foo.borrow());
assert!(*loader.loaded_bar.borrow());
assert!(*loader.loaded_baz.borrow());
assert!(!*loader.loaded_dynamic_root.borrow());
assert_eq!(graph.specifiers_count(), 3);
graph
.build(
vec![Url::parse("file:///dynamic_root.js").unwrap()],
Vec::new(),
&loader,
BuildOptions {
is_dynamic: true,
..Default::default()
},
)
.await;
assert!(*loader.loaded_dynamic_root.borrow());
assert_eq!(graph.specifiers_count(), 4);
}
#[tokio::test]
async fn missing_module_is_error() {
struct TestLoader;
impl Loader for TestLoader {
fn load(
&self,
specifier: &ModuleSpecifier,
_options: LoadOptions,
) -> LoadFuture {
let specifier = specifier.clone();
match specifier.as_str() {
"file:///foo.js" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"await import('file:///bar.js')".to_vec().into(),
}))
}),
"file:///bar.js" => Box::pin(async move { Ok(None) }),
_ => unreachable!(),
}
}
}
let loader = TestLoader;
let mut graph = ModuleGraph::new(GraphKind::All);
let roots = vec![Url::parse("file:///foo.js").unwrap()];
graph
.build(roots.clone(), Vec::new(), &loader, Default::default())
.await;
assert!(
graph
.try_get(&Url::parse("file:///foo.js").unwrap())
.is_ok()
);
assert!(matches!(
graph
.try_get(&Url::parse("file:///bar.js").unwrap())
.unwrap_err()
.as_kind(),
ModuleErrorKind::Missing { .. }
));
let specifiers = graph.specifiers().collect::<HashMap<_, _>>();
assert_eq!(specifiers.len(), 2);
assert!(
specifiers
.get(&Url::parse("file:///foo.js").unwrap())
.unwrap()
.is_ok()
);
assert!(matches!(
specifiers
.get(&Url::parse("file:///bar.js").unwrap())
.unwrap()
.as_ref()
.unwrap_err()
.as_kind(),
ModuleErrorKind::Missing { .. }
));
let error_count = graph
.walk(
roots.iter(),
WalkOptions {
follow_dynamic: false,
kind: GraphKind::All,
check_js: CheckJsOption::True,
prefer_fast_check_graph: false,
},
)
.errors()
.count();
assert_eq!(error_count, 0);
let errors = graph
.walk(
roots.iter(),
WalkOptions {
follow_dynamic: true,
kind: GraphKind::All,
check_js: CheckJsOption::True,
prefer_fast_check_graph: false,
},
)
.errors()
.collect::<Vec<_>>();
assert_eq!(errors.len(), 1);
let err = &errors[0];
match err {
ModuleGraphError::ModuleError(err) => {
assert!(matches!(
err.as_kind(),
ModuleErrorKind::MissingDynamic { .. }
));
}
_ => unreachable!(),
}
}
#[tokio::test]
async fn missing_wasm_module_dep_is_error() {
struct TestLoader;
impl Loader for TestLoader {
fn load(
&self,
specifier: &ModuleSpecifier,
_options: LoadOptions,
) -> LoadFuture {
let specifier = specifier.clone();
match specifier.as_str() {
"file:///foo.js" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"import './math_with_import.wasm';".to_vec().into(),
}))
}),
"file:///math_with_import.wasm" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: include_bytes!(
"../tests/testdata/math_with_import.wasm"
)
.to_vec()
.into(),
}))
}),
"file:///math.ts" => Box::pin(async move { Ok(None) }),
_ => unreachable!(),
}
}
}
let loader = TestLoader;
let mut graph = ModuleGraph::new(GraphKind::All);
let roots = vec![Url::parse("file:///foo.js").unwrap()];
graph
.build(roots.clone(), Vec::new(), &loader, Default::default())
.await;
let errors = graph
.walk(
roots.iter(),
WalkOptions {
follow_dynamic: false,
kind: GraphKind::All,
check_js: CheckJsOption::True,
prefer_fast_check_graph: false,
},
)
.errors()
.collect::<Vec<_>>();
assert_eq!(errors.len(), 1);
match &errors[0] {
ModuleGraphError::ModuleError(err) => {
match err.as_kind() {
ModuleErrorKind::Missing {
specifier,
maybe_referrer,
} => {
assert_eq!(specifier.as_str(), "file:///math.ts");
assert_eq!(
maybe_referrer.clone(),
Some(Range {
specifier: Url::parse("file:///math_with_import.wasm").unwrap(),
range: PositionRange {
start: Position {
line: 0,
character: 92,
},
end: Position {
line: 0,
character: 103,
}
},
resolution_mode: Some(ResolutionMode::Import),
})
);
}
_ => unreachable!(),
}
}
_ => unreachable!(),
}
}
#[tokio::test]
async fn redirected_specifiers() {
struct TestLoader;
impl Loader for TestLoader {
fn load(
&self,
specifier: &ModuleSpecifier,
_options: LoadOptions,
) -> LoadFuture {
let specifier = specifier.clone();
match specifier.as_str() {
"file:///foo.js" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: Url::parse("file:///foo_actual.js").unwrap(),
maybe_headers: None,
mtime: None,
content: b"import 'file:///bar.js'".to_vec().into(),
}))
}),
"file:///bar.js" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: Url::parse("file:///bar_actual.js").unwrap(),
maybe_headers: None,
mtime: None,
content: b"(".to_vec().into(),
}))
}),
_ => unreachable!(),
}
}
}
let loader = TestLoader;
let mut graph = ModuleGraph::new(GraphKind::All);
graph
.build(
vec![Url::parse("file:///foo.js").unwrap()],
Vec::new(),
&loader,
Default::default(),
)
.await;
let specifiers = graph.specifiers().collect::<HashMap<_, _>>();
assert_eq!(specifiers.len(), 4);
assert!(
specifiers
.get(&Url::parse("file:///foo.js").unwrap())
.unwrap()
.is_ok()
);
assert!(
specifiers
.get(&Url::parse("file:///foo_actual.js").unwrap())
.unwrap()
.is_ok()
);
assert!(matches!(
specifiers
.get(&Url::parse("file:///bar.js").unwrap())
.unwrap()
.as_ref()
.unwrap_err()
.as_kind(),
ModuleErrorKind::Parse { .. }
));
assert!(matches!(
specifiers
.get(&Url::parse("file:///bar_actual.js").unwrap())
.unwrap()
.as_ref()
.unwrap_err()
.as_kind(),
ModuleErrorKind::Parse { .. }
));
}
#[tokio::test]
async fn local_import_remote_module() {
struct TestLoader;
impl Loader for TestLoader {
fn load(
&self,
specifier: &ModuleSpecifier,
_options: LoadOptions,
) -> LoadFuture {
let specifier = specifier.clone();
match specifier.as_str() {
"https://deno.land/foo.js" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content:
b"import 'FILE:///baz.js'; import 'file:///bar.js'; import 'http://deno.land/foo.js';"
.to_vec().into(),
}))
}),
"http://deno.land/foo.js" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"export {}".to_vec().into(),
}))
}),
"file:///bar.js" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"console.log('Hello, world!')".to_vec().into(),
}))
}),
"file:///baz.js" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"console.log('Hello, world 2!')".to_vec().into(),
}))
}),
_ => unreachable!(),
}
}
}
let loader = TestLoader;
let mut graph = ModuleGraph::new(GraphKind::All);
let roots = vec![Url::parse("https://deno.land/foo.js").unwrap()];
graph
.build(roots.clone(), Vec::new(), &loader, Default::default())
.await;
assert_eq!(graph.specifiers_count(), 4);
let errors = graph
.walk(
roots.iter(),
WalkOptions {
check_js: CheckJsOption::True,
follow_dynamic: false,
kind: GraphKind::All,
prefer_fast_check_graph: false,
},
)
.errors()
.collect::<Vec<_>>();
assert_eq!(errors.len(), 3);
let errors = errors
.into_iter()
.map(|err| match err {
ModuleGraphError::ResolutionError(err) => err,
_ => unreachable!(),
})
.collect::<Vec<_>>();
assert_eq!(
errors[0],
ResolutionError::InvalidDowngrade {
range: Range {
specifier: ModuleSpecifier::parse("https://deno.land/foo.js")
.unwrap(),
range: PositionRange {
start: Position {
line: 0,
character: 57,
},
end: Position {
line: 0,
character: 82,
},
},
resolution_mode: Some(ResolutionMode::Import),
},
specifier: ModuleSpecifier::parse("http://deno.land/foo.js").unwrap(),
},
);
assert_eq!(
errors[1],
ResolutionError::InvalidLocalImport {
range: Range {
specifier: ModuleSpecifier::parse("https://deno.land/foo.js")
.unwrap(),
range: PositionRange {
start: Position {
line: 0,
character: 32,
},
end: Position {
line: 0,
character: 48,
},
},
resolution_mode: Some(ResolutionMode::Import),
},
specifier: ModuleSpecifier::parse("file:///bar.js").unwrap(),
},
);
assert_eq!(
errors[2],
ResolutionError::InvalidLocalImport {
range: Range {
specifier: ModuleSpecifier::parse("https://deno.land/foo.js")
.unwrap(),
range: PositionRange {
start: Position {
line: 0,
character: 7,
},
end: Position {
line: 0,
character: 23,
},
},
resolution_mode: Some(ResolutionMode::Import),
},
specifier: ModuleSpecifier::parse("file:///baz.js").unwrap(),
},
);
}
#[tokio::test]
async fn static_and_dynamic_dep_is_static() {
struct TestLoader {
loaded_bar: RefCell<bool>,
}
impl Loader for TestLoader {
fn load(
&self,
specifier: &ModuleSpecifier,
options: LoadOptions,
) -> LoadFuture {
let specifier = specifier.clone();
match specifier.as_str() {
"file:///foo.js" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content:
b"import 'file:///bar.js'; await import('file:///bar.js')"
.to_vec()
.into(),
}))
}),
"file:///bar.js" => {
assert!(!options.in_dynamic_branch);
*self.loaded_bar.borrow_mut() = true;
Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"console.log('Hello, world!')".to_vec().into(),
}))
})
}
_ => unreachable!(),
}
}
}
let loader = TestLoader {
loaded_bar: RefCell::new(false),
};
let mut graph = ModuleGraph::new(GraphKind::All);
graph
.build(
vec![Url::parse("file:///foo.js").unwrap()],
Default::default(),
&loader,
Default::default(),
)
.await;
assert!(*loader.loaded_bar.borrow());
}
#[tokio::test]
async fn dependency_imports() {
struct TestLoader;
impl Loader for TestLoader {
fn load(
&self,
specifier: &ModuleSpecifier,
options: LoadOptions,
) -> LoadFuture {
let specifier = specifier.clone();
match specifier.as_str() {
"file:///foo.ts" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"
/// <reference path='file:///bar.ts' />
/// <reference types='file:///bar.ts' />
/* @jsxImportSource file:///bar.ts */
import 'file:///bar.ts';
await import('file:///bar.ts');
await import('file:///bar.ts', { assert: eval('') });
import 'file:///baz.json' assert { type: 'json' };
import type {} from 'file:///bar.ts';
/** @typedef { import('file:///bar.ts') } bar */
import source mathModule from 'file:///math_with_import.wasm';
await import.source('file:///math_with_import.wasm');
"
.to_vec()
.into(),
}))
}),
"file:///bar.ts" => {
assert!(!options.in_dynamic_branch);
Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"".to_vec().into(),
}))
})
}
"file:///baz.json" => {
assert!(!options.in_dynamic_branch);
Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"{}".to_vec().into(),
}))
})
}
"file:///math_with_import.wasm" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: include_bytes!(
"../tests/testdata/math_with_import.wasm"
)
.to_vec()
.into(),
}))
}),
"file:///math.ts" => unreachable!(
"Shouldn't load dependencies of wasm modules imported by source"
),
s => unreachable!("Tried to load: {s}"),
}
}
}
let mut graph = ModuleGraph::new(GraphKind::All);
graph
.build(
vec![Url::parse("file:///foo.ts").unwrap()],
Vec::new(),
&TestLoader,
Default::default(),
)
.await;
graph.valid().unwrap();
let module = graph.get(&Url::parse("file:///foo.ts").unwrap()).unwrap();
let module = module.js().unwrap();
let dependency_a = module.dependencies.get("file:///bar.ts").unwrap();
let dependency_b = module.dependencies.get("file:///baz.json").unwrap();
let dependency_c = module
.dependencies
.get("file:///math_with_import.wasm")
.unwrap();
assert_eq!(
dependency_a.imports,
vec![
Import {
specifier: "file:///bar.ts".to_string(),
kind: ImportKind::TsReferencePath,
specifier_range: Range {
specifier: Url::parse("file:///foo.ts").unwrap(),
range: PositionRange {
start: Position {
line: 1,
character: 36
},
end: Position {
line: 1,
character: 52,
},
},
resolution_mode: None,
},
is_dynamic: false,
attributes: ImportAttributes::None,
is_side_effect: false,
},
Import {
specifier: "file:///bar.ts".to_string(),
kind: ImportKind::TsReferenceTypes,
specifier_range: Range {
specifier: Url::parse("file:///foo.ts").unwrap(),
range: PositionRange {
start: Position {
line: 2,
character: 37,
},
end: Position {
line: 2,
character: 53,
},
},
resolution_mode: None,
},
is_dynamic: false,
attributes: ImportAttributes::None,
is_side_effect: false,
},
Import {
specifier: "file:///bar.ts".to_string(),
kind: ImportKind::Es,
specifier_range: Range {
specifier: Url::parse("file:///foo.ts").unwrap(),
range: PositionRange {
start: Position {
line: 4,
character: 23,
},
end: Position {
line: 4,
character: 39,
},
},
resolution_mode: Some(ResolutionMode::Import),
},
is_dynamic: false,
attributes: ImportAttributes::None,
is_side_effect: true,
},
Import {
specifier: "file:///bar.ts".to_string(),
kind: ImportKind::Es,
specifier_range: Range {
specifier: Url::parse("file:///foo.ts").unwrap(),
range: PositionRange {
start: Position {
line: 5,
character: 29,
},
end: Position {
line: 5,
character: 45,
},
},
resolution_mode: Some(ResolutionMode::Import),
},
is_dynamic: true,
attributes: ImportAttributes::None,
is_side_effect: false,
},
Import {
specifier: "file:///bar.ts".to_string(),
kind: ImportKind::Es,
specifier_range: Range {
specifier: Url::parse("file:///foo.ts").unwrap(),
range: PositionRange {
start: Position {
line: 6,
character: 29,
},
end: Position {
line: 6,
character: 45,
},
},
resolution_mode: Some(ResolutionMode::Import),
},
is_dynamic: true,
attributes: ImportAttributes::Unknown,
is_side_effect: false,
},
Import {
specifier: "file:///bar.ts".to_string(),
kind: ImportKind::TsType,
specifier_range: Range {
specifier: Url::parse("file:///foo.ts").unwrap(),
range: PositionRange {
start: Position {
line: 8,
character: 36,
},
end: Position {
line: 8,
character: 52,
},
},
resolution_mode: Some(ResolutionMode::Import),
},
is_dynamic: false,
attributes: ImportAttributes::None,
is_side_effect: true,
},
]
);
assert_eq!(
dependency_b.imports,
vec![Import {
specifier: "file:///baz.json".to_string(),
kind: ImportKind::Es,
specifier_range: Range {
specifier: Url::parse("file:///foo.ts").unwrap(),
range: PositionRange {
start: Position {
line: 7,
character: 23,
},
end: Position {
line: 7,
character: 41,
},
},
resolution_mode: Some(ResolutionMode::Import),
},
is_dynamic: false,
attributes: ImportAttributes::Known(HashMap::from_iter(vec![(
"type".to_string(),
ImportAttribute::Known("json".to_string())
)])),
is_side_effect: true,
}]
);
assert_eq!(
dependency_c.imports,
vec![
Import {
specifier: "file:///math_with_import.wasm".to_string(),
kind: ImportKind::EsSource,
specifier_range: Range {
specifier: Url::parse("file:///foo.ts").unwrap(),
range: PositionRange {
start: Position {
line: 10,
character: 46,
},
end: Position {
line: 10,
character: 77,
},
},
resolution_mode: Some(ResolutionMode::Import),
},
is_dynamic: false,
attributes: ImportAttributes::None,
is_side_effect: false,
},
Import {
specifier: "file:///math_with_import.wasm".to_string(),
kind: ImportKind::EsSource,
specifier_range: Range {
specifier: Url::parse("file:///foo.ts").unwrap(),
range: PositionRange {
start: Position {
line: 11,
character: 36,
},
end: Position {
line: 11,
character: 67,
},
},
resolution_mode: Some(ResolutionMode::Import),
},
is_dynamic: true,
attributes: ImportAttributes::None,
is_side_effect: false,
},
],
);
}
#[tokio::test]
async fn dependency_ts_type_import_with_deno_types() {
struct TestLoader;
impl Loader for TestLoader {
fn load(
&self,
specifier: &ModuleSpecifier,
options: LoadOptions,
) -> LoadFuture {
let specifier = specifier.clone();
match specifier.as_str() {
"file:///foo.ts" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"
// @deno-types='file:///bar.d.ts'
import type { Bar as _ } from 'bar';
"
.to_vec()
.into(),
}))
}),
"file:///bar.d.ts" => {
assert!(!options.in_dynamic_branch);
Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"export type Bar = null;\n".to_vec().into(),
}))
})
}
_ => unreachable!(),
}
}
}
let mut graph = ModuleGraph::new(GraphKind::TypesOnly);
graph
.build(
vec![Url::parse("file:///foo.ts").unwrap()],
Vec::new(),
&TestLoader,
Default::default(),
)
.await;
graph.valid().unwrap();
let module = graph.get(&Url::parse("file:///foo.ts").unwrap()).unwrap();
let module = module.js().unwrap();
let dependency = module.dependencies.get("bar").unwrap();
assert_eq!(dependency.maybe_code, Resolution::None);
assert_eq!(
dependency.maybe_type.maybe_specifier().unwrap().as_str(),
"file:///bar.d.ts",
);
}
#[tokio::test]
async fn dependency_declare_module() {
#[derive(Debug)]
struct TestLoader;
impl Loader for TestLoader {
fn load(
&self,
specifier: &ModuleSpecifier,
_options: LoadOptions,
) -> LoadFuture {
let specifier = specifier.clone();
match specifier.as_str() {
"file:///main.ts" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"
declare module 'foo' {}
"
.to_vec()
.into(),
}))
}),
"file:///foo.d.ts" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: vec![].into(),
}))
}),
s => unreachable!("{s}"),
}
}
}
impl Resolver for TestLoader {
fn resolve(
&self,
specifier_text: &str,
_referrer_range: &Range,
kind: ResolutionKind,
) -> Result<ModuleSpecifier, ResolveError> {
match (specifier_text, kind) {
("foo", ResolutionKind::Types) => {
Ok(ModuleSpecifier::parse("file:///foo.d.ts").unwrap())
}
e => unreachable!("{e:?}"),
}
}
}
let mut graph = ModuleGraph::new(GraphKind::TypesOnly);
graph
.build(
vec![Url::parse("file:///main.ts").unwrap()],
Vec::new(),
&TestLoader,
BuildOptions {
resolver: Some(&TestLoader),
..Default::default()
},
)
.await;
graph.valid().unwrap();
let module = graph.get(&Url::parse("file:///main.ts").unwrap()).unwrap();
let module = module.js().unwrap();
let dependency = module.dependencies.get("foo").unwrap();
assert_eq!(dependency.maybe_code, Resolution::None);
assert_eq!(
dependency.maybe_type.maybe_specifier().unwrap().as_str(),
"file:///foo.d.ts",
);
}
#[tokio::test]
async fn dependency_jsx_import_source_types_resolution() {
let mut mem_loader = MemoryLoader::default();
mem_loader.add_source_with_text(
"file:///foo.tsx",
"
/* @jsxImportSource foo */
",
);
mem_loader.add_source(
"file:///foo/jsx-runtime",
Source::Module {
specifier: "file:///foo/jsx-runtime",
maybe_headers: Some(vec![("content-type", "application/javascript")]),
content: "",
},
);
mem_loader.add_source(
"file:///foo/types/jsx-runtime",
Source::Module {
specifier: "file:///foo/types/jsx-runtime",
maybe_headers: Some(vec![("content-type", "application/typescript")]),
content: "",
},
);
let mut graph = ModuleGraph::new(GraphKind::All);
#[derive(Debug)]
struct TestResolver;
impl Resolver for TestResolver {
fn resolve(
&self,
specifier_text: &str,
referrer_range: &Range,
resolution_kind: ResolutionKind,
) -> Result<ModuleSpecifier, ResolveError> {
if specifier_text == "foo/jsx-runtime" {
match resolution_kind {
ResolutionKind::Execution => {
Ok(ModuleSpecifier::parse("file:///foo/jsx-runtime").unwrap())
}
ResolutionKind::Types => Ok(
ModuleSpecifier::parse("file:///foo/types/jsx-runtime").unwrap(),
),
}
} else {
Ok(resolve_import(specifier_text, &referrer_range.specifier)?)
}
}
}
let resolver = TestResolver;
graph
.build(
vec![Url::parse("file:///foo.tsx").unwrap()],
Vec::new(),
&mem_loader,
BuildOptions {
resolver: Some(&resolver),
..Default::default()
},
)
.await;
graph.valid().unwrap();
let module = graph.get(&Url::parse("file:///foo.tsx").unwrap()).unwrap();
let module = module.js().unwrap();
let dependency_a = module.dependencies.get("foo/jsx-runtime").unwrap();
assert_eq!(
dependency_a.maybe_code.maybe_specifier().unwrap().as_str(),
"file:///foo/jsx-runtime"
);
assert_eq!(
dependency_a.maybe_type.maybe_specifier().unwrap().as_str(),
"file:///foo/types/jsx-runtime"
);
}
#[tokio::test]
async fn dependency_jsx_import_source_types_pragma() {
let mut mem_loader = MemoryLoader::default();
mem_loader.add_source_with_text(
"file:///foo.tsx",
"
/* @jsxImportSource http://localhost */
/* @jsxImportSourceTypes http://localhost/types */
",
);
mem_loader.add_source(
"http://localhost/jsx-runtime",
Source::Module {
specifier: "http://localhost/jsx-runtime",
maybe_headers: Some(vec![("content-type", "application/javascript")]),
content: "",
},
);
mem_loader.add_source(
"http://localhost/types/jsx-runtime",
Source::Module {
specifier: "http://localhost/types/jsx-runtime",
maybe_headers: Some(vec![("content-type", "application/typescript")]),
content: "",
},
);
let mut graph = ModuleGraph::new(GraphKind::All);
graph
.build(
vec![Url::parse("file:///foo.tsx").unwrap()],
Vec::new(),
&mem_loader,
Default::default(),
)
.await;
graph.valid().unwrap();
let module = graph.get(&Url::parse("file:///foo.tsx").unwrap()).unwrap();
let module = module.js().unwrap();
let dependency_a = module
.dependencies
.get("http://localhost/jsx-runtime")
.unwrap();
assert_eq!(
dependency_a.maybe_type.maybe_specifier().unwrap().as_str(),
"http://localhost/types/jsx-runtime"
);
assert_eq!(
dependency_a.maybe_deno_types_specifier.as_ref().unwrap(),
"http://localhost/types/jsx-runtime"
);
}
#[cfg(feature = "fast_check")]
#[tokio::test]
async fn fast_check_dts() {
use deno_ast::EmittedSourceText;
let mut exports = IndexMap::new();
exports.insert(".".to_string(), "./foo.ts".to_string());
let workspace_members = vec![WorkspaceMember {
base: Url::parse("file:///").unwrap(),
exports: exports.clone(),
name: "@foo/bar".into(),
version: Some(Version::parse_standard("1.0.0").unwrap()),
}];
let mut test_loader = MemoryLoader::default();
test_loader.add_source_with_text(
"file:///foo.ts",
"
export function add(a: number, b: number): number {
return a + b;
}
",
);
let mut graph = ModuleGraph::new(GraphKind::All);
graph
.build(
vec![Url::parse("file:///foo.ts").unwrap()],
Vec::new(),
&test_loader,
BuildOptions {
..Default::default()
},
)
.await;
graph.build_fast_check_type_graph(BuildFastCheckTypeGraphOptions {
fast_check_cache: None,
fast_check_dts: true,
workspace_fast_check: WorkspaceFastCheckOption::Enabled(
&workspace_members,
),
..Default::default()
});
graph.valid().unwrap();
let module = graph.get(&Url::parse("file:///foo.ts").unwrap()).unwrap();
let module = module.js().unwrap();
let FastCheckTypeModuleSlot::Module(fsm) =
module.fast_check.clone().unwrap()
else {
unreachable!();
};
let dts = fsm.dts.unwrap();
let source_map = SourceMap::single(
module.specifier.clone(),
module.source.text.to_string(),
);
let EmittedSourceText { text, .. } = emit(
(&dts.program).into(),
&dts.comments.as_single_threaded(),
&source_map,
&EmitOptions {
remove_comments: false,
source_map: deno_ast::SourceMapOption::None,
..Default::default()
},
)
.unwrap();
assert_eq!(
text.trim(),
"export declare function add(a: number, b: number): number;"
);
assert!(dts.diagnostics.is_empty());
}
#[cfg(feature = "fast_check")]
#[tokio::test]
async fn fast_check_external() {
use deno_ast::EmittedSourceText;
let mut exports = IndexMap::new();
exports.insert(".".to_string(), "./foo.ts".to_string());
let workspace_members = vec![WorkspaceMember {
base: Url::parse("file:///").unwrap(),
exports: exports.clone(),
name: "@foo/bar".into(),
version: Some(Version::parse_standard("1.0.0").unwrap()),
}];
let mut test_loader = MemoryLoader::default();
test_loader.add_source_with_text(
"file:///foo.ts",
"export * from 'jsr:@package/foo';",
);
test_loader.add_jsr_package_info(
"@package/foo",
&JsrPackageInfo {
versions: HashMap::from([(
Version::parse_standard("1.0.0").unwrap(),
JsrPackageInfoVersion::default(),
)]),
},
);
test_loader.add_jsr_version_info(
"@package/foo",
"1.0.0",
&JsrPackageVersionInfo {
exports: json!({ ".": "./mod.ts" }),
module_graph_1: None,
module_graph_2: None,
manifest: Default::default(),
lockfile_checksum: None,
},
);
test_loader.add_external_source("https://jsr.io/@package/foo/1.0.0/mod.ts");
let mut graph = ModuleGraph::new(GraphKind::All);
graph
.build(
vec![Url::parse("file:///foo.ts").unwrap()],
Vec::new(),
&test_loader,
BuildOptions::default(),
)
.await;
graph.build_fast_check_type_graph(BuildFastCheckTypeGraphOptions {
fast_check_cache: None,
fast_check_dts: true,
workspace_fast_check: WorkspaceFastCheckOption::Enabled(
&workspace_members,
),
..Default::default()
});
graph.valid().unwrap();
{
let module = graph.get(&Url::parse("file:///foo.ts").unwrap()).unwrap();
let FastCheckTypeModuleSlot::Module(fsm) =
module.js().unwrap().fast_check.clone().unwrap()
else {
unreachable!();
};
let dts = fsm.dts.unwrap();
let source_map = SourceMap::single(
module.specifier().clone(),
module.source().unwrap().to_string(),
);
let EmittedSourceText { text, .. } = emit(
(&dts.program).into(),
&dts.comments.as_single_threaded(),
&source_map,
&EmitOptions {
remove_comments: false,
source_map: deno_ast::SourceMapOption::None,
..Default::default()
},
)
.unwrap();
assert_eq!(text.trim(), "export * from 'jsr:@package/foo';");
assert!(dts.diagnostics.is_empty());
}
let module = graph
.get(&Url::parse("https://jsr.io/@package/foo/1.0.0/mod.ts").unwrap())
.unwrap();
assert!(module.external().is_some());
}
#[test]
fn leading_v_version_tag_err() {
{
let err =
JsrPackageFormatError::VersionTagNotSupported { tag: "v1.2".into() };
assert_eq!(
err.to_string(),
"Version tag not supported in jsr specifiers ('v1.2'). Remove leading 'v' before version."
);
}
{
let err = JsrPackageFormatError::VersionTagNotSupported {
tag: "latest".into(),
};
assert_eq!(
err.to_string(),
"Version tag not supported in jsr specifiers ('latest')."
);
}
{
let err = JsrPackageFormatError::VersionTagNotSupported {
tag: "version".into(), };
assert_eq!(
err.to_string(),
"Version tag not supported in jsr specifiers ('version')."
);
}
}
#[tokio::test]
async fn check_js_option_custom() {
#[derive(Debug)]
struct CustomResolver;
impl CheckJsResolver for CustomResolver {
fn resolve(&self, specifier: &ModuleSpecifier) -> bool {
specifier.as_str() == "file:///true.js"
}
}
struct TestLoader;
impl Loader for TestLoader {
fn load(
&self,
specifier: &ModuleSpecifier,
_options: LoadOptions,
) -> LoadFuture {
let specifier = specifier.clone();
match specifier.as_str() {
"file:///valid.js" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"export {}".to_vec().into(),
}))
}),
"file:///true.js" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"// @ts-types='invalid'\nimport {} from './valid.js';"
.to_vec()
.into(),
}))
}),
"file:///false.js" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"// @ts-types='invalid'\nimport {} from './valid.js';"
.to_vec()
.into(),
}))
}),
"file:///main.ts" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier: specifier.clone(),
maybe_headers: None,
mtime: None,
content: b"import './true.js'; import './false.js'"
.to_vec()
.into(),
}))
}),
_ => unreachable!(),
}
}
}
let loader = TestLoader;
let mut graph = ModuleGraph::new(GraphKind::All);
let roots = vec![Url::parse("file:///main.ts").unwrap()];
graph
.build(roots.clone(), Vec::new(), &loader, Default::default())
.await;
assert_eq!(graph.specifiers_count(), 4);
let errors = graph
.walk(
roots.iter(),
WalkOptions {
check_js: CheckJsOption::Custom(&CustomResolver),
follow_dynamic: false,
kind: GraphKind::All,
prefer_fast_check_graph: false,
},
)
.errors()
.collect::<Vec<_>>();
assert_eq!(errors.len(), 1);
}
#[tokio::test]
async fn build_npm_packages() {
#[derive(Debug)]
struct TestResolver;
impl Resolver for TestResolver {
fn resolve(
&self,
specifier_text: &str,
_referrer_range: &Range,
_kind: ResolutionKind,
) -> Result<ModuleSpecifier, ResolveError> {
match specifier_text {
"npm:chalk@1.0.0" => Ok(
ModuleSpecifier::parse("file:///node_modules/chalk/index.js")
.unwrap(),
),
_ => Ok(resolve_import(specifier_text, &_referrer_range.specifier)?),
}
}
}
struct TestLoader;
impl Loader for TestLoader {
fn load(
&self,
specifier: &ModuleSpecifier,
_options: LoadOptions,
) -> LoadFuture {
let specifier = specifier.clone();
match specifier.as_str() {
"file:///node_modules/chalk/index.js" => Box::pin(async move {
Ok(Some(LoadResponse::Module {
specifier,
maybe_headers: None,
mtime: None,
content: b"export default function chalk() {}".to_vec().into(),
}))
}),
_ => Box::pin(async { Ok(None) }),
}
}
}
let mut graph = ModuleGraph::new(GraphKind::All);
let file_specifier = ModuleSpecifier::parse("file:///foo.js").unwrap();
let npm_specifier = ModuleSpecifier::parse("npm:chalk@1.0.0").unwrap();
let npm_root_dir = ModuleSpecifier::parse("file:///project/").unwrap();
let chalk_file =
ModuleSpecifier::parse("file:///node_modules/chalk/index.js").unwrap();
let mut js_deps = IndexMap::new();
js_deps.insert(
"npm:chalk@1.0.0".to_string(),
Dependency {
maybe_code: Resolution::Ok(Box::new(ResolutionResolved {
specifier: npm_specifier.clone(),
range: Range {
specifier: file_specifier.clone(),
range: PositionRange::zeroed(),
resolution_mode: None,
},
})),
..Default::default()
},
);
graph.module_slots.insert(
file_specifier.clone(),
ModuleSlot::Module(Module::Js(JsModule {
specifier: file_specifier.clone(),
dependencies: js_deps,
maybe_types_dependency: None,
maybe_cache_info: None,
maybe_source_map_dependency: None,
source: ModuleTextSource::new_unknown(Default::default()),
media_type: MediaType::JavaScript,
is_script: false,
mtime: None,
#[cfg(feature = "fast_check")]
fast_check: Default::default(),
})),
);
graph.module_slots.insert(
npm_specifier.clone(),
ModuleSlot::Module(Module::Npm(NpmModule {
specifier: npm_specifier.clone(),
pkg_req_ref: deno_semver::npm::NpmPackageReqReference::from_str(
"npm:chalk@1.0.0",
)
.unwrap(),
})),
);
graph.roots.insert(file_specifier.clone());
graph.roots.insert(npm_specifier.clone());
let import_referrer = ModuleSpecifier::parse("file:///deno.json").unwrap();
let mut import_deps = IndexMap::new();
import_deps.insert(
"npm:chalk@1.0.0".to_string(),
Dependency {
maybe_code: Resolution::Ok(Box::new(ResolutionResolved {
specifier: npm_specifier.clone(),
range: Range {
specifier: import_referrer.clone(),
range: PositionRange::zeroed(),
resolution_mode: None,
},
})),
..Default::default()
},
);
graph.imports.insert(
import_referrer,
GraphImport {
dependencies: import_deps,
},
);
assert_eq!(graph.module_slots.len(), 2);
assert_eq!(graph.roots.len(), 2);
assert!(graph.get(&npm_specifier).unwrap().npm().is_some());
graph
.build_npm_packages(
&npm_root_dir,
ResolutionKind::Execution,
&TestLoader,
BuildOptions {
resolver: Some(&TestResolver),
..Default::default()
},
)
.await;
assert!(graph.get(&npm_specifier).is_none());
assert!(graph.get(&chalk_file).is_some());
assert!(graph.get(&chalk_file).unwrap().js().is_some());
let foo = graph.get(&file_specifier).unwrap().js().unwrap();
let chalk_dep = foo.dependencies.get("npm:chalk@1.0.0").unwrap();
assert_eq!(
chalk_dep.maybe_code.maybe_specifier().unwrap().as_str(),
"file:///node_modules/chalk/index.js"
);
assert!(!graph.roots.contains(&npm_specifier));
assert!(graph.roots.contains(&chalk_file));
let import_dep = graph.imports[0]
.dependencies
.get("npm:chalk@1.0.0")
.unwrap();
assert_eq!(
import_dep.maybe_code.maybe_specifier().unwrap().as_str(),
"file:///node_modules/chalk/index.js"
);
}
#[test]
fn module_text_source_bom() {
let module_text_source = ModuleTextSource {
text: "test".into(),
decoded_kind: DecodedArcSourceDetailKind::OnlyUtf8Bom,
};
assert_eq!(
module_text_source
.try_get_original_bytes()
.unwrap()
.to_vec(),
vec![0xEF, 0xBB, 0xBF, b't', b'e', b's', b't']
)
}
}