1use crate::analysis::DependencyDescriptor;
4use crate::analysis::DynamicArgument;
5use crate::analysis::DynamicDependencyKind;
6use crate::analysis::DynamicTemplatePart;
7use crate::analysis::ImportAttributes;
8use crate::analysis::ModuleAnalyzer;
9use crate::analysis::ModuleInfo;
10use crate::analysis::SpecifierWithRange;
11use crate::analysis::StaticDependencyKind;
12use crate::analysis::TypeScriptReference;
13use crate::analysis::TypeScriptTypesResolutionMode;
14use crate::collections::SeenPendingCollection;
15use crate::jsr::JsrMetadataStore;
16use crate::jsr::JsrMetadataStoreServices;
17use crate::jsr::PendingJsrPackageVersionInfoLoadItem;
18use crate::jsr::PendingResult;
19use crate::packages::JsrVersionResolver;
20use crate::ReferrerImports;
21
22use crate::module_specifier::is_fs_root_specifier;
23use crate::module_specifier::resolve_import;
24use crate::module_specifier::ModuleSpecifier;
25use crate::module_specifier::SpecifierError;
26use crate::packages::JsrPackageInfo;
27use crate::packages::JsrPackageVersionInfo;
28use crate::packages::PackageSpecifiers;
29use crate::rt::Executor;
30
31use crate::source::*;
32
33use crate::MediaType;
34use boxed_error::Boxed;
35use deno_error::JsError;
36use deno_error::JsErrorBox;
37use deno_error::JsErrorClass;
38use deno_media_type::encoding::DecodedArcSourceDetailKind;
39use deno_media_type::encoding::BOM_CHAR;
40use deno_semver::jsr::JsrDepPackageReq;
41use deno_semver::jsr::JsrPackageNvReference;
42use deno_semver::jsr::JsrPackageReqReference;
43use deno_semver::npm::NpmPackageNvReference;
44use deno_semver::npm::NpmPackageReqReference;
45use deno_semver::package::PackageNv;
46use deno_semver::package::PackageNvReference;
47use deno_semver::package::PackageReq;
48use deno_semver::package::PackageReqReferenceParseError;
49use deno_semver::package::PackageSubPath;
50use deno_semver::RangeSetOrTag;
51use deno_semver::SmallStackString;
52use deno_semver::StackString;
53use deno_semver::Version;
54use deno_semver::VersionReq;
55use futures::future::LocalBoxFuture;
56use futures::stream::FuturesOrdered;
57use futures::stream::FuturesUnordered;
58use futures::stream::StreamExt;
59use futures::FutureExt;
60use indexmap::IndexMap;
61use indexmap::IndexSet;
62use serde::ser::SerializeSeq;
63use serde::ser::SerializeStruct;
64use serde::ser::SerializeTuple;
65use serde::Deserialize;
66use serde::Serialize;
67use serde::Serializer;
68use std::borrow::Cow;
69use std::cell::RefCell;
70use std::cmp::Ordering;
71use std::collections::BTreeMap;
72use std::collections::BTreeSet;
73use std::collections::HashMap;
74use std::collections::HashSet;
75use std::collections::VecDeque;
76use std::fmt;
77use std::path::Path;
78use std::rc::Rc;
79use std::sync::Arc;
80use std::time::SystemTime;
81use sys_traits::FileType;
82use sys_traits::FsDirEntry;
83use thiserror::Error;
84use url::Url;
85use wasm::wasm_module_to_dts;
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
88pub struct Position {
89 pub line: usize,
91 pub character: usize,
93}
94
95impl std::fmt::Display for Position {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 write!(f, "{}:{}", self.line + 1, self.character + 1)
98 }
99}
100
101impl PartialOrd for Position {
102 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
103 Some(self.cmp(other))
104 }
105}
106
107impl Ord for Position {
108 fn cmp(&self, other: &Self) -> Ordering {
109 match self.line.cmp(&other.line) {
110 Ordering::Equal => self.character.cmp(&other.character),
111 Ordering::Greater => Ordering::Greater,
112 Ordering::Less => Ordering::Less,
113 }
114 }
115}
116
117impl Position {
118 pub fn new(line: usize, character: usize) -> Self {
119 Self { line, character }
120 }
121
122 pub fn zeroed() -> Self {
123 Self {
124 line: 0,
125 character: 0,
126 }
127 }
128
129 #[cfg(feature = "swc")]
130 pub fn from_source_pos(
131 pos: deno_ast::SourcePos,
132 text_info: &deno_ast::SourceTextInfo,
133 ) -> Self {
134 let line_and_column_index = text_info.line_and_column_index(pos);
135 Self {
136 line: line_and_column_index.line_index,
137 character: line_and_column_index.column_index,
138 }
139 }
140
141 #[cfg(feature = "swc")]
142 pub fn as_source_pos(
143 &self,
144 text_info: &deno_ast::SourceTextInfo,
145 ) -> deno_ast::SourcePos {
146 text_info.loc_to_source_pos(deno_ast::LineAndColumnIndex {
147 line_index: self.line,
148 column_index: self.character,
149 })
150 }
151}
152
153#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Hash)]
154pub struct PositionRange {
155 #[serde(default = "Position::zeroed")]
156 pub start: Position,
157 #[serde(default = "Position::zeroed")]
158 pub end: Position,
159}
160
161impl PositionRange {
162 pub fn zeroed() -> Self {
163 Self {
164 start: Position::zeroed(),
165 end: Position::zeroed(),
166 }
167 }
168
169 pub fn includes(&self, position: Position) -> bool {
171 (position >= self.start) && (position <= self.end)
172 }
173
174 #[cfg(feature = "swc")]
175 pub fn from_source_range(
176 range: deno_ast::SourceRange,
177 text_info: &deno_ast::SourceTextInfo,
178 ) -> Self {
179 Self {
180 start: Position::from_source_pos(range.start, text_info),
181 end: Position::from_source_pos(range.end, text_info),
182 }
183 }
184
185 #[cfg(feature = "swc")]
186 pub fn as_source_range(
187 &self,
188 text_info: &deno_ast::SourceTextInfo,
189 ) -> deno_ast::SourceRange {
190 deno_ast::SourceRange::new(
191 self.start.as_source_pos(text_info),
192 self.end.as_source_pos(text_info),
193 )
194 }
195}
196
197impl Serialize for PositionRange {
201 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
202 where
203 S: Serializer,
204 {
205 struct PositionSerializer<'a>(&'a Position);
206
207 impl Serialize for PositionSerializer<'_> {
208 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
209 where
210 S: Serializer,
211 {
212 let mut seq = serializer.serialize_tuple(2)?;
213 seq.serialize_element(&self.0.line)?;
214 seq.serialize_element(&self.0.character)?;
215 seq.end()
216 }
217 }
218
219 let mut seq = serializer.serialize_tuple(2)?;
220 seq.serialize_element(&PositionSerializer(&self.start))?;
221 seq.serialize_element(&PositionSerializer(&self.end))?;
222 seq.end()
223 }
224}
225
226#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
227pub struct Range {
228 #[serde(skip_serializing)]
229 pub specifier: ModuleSpecifier,
230 #[serde(flatten, serialize_with = "serialize_position")]
231 pub range: PositionRange,
232 #[serde(default, skip_serializing)]
233 pub resolution_mode: Option<ResolutionMode>,
234}
235
236fn serialize_position<S: Serializer>(
237 range: &PositionRange,
238 serializer: S,
239) -> Result<S::Ok, S::Error> {
240 let mut seq = serializer.serialize_struct("PositionRange", 2)?;
241 seq.serialize_field("start", &range.start)?;
242 seq.serialize_field("end", &range.end)?;
243 seq.end()
244}
245
246impl fmt::Display for Range {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 write!(f, "{}:{}", self.specifier, self.range.start)
249 }
250}
251
252impl Range {
253 pub fn includes(&self, position: Position) -> bool {
255 self.range.includes(position)
256 }
257}
258
259#[derive(Debug, Clone, Error, JsError)]
260pub enum JsrLoadError {
261 #[class(type)]
262 #[error(
263 "Unsupported checksum in JSR package manifest. Maybe try upgrading deno?"
264 )]
265 UnsupportedManifestChecksum,
266 #[class(inherit)]
267 #[error(transparent)]
268 ContentChecksumIntegrity(ChecksumIntegrityError),
269 #[class(generic)]
270 #[error("Loader should never return an external specifier for a jsr: specifier content load.")]
271 ContentLoadExternalSpecifier,
272 #[class(inherit)]
273 #[error(transparent)]
274 ContentLoad(Arc<LoadError>),
275 #[class(inherit)]
276 #[error("JSR package manifest for '{}' failed to load. {:#}", .0, .1)]
277 PackageManifestLoad(String, #[inherit] Arc<LoadError>),
278 #[class("NotFound")]
279 #[error("JSR package not found: {}", .0)]
280 PackageNotFound(String),
281 #[class("NotFound")]
282 #[error("JSR package version not found: {}", .0)]
283 PackageVersionNotFound(Box<PackageNv>),
284 #[class(inherit)]
285 #[error("JSR package version manifest for '{}' failed to load: {:#}", .0, .1)]
286 PackageVersionManifestLoad(Box<PackageNv>, #[inherit] Arc<dyn JsErrorClass>),
287 #[class(inherit)]
288 #[error("JSR package version manifest for '{}' failed to load: {:#}", .0, .1)]
289 PackageVersionManifestChecksumIntegrity(
290 Box<PackageNv>,
291 #[inherit] ChecksumIntegrityError,
292 ),
293 #[class(inherit)]
294 #[error(transparent)]
295 PackageFormat(JsrPackageFormatError),
296 #[class(inherit)]
297 #[error(transparent)]
298 PackageReqNotFound(JsrPackageReqNotFoundError),
299 #[class(generic)]
300 #[error("Redirects in the JSR registry are not supported (redirected to '{}')", .0)]
301 RedirectInPackage(ModuleSpecifier),
302 #[class("NotFound")]
303 #[error("Unknown export '{}' for '{}'.\n Package exports:\n{}", export_name, .nv, .exports.iter().map(|e| format!(" * {}", e)).collect::<Vec<_>>().join("\n"))]
304 UnknownExport {
305 nv: Box<PackageNv>,
306 export_name: String,
307 exports: Vec<String>,
308 },
309}
310
311#[derive(Error, Debug, Clone, JsError)]
312#[class("NotFound")]
313#[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))]
314pub struct JsrPackageReqNotFoundError {
315 pub req: PackageReq,
316 pub newest_dependency_date: Option<chrono::DateTime<chrono::Utc>>,
317}
318
319#[derive(Error, Debug, Clone, JsError)]
320#[class(type)]
321pub enum JsrPackageFormatError {
322 #[error(transparent)]
323 JsrPackageParseError(PackageReqReferenceParseError),
324 #[error("Version tag not supported in jsr specifiers ('{}').{}",
325 .tag,
326 match .tag.strip_prefix('v').and_then(|v| VersionReq::parse_from_specifier(v).ok().map(|s| s.tag().is_none())).unwrap_or(false) {
327 true => " Remove leading 'v' before version.",
328 false => ""
329 }
330 )]
331 VersionTagNotSupported { tag: SmallStackString },
332}
333
334#[derive(Debug, Clone, Error, JsError)]
335pub enum NpmLoadError {
336 #[class(type)]
337 #[error("npm specifiers are not supported in this environment")]
338 NotSupportedEnvironment,
339 #[class(inherit)]
340 #[error(transparent)]
341 PackageReqResolution(Arc<dyn JsErrorClass>),
342 #[class(inherit)]
343 #[error(transparent)]
344 PackageReqReferenceParse(PackageReqReferenceParseError),
345 #[class(inherit)]
346 #[error(transparent)]
347 RegistryInfo(Arc<dyn JsErrorClass>),
348}
349
350#[derive(Debug, Error, Clone, JsError)]
351pub enum ModuleLoadError {
352 #[class(inherit)]
353 #[error(transparent)]
354 HttpsChecksumIntegrity(ChecksumIntegrityError),
355 #[class(inherit)]
356 #[error(transparent)]
357 Decode(Arc<DecodeError>),
358 #[class(inherit)]
359 #[error(transparent)]
360 Loader(Arc<LoadError>),
361 #[class(inherit)]
362 #[error(transparent)]
363 Jsr(#[from] JsrLoadError),
364 #[class(inherit)]
365 #[error(transparent)]
366 Npm(#[from] NpmLoadError),
367 #[class(generic)]
368 #[error("Too many redirects.")]
369 TooManyRedirects,
370}
371
372#[derive(Debug, JsError)]
373#[class(inherit)]
374pub struct DecodeError {
375 pub mtime: Option<SystemTime>,
378 #[inherit]
379 pub err: std::io::Error,
380}
381
382impl std::error::Error for DecodeError {
383 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
384 self.err.source()
385 }
386}
387
388impl std::fmt::Display for DecodeError {
389 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390 self.err.fmt(f)
391 }
392}
393
394#[derive(Debug, Clone, JsError, Boxed)]
395pub struct ModuleError(pub Box<ModuleErrorKind>);
396
397impl ModuleError {
398 pub fn specifier(&self) -> &ModuleSpecifier {
399 self.as_kind().specifier()
400 }
401
402 pub fn maybe_referrer(&self) -> Option<&Range> {
403 self.as_kind().maybe_referrer()
404 }
405
406 pub fn mtime(&self) -> Option<SystemTime> {
408 self.as_kind().mtime()
409 }
410
411 pub fn to_string_with_range(&self) -> String {
413 self.as_kind().to_string_with_range()
414 }
415}
416
417#[derive(Debug, Clone, JsError)]
418pub enum ModuleErrorKind {
419 #[class(inherit)]
420 Load {
421 specifier: ModuleSpecifier,
422 maybe_referrer: Option<Range>,
423 #[inherit]
424 err: ModuleLoadError,
425 },
426 #[class("NotFound")]
427 Missing {
428 specifier: ModuleSpecifier,
429 maybe_referrer: Option<Range>,
430 },
431 #[class("NotFound")]
432 MissingDynamic {
433 specifier: ModuleSpecifier,
434 referrer: Range,
435 },
436 #[class(inherit)]
437 Parse {
438 specifier: ModuleSpecifier,
439 mtime: Option<SystemTime>,
442 #[inherit]
443 diagnostic: Arc<JsErrorBox>,
444 },
445 #[class(inherit)]
446 WasmParse {
447 specifier: ModuleSpecifier,
448 mtime: Option<SystemTime>,
451 #[inherit]
452 err: wasm_dep_analyzer::ParseError,
453 },
454 #[class(type)]
455 UnsupportedMediaType {
456 specifier: ModuleSpecifier,
457 media_type: MediaType,
458 maybe_referrer: Option<Range>,
459 },
460 #[class(syntax)]
461 InvalidTypeAssertion {
462 specifier: ModuleSpecifier,
463 referrer: Range,
464 actual_media_type: MediaType,
465 expected_media_type: MediaType,
466 },
467 #[class(type)]
468 UnsupportedImportAttributeType {
469 specifier: ModuleSpecifier,
470 referrer: Range,
471 kind: String,
472 },
473}
474
475impl ModuleErrorKind {
476 pub fn specifier(&self) -> &ModuleSpecifier {
477 match self {
478 Self::Load { specifier, .. }
479 | Self::Parse { specifier, .. }
480 | Self::WasmParse { specifier, .. }
481 | Self::UnsupportedMediaType { specifier, .. }
482 | Self::Missing { specifier, .. }
483 | Self::MissingDynamic { specifier, .. }
484 | Self::InvalidTypeAssertion { specifier, .. }
485 | Self::UnsupportedImportAttributeType { specifier, .. } => specifier,
486 }
487 }
488
489 pub fn maybe_referrer(&self) -> Option<&Range> {
490 match self {
491 Self::Load { maybe_referrer, .. }
492 | Self::Missing { maybe_referrer, .. } => maybe_referrer.as_ref(),
493 Self::UnsupportedMediaType { maybe_referrer, .. } => {
494 maybe_referrer.as_ref()
495 }
496 Self::Parse { .. } => None,
497 Self::WasmParse { .. } => None,
498 Self::MissingDynamic { referrer, .. }
499 | Self::InvalidTypeAssertion { referrer, .. }
500 | Self::UnsupportedImportAttributeType { referrer, .. } => Some(referrer),
501 }
502 }
503
504 pub fn mtime(&self) -> Option<SystemTime> {
506 match self {
507 Self::Parse { mtime, .. } | Self::WasmParse { mtime, .. } => *mtime,
508 Self::Load { err, .. } => match err {
509 ModuleLoadError::Decode(decode_error) => decode_error.mtime,
510 ModuleLoadError::HttpsChecksumIntegrity { .. }
511 | ModuleLoadError::Loader { .. }
512 | ModuleLoadError::Jsr { .. }
513 | ModuleLoadError::Npm { .. }
514 | ModuleLoadError::TooManyRedirects => None,
515 },
516 Self::Missing { .. }
517 | Self::MissingDynamic { .. }
518 | Self::UnsupportedMediaType { .. }
519 | Self::InvalidTypeAssertion { .. }
520 | Self::UnsupportedImportAttributeType { .. } => None,
521 }
522 }
523
524 pub fn to_string_with_range(&self) -> String {
526 if let Some(range) = self.maybe_referrer() {
527 format!("{self:#}\n at {range}")
528 } else {
529 format!("{self:#}")
530 }
531 }
532}
533
534impl std::error::Error for ModuleErrorKind {
535 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
536 match self {
537 Self::Load { err, .. } => Some(err),
538 Self::Missing { .. }
539 | Self::MissingDynamic { .. }
540 | Self::Parse { .. }
541 | Self::WasmParse { .. }
542 | Self::UnsupportedMediaType { .. }
543 | Self::InvalidTypeAssertion { .. }
544 | Self::UnsupportedImportAttributeType { .. } => None,
545 }
546 }
547}
548
549impl fmt::Display for ModuleErrorKind {
550 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
551 match self {
552 Self::Load { err, .. } => err.fmt(f),
553 Self::Parse { diagnostic, .. } => write!(f, "The module's source code could not be parsed: {diagnostic}"),
554 Self::WasmParse { specifier, err, .. } => write!(f, "The Wasm module could not be parsed: {err}\n Specifier: {specifier}"),
555 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}"),
556 Self::UnsupportedMediaType { specifier, media_type: MediaType::Cjs | MediaType::Cts, .. } if specifier.scheme() != "file" => write!(f, "Remote CJS modules are not supported.\n Specifier: {specifier}"),
557 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}"),
558 Self::Missing{ specifier, .. } => write!(f, "Module not found \"{specifier}\"."),
559 Self::MissingDynamic{ specifier, .. } => write!(f, "Dynamic import not found \"{specifier}\"."),
560 Self::InvalidTypeAssertion { specifier, actual_media_type: MediaType::Json, expected_media_type, .. } =>
561 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}"),
562 Self::InvalidTypeAssertion { specifier, actual_media_type, expected_media_type, .. } =>
563 write!(f, "Expected a {expected_media_type} module, but identified a {actual_media_type} module.\n Specifier: {specifier}"),
564 Self::UnsupportedImportAttributeType { specifier, kind, .. } =>
565 write!(f, "The import attribute type of \"{kind}\" is unsupported.\n Specifier: {specifier}"),
566 }
567 }
568}
569
570#[derive(Debug, Clone, JsError)]
571pub enum ModuleGraphError {
572 #[class(inherit)]
573 ModuleError(ModuleError),
574 #[class(inherit)]
575 ResolutionError(ResolutionError),
576 #[class(inherit)]
577 TypesResolutionError(ResolutionError),
578}
579
580impl ModuleGraphError {
581 pub fn as_module_error_kind(&self) -> Option<&ModuleErrorKind> {
582 match self {
583 Self::ModuleError(err) => Some(err.as_kind()),
584 _ => None,
585 }
586 }
587
588 fn for_resolution_kind(kind: ResolutionKind, error: ResolutionError) -> Self {
589 match kind {
590 ResolutionKind::Execution => Self::ResolutionError(error),
591 ResolutionKind::Types => Self::TypesResolutionError(error),
592 }
593 }
594
595 pub fn to_string_with_range(&self) -> String {
600 match self {
601 ModuleGraphError::ModuleError(err) => err.to_string_with_range(),
602 ModuleGraphError::ResolutionError(err)
603 | ModuleGraphError::TypesResolutionError(err) => {
604 err.to_string_with_range()
605 }
606 }
607 }
608
609 pub fn maybe_range(&self) -> Option<&Range> {
610 match self {
611 Self::ModuleError(err) => err.maybe_referrer(),
612 Self::ResolutionError(err) | Self::TypesResolutionError(err) => {
613 Some(err.range())
614 }
615 }
616 }
617}
618
619impl std::error::Error for ModuleGraphError {
620 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
621 match self {
622 Self::ModuleError(ref err) => Some(err),
623 Self::ResolutionError(ref err) | Self::TypesResolutionError(ref err) => {
624 Some(err)
625 }
626 }
627 }
628}
629
630impl fmt::Display for ModuleGraphError {
631 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
632 match self {
633 Self::ModuleError(err) => err.fmt(f),
634 Self::ResolutionError(err) => err.fmt(f),
635 Self::TypesResolutionError(err) => {
636 f.write_str("Failed resolving types. ")?;
637 err.fmt(f)
638 }
639 }
640 }
641}
642
643#[derive(Debug, Clone, JsError)]
644#[class(type)]
645pub enum ResolutionError {
646 InvalidDowngrade {
647 specifier: ModuleSpecifier,
648 range: Range,
649 },
650 InvalidJsrHttpsTypesImport {
651 specifier: ModuleSpecifier,
652 range: Range,
653 },
654 InvalidLocalImport {
655 specifier: ModuleSpecifier,
656 range: Range,
657 },
658 InvalidSpecifier {
659 error: SpecifierError,
660 range: Range,
661 },
662 ResolverError {
663 error: Arc<ResolveError>,
664 specifier: String,
665 range: Range,
666 },
667}
668
669impl ResolutionError {
670 pub fn range(&self) -> &Range {
672 match self {
673 Self::InvalidDowngrade { range, .. }
674 | Self::InvalidJsrHttpsTypesImport { range, .. }
675 | Self::InvalidLocalImport { range, .. }
676 | Self::InvalidSpecifier { range, .. }
677 | Self::ResolverError { range, .. } => range,
678 }
679 }
680
681 pub fn to_string_with_range(&self) -> String {
683 format!("{}\n at {}", self, self.range())
684 }
685}
686
687impl std::error::Error for ResolutionError {
688 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
689 match self {
690 Self::InvalidDowngrade { .. }
691 | Self::InvalidJsrHttpsTypesImport { .. }
692 | Self::InvalidLocalImport { .. } => None,
693 Self::InvalidSpecifier { ref error, .. } => Some(error),
694 Self::ResolverError { error, .. } => Some(error.as_ref()),
695 }
696 }
697}
698
699impl PartialEq for ResolutionError {
700 fn eq(&self, other: &Self) -> bool {
701 match (self, other) {
702 (
703 Self::ResolverError {
704 specifier: a,
705 range: a_range,
706 ..
707 },
708 Self::ResolverError {
709 specifier: b,
710 range: b_range,
711 ..
712 },
713 ) => a == b && a_range == b_range,
714 (
715 Self::InvalidDowngrade {
716 specifier: a,
717 range: a_range,
718 ..
719 },
720 Self::InvalidDowngrade {
721 specifier: b,
722 range: b_range,
723 ..
724 },
725 )
726 | (
727 Self::InvalidJsrHttpsTypesImport {
728 specifier: a,
729 range: a_range,
730 ..
731 },
732 Self::InvalidJsrHttpsTypesImport {
733 specifier: b,
734 range: b_range,
735 ..
736 },
737 )
738 | (
739 Self::InvalidLocalImport {
740 specifier: a,
741 range: a_range,
742 ..
743 },
744 Self::InvalidLocalImport {
745 specifier: b,
746 range: b_range,
747 ..
748 },
749 ) => a == b && a_range == b_range,
750 (
751 Self::InvalidSpecifier {
752 error: a,
753 range: a_range,
754 ..
755 },
756 Self::InvalidSpecifier {
757 error: b,
758 range: b_range,
759 ..
760 },
761 ) => a == b && a_range == b_range,
762 _ => false,
763 }
764 }
765}
766
767impl Eq for ResolutionError {}
768
769impl fmt::Display for ResolutionError {
770 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
771 match self {
772 Self::InvalidDowngrade { specifier, .. } => write!(f, "Modules imported via https are not allowed to import http modules.\n Importing: {specifier}"),
773 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}"),
774 Self::InvalidLocalImport { specifier, .. } => write!(f, "Remote modules are not allowed to import local modules. Consider using a dynamic import instead.\n Importing: {specifier}"),
775 Self::ResolverError { error, .. } => error.fmt(f),
776 Self::InvalidSpecifier { error, .. } => error.fmt(f),
777 }
778 }
779}
780
781#[derive(Debug, Clone, PartialEq, Eq)]
782pub struct ResolutionResolved {
783 pub specifier: ModuleSpecifier,
785 pub range: Range,
787}
788
789#[derive(Debug, Clone, PartialEq, Eq)]
790pub enum Resolution {
791 None,
792 Ok(Box<ResolutionResolved>),
793 Err(Box<ResolutionError>),
794}
795
796impl Resolution {
797 pub fn from_resolve_result(
798 result: Result<ModuleSpecifier, ResolveError>,
799 specifier_text: &str,
800 range: Range,
801 ) -> Self {
802 match result {
803 Ok(specifier) => {
804 Resolution::Ok(Box::new(ResolutionResolved { specifier, range }))
805 }
806 Err(err) => {
807 let resolution_error =
808 if let ResolveError::Specifier(specifier_error) = err {
809 ResolutionError::InvalidSpecifier {
810 error: specifier_error.clone(),
811 range,
812 }
813 } else {
814 ResolutionError::ResolverError {
815 error: Arc::new(err),
816 specifier: specifier_text.to_string(),
817 range,
818 }
819 };
820 Self::Err(Box::new(resolution_error))
821 }
822 }
823 }
824
825 pub fn includes(&self, position: Position) -> Option<&Range> {
826 match self {
827 Self::Ok(resolution) if resolution.range.includes(position) => {
828 Some(&resolution.range)
829 }
830 Self::Err(err) => {
831 let range = err.range();
832 if range.includes(position) {
833 Some(range)
834 } else {
835 None
836 }
837 }
838 _ => None,
839 }
840 }
841
842 pub fn is_none(&self) -> bool {
843 matches!(self, Self::None)
844 }
845
846 pub fn maybe_specifier(&self) -> Option<&ModuleSpecifier> {
847 self.ok().map(|r| &r.specifier)
848 }
849
850 pub fn maybe_range(&self) -> Option<&Range> {
851 match self {
852 Resolution::None => None,
853 Resolution::Ok(r) => Some(&r.range),
854 Resolution::Err(e) => Some(e.range()),
855 }
856 }
857
858 pub fn ok(&self) -> Option<&ResolutionResolved> {
859 if let Resolution::Ok(resolved) = self {
860 Some(&**resolved)
861 } else {
862 None
863 }
864 }
865
866 pub fn err(&self) -> Option<&ResolutionError> {
867 if let Resolution::Err(err) = self {
868 Some(&**err)
869 } else {
870 None
871 }
872 }
873}
874
875impl Default for Resolution {
876 fn default() -> Self {
877 Self::None
878 }
879}
880
881fn is_false(v: &bool) -> bool {
882 !v
883}
884
885#[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq)]
886#[serde(rename_all = "camelCase")]
887pub enum ImportKind {
888 Es,
890 Require,
892 TsType,
894 TsModuleAugmentation,
896 TsReferencePath,
898 TsReferenceTypes,
900 JsxImportSource,
902 JsDoc,
904}
905
906impl ImportKind {
907 pub fn is_runtime(&self) -> bool {
908 match self {
909 ImportKind::Es | ImportKind::Require | ImportKind::JsxImportSource => {
910 true
911 }
912 ImportKind::TsType
913 | ImportKind::TsModuleAugmentation
914 | ImportKind::TsReferencePath
915 | ImportKind::TsReferenceTypes
916 | ImportKind::JsDoc => false,
917 }
918 }
919
920 fn is_es(&self) -> bool {
921 matches!(self, ImportKind::Es)
922 }
923}
924
925#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
926#[serde(rename_all = "camelCase")]
927pub struct Import {
928 pub specifier: String,
929 #[serde(skip_serializing_if = "ImportKind::is_es")]
930 pub kind: ImportKind,
931 #[serde(rename = "range")]
932 pub specifier_range: Range,
933 #[serde(skip_serializing_if = "is_false")]
934 pub is_dynamic: bool,
935 #[serde(skip_serializing)]
937 pub is_side_effect: bool,
938 #[serde(skip_serializing)]
941 pub attributes: ImportAttributes,
942}
943
944#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize)]
945#[serde(rename_all = "camelCase")]
946pub struct Dependency {
947 #[serde(rename = "code", skip_serializing_if = "Resolution::is_none")]
948 pub maybe_code: Resolution,
949 #[serde(rename = "type", skip_serializing_if = "Resolution::is_none")]
950 pub maybe_type: Resolution,
951 #[serde(skip_serializing)]
952 pub maybe_deno_types_specifier: Option<String>,
953 #[serde(skip_serializing_if = "is_false")]
954 pub is_dynamic: bool,
955 #[serde(rename = "assertionType", skip_serializing_if = "Option::is_none")]
957 pub maybe_attribute_type: Option<String>,
958 #[serde(skip_serializing)]
961 pub imports: Vec<Import>,
962}
963
964impl Dependency {
965 pub fn get_code(&self) -> Option<&ModuleSpecifier> {
968 self.maybe_code.maybe_specifier()
969 }
970
971 pub fn get_type(&self) -> Option<&ModuleSpecifier> {
974 self.maybe_type.maybe_specifier()
975 }
976
977 pub fn includes(&self, position: Position) -> Option<&Range> {
981 for import in &self.imports {
982 if import.specifier_range.includes(position) {
983 return Some(&import.specifier_range);
984 }
985 }
986 if let Some(range) = self.maybe_type.includes(position) {
988 return Some(range);
989 }
990 None
991 }
992
993 pub fn with_new_resolver(
994 &self,
995 specifier: &str,
996 jsr_url_provider: &dyn JsrUrlProvider,
997 maybe_resolver: Option<&dyn Resolver>,
998 ) -> Self {
999 let maybe_code = self
1000 .maybe_code
1001 .maybe_range()
1002 .map(|r| {
1003 resolve(
1004 specifier,
1005 r.clone(),
1006 ResolutionKind::Execution,
1007 jsr_url_provider,
1008 maybe_resolver,
1009 )
1010 })
1011 .unwrap_or_default();
1012 let maybe_type = self
1013 .maybe_type
1014 .maybe_range()
1015 .map(|r| {
1016 resolve(
1017 self
1018 .maybe_deno_types_specifier
1019 .as_deref()
1020 .unwrap_or(specifier),
1021 r.clone(),
1022 ResolutionKind::Types,
1023 jsr_url_provider,
1024 maybe_resolver,
1025 )
1026 })
1027 .unwrap_or_default();
1028 Self {
1029 maybe_code,
1030 maybe_type,
1031 maybe_deno_types_specifier: self.maybe_deno_types_specifier.clone(),
1032 is_dynamic: self.is_dynamic,
1033 maybe_attribute_type: self.maybe_attribute_type.clone(),
1034 imports: self.imports.clone(),
1035 }
1036 }
1037}
1038
1039#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
1040#[serde(rename_all = "camelCase")]
1041pub struct TypesDependency {
1042 pub specifier: String,
1043 pub dependency: Resolution,
1044}
1045
1046impl TypesDependency {
1047 pub fn with_new_resolver(
1048 &self,
1049 jsr_url_provider: &dyn JsrUrlProvider,
1050 maybe_resolver: Option<&dyn Resolver>,
1051 ) -> Self {
1052 let dependency = self
1053 .dependency
1054 .maybe_range()
1055 .map(|r| {
1056 resolve(
1057 &self.specifier,
1058 r.clone(),
1059 ResolutionKind::Types,
1060 jsr_url_provider,
1061 maybe_resolver,
1062 )
1063 })
1064 .unwrap_or_default();
1065 Self {
1066 specifier: self.specifier.clone(),
1067 dependency,
1068 }
1069 }
1070}
1071
1072fn is_media_type_unknown(media_type: &MediaType) -> bool {
1073 matches!(media_type, MediaType::Unknown)
1074}
1075
1076#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1077pub struct WorkspaceMember {
1078 pub base: Url,
1079 pub name: StackString,
1080 #[serde(skip_serializing_if = "Option::is_none")]
1081 pub version: Option<Version>,
1082 pub exports: IndexMap<String, String>,
1083}
1084
1085impl WorkspaceMember {
1086 pub fn as_nv(&self) -> PackageNv {
1087 PackageNv {
1088 name: self.name.clone(),
1089 version: self
1090 .version
1091 .clone()
1092 .unwrap_or_else(|| Version::parse_standard("0.0.0").unwrap()),
1094 }
1095 }
1096}
1097
1098#[derive(Debug, Clone, Serialize)]
1099#[serde(rename_all = "camelCase")]
1100#[serde(tag = "kind")]
1101pub enum Module {
1102 #[serde(rename = "esm")]
1104 Js(JsModule),
1105 #[serde(rename = "asserted")]
1107 Json(JsonModule),
1108 Wasm(WasmModule),
1109 Npm(NpmModule),
1110 Node(BuiltInNodeModule),
1111 External(ExternalModule),
1112}
1113
1114impl Module {
1115 pub fn specifier(&self) -> &ModuleSpecifier {
1116 match self {
1117 Module::Js(module) => &module.specifier,
1118 Module::Json(module) => &module.specifier,
1119 Module::Wasm(module) => &module.specifier,
1120 Module::Npm(module) => &module.specifier,
1121 Module::Node(module) => &module.specifier,
1122 Module::External(module) => &module.specifier,
1123 }
1124 }
1125
1126 pub fn media_type(&self) -> MediaType {
1127 match self {
1128 Module::Js(module) => module.media_type,
1129 Module::Json(module) => module.media_type,
1130 Module::Wasm(_) => MediaType::Wasm,
1131 Module::Node(_) => MediaType::JavaScript,
1132 Module::Npm(_) | Module::External(_) => MediaType::Unknown,
1133 }
1134 }
1135
1136 pub fn mtime(&self) -> Option<SystemTime> {
1137 match self {
1138 Module::Js(m) => m.mtime,
1139 Module::Json(m) => m.mtime,
1140 Module::Wasm(m) => m.mtime,
1141 Module::Npm(_) | Module::Node(_) | Module::External(_) => None,
1142 }
1143 }
1144
1145 pub fn json(&self) -> Option<&JsonModule> {
1146 if let Module::Json(module) = &self {
1147 Some(module)
1148 } else {
1149 None
1150 }
1151 }
1152
1153 pub fn js(&self) -> Option<&JsModule> {
1154 if let Module::Js(module) = &self {
1155 Some(module)
1156 } else {
1157 None
1158 }
1159 }
1160
1161 pub fn npm(&self) -> Option<&NpmModule> {
1162 if let Module::Npm(module) = &self {
1163 Some(module)
1164 } else {
1165 None
1166 }
1167 }
1168
1169 pub fn node(&self) -> Option<&BuiltInNodeModule> {
1170 if let Module::Node(module) = &self {
1171 Some(module)
1172 } else {
1173 None
1174 }
1175 }
1176
1177 pub fn external(&self) -> Option<&ExternalModule> {
1178 if let Module::External(module) = &self {
1179 Some(module)
1180 } else {
1181 None
1182 }
1183 }
1184
1185 pub fn source(&self) -> Option<&Arc<str>> {
1186 match self {
1187 crate::Module::Js(m) => Some(&m.source.text),
1188 crate::Module::Json(m) => Some(&m.source.text),
1189 crate::Module::Wasm(_)
1190 | crate::Module::Npm(_)
1191 | crate::Module::Node(_)
1192 | crate::Module::External(_) => None,
1193 }
1194 }
1195
1196 pub fn maybe_types_dependency(&self) -> Option<&TypesDependency> {
1197 match self {
1198 Module::Js(js_module) => js_module.maybe_types_dependency.as_ref(),
1199 Module::Wasm(_)
1200 | Module::Json(_)
1201 | Module::Npm(_)
1202 | Module::Node(_)
1203 | Module::External(_) => None,
1204 }
1205 }
1206
1207 pub fn dependencies(&self) -> &IndexMap<String, Dependency> {
1208 match self {
1209 Module::Js(js_module) => &js_module.dependencies,
1210 Module::Wasm(wasm_module) => &wasm_module.dependencies,
1211 Module::Npm(_)
1212 | Module::Node(_)
1213 | Module::External(_)
1214 | Module::Json(_) => EMPTY_DEPS.get_or_init(Default::default),
1215 }
1216 }
1217
1218 pub fn dependencies_prefer_fast_check(
1219 &self,
1220 ) -> &IndexMap<String, Dependency> {
1221 match self {
1222 Module::Js(js_module) => js_module.dependencies_prefer_fast_check(),
1223 Module::Wasm(wasm_module) => &wasm_module.dependencies,
1224 Module::Npm(_)
1225 | Module::Node(_)
1226 | Module::External(_)
1227 | Module::Json(_) => EMPTY_DEPS.get_or_init(Default::default),
1228 }
1229 }
1230}
1231
1232static EMPTY_DEPS: std::sync::OnceLock<IndexMap<String, Dependency>> =
1233 std::sync::OnceLock::new();
1234
1235#[derive(Debug, Clone, Serialize)]
1237#[serde(rename_all = "camelCase")]
1238pub struct NpmModule {
1239 pub specifier: ModuleSpecifier,
1240 #[serde(skip_serializing)]
1241 pub nv_reference: NpmPackageNvReference,
1242}
1243
1244#[derive(Debug, Clone, Serialize)]
1249#[serde(rename_all = "camelCase")]
1250pub struct ExternalModule {
1251 pub specifier: ModuleSpecifier,
1252 #[serde(flatten, skip_serializing_if = "Option::is_none")]
1253 pub maybe_cache_info: Option<CacheInfo>,
1254 #[serde(skip_serializing)]
1256 pub was_asset_load: bool,
1257}
1258
1259#[derive(Debug, Clone, Serialize)]
1260#[serde(rename_all = "camelCase")]
1261pub struct BuiltInNodeModule {
1262 pub specifier: ModuleSpecifier,
1264 pub module_name: String,
1266}
1267
1268#[derive(Debug, Clone, Serialize)]
1269#[serde(rename_all = "camelCase")]
1270pub struct JsonModule {
1271 pub specifier: ModuleSpecifier,
1272 #[serde(flatten, skip_serializing_if = "Option::is_none")]
1273 pub maybe_cache_info: Option<CacheInfo>,
1274 #[serde(rename = "size", serialize_with = "serialize_source")]
1275 pub source: ModuleTextSource,
1276 #[serde(skip_serializing)]
1277 pub mtime: Option<SystemTime>,
1278 pub media_type: MediaType,
1281}
1282
1283impl JsonModule {
1284 pub fn size(&self) -> usize {
1286 self.source.text.len()
1287 }
1288}
1289
1290#[cfg(feature = "fast_check")]
1291#[derive(Debug, Clone)]
1292pub enum FastCheckTypeModuleSlot {
1293 Module(Box<FastCheckTypeModule>),
1294 Error(Vec<crate::fast_check::FastCheckDiagnostic>),
1295}
1296
1297#[cfg(feature = "fast_check")]
1298#[derive(Debug, Clone)]
1299pub struct FastCheckTypeModule {
1300 pub dependencies: IndexMap<String, Dependency>,
1301 pub source: Arc<str>,
1302 pub source_map: Arc<str>,
1303 pub dts: Option<crate::fast_check::FastCheckDtsModule>,
1304}
1305
1306#[derive(Debug, Clone)]
1307pub struct ModuleTextSource {
1308 pub text: Arc<str>,
1312 pub decoded_kind: DecodedArcSourceDetailKind,
1313}
1314
1315impl ModuleTextSource {
1316 pub fn new_unknown(text: Arc<str>) -> Self {
1317 Self {
1318 text,
1319 decoded_kind: DecodedArcSourceDetailKind::Changed,
1322 }
1323 }
1324
1325 pub fn try_get_original_bytes(&self) -> Option<Arc<[u8]>> {
1328 match self.decoded_kind {
1329 DecodedArcSourceDetailKind::Unchanged => unsafe {
1330 let raw_ptr = Arc::into_raw(self.text.clone());
1331 Some(Arc::from_raw(
1332 std::mem::transmute::<*const str, *const [u8]>(raw_ptr),
1333 ))
1334 },
1335 DecodedArcSourceDetailKind::Changed => None,
1336 DecodedArcSourceDetailKind::OnlyUtf8Bom => {
1337 let mut bytes =
1339 Vec::with_capacity(self.text.len() + BOM_CHAR.len_utf8());
1340 bytes.extend([0xEF, 0xBB, 0xBF]);
1341 bytes.extend(self.text.as_bytes());
1342 Some(Arc::from(bytes))
1343 }
1344 }
1345 }
1346}
1347
1348#[derive(Debug, Clone, Serialize)]
1349#[serde(rename_all = "camelCase")]
1350pub struct JsModule {
1351 #[serde(skip_serializing)]
1352 pub is_script: bool,
1353 #[serde(
1354 skip_serializing_if = "IndexMap::is_empty",
1355 serialize_with = "serialize_dependencies"
1356 )]
1357 pub dependencies: IndexMap<String, Dependency>,
1358 #[serde(flatten, skip_serializing_if = "Option::is_none")]
1359 pub maybe_cache_info: Option<CacheInfo>,
1360 #[serde(skip_serializing)]
1361 pub mtime: Option<SystemTime>,
1362 #[serde(rename = "size", serialize_with = "serialize_source")]
1363 pub source: ModuleTextSource,
1364 #[serde(rename = "typesDependency", skip_serializing_if = "Option::is_none")]
1365 pub maybe_types_dependency: Option<TypesDependency>,
1366 #[serde(skip_serializing_if = "is_media_type_unknown")]
1367 pub media_type: MediaType,
1368 pub specifier: ModuleSpecifier,
1369 #[cfg(feature = "fast_check")]
1370 #[serde(skip_serializing)]
1371 pub fast_check: Option<FastCheckTypeModuleSlot>,
1372}
1373
1374impl JsModule {
1375 pub fn size(&self) -> usize {
1377 self.source.text.len()
1378 }
1379
1380 #[cfg(feature = "fast_check")]
1381 pub fn fast_check_diagnostics(
1382 &self,
1383 ) -> Option<&Vec<crate::fast_check::FastCheckDiagnostic>> {
1384 let module_slot = self.fast_check.as_ref()?;
1385 match module_slot {
1386 FastCheckTypeModuleSlot::Module(_) => None,
1387 FastCheckTypeModuleSlot::Error(d) => Some(d),
1388 }
1389 }
1390
1391 #[cfg(feature = "fast_check")]
1392 pub fn fast_check_module(&self) -> Option<&FastCheckTypeModule> {
1393 let module_slot = self.fast_check.as_ref()?;
1394 match module_slot {
1395 FastCheckTypeModuleSlot::Module(m) => Some(m),
1396 FastCheckTypeModuleSlot::Error(_) => None,
1397 }
1398 }
1399
1400 pub fn dependencies_prefer_fast_check(
1401 &self,
1402 ) -> &IndexMap<String, Dependency> {
1403 #[cfg(feature = "fast_check")]
1404 {
1405 match self.fast_check_module() {
1406 Some(fast_check) => &fast_check.dependencies,
1407 None => &self.dependencies,
1408 }
1409 }
1410 #[cfg(not(feature = "fast_check"))]
1411 &self.dependencies
1412 }
1413}
1414
1415#[derive(Debug, Clone, Serialize)]
1416#[serde(rename_all = "camelCase")]
1417pub struct WasmModule {
1418 pub specifier: ModuleSpecifier,
1419 #[serde(skip_serializing)]
1420 pub mtime: Option<SystemTime>,
1421 #[serde(
1422 skip_serializing_if = "IndexMap::is_empty",
1423 serialize_with = "serialize_dependencies"
1424 )]
1425 pub dependencies: IndexMap<String, Dependency>,
1426 #[serde(rename = "size", serialize_with = "serialize_source_bytes")]
1427 pub source: Arc<[u8]>,
1428 #[serde(skip_serializing)]
1429 pub source_dts: Arc<str>,
1430 #[serde(flatten, skip_serializing_if = "Option::is_none")]
1431 pub maybe_cache_info: Option<CacheInfo>,
1432}
1433
1434impl WasmModule {
1435 pub fn size(&self) -> usize {
1437 self.source.len()
1438 }
1439}
1440
1441#[allow(clippy::large_enum_variant)]
1442#[derive(Debug, Clone)]
1443pub(crate) enum ModuleSlot {
1444 Module(Module),
1446 Err(ModuleError),
1448 Pending {
1450 is_asset: bool,
1452 },
1453}
1454
1455impl ModuleSlot {
1456 #[cfg(test)]
1457 pub fn module(&self) -> Option<&Module> {
1458 if let ModuleSlot::Module(module) = self {
1459 Some(module)
1460 } else {
1461 None
1462 }
1463 }
1464
1465 pub fn is_pending(&self) -> bool {
1466 matches!(self, ModuleSlot::Pending { .. })
1467 }
1468
1469 pub fn was_external_asset_load(&self) -> bool {
1470 matches!(
1471 self,
1472 ModuleSlot::Module(Module::External(ExternalModule {
1473 was_asset_load: true,
1474 ..
1475 }))
1476 )
1477 }
1478
1479 pub fn is_pending_asset_load(&self) -> bool {
1480 matches!(self, ModuleSlot::Pending { is_asset: true })
1481 }
1482
1483 fn as_err_kind(&self) -> Option<&ModuleErrorKind> {
1484 match self {
1485 ModuleSlot::Err(err) => Some(err.as_kind()),
1486 _ => None,
1487 }
1488 }
1489}
1490
1491type ModuleResult<'a> =
1492 (&'a ModuleSpecifier, Result<&'a Module, &'a ModuleError>);
1493
1494fn to_result<'a>(
1497 (specifier, module_slot): (&'a ModuleSpecifier, &'a ModuleSlot),
1498) -> Option<ModuleResult<'a>> {
1499 match module_slot {
1500 ModuleSlot::Err(err) => Some((specifier, Err(err))),
1501 ModuleSlot::Module(module) => Some((specifier, Ok(module))),
1502 ModuleSlot::Pending { .. } => None,
1503 }
1504}
1505
1506#[derive(Debug, Clone, Serialize)]
1511pub struct GraphImport {
1512 #[serde(serialize_with = "serialize_dependencies")]
1515 pub dependencies: IndexMap<String, Dependency>,
1516}
1517
1518impl GraphImport {
1519 pub fn new(
1520 referrer: &ModuleSpecifier,
1521 imports: Vec<String>,
1522 jsr_url_provider: &dyn JsrUrlProvider,
1523 maybe_resolver: Option<&dyn Resolver>,
1524 ) -> Self {
1525 let dependencies = imports
1526 .into_iter()
1527 .map(|import| {
1528 let referrer_range = Range {
1529 specifier: referrer.clone(),
1530 range: PositionRange::zeroed(),
1531 resolution_mode: None,
1532 };
1533 let maybe_type = resolve(
1534 &import,
1535 referrer_range,
1536 ResolutionKind::Types,
1537 jsr_url_provider,
1538 maybe_resolver,
1539 );
1540 (
1541 import,
1542 Dependency {
1543 is_dynamic: false,
1544 maybe_code: Resolution::None,
1545 maybe_type,
1546 maybe_deno_types_specifier: None,
1547 maybe_attribute_type: None,
1548 imports: vec![],
1549 },
1550 )
1551 })
1552 .collect();
1553 Self { dependencies }
1554 }
1555}
1556
1557#[cfg(feature = "fast_check")]
1558#[derive(Debug, Default)]
1559pub enum WorkspaceFastCheckOption<'a> {
1560 #[default]
1561 Disabled,
1562 Enabled(&'a [WorkspaceMember]),
1563}
1564
1565#[cfg(feature = "fast_check")]
1566#[derive(Default)]
1567pub struct BuildFastCheckTypeGraphOptions<'a> {
1568 pub fast_check_cache: Option<&'a dyn crate::fast_check::FastCheckCache>,
1569 pub fast_check_dts: bool,
1570 pub jsr_url_provider: &'a dyn JsrUrlProvider,
1571 pub es_parser: Option<&'a dyn crate::ast::EsParser>,
1572 pub resolver: Option<&'a dyn Resolver>,
1573 pub workspace_fast_check: WorkspaceFastCheckOption<'a>,
1575}
1576
1577pub struct BuildOptions<'a> {
1578 pub is_dynamic: bool,
1579 pub skip_dynamic_deps: bool,
1581 pub unstable_bytes_imports: bool,
1583 pub unstable_text_imports: bool,
1585 pub executor: &'a dyn Executor,
1586 pub locker: Option<&'a mut dyn Locker>,
1587 pub file_system: &'a FileSystem,
1588 pub jsr_url_provider: &'a dyn JsrUrlProvider,
1589 pub jsr_version_resolver: Cow<'a, JsrVersionResolver>,
1590 pub passthrough_jsr_specifiers: bool,
1594 pub module_analyzer: &'a dyn ModuleAnalyzer,
1595 pub module_info_cacher: &'a dyn ModuleInfoCacher,
1596 pub npm_resolver: Option<&'a dyn NpmResolver>,
1597 pub reporter: Option<&'a dyn Reporter>,
1598 pub resolver: Option<&'a dyn Resolver>,
1599 pub jsr_metadata_store: Option<Rc<JsrMetadataStore>>,
1600}
1601
1602impl Default for BuildOptions<'_> {
1603 fn default() -> Self {
1604 Self {
1605 is_dynamic: false,
1606 skip_dynamic_deps: false,
1607 unstable_bytes_imports: false,
1608 unstable_text_imports: false,
1609 executor: Default::default(),
1610 locker: None,
1611 file_system: &NullFileSystem,
1612 jsr_url_provider: Default::default(),
1613 jsr_version_resolver: Default::default(),
1614 passthrough_jsr_specifiers: false,
1615 module_analyzer: Default::default(),
1616 module_info_cacher: Default::default(),
1617 npm_resolver: None,
1618 reporter: None,
1619 resolver: None,
1620 jsr_metadata_store: None,
1621 }
1622 }
1623}
1624
1625#[derive(Debug, Copy, Clone)]
1626pub enum ModuleEntryRef<'a> {
1627 Module(&'a Module),
1628 Err(&'a ModuleError),
1629 Redirect(&'a ModuleSpecifier),
1630}
1631
1632pub trait CheckJsResolver: std::fmt::Debug {
1633 fn resolve(&self, specifier: &ModuleSpecifier) -> bool;
1634}
1635
1636#[derive(Debug, Clone, Copy)]
1637pub enum CheckJsOption<'a> {
1638 True,
1639 False,
1640 Custom(&'a dyn CheckJsResolver),
1641}
1642
1643impl CheckJsOption<'_> {
1644 pub fn resolve(&self, specifier: &ModuleSpecifier) -> bool {
1645 match self {
1646 CheckJsOption::True => true,
1647 CheckJsOption::False => false,
1648 CheckJsOption::Custom(check_js_resolver) => {
1649 check_js_resolver.resolve(specifier)
1650 }
1651 }
1652 }
1653}
1654
1655#[derive(Debug, Clone)]
1656pub struct WalkOptions<'a> {
1657 pub check_js: CheckJsOption<'a>,
1659 pub follow_dynamic: bool,
1660 pub kind: GraphKind,
1662 pub prefer_fast_check_graph: bool,
1669}
1670
1671pub struct FillFromLockfileOptions<
1672 'a,
1673 TRedirectIter: Iterator<Item = (&'a str, &'a str)>,
1674 TPackageSpecifiersIter: Iterator<Item = (&'a JsrDepPackageReq, &'a str)>,
1675> {
1676 pub redirects: TRedirectIter,
1677 pub package_specifiers: TPackageSpecifiersIter,
1678}
1679
1680pub struct ModuleEntryIterator<'a, 'options> {
1681 graph: &'a ModuleGraph,
1682 seen: HashSet<&'a ModuleSpecifier>,
1683 visiting: VecDeque<&'a ModuleSpecifier>,
1684 follow_dynamic: bool,
1685 kind: GraphKind,
1686 check_js: CheckJsOption<'options>,
1687 prefer_fast_check_graph: bool,
1688 previous_module: Option<ModuleEntryRef<'a>>,
1689}
1690
1691impl<'a, 'options> ModuleEntryIterator<'a, 'options> {
1692 fn new(
1693 graph: &'a ModuleGraph,
1694 roots: impl Iterator<Item = &'a ModuleSpecifier>,
1695 options: WalkOptions<'options>,
1696 ) -> Self {
1697 let mut seen =
1698 HashSet::<&'a ModuleSpecifier>::with_capacity(graph.specifiers_count());
1699 let mut visiting = VecDeque::<&'a ModuleSpecifier>::new();
1700 for root in roots {
1701 seen.insert(root);
1702 visiting.push_back(root);
1703 }
1704 for (_, dep) in graph.imports.values().flat_map(|i| &i.dependencies) {
1705 let mut resolutions = Vec::with_capacity(2);
1706 resolutions.push(&dep.maybe_code);
1707 if options.kind.include_types() {
1708 resolutions.push(&dep.maybe_type);
1709 }
1710 #[allow(clippy::manual_flatten)]
1711 for resolution in resolutions {
1712 if let Resolution::Ok(resolved) = resolution {
1713 let specifier = &resolved.specifier;
1714 if seen.insert(specifier) {
1715 visiting.push_front(specifier);
1716 }
1717 }
1718 }
1719 }
1720
1721 Self {
1722 graph,
1723 seen,
1724 visiting,
1725 follow_dynamic: options.follow_dynamic,
1726 kind: options.kind,
1727 check_js: options.check_js,
1728 prefer_fast_check_graph: options.prefer_fast_check_graph,
1729 previous_module: None,
1730 }
1731 }
1732
1733 pub fn skip_previous_dependencies(&mut self) {
1735 self.previous_module = None;
1736 }
1737
1738 pub fn errors(self) -> ModuleGraphErrorIterator<'a, 'options> {
1742 ModuleGraphErrorIterator::new(self)
1743 }
1744
1745 #[allow(clippy::result_large_err)]
1752 pub fn validate(self) -> Result<(), ModuleGraphError> {
1753 if let Some(err) = self.errors().next() {
1754 Err(err)
1755 } else {
1756 Ok(())
1757 }
1758 }
1759
1760 fn is_checkable(
1762 &self,
1763 specifier: &ModuleSpecifier,
1764 media_type: MediaType,
1765 ) -> bool {
1766 match media_type {
1767 MediaType::TypeScript
1768 | MediaType::Mts
1769 | MediaType::Cts
1770 | MediaType::Dts
1771 | MediaType::Dmts
1772 | MediaType::Dcts
1773 | MediaType::Tsx
1774 | MediaType::Json
1775 | MediaType::Wasm => true,
1776 MediaType::Css
1777 | MediaType::SourceMap
1778 | MediaType::Html
1779 | MediaType::Sql
1780 | MediaType::Unknown => false,
1781 MediaType::JavaScript
1782 | MediaType::Jsx
1783 | MediaType::Mjs
1784 | MediaType::Cjs => self.check_js.resolve(specifier),
1785 }
1786 }
1787
1788 fn analyze_module_deps(
1789 &mut self,
1790 module_deps: &'a IndexMap<String, Dependency>,
1791 ) {
1792 for dep in module_deps.values().rev() {
1793 if !dep.is_dynamic || self.follow_dynamic {
1794 let mut resolutions = Vec::with_capacity(2);
1795 resolutions.push(&dep.maybe_code);
1796 if self.kind.include_types() {
1797 resolutions.push(&dep.maybe_type);
1798 }
1799 #[allow(clippy::manual_flatten)]
1800 for resolution in resolutions {
1801 if let Resolution::Ok(resolved) = resolution {
1802 let specifier = &resolved.specifier;
1803 if self.seen.insert(specifier) {
1804 self.visiting.push_front(specifier);
1805 }
1806 }
1807 }
1808 }
1809 }
1810 }
1811}
1812
1813impl<'a> Iterator for ModuleEntryIterator<'a, '_> {
1814 type Item = (&'a ModuleSpecifier, ModuleEntryRef<'a>);
1815
1816 fn next(&mut self) -> Option<Self::Item> {
1817 match self.previous_module.take() {
1818 Some(ModuleEntryRef::Module(module)) => {
1819 let check_types = self.kind.include_types()
1820 && self.is_checkable(module.specifier(), module.media_type());
1821 let module_deps = if check_types && self.prefer_fast_check_graph {
1822 module.dependencies_prefer_fast_check()
1823 } else {
1824 module.dependencies()
1825 };
1826 self.analyze_module_deps(module_deps);
1827 }
1828 Some(ModuleEntryRef::Redirect(specifier)) => {
1829 if self.seen.insert(specifier) {
1830 self.visiting.push_front(specifier);
1831 }
1832 }
1833 Some(ModuleEntryRef::Err(_)) | None => {}
1834 }
1835
1836 let (specifier, module_entry) = loop {
1837 let specifier = self.visiting.pop_front()?;
1838 match self.graph.module_slots.get_key_value(specifier) {
1839 Some((specifier, module_slot)) => {
1840 match module_slot {
1841 ModuleSlot::Pending { .. } => {
1842 }
1844 ModuleSlot::Module(module) => {
1845 if let Module::Js(module) = &module {
1846 if self.kind.include_types() {
1847 if let Some(Resolution::Ok(resolved)) = module
1848 .maybe_types_dependency
1849 .as_ref()
1850 .map(|d| &d.dependency)
1851 {
1852 let specifier = &resolved.specifier;
1853 if self.seen.insert(specifier) {
1854 self.visiting.push_front(specifier);
1855 }
1856 if self.kind == GraphKind::TypesOnly {
1857 continue; }
1859 } else if self.kind == GraphKind::TypesOnly
1860 && !self.is_checkable(&module.specifier, module.media_type)
1861 {
1862 continue; }
1864 }
1865 }
1866 break (specifier, ModuleEntryRef::Module(module));
1867 }
1868 ModuleSlot::Err(err) => {
1869 break (specifier, ModuleEntryRef::Err(err))
1870 }
1871 }
1872 }
1873 None => {
1874 if let Some((specifier, to)) =
1875 self.graph.redirects.get_key_value(specifier)
1876 {
1877 break (specifier, ModuleEntryRef::Redirect(to));
1878 }
1879 }
1880 }
1881 };
1882
1883 self.previous_module = Some(module_entry);
1884
1885 Some((specifier, module_entry))
1886 }
1887}
1888
1889pub struct ModuleGraphErrorIterator<'a, 'options> {
1890 iterator: ModuleEntryIterator<'a, 'options>,
1891 next_errors: Vec<ModuleGraphError>,
1892}
1893
1894impl<'a, 'options> ModuleGraphErrorIterator<'a, 'options> {
1895 pub fn new(iterator: ModuleEntryIterator<'a, 'options>) -> Self {
1896 Self {
1897 iterator,
1898 next_errors: Default::default(),
1899 }
1900 }
1901
1902 fn check_resolution(
1903 &self,
1904 module: &Module,
1905 kind: ResolutionKind,
1906 specifier_text: &str,
1907 resolution: &Resolution,
1908 is_dynamic: bool,
1909 ) -> Option<ModuleGraphError> {
1910 match resolution {
1911 Resolution::Ok(resolved) => {
1912 let referrer_scheme = module.specifier().scheme();
1913 let specifier_scheme = resolved.specifier.scheme();
1914 if referrer_scheme == "https" && specifier_scheme == "http" {
1915 Some(ModuleGraphError::for_resolution_kind(
1916 kind,
1917 ResolutionError::InvalidDowngrade {
1918 specifier: resolved.specifier.clone(),
1919 range: resolved.range.clone(),
1920 },
1921 ))
1922 } else if matches!(referrer_scheme, "https" | "http")
1923 && matches!(specifier_scheme, "file")
1924 && specifier_text.to_lowercase().starts_with("file://")
1925 {
1926 Some(ModuleGraphError::for_resolution_kind(
1927 kind,
1928 ResolutionError::InvalidLocalImport {
1929 specifier: resolved.specifier.clone(),
1930 range: resolved.range.clone(),
1931 },
1932 ))
1933 } else if self.iterator.follow_dynamic {
1934 let resolved_specifier =
1935 self.iterator.graph.resolve(&resolved.specifier);
1936 let module_slot =
1937 self.iterator.graph.module_slots.get(resolved_specifier);
1938 if let Some(ModuleErrorKind::Missing {
1939 specifier,
1940 maybe_referrer,
1941 }) = module_slot.and_then(|m| m.as_err_kind())
1942 {
1943 if is_dynamic {
1945 Some(ModuleGraphError::ModuleError(
1946 ModuleErrorKind::MissingDynamic {
1947 specifier: specifier.clone(),
1948 referrer: resolved.range.clone(),
1949 }
1950 .into_box(),
1951 ))
1952 } else {
1953 Some(ModuleGraphError::ModuleError(
1954 ModuleErrorKind::Missing {
1955 specifier: specifier.clone(),
1956 maybe_referrer: maybe_referrer.clone(),
1957 }
1958 .into_box(),
1959 ))
1960 }
1961 } else {
1962 None
1963 }
1964 } else {
1965 None
1966 }
1967 }
1968 Resolution::Err(err) => {
1969 Some(ModuleGraphError::for_resolution_kind(kind, *err.clone()))
1970 }
1971 Resolution::None => None,
1972 }
1973 }
1974}
1975
1976impl Iterator for ModuleGraphErrorIterator<'_, '_> {
1977 type Item = ModuleGraphError;
1978
1979 fn next(&mut self) -> Option<Self::Item> {
1980 while self.next_errors.is_empty() {
1981 let kind = self.iterator.kind;
1982 let follow_dynamic = self.iterator.follow_dynamic;
1983 let prefer_fast_check_graph = self.iterator.prefer_fast_check_graph;
1984
1985 if let Some((_, module_entry)) = self.iterator.next() {
1986 match module_entry {
1987 ModuleEntryRef::Module(module) => {
1988 if kind.include_types() {
1989 if let Some(dep) = module.maybe_types_dependency().as_ref() {
1990 if let Some(err) = self.check_resolution(
1991 module,
1992 ResolutionKind::Types,
1993 &dep.specifier,
1994 &dep.dependency,
1995 false,
1996 ) {
1997 self.next_errors.push(err);
1998 }
1999 }
2000 }
2001
2002 let check_types = kind.include_types()
2003 && self
2004 .iterator
2005 .is_checkable(module.specifier(), module.media_type());
2006 let module_deps = if check_types && prefer_fast_check_graph {
2007 module.dependencies_prefer_fast_check()
2008 } else {
2009 module.dependencies()
2010 };
2011 for (specifier_text, dep) in module_deps {
2012 if follow_dynamic || !dep.is_dynamic {
2013 if let Some(err) = self.check_resolution(
2014 module,
2015 ResolutionKind::Execution,
2016 specifier_text,
2017 &dep.maybe_code,
2018 dep.is_dynamic,
2019 ) {
2020 self.next_errors.push(err);
2021 }
2022 if check_types {
2023 if let Some(err) = self.check_resolution(
2024 module,
2025 ResolutionKind::Types,
2026 specifier_text,
2027 &dep.maybe_type,
2028 dep.is_dynamic,
2029 ) {
2030 self.next_errors.push(err);
2031 }
2032 }
2033 }
2034 }
2035 }
2036 ModuleEntryRef::Err(error) => {
2037 let should_ignore = follow_dynamic
2040 && matches!(error.as_kind(), ModuleErrorKind::Missing { .. });
2041 if !should_ignore {
2042 self
2043 .next_errors
2044 .push(ModuleGraphError::ModuleError(error.clone()));
2045 }
2046 }
2047 ModuleEntryRef::Redirect(_) => {
2048 }
2050 }
2051 } else {
2052 break; }
2054 }
2055
2056 self.next_errors.pop()
2057 }
2058}
2059
2060#[derive(Debug, Clone, Serialize)]
2066pub struct ModuleGraph {
2067 #[serde(skip_serializing)]
2068 graph_kind: GraphKind,
2069 pub roots: IndexSet<ModuleSpecifier>,
2070 #[serde(rename = "modules")]
2071 #[serde(serialize_with = "serialize_module_slots")]
2072 pub(crate) module_slots: BTreeMap<ModuleSpecifier, ModuleSlot>,
2073 #[serde(skip_serializing_if = "IndexMap::is_empty")]
2074 #[serde(serialize_with = "serialize_graph_imports")]
2075 pub imports: IndexMap<ModuleSpecifier, GraphImport>,
2076 pub redirects: BTreeMap<ModuleSpecifier, ModuleSpecifier>,
2077 #[serde(skip_serializing)]
2078 pub npm_packages: IndexSet<PackageNv>,
2079 #[serde(skip_serializing)]
2080 pub has_node_specifier: bool,
2081 #[serde(rename = "packages")]
2082 #[serde(skip_serializing_if = "PackageSpecifiers::is_empty")]
2083 pub packages: PackageSpecifiers,
2084 #[serde(skip_serializing)]
2087 pub npm_dep_graph_result: Result<(), Arc<dyn JsErrorClass>>,
2088}
2089
2090impl ModuleGraph {
2091 pub fn new(graph_kind: GraphKind) -> Self {
2092 Self {
2093 graph_kind,
2094 roots: Default::default(),
2095 module_slots: Default::default(),
2096 imports: Default::default(),
2097 redirects: Default::default(),
2098 npm_packages: Default::default(),
2099 has_node_specifier: false,
2100 packages: Default::default(),
2101 npm_dep_graph_result: Ok(()),
2102 }
2103 }
2104
2105 pub fn graph_kind(&self) -> GraphKind {
2106 self.graph_kind
2107 }
2108
2109 pub fn fill_from_lockfile<
2112 'a,
2113 TRedirectIter: Iterator<Item = (&'a str, &'a str)>,
2114 TPackageSpecifiersIter: Iterator<Item = (&'a JsrDepPackageReq, &'a str)>,
2115 >(
2116 &mut self,
2117 options: FillFromLockfileOptions<'a, TRedirectIter, TPackageSpecifiersIter>,
2118 ) {
2119 for (from, to) in options.redirects {
2120 if let Ok(from) = ModuleSpecifier::parse(from) {
2121 if let Ok(to) = ModuleSpecifier::parse(to) {
2122 if !matches!(from.scheme(), "file" | "npm" | "jsr") {
2123 self.redirects.insert(from, to);
2124 }
2125 }
2126 }
2127 }
2128 for (req_dep, value) in options.package_specifiers {
2129 match req_dep.kind {
2130 deno_semver::package::PackageKind::Jsr => {
2131 if let Ok(version) = Version::parse_standard(value) {
2132 self.packages.add_nv(
2133 req_dep.req.clone(),
2134 PackageNv {
2135 name: req_dep.req.name.clone(),
2136 version,
2137 },
2138 );
2139 }
2140 }
2141 deno_semver::package::PackageKind::Npm => {
2142 }
2144 }
2145 }
2146 }
2147
2148 pub async fn build<'a>(
2149 &mut self,
2150 roots: Vec<ModuleSpecifier>,
2151 imports: Vec<ReferrerImports>,
2152 loader: &'a dyn Loader,
2153 options: BuildOptions<'a>,
2154 ) {
2155 Builder::new(self, loader, options)
2156 .build(roots, imports)
2157 .await
2158 }
2159
2160 pub async fn reload<'a>(
2161 &mut self,
2162 specifiers: Vec<ModuleSpecifier>,
2163 loader: &'a dyn Loader,
2164 options: BuildOptions<'a>,
2165 ) {
2166 Builder::new(self, loader, options).reload(specifiers).await
2167 }
2168
2169 #[cfg(feature = "fast_check")]
2170 pub fn build_fast_check_type_graph(
2171 &mut self,
2172 options: BuildFastCheckTypeGraphOptions,
2173 ) {
2174 if !self.graph_kind().include_types() {
2175 return;
2176 }
2177
2178 let mut pending_nvs = self
2179 .packages
2180 .top_level_packages()
2181 .iter()
2182 .cloned()
2183 .collect::<VecDeque<_>>();
2184 if let WorkspaceFastCheckOption::Enabled(workspace_members) =
2185 options.workspace_fast_check
2186 {
2187 pending_nvs.extend(workspace_members.iter().map(|n| n.as_nv()));
2188 }
2189 if pending_nvs.is_empty() {
2190 return;
2191 }
2192
2193 let default_es_parser = crate::ast::CapturingModuleAnalyzer::default();
2194 let root_symbol = crate::symbols::RootSymbol::new(
2195 self,
2196 options.es_parser.unwrap_or(&default_es_parser),
2197 );
2198
2199 let modules = crate::fast_check::build_fast_check_type_graph(
2200 options.fast_check_cache,
2201 options.jsr_url_provider,
2202 self,
2203 &root_symbol,
2204 pending_nvs,
2205 &crate::fast_check::TransformOptions {
2206 workspace_members: match options.workspace_fast_check {
2207 WorkspaceFastCheckOption::Disabled => &[],
2208 WorkspaceFastCheckOption::Enabled(members) => members,
2209 },
2210 should_error_on_first_diagnostic: match options.workspace_fast_check {
2211 WorkspaceFastCheckOption::Disabled => true,
2212 WorkspaceFastCheckOption::Enabled(_) => false,
2213 },
2214 dts: options.fast_check_dts,
2215 },
2216 );
2217 for (specifier, fast_check_module_result) in modules {
2218 let module_slot = self.module_slots.get_mut(&specifier).unwrap();
2219 let module = match module_slot {
2220 ModuleSlot::Module(m) => match m {
2221 Module::Js(m) => m,
2222 _ => continue,
2223 },
2224 ModuleSlot::Err(_) | ModuleSlot::Pending { .. } => continue,
2225 };
2226 module.fast_check = Some(match fast_check_module_result {
2227 Ok(fast_check_module) => {
2228 let mut dependencies: IndexMap<String, Dependency> =
2229 Default::default();
2230 fill_module_dependencies(
2231 GraphKind::TypesOnly,
2232 module.media_type,
2233 match Arc::try_unwrap(fast_check_module.module_info) {
2234 Ok(module_info) => module_info.dependencies,
2235 Err(module_info) => module_info.dependencies.clone(),
2236 },
2237 &module.specifier,
2238 &mut dependencies,
2239 &NullFileSystem,
2241 options.jsr_url_provider,
2242 options.resolver,
2243 );
2244 FastCheckTypeModuleSlot::Module(Box::new(FastCheckTypeModule {
2245 dependencies,
2246 source: fast_check_module.text,
2247 source_map: fast_check_module.source_map,
2248 dts: fast_check_module.dts,
2249 }))
2250 }
2251 Err(diagnostic) => FastCheckTypeModuleSlot::Error(diagnostic),
2252 });
2253 }
2254 }
2255
2256 pub fn segment(&self, roots: &[ModuleSpecifier]) -> Self {
2258 let roots = roots.iter().collect::<IndexSet<_>>();
2259 if roots.iter().all(|r| self.roots.contains(*r)) {
2260 return self.clone();
2262 }
2263
2264 let mut new_graph = ModuleGraph::new(self.graph_kind);
2265 let entries = self.walk(
2266 roots.iter().copied(),
2267 WalkOptions {
2268 follow_dynamic: true,
2269 kind: self.graph_kind,
2270 check_js: CheckJsOption::True,
2271 prefer_fast_check_graph: false,
2272 },
2273 );
2274
2275 for (specifier, module_entry) in entries {
2276 match module_entry {
2277 ModuleEntryRef::Module(module) => {
2278 new_graph
2279 .module_slots
2280 .insert(specifier.clone(), ModuleSlot::Module(module.clone()));
2281 }
2282 ModuleEntryRef::Err(err) => {
2283 new_graph
2284 .module_slots
2285 .insert(specifier.clone(), ModuleSlot::Err(err.clone()));
2286 }
2287 ModuleEntryRef::Redirect(specifier_to) => {
2288 new_graph
2289 .redirects
2290 .insert(specifier.clone(), specifier_to.clone());
2291 }
2292 }
2293 }
2294 new_graph.imports.clone_from(&self.imports);
2295 new_graph.roots = roots.iter().map(|r| (*r).to_owned()).collect();
2296 new_graph.npm_packages.clone_from(&self.npm_packages);
2297 new_graph.packages.clone_from(&self.packages);
2299 new_graph.has_node_specifier = self.has_node_specifier;
2300
2301 new_graph
2302 }
2303
2304 pub fn prune_types(&mut self) {
2306 if !self.graph_kind.include_types() {
2307 return; }
2309
2310 self.graph_kind = GraphKind::CodeOnly;
2311
2312 let specifiers_count = self.specifiers_count();
2313 let mut seen_pending =
2314 SeenPendingCollection::with_capacity(specifiers_count);
2315 seen_pending.extend(self.roots.iter().cloned());
2316 let mut found_nvs = HashSet::with_capacity(self.npm_packages.len());
2317 let mut has_node_specifier = false;
2318
2319 let handle_dependencies =
2320 |seen_pending: &mut SeenPendingCollection<Url>,
2321 dependencies: &mut IndexMap<String, Dependency>| {
2322 for dependency in dependencies.values_mut() {
2323 dependency.maybe_deno_types_specifier = None;
2324 dependency.maybe_type = Resolution::None;
2325 if let Some(url) = dependency.get_code() {
2326 seen_pending.add(url.clone());
2327 }
2328 if let Some(url) = dependency.get_type() {
2329 seen_pending.add(url.clone());
2330 }
2331 }
2332 };
2333
2334 self.imports.clear();
2336
2337 while let Some(specifier) = seen_pending.next_pending() {
2339 let specifier = match self.redirects.get(&specifier) {
2340 Some(redirected_specifier) => {
2341 seen_pending.add(redirected_specifier.clone());
2342 continue;
2343 }
2344 None => specifier,
2345 };
2346 let Some(module) = self.module_slots.get_mut(&specifier) else {
2347 continue;
2348 };
2349 let module = match module {
2350 ModuleSlot::Module(module) => module,
2351 ModuleSlot::Err(_) | ModuleSlot::Pending { .. } => {
2352 continue;
2353 }
2354 };
2355 match module {
2356 Module::Js(js_module) => {
2357 #[cfg(feature = "fast_check")]
2358 {
2359 js_module.fast_check = None;
2360 }
2361 js_module.maybe_types_dependency = None;
2362 handle_dependencies(&mut seen_pending, &mut js_module.dependencies);
2363 }
2364 Module::Wasm(wasm_module) => {
2365 wasm_module.source_dts = Default::default();
2366 handle_dependencies(&mut seen_pending, &mut wasm_module.dependencies);
2367 }
2368 Module::Npm(module) => {
2369 found_nvs.insert(module.nv_reference.nv().clone());
2370 }
2371 Module::Node(_) => {
2372 has_node_specifier = true;
2373 }
2374 Module::Json(_) | Module::External(_) => {
2375 }
2377 }
2378 }
2379
2380 self
2383 .module_slots
2384 .retain(|specifier, _| seen_pending.has_seen(specifier));
2385 self.npm_packages.retain(|nv| found_nvs.contains(nv));
2386 self
2387 .redirects
2388 .retain(|redirect, _| seen_pending.has_seen(redirect));
2389 self.has_node_specifier = has_node_specifier;
2390 }
2391
2392 pub fn walk<'a, 'options>(
2394 &'a self,
2395 roots: impl Iterator<Item = &'a ModuleSpecifier>,
2396 options: WalkOptions<'options>,
2397 ) -> ModuleEntryIterator<'a, 'options> {
2398 ModuleEntryIterator::new(self, roots, options)
2399 }
2400
2401 pub fn contains(&self, specifier: &ModuleSpecifier) -> bool {
2404 let specifier = self.resolve(specifier);
2405 self
2406 .module_slots
2407 .get(specifier)
2408 .is_some_and(|ms| matches!(ms, ModuleSlot::Module(_)))
2409 }
2410
2411 pub fn module_errors(&self) -> impl Iterator<Item = &ModuleError> {
2415 self.module_slots.values().filter_map(|ms| match ms {
2416 ModuleSlot::Err(err) => Some(err),
2417 ModuleSlot::Module(_) | ModuleSlot::Pending { .. } => None,
2418 })
2419 }
2420
2421 pub fn get(&self, specifier: &ModuleSpecifier) -> Option<&Module> {
2426 let specifier = self.resolve(specifier);
2427 match self.module_slots.get(specifier) {
2428 Some(ModuleSlot::Module(module)) => Some(module),
2429 _ => None,
2430 }
2431 }
2432
2433 pub fn asset_module_urls(&self) -> IndexSet<&Url> {
2436 let mut result = IndexSet::with_capacity(self.module_slots.len());
2437 for module in self.module_slots.values() {
2438 match module {
2439 ModuleSlot::Module(module) => {
2440 for dep in module.dependencies().values() {
2441 let is_asset = dep.imports.iter().all(|i| i.attributes.has_asset());
2442 if is_asset {
2443 if let Some(url) = dep.get_code() {
2444 let url = self.resolve(url);
2445 result.insert(url);
2446 }
2447 }
2448 }
2449 }
2450 ModuleSlot::Pending { .. } | ModuleSlot::Err { .. } => {}
2451 }
2452 }
2453 result
2454 }
2455
2456 pub fn modules(&self) -> impl Iterator<Item = &Module> {
2462 self.module_slots.values().filter_map(|ms| match ms {
2463 ModuleSlot::Module(m) => Some(m),
2464 _ => None,
2465 })
2466 }
2467
2468 pub fn resolve<'a>(
2471 &'a self,
2472 specifier: &'a ModuleSpecifier,
2473 ) -> &'a ModuleSpecifier {
2474 const MAX_REDIRECTS: usize = 10;
2475 let mut redirected_specifier = specifier;
2476 if let Some(specifier) = self.redirects.get(specifier) {
2477 let mut seen = HashSet::with_capacity(MAX_REDIRECTS);
2479 seen.insert(redirected_specifier);
2480 seen.insert(specifier);
2481 redirected_specifier = specifier;
2482 while let Some(specifier) = self.redirects.get(redirected_specifier) {
2483 if !seen.insert(specifier) {
2484 log::warn!("An infinite loop of redirections detected.\n Original specifier: {specifier}");
2485 break;
2486 }
2487 redirected_specifier = specifier;
2488 if seen.len() >= MAX_REDIRECTS {
2489 log::warn!("An excessive number of redirections detected.\n Original specifier: {specifier}");
2490 break;
2491 }
2492 }
2493 }
2494 redirected_specifier
2495 }
2496
2497 pub fn resolve_dependency<'a>(
2507 &'a self,
2508 specifier: &str,
2509 referrer: &ModuleSpecifier,
2510 prefer_types: bool,
2511 ) -> Option<&'a ModuleSpecifier> {
2512 let referrer = self.resolve(referrer);
2513 if let Some(ModuleSlot::Module(referring_module)) =
2514 self.module_slots.get(referrer)
2515 {
2516 self.resolve_dependency_from_module(
2517 specifier,
2518 referring_module,
2519 prefer_types,
2520 )
2521 } else if let Some(graph_import) = self.imports.get(referrer) {
2522 let dependency = graph_import.dependencies.get(specifier)?;
2523 self.resolve_dependency_from_dep(dependency, prefer_types)
2524 } else {
2525 None
2526 }
2527 }
2528
2529 pub fn resolve_dependency_from_module<'a>(
2530 &'a self,
2531 specifier: &str,
2532 referring_module: &'a Module,
2533 prefer_types: bool,
2534 ) -> Option<&'a ModuleSpecifier> {
2535 match referring_module {
2536 Module::Js(referring_module) => {
2537 let dependency = referring_module.dependencies.get(specifier)?;
2538 self.resolve_dependency_from_dep(dependency, prefer_types)
2539 }
2540 Module::Wasm(referring_module) => {
2541 let dependency = referring_module.dependencies.get(specifier)?;
2542 self.resolve_dependency_from_dep(dependency, prefer_types)
2543 }
2544 Module::Json(_)
2545 | Module::Npm(_)
2546 | Module::Node(_)
2547 | Module::External(_) => None,
2548 }
2549 }
2550
2551 pub fn resolve_dependency_from_dep<'a>(
2552 &'a self,
2553 dependency: &'a Dependency,
2554 prefer_types: bool,
2555 ) -> Option<&'a ModuleSpecifier> {
2556 let (maybe_first, maybe_second) = if prefer_types {
2557 (&dependency.maybe_type, &dependency.maybe_code)
2558 } else {
2559 (&dependency.maybe_code, &dependency.maybe_type)
2560 };
2561 let unresolved_specifier = maybe_first
2562 .maybe_specifier()
2563 .or_else(|| maybe_second.maybe_specifier())?;
2564 let resolved_specifier = self.resolve(unresolved_specifier);
2565 match self.module_slots.get(resolved_specifier) {
2568 Some(ModuleSlot::Module(Module::Js(module))) if prefer_types => {
2569 if let Some(Resolution::Ok(resolved)) = module
2571 .maybe_types_dependency
2572 .as_ref()
2573 .map(|d| &d.dependency)
2574 {
2575 let resolved_specifier = self.resolve(&resolved.specifier);
2576 if matches!(
2577 self.module_slots.get(resolved_specifier),
2578 Some(ModuleSlot::Module(_))
2579 ) {
2580 return Some(resolved_specifier);
2581 }
2582 }
2583 Some(resolved_specifier)
2584 }
2585 Some(ModuleSlot::Module(_)) => Some(resolved_specifier),
2586 _ => None,
2587 }
2588 }
2589
2590 pub fn specifiers(
2595 &self,
2596 ) -> impl Iterator<Item = (&ModuleSpecifier, Result<&Module, &ModuleError>)>
2597 {
2598 self.module_slots.iter().filter_map(to_result).chain(
2599 self.redirects.iter().filter_map(|(specifier, found)| {
2600 let module_slot = self.module_slots.get(found)?;
2601 to_result((specifier, module_slot))
2602 }),
2603 )
2604 }
2605
2606 pub fn try_get(
2612 &self,
2613 specifier: &ModuleSpecifier,
2614 ) -> Result<Option<&Module>, &ModuleError> {
2615 let specifier = self.resolve(specifier);
2616 match self.module_slots.get(specifier) {
2617 Some(ModuleSlot::Module(module)) => Ok(Some(module)),
2618 Some(ModuleSlot::Err(err)) => Err(err),
2619 _ => Ok(None),
2620 }
2621 }
2622
2623 pub fn try_get_prefer_types(
2626 &self,
2627 specifier: &ModuleSpecifier,
2628 ) -> Result<Option<&Module>, &ModuleError> {
2629 let Some(module) = self.try_get(specifier)? else {
2630 return Ok(None);
2631 };
2632
2633 if let Some(specifier) = module.js().and_then(|m| {
2634 m.maybe_types_dependency
2635 .as_ref()
2636 .and_then(|d| d.dependency.ok())
2637 .map(|r| &r.specifier)
2638 }) {
2639 self.try_get(specifier)
2640 } else {
2641 Ok(Some(module))
2642 }
2643 }
2644
2645 #[allow(clippy::result_large_err)]
2649 pub fn valid(&self) -> Result<(), ModuleGraphError> {
2650 self
2651 .walk(
2652 self.roots.iter(),
2653 WalkOptions {
2654 check_js: CheckJsOption::True,
2655 kind: GraphKind::CodeOnly,
2656 follow_dynamic: false,
2657 prefer_fast_check_graph: false,
2658 },
2659 )
2660 .validate()
2661 }
2662
2663 pub fn specifiers_count(&self) -> usize {
2668 self.module_slots.len() + self.redirects.len() + self.imports.len()
2669 }
2670}
2671
2672fn resolve(
2675 specifier_text: &str,
2676 referrer_range: Range,
2677 resolution_kind: ResolutionKind,
2678 jsr_url_provider: &dyn JsrUrlProvider,
2679 maybe_resolver: Option<&dyn Resolver>,
2680) -> Resolution {
2681 let response = if let Some(resolver) = maybe_resolver {
2682 resolver.resolve(specifier_text, &referrer_range, resolution_kind)
2683 } else {
2684 resolve_import(specifier_text, &referrer_range.specifier)
2685 .map_err(|err| err.into())
2686 };
2687 if resolution_kind.is_types() {
2688 if let Ok(resolved_url) = &response {
2689 if let Some(package_nv) = jsr_url_provider.package_url_to_nv(resolved_url)
2690 {
2691 if Some(package_nv)
2692 != jsr_url_provider.package_url_to_nv(&referrer_range.specifier)
2693 {
2694 return Resolution::Err(Box::new(
2695 ResolutionError::InvalidJsrHttpsTypesImport {
2696 specifier: resolved_url.clone(),
2697 range: referrer_range.clone(),
2698 },
2699 ));
2700 }
2701 }
2702 }
2703 }
2704 Resolution::from_resolve_result(response, specifier_text, referrer_range)
2705}
2706
2707fn serialize_module_slots<S>(
2708 module_slots: &BTreeMap<ModuleSpecifier, ModuleSlot>,
2709 serializer: S,
2710) -> Result<S::Ok, S::Error>
2711where
2712 S: Serializer,
2713{
2714 let mut seq = serializer.serialize_seq(Some(module_slots.len()))?;
2715 for (specifier, slot) in module_slots.iter() {
2716 match slot {
2717 ModuleSlot::Module(module) => seq.serialize_element(module)?,
2718 ModuleSlot::Err(err) => seq.serialize_element(&serde_json::json!({
2719 "specifier": specifier,
2720 "error": err.to_string(),
2721 }))?,
2722 ModuleSlot::Pending { .. } => {
2723 seq.serialize_element(&serde_json::json!({
2724 "specifier": specifier,
2725 "error": "[INTERNAL ERROR] A pending module load never completed.",
2726 }))?
2727 }
2728 };
2729 }
2730 seq.end()
2731}
2732
2733fn serialize_graph_imports<S>(
2734 graph_imports: &IndexMap<ModuleSpecifier, GraphImport>,
2735 serializer: S,
2736) -> Result<S::Ok, S::Error>
2737where
2738 S: Serializer,
2739{
2740 #[derive(Serialize)]
2741 struct GraphImportWithReferrer<'a> {
2742 referrer: &'a ModuleSpecifier,
2743 #[serde(flatten)]
2744 graph_import: &'a GraphImport,
2745 }
2746 let mut seq = serializer.serialize_seq(Some(graph_imports.len()))?;
2747 for (referrer, graph_import) in graph_imports {
2748 seq.serialize_element(&GraphImportWithReferrer {
2749 referrer,
2750 graph_import,
2751 })?
2752 }
2753 seq.end()
2754}
2755
2756#[derive(Clone)]
2757pub(crate) enum ModuleSourceAndInfo {
2758 Json {
2759 specifier: ModuleSpecifier,
2760 mtime: Option<SystemTime>,
2761 source: ModuleTextSource,
2762 },
2763 Js {
2764 specifier: ModuleSpecifier,
2765 media_type: MediaType,
2766 maybe_headers: Option<HashMap<String, String>>,
2767 module_info: Box<ModuleInfo>,
2768 mtime: Option<SystemTime>,
2769 source: ModuleTextSource,
2770 },
2771 Wasm {
2772 specifier: ModuleSpecifier,
2773 module_info: Box<ModuleInfo>,
2774 mtime: Option<SystemTime>,
2775 source: Arc<[u8]>,
2776 source_dts: Arc<str>,
2777 },
2778}
2779
2780impl ModuleSourceAndInfo {
2781 pub fn specifier(&self) -> &ModuleSpecifier {
2782 match self {
2783 Self::Json { specifier, .. } => specifier,
2784 Self::Js { specifier, .. } => specifier,
2785 Self::Wasm { specifier, .. } => specifier,
2786 }
2787 }
2788
2789 pub fn media_type(&self) -> MediaType {
2790 match self {
2791 Self::Json { .. } => MediaType::Json,
2792 Self::Js { media_type, .. } => *media_type,
2793 Self::Wasm { .. } => MediaType::Wasm,
2794 }
2795 }
2796
2797 pub fn source_bytes(&self) -> &[u8] {
2798 match self {
2799 Self::Json { source, .. } => source.text.as_bytes(),
2800 Self::Js { source, .. } => source.text.as_bytes(),
2801 Self::Wasm { source, .. } => source,
2802 }
2803 }
2804}
2805
2806pub(crate) struct ParseModuleAndSourceInfoOptions<'a> {
2807 pub specifier: ModuleSpecifier,
2808 pub maybe_headers: Option<HashMap<String, String>>,
2809 pub mtime: Option<SystemTime>,
2810 pub content: Arc<[u8]>,
2811 pub maybe_attribute_type: Option<&'a AttributeTypeWithRange>,
2812 pub maybe_referrer: Option<&'a Range>,
2813 pub is_root: bool,
2814 pub is_dynamic_branch: bool,
2815}
2816
2817#[allow(clippy::too_many_arguments)]
2819#[allow(clippy::result_large_err)]
2820pub(crate) async fn parse_module_source_and_info(
2821 module_analyzer: &dyn ModuleAnalyzer,
2822 opts: ParseModuleAndSourceInfoOptions<'_>,
2823) -> Result<ModuleSourceAndInfo, ModuleError> {
2824 let (mut media_type, maybe_charset) =
2825 resolve_media_type_and_charset_from_headers(
2826 &opts.specifier,
2827 opts.maybe_headers.as_ref(),
2828 );
2829
2830 if opts.is_root && media_type == MediaType::Unknown {
2831 media_type = MediaType::JavaScript;
2833 }
2834
2835 if media_type == MediaType::Json
2838 && (opts.is_root
2839 || opts.is_dynamic_branch
2840 || matches!(
2841 opts.maybe_attribute_type.map(|t| t.kind.as_str()),
2842 Some("json")
2843 ))
2844 {
2845 return new_source_with_text(
2846 &opts.specifier,
2847 opts.content,
2848 maybe_charset,
2849 opts.mtime,
2850 )
2851 .map(|source| ModuleSourceAndInfo::Json {
2852 specifier: opts.specifier,
2853 mtime: opts.mtime,
2854 source,
2855 });
2856 }
2857
2858 if let Some(attribute_type) = opts.maybe_attribute_type {
2859 if attribute_type.kind == "json" {
2860 return Err(
2861 ModuleErrorKind::InvalidTypeAssertion {
2862 specifier: opts.specifier.clone(),
2863 referrer: attribute_type.range.clone(),
2864 actual_media_type: media_type,
2865 expected_media_type: MediaType::Json,
2866 }
2867 .into_box(),
2868 );
2869 } else if !matches!(attribute_type.kind.as_str(), "text" | "bytes") {
2870 return Err(
2871 ModuleErrorKind::UnsupportedImportAttributeType {
2872 specifier: opts.specifier,
2873 referrer: attribute_type.range.clone(),
2874 kind: attribute_type.kind.clone(),
2875 }
2876 .into_box(),
2877 );
2878 }
2879 }
2880
2881 if matches!(media_type, MediaType::Cjs | MediaType::Cts)
2882 && opts.specifier.scheme() != "file"
2883 {
2884 return Err(
2885 ModuleErrorKind::UnsupportedMediaType {
2886 specifier: opts.specifier,
2887 media_type,
2888 maybe_referrer: opts.maybe_referrer.map(|r| r.to_owned()),
2889 }
2890 .into_box(),
2891 );
2892 }
2893
2894 match media_type {
2896 MediaType::JavaScript
2897 | MediaType::Mjs
2898 | MediaType::Jsx
2899 | MediaType::TypeScript
2900 | MediaType::Mts
2901 | MediaType::Tsx
2902 | MediaType::Cjs
2903 | MediaType::Cts
2904 | MediaType::Dts
2905 | MediaType::Dmts
2906 | MediaType::Dcts => {
2907 let source = new_source_with_text(
2908 &opts.specifier,
2909 opts.content,
2910 maybe_charset,
2911 opts.mtime,
2912 )?;
2913 match module_analyzer
2914 .analyze(&opts.specifier, source.text.clone(), media_type)
2915 .await
2916 {
2917 Ok(module_info) => {
2918 Ok(ModuleSourceAndInfo::Js {
2920 specifier: opts.specifier,
2921 media_type,
2922 mtime: opts.mtime,
2923 source,
2924 maybe_headers: opts.maybe_headers,
2925 module_info: Box::new(module_info),
2926 })
2927 }
2928 Err(diagnostic) => Err(
2929 ModuleErrorKind::Parse {
2930 specifier: opts.specifier,
2931 mtime: opts.mtime,
2932 diagnostic: Arc::new(diagnostic),
2933 }
2934 .into_box(),
2935 ),
2936 }
2937 }
2938 MediaType::Wasm => {
2939 let source_dts_result = wasm_module_to_dts(&opts.content);
2940 match source_dts_result {
2941 Ok(source_dts) => {
2942 let source_dts: Arc<str> = source_dts.into();
2943 match module_analyzer
2944 .analyze(&opts.specifier, source_dts.clone(), MediaType::Dmts)
2945 .await
2946 {
2947 Ok(module_info) => {
2948 Ok(ModuleSourceAndInfo::Wasm {
2950 specifier: opts.specifier,
2951 module_info: Box::new(module_info),
2952 mtime: opts.mtime,
2953 source: opts.content,
2954 source_dts,
2955 })
2956 }
2957 Err(diagnostic) => Err(
2958 ModuleErrorKind::Parse {
2959 specifier: opts.specifier,
2960 mtime: opts.mtime,
2961 diagnostic: Arc::new(diagnostic),
2962 }
2963 .into_box(),
2964 ),
2965 }
2966 }
2967 Err(err) => Err(
2968 ModuleErrorKind::WasmParse {
2969 specifier: opts.specifier,
2970 mtime: opts.mtime,
2971 err,
2972 }
2973 .into_box(),
2974 ),
2975 }
2976 }
2977 MediaType::Css
2978 | MediaType::Json
2979 | MediaType::SourceMap
2980 | MediaType::Html
2981 | MediaType::Sql
2982 | MediaType::Unknown => Err(
2983 ModuleErrorKind::UnsupportedMediaType {
2984 specifier: opts.specifier,
2985 media_type,
2986 maybe_referrer: opts.maybe_referrer.map(|r| r.to_owned()),
2987 }
2988 .into_box(),
2989 ),
2990 }
2991}
2992
2993pub(crate) struct ParseModuleOptions {
2994 pub graph_kind: GraphKind,
2995 pub module_source_and_info: ModuleSourceAndInfo,
2996}
2997
2998#[allow(clippy::result_large_err)]
3000pub(crate) fn parse_module(
3001 file_system: &FileSystem,
3002 jsr_url_provider: &dyn JsrUrlProvider,
3003 maybe_resolver: Option<&dyn Resolver>,
3004 options: ParseModuleOptions,
3005) -> Module {
3006 match options.module_source_and_info {
3007 ModuleSourceAndInfo::Json {
3008 specifier,
3009 mtime,
3010 source,
3011 } => Module::Json(JsonModule {
3012 maybe_cache_info: None,
3013 source,
3014 mtime,
3015 media_type: MediaType::Json,
3016 specifier,
3017 }),
3018 ModuleSourceAndInfo::Js {
3019 specifier,
3020 media_type,
3021 mtime,
3022 maybe_headers,
3023 module_info,
3024 source,
3025 } => Module::Js(parse_js_module_from_module_info(
3026 options.graph_kind,
3027 specifier,
3028 media_type,
3029 maybe_headers.as_ref(),
3030 *module_info,
3031 mtime,
3032 source,
3033 file_system,
3034 jsr_url_provider,
3035 maybe_resolver,
3036 )),
3037 ModuleSourceAndInfo::Wasm {
3038 specifier,
3039 mtime,
3040 source,
3041 source_dts,
3042 module_info,
3043 } => Module::Wasm(parse_wasm_module_from_module_info(
3044 options.graph_kind,
3045 specifier,
3046 *module_info,
3047 mtime,
3048 source,
3049 source_dts,
3050 file_system,
3051 jsr_url_provider,
3052 maybe_resolver,
3053 )),
3054 }
3055}
3056
3057#[allow(clippy::too_many_arguments)]
3058pub(crate) fn parse_js_module_from_module_info(
3059 graph_kind: GraphKind,
3060 specifier: ModuleSpecifier,
3061 media_type: MediaType,
3062 maybe_headers: Option<&HashMap<String, String>>,
3063 module_info: ModuleInfo,
3064 mtime: Option<SystemTime>,
3065 source: ModuleTextSource,
3066 file_system: &FileSystem,
3067 jsr_url_provider: &dyn JsrUrlProvider,
3068 maybe_resolver: Option<&dyn Resolver>,
3069) -> JsModule {
3070 let mut module = JsModule {
3071 is_script: module_info.is_script,
3072 dependencies: Default::default(),
3073 maybe_cache_info: None,
3074 mtime,
3075 source,
3076 maybe_types_dependency: None,
3077 media_type,
3078 specifier,
3079 #[cfg(feature = "fast_check")]
3080 fast_check: None,
3081 };
3082
3083 if graph_kind.include_types() {
3085 if let Some(specifier) = module_info.self_types_specifier.as_ref() {
3086 let range = Range {
3087 specifier: module.specifier.clone(),
3088 range: specifier.range,
3089 resolution_mode: None,
3090 };
3091 module.maybe_types_dependency = Some(TypesDependency {
3092 specifier: specifier.text.clone(),
3093 dependency: resolve(
3094 &specifier.text,
3095 range.clone(),
3096 ResolutionKind::Types,
3097 jsr_url_provider,
3098 maybe_resolver,
3099 ),
3100 });
3101 }
3102
3103 for reference in module_info.ts_references {
3104 match reference {
3105 TypeScriptReference::Path(specifier) => {
3106 let dep = module
3107 .dependencies
3108 .entry(specifier.text.clone())
3109 .or_default();
3110 let range = Range {
3111 specifier: module.specifier.clone(),
3112 range: specifier.range,
3113 resolution_mode: None,
3114 };
3115 if dep.maybe_type.is_none() {
3116 dep.maybe_type = resolve(
3117 &specifier.text,
3118 range.clone(),
3119 ResolutionKind::Types,
3120 jsr_url_provider,
3121 maybe_resolver,
3122 );
3123 }
3124 dep.imports.push(Import {
3125 specifier: specifier.text,
3126 kind: ImportKind::TsReferencePath,
3127 specifier_range: range,
3128 is_dynamic: false,
3129 attributes: Default::default(),
3130 is_side_effect: false,
3131 });
3132 }
3133 TypeScriptReference::Types {
3134 specifier,
3135 resolution_mode: mode,
3136 } => {
3137 let is_untyped = !module.media_type.is_typed();
3138 if is_untyped && module.maybe_types_dependency.is_some() {
3139 continue; }
3141 let range = Range {
3142 specifier: module.specifier.clone(),
3143 range: specifier.range,
3144 resolution_mode: mode.map(|mode| mode.as_deno_graph()),
3145 };
3146 let dep_resolution = resolve(
3147 &specifier.text,
3148 range.clone(),
3149 ResolutionKind::Types,
3150 jsr_url_provider,
3151 maybe_resolver,
3152 );
3153 if is_untyped {
3154 module.maybe_types_dependency = Some(TypesDependency {
3155 specifier: specifier.text.clone(),
3156 dependency: dep_resolution,
3157 });
3158 } else {
3159 let dep = module
3160 .dependencies
3161 .entry(specifier.text.clone())
3162 .or_default();
3163 if dep.maybe_type.is_none() {
3164 dep.maybe_type = dep_resolution;
3165 }
3166 dep.imports.push(Import {
3167 specifier: specifier.text,
3168 kind: ImportKind::TsReferenceTypes,
3169 specifier_range: range,
3170 is_dynamic: false,
3171 attributes: Default::default(),
3172 is_side_effect: false,
3173 });
3174 }
3175 }
3176 }
3177 }
3178 }
3179
3180 if media_type.is_jsx() {
3203 let has_jsx_import_source_pragma = module_info.jsx_import_source.is_some();
3204 let res = module_info.jsx_import_source.or_else(|| {
3205 maybe_resolver.and_then(|r| {
3206 r.default_jsx_import_source(&module.specifier)
3207 .map(|import_source| SpecifierWithRange {
3208 text: import_source,
3209 range: PositionRange {
3210 start: Position::zeroed(),
3211 end: Position::zeroed(),
3212 },
3213 })
3214 })
3215 });
3216 if let Some(import_source) = res {
3217 let jsx_import_source_module = maybe_resolver
3218 .map(|r| r.jsx_import_source_module(&module.specifier))
3219 .unwrap_or(DEFAULT_JSX_IMPORT_SOURCE_MODULE);
3220 let specifier_text =
3221 format!("{}/{}", import_source.text, jsx_import_source_module);
3222 let dep = module
3223 .dependencies
3224 .entry(specifier_text.clone())
3225 .or_default();
3226 let range = Range {
3227 specifier: module.specifier.clone(),
3228 range: import_source.range,
3229 resolution_mode: None,
3230 };
3231 if dep.maybe_code.is_none() {
3232 dep.maybe_code = resolve(
3233 &specifier_text,
3234 range.clone(),
3235 ResolutionKind::Execution,
3236 jsr_url_provider,
3237 maybe_resolver,
3238 );
3239 }
3240 if graph_kind.include_types() && dep.maybe_type.is_none() {
3241 let mut types_res = module_info.jsx_import_source_types;
3242 if types_res.is_none() && !has_jsx_import_source_pragma {
3243 types_res = maybe_resolver.and_then(|r| {
3244 r.default_jsx_import_source_types(&module.specifier).map(
3245 |import_source| SpecifierWithRange {
3246 text: import_source,
3247 range: PositionRange {
3248 start: Position::zeroed(),
3249 end: Position::zeroed(),
3250 },
3251 },
3252 )
3253 });
3254 }
3255 if let Some(import_source_types) = types_res {
3256 let specifier_text = format!(
3257 "{}/{}",
3258 import_source_types.text, jsx_import_source_module
3259 );
3260 let range = Range {
3261 specifier: module.specifier.clone(),
3262 range: import_source_types.range,
3263 resolution_mode: None,
3264 };
3265 dep.maybe_type = resolve(
3266 &specifier_text,
3267 range,
3268 ResolutionKind::Types,
3269 jsr_url_provider,
3270 maybe_resolver,
3271 );
3272 dep.maybe_deno_types_specifier = Some(specifier_text);
3273 } else {
3274 let types_resolution = resolve(
3275 &specifier_text,
3276 range.clone(),
3277 ResolutionKind::Types,
3278 jsr_url_provider,
3279 maybe_resolver,
3280 );
3281 if types_resolution.maybe_specifier()
3282 != dep.maybe_code.maybe_specifier()
3283 {
3284 dep.maybe_type = types_resolution;
3285 }
3286 }
3287 }
3288 dep.imports.push(Import {
3289 specifier: specifier_text,
3290 kind: ImportKind::JsxImportSource,
3291 specifier_range: range,
3292 is_dynamic: false,
3293 attributes: Default::default(),
3294 is_side_effect: false,
3295 });
3296 }
3297 }
3298
3299 if graph_kind.include_types() {
3301 for jsdoc_import in module_info.jsdoc_imports {
3302 let specifier = jsdoc_import.specifier;
3303 let dep = module
3304 .dependencies
3305 .entry(specifier.text.clone())
3306 .or_default();
3307 let specifier_range = Range {
3308 specifier: module.specifier.clone(),
3309 range: specifier.range,
3310 resolution_mode: jsdoc_import
3311 .resolution_mode
3312 .map(|mode| mode.as_deno_graph()),
3313 };
3314 if dep.maybe_type.is_none() {
3315 dep.maybe_type = resolve(
3316 &specifier.text,
3317 specifier_range.clone(),
3318 ResolutionKind::Types,
3319 jsr_url_provider,
3320 maybe_resolver,
3321 );
3322 }
3323 dep.imports.push(Import {
3324 specifier: specifier.text,
3325 kind: ImportKind::JsDoc,
3326 specifier_range,
3327 is_dynamic: false,
3328 attributes: Default::default(),
3329 is_side_effect: false,
3330 });
3331 }
3332 }
3333
3334 if graph_kind.include_types() && module.maybe_types_dependency.is_none() {
3336 if let Some(headers) = maybe_headers {
3337 if let Some(types_header) = headers.get("x-typescript-types") {
3338 let range = Range {
3339 specifier: module.specifier.clone(),
3340 range: PositionRange::zeroed(),
3341 resolution_mode: None,
3342 };
3343 module.maybe_types_dependency = Some(TypesDependency {
3344 specifier: types_header.to_string(),
3345 dependency: resolve(
3346 types_header,
3347 range,
3348 ResolutionKind::Types,
3349 jsr_url_provider,
3350 maybe_resolver,
3351 ),
3352 });
3353 }
3354 }
3355 }
3356
3357 if let Some(resolver) = maybe_resolver {
3359 if graph_kind.include_types()
3362 && module.maybe_types_dependency.is_none()
3363 && !module.media_type.is_typed()
3364 {
3365 module.maybe_types_dependency =
3366 match resolver.resolve_types(&module.specifier) {
3367 Ok(Some((specifier, maybe_range))) => {
3368 let specifier_text = module.specifier.to_string();
3369 Some(TypesDependency {
3370 specifier: specifier_text,
3371 dependency: Resolution::Ok(Box::new(ResolutionResolved {
3372 specifier: specifier.clone(),
3373 range: maybe_range.unwrap_or_else(|| Range {
3374 specifier,
3375 range: PositionRange::zeroed(),
3376 resolution_mode: None,
3377 }),
3378 })),
3379 })
3380 }
3381 Ok(None) => None,
3382 Err(err) => Some(TypesDependency {
3383 specifier: module.specifier.to_string(),
3384 dependency: Resolution::Err(Box::new(
3385 ResolutionError::ResolverError {
3386 error: Arc::new(err),
3387 specifier: module.specifier.to_string(),
3388 range: Range {
3389 specifier: module.specifier.clone(),
3390 range: PositionRange::zeroed(),
3391 resolution_mode: None,
3392 },
3393 },
3394 )),
3395 }),
3396 };
3397 }
3398 }
3399
3400 fill_module_dependencies(
3402 graph_kind,
3403 module.media_type,
3404 module_info.dependencies,
3405 &module.specifier,
3406 &mut module.dependencies,
3407 file_system,
3408 jsr_url_provider,
3409 maybe_resolver,
3410 );
3411
3412 module
3414}
3415
3416#[allow(clippy::too_many_arguments)]
3417fn parse_wasm_module_from_module_info(
3418 graph_kind: GraphKind,
3419 specifier: Url,
3420 module_info: ModuleInfo,
3421 mtime: Option<SystemTime>,
3422 source: Arc<[u8]>,
3423 source_dts: Arc<str>,
3424 file_system: &FileSystem,
3425 jsr_url_provider: &dyn JsrUrlProvider,
3426 maybe_resolver: Option<&dyn Resolver>,
3427) -> WasmModule {
3428 let mut module = WasmModule {
3429 specifier,
3430 dependencies: Default::default(),
3431 mtime,
3432 source,
3433 source_dts,
3434 maybe_cache_info: None,
3435 };
3436 fill_module_dependencies(
3437 graph_kind,
3438 MediaType::Wasm,
3439 module_info.dependencies,
3440 &module.specifier,
3441 &mut module.dependencies,
3442 file_system,
3443 jsr_url_provider,
3444 maybe_resolver,
3445 );
3446 module
3447}
3448
3449#[allow(clippy::too_many_arguments)]
3450fn fill_module_dependencies(
3451 graph_kind: GraphKind,
3452 media_type: MediaType,
3453 dependencies: Vec<DependencyDescriptor>,
3454 module_specifier: &ModuleSpecifier,
3455 module_dependencies: &mut IndexMap<String, Dependency>,
3456 file_system: &FileSystem,
3457 jsr_url_provider: &dyn JsrUrlProvider,
3458 maybe_resolver: Option<&dyn Resolver>,
3459) {
3460 for desc in dependencies {
3461 let (imports, types_specifier) = match desc {
3462 DependencyDescriptor::Static(desc)
3463 if desc.kind == StaticDependencyKind::MaybeTsModuleAugmentation =>
3464 {
3465 if !graph_kind.include_types() {
3466 continue;
3467 }
3468 (
3469 vec![Import {
3470 specifier: desc.specifier,
3471 kind: ImportKind::TsModuleAugmentation,
3472 specifier_range: Range {
3473 specifier: module_specifier.clone(),
3474 range: desc.specifier_range,
3475 resolution_mode: Some(ResolutionMode::Import),
3476 },
3477 is_dynamic: false,
3478 attributes: desc.import_attributes,
3479 is_side_effect: desc.is_side_effect,
3480 }],
3481 desc.types_specifier,
3482 )
3483 }
3484 DependencyDescriptor::Static(desc) => {
3485 let is_import_or_export_type = matches!(
3486 desc.kind,
3487 StaticDependencyKind::ImportType | StaticDependencyKind::ExportType
3488 );
3489 if is_import_or_export_type && !graph_kind.include_types() {
3490 continue; }
3492 let is_types = is_import_or_export_type || media_type.is_declaration();
3493 let specifier_range = Range {
3494 specifier: module_specifier.clone(),
3495 range: desc.specifier_range,
3496 resolution_mode: match desc.kind {
3497 StaticDependencyKind::Import
3498 | StaticDependencyKind::Export
3499 | StaticDependencyKind::ImportType
3500 | StaticDependencyKind::ExportType => is_types
3501 .then(|| {
3502 desc
3503 .import_attributes
3504 .get("resolution-mode")
3505 .and_then(TypeScriptTypesResolutionMode::from_str)
3506 .map(|m| m.as_deno_graph())
3507 })
3508 .flatten()
3509 .or_else(|| {
3510 if media_type.is_declaration() {
3511 None
3512 } else {
3513 Some(ResolutionMode::Import)
3514 }
3515 }),
3516 StaticDependencyKind::ImportEquals
3517 | StaticDependencyKind::ExportEquals => {
3518 Some(ResolutionMode::Require)
3519 }
3520 StaticDependencyKind::MaybeTsModuleAugmentation => unreachable!(),
3521 },
3522 };
3523 (
3524 vec![Import {
3525 specifier: desc.specifier,
3526 kind: match is_import_or_export_type {
3527 true => ImportKind::TsType,
3528 false => ImportKind::Es,
3529 },
3530 specifier_range,
3531 is_dynamic: false,
3532 attributes: desc.import_attributes,
3533 is_side_effect: desc.is_side_effect,
3534 }],
3535 desc.types_specifier,
3536 )
3537 }
3538 DependencyDescriptor::Dynamic(desc) => {
3539 let import_attributes = desc.import_attributes;
3540 let specifiers = match desc.argument {
3541 DynamicArgument::String(text) => {
3542 vec![text]
3543 }
3544 DynamicArgument::Template(parts)
3545 if module_specifier.scheme() == "file" =>
3546 {
3547 let mut parts = analyze_dynamic_arg_template_parts(
3548 &parts,
3549 module_specifier,
3550 &desc.argument_range,
3551 &import_attributes,
3552 file_system,
3553 );
3554 parts.sort();
3557 parts
3558 }
3559 _ => continue,
3560 };
3561 let specifier_range = Range {
3562 specifier: module_specifier.clone(),
3563 range: desc.argument_range,
3564 resolution_mode: match desc.kind {
3565 DynamicDependencyKind::Import => {
3566 if media_type.is_declaration() {
3567 None
3568 } else {
3569 Some(ResolutionMode::Import)
3570 }
3571 }
3572 DynamicDependencyKind::Require => Some(ResolutionMode::Require),
3573 },
3574 };
3575 (
3576 specifiers
3577 .into_iter()
3578 .map(|specifier| Import {
3579 specifier,
3580 kind: match desc.kind {
3581 DynamicDependencyKind::Import => ImportKind::Es,
3582 DynamicDependencyKind::Require => ImportKind::Require,
3583 },
3584 specifier_range: specifier_range.clone(),
3585 is_dynamic: true,
3586 attributes: import_attributes.clone(),
3587 is_side_effect: false,
3588 })
3589 .collect::<Vec<_>>(),
3590 desc.types_specifier,
3591 )
3592 }
3593 };
3594
3595 for import in imports {
3596 let dep = module_dependencies
3597 .entry(import.specifier.clone())
3598 .or_default();
3599 if dep.maybe_attribute_type.is_none() {
3602 dep.maybe_attribute_type =
3603 import.attributes.get("type").map(|s| s.to_string());
3604 }
3605
3606 if let Some(types_specifier) = &types_specifier {
3607 if graph_kind.include_types() && dep.maybe_type.is_none() {
3608 dep.maybe_deno_types_specifier = Some(types_specifier.text.clone());
3609 dep.maybe_type = resolve(
3610 &types_specifier.text,
3611 Range {
3612 specifier: module_specifier.clone(),
3613 range: types_specifier.range,
3614 resolution_mode: import.specifier_range.resolution_mode,
3615 },
3616 ResolutionKind::Types,
3617 jsr_url_provider,
3618 maybe_resolver,
3619 );
3620 }
3621 }
3622 if matches!(
3623 import.kind,
3624 ImportKind::TsType | ImportKind::TsModuleAugmentation
3625 ) {
3626 if dep.maybe_type.is_none() {
3627 dep.maybe_type = resolve(
3628 &import.specifier,
3629 import.specifier_range.clone(),
3630 ResolutionKind::Types,
3631 jsr_url_provider,
3632 maybe_resolver,
3633 );
3634 }
3635 } else if !media_type.is_declaration() {
3636 if dep.maybe_code.is_none() {
3637 dep.maybe_code = resolve(
3640 &import.specifier,
3641 import.specifier_range.clone(),
3642 ResolutionKind::Execution,
3643 jsr_url_provider,
3644 maybe_resolver,
3645 );
3646 dep.is_dynamic = import.is_dynamic;
3647 } else {
3648 dep.is_dynamic = dep.is_dynamic && import.is_dynamic;
3653 }
3654 }
3655 if graph_kind.include_types() && dep.maybe_type.is_none() {
3656 let maybe_type = resolve(
3657 &import.specifier,
3658 import.specifier_range.clone(),
3659 ResolutionKind::Types,
3660 jsr_url_provider,
3661 maybe_resolver,
3662 );
3663 let is_side_effect_import_error =
3665 import.is_side_effect && maybe_type.err().is_some();
3666 if !is_side_effect_import_error
3667 && maybe_type.maybe_specifier() != dep.maybe_code.maybe_specifier()
3669 {
3670 dep.maybe_type = maybe_type
3671 }
3672 }
3673 dep.imports.push(import);
3674 }
3675 }
3676 if media_type.is_typed() {
3680 module_dependencies.retain(|_, dep| {
3681 if dep.get_type().is_some() {
3682 return true;
3683 }
3684 dep
3685 .imports
3686 .retain(|i| i.kind != ImportKind::TsModuleAugmentation);
3687 !dep.imports.is_empty()
3688 });
3689 }
3690}
3691
3692fn analyze_dynamic_arg_template_parts(
3693 parts: &[DynamicTemplatePart],
3694 referrer: &Url,
3695 referrer_range: &PositionRange,
3696 import_attributes: &ImportAttributes,
3697 file_system: &FileSystem,
3698) -> Vec<String> {
3699 fn resolve_initial_dir_path(
3700 parts: &[DynamicTemplatePart],
3701 referrer: &Url,
3702 ) -> Option<ModuleSpecifier> {
3703 match parts.first()? {
3704 DynamicTemplatePart::String { value } => {
3705 if value.starts_with("./") {
3706 referrer.join(value).ok()
3707 } else if value.starts_with("file://") {
3708 ModuleSpecifier::parse(value).ok()
3709 } else {
3710 None
3711 }
3712 }
3713 DynamicTemplatePart::Expr => None,
3715 }
3716 }
3717
3718 fn validate_string_parts(
3719 string_parts: &[&String],
3720 is_last_string: bool,
3721 ) -> bool {
3722 fn validate_part(
3723 index: usize,
3724 part: &str,
3725 path_parts: &[&String],
3726 is_last_string: bool,
3727 ) -> bool {
3728 !part.contains("/../")
3729 && if index == 0 {
3730 let valid_start = part.starts_with("./") || part.starts_with('/');
3731 let valid_end = part.ends_with('/');
3732 valid_start && valid_end
3733 } else if is_last_string && index == path_parts.len() - 1 {
3734 part.starts_with('/') || !part.contains('/')
3735 } else {
3736 part.starts_with('/') && part.ends_with('/')
3737 }
3738 }
3739
3740 string_parts.iter().enumerate().all(|(index, part)| {
3741 validate_part(index, part, string_parts, is_last_string)
3742 })
3743 }
3744
3745 let Some(dir_path) = resolve_initial_dir_path(parts, referrer) else {
3746 return Vec::new();
3747 };
3748
3749 let string_parts = parts
3750 .iter()
3751 .enumerate()
3752 .filter_map(|(i, p)| match p {
3753 DynamicTemplatePart::String { value } => {
3754 if i == 0 && value.starts_with("file://") {
3755 None } else {
3757 Some(value)
3758 }
3759 }
3760 DynamicTemplatePart::Expr => None,
3761 })
3762 .collect::<Vec<_>>();
3763 let is_last_string =
3764 matches!(parts.last(), Some(DynamicTemplatePart::String { .. }));
3765 if !validate_string_parts(&string_parts, is_last_string) {
3766 return Vec::new(); }
3768
3769 let matching_media_types = if import_attributes.get("type") == Some("json") {
3770 vec![MediaType::Json]
3771 } else {
3772 vec![
3773 MediaType::JavaScript,
3774 MediaType::TypeScript,
3775 MediaType::Jsx,
3776 MediaType::Tsx,
3777 MediaType::Mjs,
3778 MediaType::Mts,
3779 ]
3780 };
3781 let mut specifiers = Vec::new();
3782 if is_fs_root_specifier(&dir_path) {
3784 return specifiers;
3785 }
3786 let Ok(dir_path) = deno_path_util::url_to_file_path(&dir_path) else {
3787 return specifiers;
3788 };
3789 let mut pending_dirs = VecDeque::from([dir_path]);
3790 let handle_err = |path: &Path, err: &std::io::Error| {
3791 if matches!(
3792 err.kind(),
3793 std::io::ErrorKind::PermissionDenied | std::io::ErrorKind::NotFound
3794 ) {
3795 return;
3796 }
3797 log::warn!(
3803 "Graph failed resolving '{}'. {:#}\n at {}:{}:{}",
3804 path.display(),
3805 err,
3806 referrer,
3807 referrer_range.start.line + 1,
3808 referrer_range.start.character + 1,
3809 );
3810 };
3811 while let Some(dir_path) = pending_dirs.pop_front() {
3812 let entries = match file_system.fs_read_dir_boxed(&dir_path) {
3813 Ok(entries) => entries,
3814 Err(err) => {
3815 handle_err(&dir_path, &err);
3816 continue;
3817 }
3818 };
3819 for entry in entries {
3820 let entry = match entry {
3821 Ok(entry) => entry,
3822 Err(err) => {
3823 handle_err(&dir_path, &err);
3824 continue;
3825 }
3826 };
3827 let path = entry.path();
3828 match entry.file_type() {
3829 Ok(FileType::File) => {
3830 let Ok(url) = deno_path_util::url_from_file_path(&path) else {
3831 continue;
3832 };
3833 if matching_media_types.contains(&MediaType::from_specifier(&url)) {
3834 if url == *referrer {
3835 continue; }
3837 if let Some(specifier) = referrer.make_relative(&url) {
3838 let specifier = if !specifier.starts_with("../") {
3839 format!("./{}", specifier)
3840 } else {
3841 specifier
3842 };
3843 let mut valid = true;
3844 let mut last_index = 0;
3845 for part in &string_parts {
3846 if let Some(index) = &specifier[last_index..].find(*part) {
3847 last_index += index + part.len();
3848 } else {
3849 valid = false;
3850 break;
3851 }
3852 }
3853 if valid {
3854 if let Some(DynamicTemplatePart::String { value }) =
3855 parts.last()
3856 {
3857 if specifier.ends_with(value) {
3858 specifiers.push(specifier);
3859 }
3860 } else {
3861 specifiers.push(specifier);
3862 }
3863 }
3864 }
3865 }
3866 }
3867 Ok(FileType::Dir) => {
3868 let is_allowed_dir = path
3870 .file_name()
3871 .map(|c| {
3872 !c.to_string_lossy().starts_with('.')
3873 && c != "node_modules"
3874 && c != "vendor"
3875 })
3876 .unwrap_or(true);
3877 if is_allowed_dir {
3878 pending_dirs.push_back(path.into_owned());
3879 }
3880 }
3881 Ok(_) => {
3882 }
3884 Err(err) => {
3885 handle_err(&path, &err);
3886 }
3887 };
3888 }
3889 }
3890
3891 specifiers
3892}
3893
3894#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3896pub enum GraphKind {
3897 All,
3899 CodeOnly,
3903 TypesOnly,
3912}
3913
3914impl Default for GraphKind {
3915 fn default() -> Self {
3916 Self::All
3917 }
3918}
3919
3920impl GraphKind {
3921 pub fn include_types(&self) -> bool {
3922 matches!(self, Self::All | Self::TypesOnly)
3923 }
3924
3925 pub fn include_code(&self) -> bool {
3926 matches!(self, Self::All | Self::CodeOnly)
3927 }
3928}
3929
3930enum PendingInfoResponse {
3931 External {
3932 specifier: ModuleSpecifier,
3933 is_root: bool,
3934 is_asset: bool,
3935 },
3936 Module {
3937 specifier: ModuleSpecifier,
3938 module_source_and_info: ModuleSourceAndInfo,
3939 pending_load: Option<Box<(LoaderChecksum, ModuleInfo)>>,
3940 is_root: bool,
3941 },
3942 Redirect {
3943 count: usize,
3944 specifier: ModuleSpecifier,
3945 maybe_attribute_type: Option<AttributeTypeWithRange>,
3946 is_asset: bool,
3947 is_dynamic: bool,
3948 is_root: bool,
3949 },
3950}
3951
3952impl PendingInfoResponse {
3953 fn specifier(&self) -> &ModuleSpecifier {
3954 match self {
3955 Self::External { specifier, .. } => specifier,
3956 Self::Module {
3957 module_source_and_info,
3958 ..
3959 } => module_source_and_info.specifier(),
3960 Self::Redirect { specifier, .. } => specifier,
3961 }
3962 }
3963}
3964
3965#[derive(Debug, Clone)]
3966struct JsrPackageVersionInfoExt {
3967 base_url: Url,
3968 inner: Arc<JsrPackageVersionInfo>,
3969}
3970
3971impl JsrPackageVersionInfoExt {
3972 pub fn get_subpath<'a>(&self, specifier: &'a Url) -> Option<&'a str> {
3973 let base_url = self.base_url.as_str();
3974 let base_url = base_url.strip_suffix('/').unwrap_or(base_url);
3975 specifier.as_str().strip_prefix(base_url)
3976 }
3977
3978 pub fn get_checksum(&self, sub_path: &str) -> Result<&str, ModuleLoadError> {
3979 match self.inner.manifest.get(sub_path) {
3980 Some(manifest_entry) => {
3981 match manifest_entry.checksum.strip_prefix("sha256-") {
3982 Some(checksum) => Ok(checksum),
3983 None => Err(ModuleLoadError::Jsr(
3984 JsrLoadError::UnsupportedManifestChecksum,
3985 )),
3986 }
3987 }
3988 None => Ok("package-manifest-missing-checksum"),
3994 }
3995 }
3996}
3997
3998enum LoadSpecifierKind {
3999 Jsr(JsrPackageReqReference),
4000 Npm(NpmPackageReqReference),
4001 Node(String),
4002 Url,
4003}
4004
4005struct PendingInfo {
4006 requested_specifier: ModuleSpecifier,
4007 maybe_range: Option<Range>,
4008 result: Result<PendingInfoResponse, ModuleError>,
4009 maybe_version_info: Option<JsrPackageVersionInfoExt>,
4010 loaded_package_via_https_url: Option<LoadedJsrPackageViaHttpsUrl>,
4011}
4012
4013struct PendingModuleLoadItem {
4014 redirect_count: usize,
4015 requested_specifier: Url,
4016 maybe_attribute_type: Option<AttributeTypeWithRange>,
4017 maybe_range: Option<Range>,
4018 load_specifier: Url,
4019 in_dynamic_branch: bool,
4020 is_asset: bool,
4021 is_root: bool,
4022 maybe_checksum: Option<LoaderChecksum>,
4023 maybe_version_info: Option<JsrPackageVersionInfoExt>,
4024}
4025
4026struct LoadedJsrPackageViaHttpsUrl {
4027 nv: PackageNv,
4028 manifest_checksum_for_locker: Option<LoaderChecksum>,
4029}
4030
4031type PendingInfoFuture<'a> = LocalBoxFuture<'a, PendingInfo>;
4032
4033#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4034pub(crate) struct AttributeTypeWithRange {
4035 range: Range,
4036 kind: String,
4038}
4039
4040#[derive(Debug, Default)]
4041struct PendingNpmState {
4042 requested_registry_info_loads: HashSet<StackString>,
4043 pending_resolutions: Vec<PendingNpmResolutionItem>,
4044}
4045
4046#[derive(Debug)]
4047struct PendingJsrReqResolutionItem {
4048 specifier: ModuleSpecifier,
4049 package_ref: JsrPackageReqReference,
4050 maybe_attribute_type: Option<AttributeTypeWithRange>,
4051 maybe_range: Option<Range>,
4052 in_dynamic_branch: bool,
4053 is_asset: bool,
4054 is_root: bool,
4055}
4056
4057#[derive(Debug)]
4058struct PendingJsrNvResolutionItem {
4059 specifier: ModuleSpecifier,
4060 nv_ref: JsrPackageNvReference,
4061 maybe_attribute_type: Option<AttributeTypeWithRange>,
4062 maybe_range: Option<Range>,
4063 is_asset: bool,
4064 is_dynamic: bool,
4065 is_root: bool,
4066}
4067
4068#[derive(Debug)]
4069struct PendingContentLoadItem {
4070 specifier: ModuleSpecifier,
4071 maybe_range: Option<Range>,
4072 result: LoadResult,
4073 module_info: ModuleInfo,
4074}
4075
4076#[derive(Debug, Default)]
4077struct PendingJsrState {
4078 pending_resolutions: VecDeque<PendingJsrReqResolutionItem>,
4079 pending_content_loads:
4080 FuturesUnordered<LocalBoxFuture<'static, PendingContentLoadItem>>,
4081 metadata: Rc<JsrMetadataStore>,
4082}
4083
4084#[derive(Debug)]
4085struct PendingDynamicBranch {
4086 range: Range,
4087 is_asset: bool,
4088 maybe_attribute_type: Option<AttributeTypeWithRange>,
4089 maybe_version_info: Option<JsrPackageVersionInfoExt>,
4090}
4091
4092#[derive(Debug)]
4093struct DeferredLoad {
4094 maybe_range: Option<Range>,
4095 in_dynamic_branch: bool,
4096 is_root: bool,
4097 maybe_attribute_type: Option<AttributeTypeWithRange>,
4098 maybe_version_info: Option<JsrPackageVersionInfoExt>,
4099}
4100
4101struct LoadOptionsRef<'a> {
4102 specifier: &'a ModuleSpecifier,
4103 maybe_range: Option<&'a Range>,
4104 is_asset: bool,
4105 in_dynamic_branch: bool,
4106 is_root: bool,
4107 maybe_attribute_type: Option<AttributeTypeWithRange>,
4108 maybe_version_info: Option<&'a JsrPackageVersionInfoExt>,
4109}
4110
4111#[derive(Debug, Default)]
4112struct PendingState<'a> {
4113 deferred: HashMap<ModuleSpecifier, DeferredLoad>,
4114 pending: FuturesOrdered<PendingInfoFuture<'a>>,
4115 jsr: PendingJsrState,
4116 npm: PendingNpmState,
4117 dynamic_branches: HashMap<ModuleSpecifier, PendingDynamicBranch>,
4118}
4119
4120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4121enum FillPassMode {
4122 AllowRestart,
4123 NoRestart,
4124 CacheBusting,
4125}
4126
4127impl FillPassMode {
4128 fn to_cache_setting(self) -> CacheSetting {
4129 if self == FillPassMode::CacheBusting {
4130 CacheSetting::Reload
4131 } else {
4132 CacheSetting::Use
4133 }
4134 }
4135}
4136
4137struct Builder<'a, 'graph> {
4138 in_dynamic_branch: bool,
4139 skip_dynamic_deps: bool,
4140 was_dynamic_root: bool,
4141 unstable_bytes_imports: bool,
4142 unstable_text_imports: bool,
4143 file_system: &'a FileSystem,
4144 jsr_url_provider: &'a dyn JsrUrlProvider,
4145 jsr_version_resolver: Cow<'a, JsrVersionResolver>,
4146 passthrough_jsr_specifiers: bool,
4147 loader: &'a dyn Loader,
4148 locker: Option<&'a mut dyn Locker>,
4149 resolver: Option<&'a dyn Resolver>,
4150 module_analyzer: &'a dyn ModuleAnalyzer,
4151 module_info_cacher: &'a dyn ModuleInfoCacher,
4152 npm_resolver: Option<&'a dyn NpmResolver>,
4153 reporter: Option<&'a dyn Reporter>,
4154 graph: &'graph mut ModuleGraph,
4155 state: PendingState<'a>,
4156 fill_pass_mode: FillPassMode,
4157 executor: &'a dyn Executor,
4158 resolved_roots: BTreeSet<ModuleSpecifier>,
4159}
4160
4161impl<'a, 'graph> Builder<'a, 'graph> {
4162 pub fn new(
4163 graph: &'graph mut ModuleGraph,
4164 loader: &'a dyn Loader,
4165 options: BuildOptions<'a>,
4166 ) -> Self {
4167 let fill_pass_mode = match graph.roots.is_empty() {
4168 true => FillPassMode::AllowRestart,
4169 false => FillPassMode::NoRestart,
4170 };
4171 Self {
4172 in_dynamic_branch: options.is_dynamic,
4173 skip_dynamic_deps: options.skip_dynamic_deps,
4174 was_dynamic_root: options.is_dynamic,
4175 unstable_bytes_imports: options.unstable_bytes_imports,
4176 unstable_text_imports: options.unstable_text_imports,
4177 file_system: options.file_system,
4178 jsr_url_provider: options.jsr_url_provider,
4179 jsr_version_resolver: options.jsr_version_resolver,
4180 passthrough_jsr_specifiers: options.passthrough_jsr_specifiers,
4181 loader,
4182 locker: options.locker,
4183 resolver: options.resolver,
4184 npm_resolver: options.npm_resolver,
4185 module_analyzer: options.module_analyzer,
4186 module_info_cacher: options.module_info_cacher,
4187 reporter: options.reporter,
4188 graph,
4189 state: PendingState {
4190 jsr: PendingJsrState {
4191 metadata: options
4192 .jsr_metadata_store
4193 .unwrap_or(Rc::new(JsrMetadataStore::default())),
4194 ..Default::default()
4195 },
4196 ..Default::default()
4197 },
4198 fill_pass_mode,
4199 executor: options.executor,
4200 resolved_roots: Default::default(),
4201 }
4202 }
4203
4204 pub async fn build(
4205 &mut self,
4206 roots: Vec<ModuleSpecifier>,
4207 imports: Vec<ReferrerImports>,
4208 ) {
4209 let provided_roots = roots;
4210 let provided_imports = imports;
4211 let roots = provided_roots
4212 .iter()
4213 .filter(|r| !self.graph.roots.contains(*r))
4214 .cloned()
4215 .collect::<Vec<_>>();
4216 let imports = provided_imports
4217 .iter()
4218 .filter(|r| !self.graph.imports.contains_key(&r.referrer))
4219 .cloned()
4220 .collect::<Vec<_>>();
4221
4222 self.graph.roots.extend(roots.clone());
4223
4224 for root in roots {
4225 self.load(LoadOptionsRef {
4226 specifier: &root,
4227 maybe_range: None,
4228 is_asset: false,
4229 in_dynamic_branch: self.in_dynamic_branch,
4230 is_root: true,
4231 maybe_attribute_type: None,
4232 maybe_version_info: None,
4233 });
4234 }
4235
4236 self.handle_provided_imports(imports);
4238
4239 let should_restart = self.resolve_pending().await;
4240 if should_restart {
4241 self.restart(provided_roots, provided_imports).await
4242 }
4243 }
4244
4245 pub async fn reload(&mut self, specifiers: Vec<ModuleSpecifier>) {
4248 let specifiers = specifiers
4249 .into_iter()
4250 .map(|s| {
4251 let resolved = self.graph.resolve(&s);
4252 if *resolved == s {
4253 s } else {
4255 resolved.clone()
4256 }
4257 })
4258 .collect::<Vec<_>>();
4259
4260 for specifier in &specifiers {
4261 self.graph.module_slots.remove(specifier);
4262 self.load(LoadOptionsRef {
4263 specifier,
4264 maybe_range: None,
4265 is_asset: false,
4266 in_dynamic_branch: self.in_dynamic_branch,
4267 is_root: true,
4268 maybe_attribute_type: None,
4269 maybe_version_info: None,
4270 });
4271 }
4272 self.resolve_pending().await;
4273 }
4274
4275 async fn resolve_pending(&mut self) -> bool {
4276 while !(self.state.pending.is_empty()
4277 && self.state.jsr.pending_resolutions.is_empty()
4278 && self.state.dynamic_branches.is_empty()
4279 && self.state.deferred.is_empty())
4280 {
4281 let specifier = match self.state.pending.next().await {
4282 Some(PendingInfo {
4283 requested_specifier,
4284 maybe_range,
4285 result,
4286 maybe_version_info,
4287 loaded_package_via_https_url,
4288 }) => {
4289 if let Some(pkg) = loaded_package_via_https_url {
4290 if let Some(locker) = &mut self.locker {
4291 if let Some(checksum) = pkg.manifest_checksum_for_locker {
4292 locker.set_pkg_manifest_checksum(&pkg.nv, checksum);
4293 }
4294 }
4295 self.graph.packages.ensure_package(pkg.nv);
4296 }
4297
4298 match result {
4299 Ok(response) => {
4300 self.check_specifier(&requested_specifier, response.specifier());
4301
4302 self.visit(
4303 response,
4304 maybe_range.clone(),
4305 maybe_version_info.as_ref(),
4306 );
4307
4308 Some(requested_specifier)
4309 }
4310 Err(err) => {
4311 self.check_specifier(&requested_specifier, err.specifier());
4312 self
4313 .graph
4314 .module_slots
4315 .insert(err.specifier().clone(), ModuleSlot::Err(err));
4316 Some(requested_specifier)
4317 }
4318 }
4319 }
4320 None => None,
4321 };
4322
4323 if let (Some(specifier), Some(reporter)) = (specifier, self.reporter) {
4324 let modules_total = self.graph.module_slots.len();
4325 let modules_done = modules_total - self.state.pending.len();
4326 reporter.on_load(&specifier, modules_done, modules_total);
4327 }
4328
4329 if self.state.pending.is_empty() {
4330 if !self.state.deferred.is_empty() {
4331 let items = std::mem::take(&mut self.state.deferred);
4332 for (specifier, item) in items {
4333 self.load(LoadOptionsRef {
4334 specifier: &specifier,
4335 maybe_range: item.maybe_range.as_ref(),
4336 is_asset: false, in_dynamic_branch: item.in_dynamic_branch,
4338 is_root: item.is_root,
4339 maybe_attribute_type: item.maybe_attribute_type,
4340 maybe_version_info: item.maybe_version_info.as_ref(),
4341 });
4342 }
4343 } else {
4344 let should_restart = self.resolve_pending_jsr_specifiers().await;
4345 if should_restart {
4346 return true;
4347 }
4348
4349 if self.state.pending.is_empty() {
4351 self.resolve_dynamic_branches();
4352 }
4353 }
4354 }
4355 }
4356
4357 self.handle_jsr_registry_pending_content_loads().await;
4359
4360 self.fill_graph_with_cache_info();
4362
4363 NpmSpecifierResolver::fill_builder(self).await;
4365
4366 false
4367 }
4368
4369 fn handle_provided_imports(&mut self, imports: Vec<ReferrerImports>) {
4370 for referrer_imports in imports {
4371 let referrer = referrer_imports.referrer;
4372 let imports = referrer_imports.imports;
4373 let graph_import = GraphImport::new(
4374 &referrer,
4375 imports,
4376 self.jsr_url_provider,
4377 self.resolver,
4378 );
4379 for dep in graph_import.dependencies.values() {
4380 if let Resolution::Ok(resolved) = &dep.maybe_type {
4381 self.load(LoadOptionsRef {
4382 specifier: &resolved.specifier,
4383 maybe_range: Some(&resolved.range),
4384 is_asset: false,
4385 in_dynamic_branch: self.in_dynamic_branch,
4386 is_root: self.resolved_roots.contains(&resolved.specifier),
4387 maybe_attribute_type: None,
4388 maybe_version_info: None,
4389 });
4390 }
4391 }
4392 self.graph.imports.insert(referrer, graph_import);
4393 }
4394 }
4395
4396 async fn resolve_pending_jsr_specifiers(&mut self) -> bool {
4397 let mut pending_resolutions =
4399 std::mem::take(&mut self.state.jsr.pending_resolutions);
4400 let mut pending_version_resolutions =
4401 Vec::with_capacity(pending_resolutions.len());
4402 let should_collect_top_level_nvs =
4403 self.graph.packages.top_level_packages().is_empty()
4404 && self.graph.graph_kind.include_types();
4405 let mut restarted_pkgs = HashSet::new();
4407 while let Some(pending_resolution) = pending_resolutions.pop_front() {
4408 let package_name = &pending_resolution.package_ref.req().name;
4409 let fut = self
4410 .state
4411 .jsr
4412 .metadata
4413 .get_package_metadata(package_name)
4414 .unwrap();
4415 match fut.await {
4416 Ok(info) => {
4417 let package_req = pending_resolution.package_ref.req();
4418 match self.resolve_jsr_nv(package_req, &info) {
4419 Ok(package_nv) => {
4420 self.queue_load_package_version_info(&package_nv);
4422 pending_version_resolutions.push(PendingJsrNvResolutionItem {
4423 specifier: pending_resolution.specifier,
4424 nv_ref: JsrPackageNvReference::new(PackageNvReference {
4425 nv: package_nv,
4426 sub_path: pending_resolution
4427 .package_ref
4428 .into_inner()
4429 .sub_path,
4430 }),
4431 maybe_attribute_type: pending_resolution.maybe_attribute_type,
4432 maybe_range: pending_resolution.maybe_range,
4433 is_asset: pending_resolution.is_asset,
4434 is_dynamic: pending_resolution.in_dynamic_branch,
4435 is_root: pending_resolution.is_root,
4436 });
4437 }
4438 Err(package_req_not_found_err) => {
4439 if self.fill_pass_mode == FillPassMode::AllowRestart {
4445 return true; } else if self.fill_pass_mode != FillPassMode::CacheBusting
4447 && restarted_pkgs.insert(package_name.clone())
4448 {
4449 self
4450 .state
4451 .jsr
4452 .metadata
4453 .remove_package_metadata(package_name);
4454 self.state.jsr.metadata.queue_load_package_info(
4455 package_name,
4456 CacheSetting::Reload, JsrMetadataStoreServices {
4458 executor: self.executor,
4459 jsr_url_provider: self.jsr_url_provider,
4460 loader: self.loader,
4461 },
4462 );
4463 pending_resolutions.push_front(pending_resolution);
4464 } else {
4465 self.graph.module_slots.insert(
4466 pending_resolution.specifier.clone(),
4467 ModuleSlot::Err(
4468 ModuleErrorKind::Load {
4469 specifier: pending_resolution.specifier.clone(),
4470 maybe_referrer: pending_resolution.maybe_range.clone(),
4471 err: JsrLoadError::PackageReqNotFound(
4472 package_req_not_found_err,
4473 )
4474 .into(),
4475 }
4476 .into_box(),
4477 ),
4478 );
4479 }
4480 }
4481 }
4482 }
4483 Err(err) => {
4484 self.graph.module_slots.insert(
4485 pending_resolution.specifier.clone(),
4486 ModuleSlot::Err(
4487 ModuleErrorKind::Load {
4488 specifier: pending_resolution.specifier,
4489 maybe_referrer: pending_resolution.maybe_range,
4490 err: err.into(),
4491 }
4492 .into_box(),
4493 ),
4494 );
4495 }
4496 }
4497 }
4498
4499 for resolution_item in pending_version_resolutions {
4501 let nv = resolution_item.nv_ref.nv();
4502 let version_info_result = self
4503 .state
4504 .jsr
4505 .metadata
4506 .get_package_version_metadata(nv)
4507 .unwrap()
4508 .await;
4509 match version_info_result {
4510 Ok(version_info_load_item) => {
4511 let version_info = version_info_load_item.info;
4512 self.graph.packages.ensure_package(nv.clone());
4513 if let Some(locker) = &mut self.locker {
4514 if let Some(checksum) = version_info_load_item.checksum_for_locker {
4515 locker.set_pkg_manifest_checksum(nv, checksum);
4516 }
4517 }
4518 let base_url = self.jsr_url_provider.package_url(nv);
4519 let export_name = resolution_item.nv_ref.export_name();
4520 match version_info.export(&export_name) {
4521 Some(export_value) => {
4522 self.graph.packages.add_export(
4523 nv,
4524 (
4525 resolution_item.nv_ref.export_name().into_owned(),
4526 export_value.to_string(),
4527 ),
4528 );
4529 if should_collect_top_level_nvs {
4530 self.graph.packages.add_top_level_package(nv.clone());
4531 }
4532
4533 let specifier = base_url.join(export_value).unwrap();
4534 self
4535 .graph
4536 .redirects
4537 .insert(resolution_item.specifier, specifier.clone());
4538 if resolution_item.is_root {
4539 self.resolved_roots.insert(specifier.clone());
4540 }
4541 let version_info = JsrPackageVersionInfoExt {
4542 base_url,
4543 inner: version_info,
4544 };
4545 self.load(LoadOptionsRef {
4546 specifier: &specifier,
4547 maybe_range: resolution_item.maybe_range.as_ref(),
4548 is_asset: resolution_item.is_asset,
4549 in_dynamic_branch: resolution_item.is_dynamic,
4550 is_root: resolution_item.is_root,
4551 maybe_attribute_type: resolution_item.maybe_attribute_type,
4552 maybe_version_info: Some(&version_info),
4553 });
4554 }
4555 None => {
4556 self.graph.module_slots.insert(
4557 resolution_item.specifier.clone(),
4558 ModuleSlot::Err(
4559 ModuleErrorKind::Load {
4560 specifier: resolution_item.specifier,
4561 maybe_referrer: resolution_item.maybe_range,
4562 err: JsrLoadError::UnknownExport {
4563 export_name: export_name.to_string(),
4564 nv: Box::new(resolution_item.nv_ref.into_inner().nv),
4565 exports: version_info
4566 .exports()
4567 .map(|(k, _)| k.to_string())
4568 .collect::<Vec<_>>(),
4569 }
4570 .into(),
4571 }
4572 .into_box(),
4573 ),
4574 );
4575 }
4576 }
4577 }
4578 Err(err) => {
4579 self.graph.module_slots.insert(
4580 resolution_item.specifier.clone(),
4581 ModuleSlot::Err(
4582 ModuleErrorKind::Load {
4583 specifier: resolution_item.specifier,
4584 maybe_referrer: resolution_item.maybe_range,
4585 err: err.into(),
4586 }
4587 .into_box(),
4588 ),
4589 );
4590 }
4591 }
4592 }
4593
4594 false }
4596
4597 fn resolve_dynamic_branches(&mut self) {
4598 if !self.in_dynamic_branch {
4606 self.in_dynamic_branch = true;
4607 for (specifier, dynamic_branch) in
4608 std::mem::take(&mut self.state.dynamic_branches)
4609 {
4610 self.load(LoadOptionsRef {
4611 specifier: &specifier,
4612 maybe_range: Some(&dynamic_branch.range),
4613 is_asset: dynamic_branch.is_asset,
4614 in_dynamic_branch: true,
4615 is_root: self.resolved_roots.contains(&specifier),
4616 maybe_attribute_type: dynamic_branch.maybe_attribute_type,
4617 maybe_version_info: dynamic_branch.maybe_version_info.as_ref(),
4618 });
4619 }
4620 }
4621 }
4622
4623 async fn handle_jsr_registry_pending_content_loads(&mut self) {
4624 while let Some(item) = self.state.jsr.pending_content_loads.next().await {
4625 match item.result {
4626 Ok(Some(response)) => {
4627 match response {
4628 LoadResponse::External { .. } => {
4629 self.graph.module_slots.insert(
4632 item.specifier.clone(),
4633 ModuleSlot::Err(
4634 ModuleErrorKind::Load {
4635 specifier: item.specifier,
4636 maybe_referrer: item.maybe_range,
4637 err: JsrLoadError::ContentLoadExternalSpecifier.into(),
4638 }
4639 .into_box(),
4640 ),
4641 );
4642 }
4643 LoadResponse::Module {
4644 content,
4645 specifier,
4646 mtime: _,
4647 maybe_headers: _maybe_headers,
4648 } if specifier == item.specifier => {
4649 let slot = self.graph.module_slots.get_mut(&specifier).unwrap();
4651 match slot {
4652 ModuleSlot::Module(module) => {
4653 match module {
4654 Module::Js(module) => {
4655 self.module_info_cacher.cache_module_info(
4656 &specifier,
4657 module.media_type,
4658 &content,
4659 &item.module_info,
4660 );
4661 match new_source_with_text(
4662 &module.specifier,
4663 content,
4664 None, None, ) {
4667 Ok(source) => {
4668 module.source = source;
4669 }
4670 Err(err) => *slot = ModuleSlot::Err(err),
4671 }
4672 }
4673 Module::Json(module) => {
4674 match new_source_with_text(
4675 &module.specifier,
4676 content,
4677 None, None, ) {
4680 Ok(source) => {
4681 module.source = source;
4682 }
4683 Err(err) => *slot = ModuleSlot::Err(err),
4684 }
4685 }
4686 Module::Wasm(module) => {
4687 match wasm_module_to_dts(&content) {
4688 Ok(source_dts) => {
4689 module.source = content.clone();
4690 module.source_dts = source_dts.into();
4691 }
4692 Err(err) => {
4693 *slot = ModuleSlot::Err(
4694 ModuleErrorKind::WasmParse {
4695 specifier: module.specifier.clone(),
4696 mtime: None,
4697 err,
4698 }
4699 .into_box(),
4700 );
4701 }
4702 }
4703 }
4704 Module::Npm(_) | Module::Node(_) | Module::External(_) => {
4705 unreachable!(); }
4707 }
4708 }
4709 ModuleSlot::Err(_) => {
4710 }
4712 ModuleSlot::Pending { .. } => {
4713 unreachable!(); }
4715 }
4716 }
4717 LoadResponse::Redirect { specifier }
4718 | LoadResponse::Module { specifier, .. } => {
4719 self.graph.module_slots.insert(
4721 item.specifier.clone(),
4722 ModuleSlot::Err(
4723 ModuleErrorKind::Load {
4724 specifier: item.specifier,
4725 maybe_referrer: item.maybe_range,
4726 err: JsrLoadError::RedirectInPackage(specifier).into(),
4727 }
4728 .into_box(),
4729 ),
4730 );
4731 }
4732 }
4733 }
4734 Ok(None) => {
4735 self.graph.module_slots.insert(
4736 item.specifier.clone(),
4737 ModuleSlot::Err(
4738 ModuleErrorKind::Missing {
4739 specifier: item.specifier,
4740 maybe_referrer: item.maybe_range,
4741 }
4742 .into_box(),
4743 ),
4744 );
4745 }
4746 Err(err) => {
4747 self.graph.module_slots.insert(
4748 item.specifier.clone(),
4749 ModuleSlot::Err(
4750 ModuleErrorKind::Load {
4751 specifier: item.specifier,
4752 maybe_referrer: item.maybe_range,
4753 err: JsrLoadError::ContentLoad(Arc::new(err)).into(),
4754 }
4755 .into_box(),
4756 ),
4757 );
4758 }
4759 }
4760 }
4761 }
4762
4763 fn fill_graph_with_cache_info(&mut self) {
4764 if !self.loader.cache_info_enabled() {
4765 return;
4766 }
4767 for slot in self.graph.module_slots.values_mut() {
4768 if let ModuleSlot::Module(ref mut module) = slot {
4769 match module {
4770 Module::Json(module) => {
4771 module.maybe_cache_info =
4772 self.loader.get_cache_info(&module.specifier);
4773 }
4774 Module::Js(module) => {
4775 module.maybe_cache_info =
4776 self.loader.get_cache_info(&module.specifier);
4777 }
4778 Module::Wasm(module) => {
4779 module.maybe_cache_info =
4780 self.loader.get_cache_info(&module.specifier);
4781 }
4782 Module::External(module) => {
4783 module.maybe_cache_info =
4784 self.loader.get_cache_info(&module.specifier);
4785 }
4786 Module::Npm(_) | Module::Node(_) => {}
4787 }
4788 }
4789 }
4790 }
4791
4792 fn restart(
4793 &mut self,
4794 roots: Vec<ModuleSpecifier>,
4795 imports: Vec<ReferrerImports>,
4796 ) -> LocalBoxFuture<'_, ()> {
4797 *self.graph = ModuleGraph::new(self.graph.graph_kind);
4799 self.state = PendingState::default();
4800 self.fill_pass_mode = FillPassMode::CacheBusting;
4801
4802 async move { self.build(roots, imports).await }.boxed_local()
4804 }
4805
4806 fn resolve_jsr_nv(
4807 &mut self,
4808 package_req: &PackageReq,
4809 package_info: &JsrPackageInfo,
4810 ) -> Result<PackageNv, JsrPackageReqNotFoundError> {
4811 let resolved_version = self.jsr_version_resolver.resolve_version(
4812 package_req,
4813 package_info,
4814 self
4815 .graph
4816 .packages
4817 .versions_by_name(&package_req.name)
4818 .into_iter()
4819 .flatten()
4820 .map(|nv| &nv.version),
4821 )?;
4822 let version = resolved_version.version.clone();
4823 if resolved_version.is_yanked {
4824 self.graph.packages.add_used_yanked_package(PackageNv {
4825 name: package_req.name.clone(),
4826 version: version.clone(),
4827 });
4828 }
4829 let package_nv = PackageNv {
4830 name: package_req.name.clone(),
4831 version,
4832 };
4833 if let Some(reporter) = &self.reporter {
4834 reporter.on_resolve(package_req, &package_nv);
4835 }
4836 self
4837 .graph
4838 .packages
4839 .add_nv(package_req.clone(), package_nv.clone());
4840 Ok(package_nv)
4841 }
4842
4843 fn check_specifier(
4846 &mut self,
4847 requested_specifier: &ModuleSpecifier,
4848 specifier: &ModuleSpecifier,
4849 ) {
4850 if requested_specifier != specifier {
4852 self.add_redirect(requested_specifier.clone(), specifier.clone());
4853 }
4854 }
4855
4856 fn add_redirect(
4857 &mut self,
4858 requested_specifier: ModuleSpecifier,
4859 specifier: ModuleSpecifier,
4860 ) {
4861 debug_assert_ne!(requested_specifier, specifier);
4862 if let Some(slot) = self.graph.module_slots.get(&requested_specifier) {
4864 if matches!(slot, ModuleSlot::Pending { .. }) {
4865 self.graph.module_slots.remove(&requested_specifier);
4866 }
4867 }
4868
4869 self
4870 .graph
4871 .redirects
4872 .entry(requested_specifier)
4873 .or_insert(specifier);
4874 }
4875
4876 fn load(&mut self, options: LoadOptionsRef) {
4878 self.load_with_redirect_count(0, options)
4879 }
4880
4881 #[allow(clippy::too_many_arguments)]
4882 fn load_with_redirect_count(
4883 &mut self,
4884 redirect_count: usize,
4885 options: LoadOptionsRef,
4886 ) {
4887 let specifier = options.specifier;
4888 let maybe_range = options.maybe_range;
4889 let original_specifier = specifier;
4890 let specifier = self.graph.redirects.get(specifier).unwrap_or(specifier);
4891 if options.is_asset {
4892 if let Some(attribute) = &options.maybe_attribute_type {
4894 let is_allowed = match attribute.kind.as_str() {
4895 "bytes" => self.unstable_bytes_imports,
4896 "text" => self.unstable_text_imports,
4897 _ => false,
4898 };
4899 if !is_allowed {
4900 self.graph.module_slots.insert(
4901 specifier.clone(),
4902 ModuleSlot::Err(
4903 ModuleErrorKind::UnsupportedImportAttributeType {
4904 specifier: specifier.clone(),
4905 referrer: attribute.range.clone(),
4906 kind: attribute.kind.clone(),
4907 }
4908 .into_box(),
4909 ),
4910 );
4911 return;
4912 }
4913 }
4914 }
4915 if let Some(module_slot) = self.graph.module_slots.get(specifier) {
4916 let should_reload_immediately =
4917 module_slot.was_external_asset_load() && !options.is_asset;
4918 if !should_reload_immediately {
4919 let should_defer_reload =
4920 module_slot.is_pending_asset_load() && !options.is_asset;
4921 if should_defer_reload {
4922 self
4927 .state
4928 .deferred
4929 .entry(specifier.clone())
4930 .or_insert_with(|| DeferredLoad {
4931 maybe_range: maybe_range.cloned(),
4932 in_dynamic_branch: options.in_dynamic_branch,
4933 is_root: options.is_root,
4934 maybe_attribute_type: options.maybe_attribute_type,
4935 maybe_version_info: options.maybe_version_info.cloned(),
4936 });
4937 }
4938
4939 if matches!(original_specifier.scheme(), "jsr" | "npm") {
4942 if let Ok(load_specifier) =
4943 self.parse_load_specifier_kind(original_specifier, maybe_range)
4944 {
4945 self.maybe_mark_dep(&load_specifier, maybe_range);
4946 }
4947 }
4948
4949 return;
4950 }
4951 }
4952
4953 if let Some(version_info) = options.maybe_version_info {
4954 let specifier = specifier.clone();
4955 if let Some(sub_path) = version_info.get_subpath(&specifier) {
4956 self.load_jsr_subpath(
4957 redirect_count,
4958 &specifier,
4959 version_info,
4960 sub_path,
4961 options,
4962 );
4963 return;
4964 }
4965 }
4966
4967 let specifier = specifier.clone();
4968 match self.parse_load_specifier_kind(&specifier, maybe_range) {
4969 Ok(LoadSpecifierKind::Jsr(package_req_ref)) => {
4970 self.mark_jsr_dep(&package_req_ref, maybe_range);
4971 if self.passthrough_jsr_specifiers {
4972 self.graph.module_slots.insert(
4974 specifier.clone(),
4975 ModuleSlot::Module(Module::External(ExternalModule {
4976 specifier: specifier.clone(),
4977 maybe_cache_info: None,
4978 was_asset_load: false,
4979 })),
4980 );
4981 } else {
4982 self.load_jsr_specifier(
4983 specifier,
4984 package_req_ref,
4985 options.maybe_attribute_type,
4986 maybe_range,
4987 options.is_asset,
4988 options.in_dynamic_branch,
4989 options.is_root,
4990 );
4991 }
4992 }
4993 Ok(LoadSpecifierKind::Npm(package_req_ref)) => {
4994 self.mark_npm_dep(&package_req_ref, maybe_range);
4995 if let Some(npm_resolver) = self.npm_resolver {
4996 self.load_npm_specifier(
4997 npm_resolver,
4998 specifier.clone(),
4999 package_req_ref,
5000 maybe_range,
5001 options.in_dynamic_branch,
5002 );
5003 } else {
5004 self.graph.module_slots.insert(
5006 specifier.clone(),
5007 ModuleSlot::Module(Module::External(ExternalModule {
5008 maybe_cache_info: None,
5009 specifier: specifier.clone(),
5010 was_asset_load: false,
5011 })),
5012 );
5013 }
5014 }
5015 Ok(LoadSpecifierKind::Node(module_name)) => {
5016 self.graph.has_node_specifier = true;
5017 self.graph.module_slots.insert(
5018 specifier.clone(),
5019 ModuleSlot::Module(Module::Node(BuiltInNodeModule {
5020 specifier: specifier.clone(),
5021 module_name,
5022 })),
5023 );
5024 }
5025 Ok(LoadSpecifierKind::Url) => {
5026 self.load_pending_module(PendingModuleLoadItem {
5027 redirect_count,
5028 requested_specifier: specifier.clone(),
5029 maybe_attribute_type: options.maybe_attribute_type,
5030 maybe_range: maybe_range.cloned(),
5031 load_specifier: specifier.clone(),
5032 is_asset: options.is_asset,
5033 in_dynamic_branch: options.in_dynamic_branch,
5034 is_root: options.is_root,
5035 maybe_checksum: None,
5036 maybe_version_info: None,
5037 });
5038 }
5039 Err(err) => {
5040 self
5041 .graph
5042 .module_slots
5043 .insert(specifier.clone(), ModuleSlot::Err(err));
5044 }
5045 }
5046 }
5047
5048 fn load_jsr_subpath(
5049 &mut self,
5050 redirect_count: usize,
5051 specifier: &ModuleSpecifier,
5052 version_info: &JsrPackageVersionInfoExt,
5053 sub_path: &str,
5054 options: LoadOptionsRef,
5055 ) {
5056 struct ProvidedModuleAnalyzer(RefCell<Option<ModuleInfo>>);
5057
5058 #[async_trait::async_trait(?Send)]
5059 impl ModuleAnalyzer for ProvidedModuleAnalyzer {
5060 async fn analyze(
5061 &self,
5062 _specifier: &ModuleSpecifier,
5063 _source: Arc<str>,
5064 _media_type: MediaType,
5065 ) -> Result<ModuleInfo, JsErrorBox> {
5066 Ok(self.0.borrow_mut().take().unwrap()) }
5068 }
5069
5070 let checksum = match version_info.get_checksum(sub_path) {
5071 Ok(checksum) => checksum,
5072 Err(err) => {
5073 self.graph.module_slots.insert(
5074 specifier.clone(),
5075 ModuleSlot::Err(
5076 ModuleErrorKind::Load {
5077 specifier: specifier.clone(),
5078 maybe_referrer: options.maybe_range.cloned(),
5079 err,
5080 }
5081 .into_box(),
5082 ),
5083 );
5084 return;
5085 }
5086 };
5087 let checksum = LoaderChecksum::new(checksum.to_string());
5088
5089 if let Some(module_info) = (!options.is_asset)
5090 .then(|| version_info.inner.module_info(sub_path))
5091 .flatten()
5092 {
5093 let fut = self.loader.load(
5097 specifier,
5098 LoadOptions {
5099 in_dynamic_branch: self.in_dynamic_branch,
5100 was_dynamic_root: self.was_dynamic_root,
5101 cache_setting: CacheSetting::Only,
5102 maybe_checksum: Some(checksum.clone()),
5103 },
5104 );
5105 let is_dynamic_branch = self.in_dynamic_branch;
5106 let module_analyzer = self.module_analyzer;
5107 self.state.pending.push_back({
5108 let requested_specifier = specifier.clone();
5109 let maybe_range = options.maybe_range.cloned();
5110 let version_info = version_info.clone();
5111 async move {
5112 let response = fut.await;
5113 let result = match response {
5114 Ok(None) => {
5115 parse_module_source_and_info(
5116 &ProvidedModuleAnalyzer(RefCell::new(Some(
5117 module_info.clone(),
5118 ))),
5119 ParseModuleAndSourceInfoOptions {
5120 specifier: requested_specifier.clone(),
5121 maybe_headers: Default::default(),
5122 mtime: None, content: if MediaType::from_specifier(&requested_specifier)
5125 == MediaType::Wasm
5126 {
5127 Arc::new([
5129 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x0A,
5131 0x01, 0x00,
5132 ])
5133 } else {
5134 Arc::new([]) as Arc<[u8]>
5135 },
5136 maybe_attribute_type: options.maybe_attribute_type.as_ref(),
5137 maybe_referrer: maybe_range.as_ref(),
5138 is_root: options.is_root,
5139 is_dynamic_branch,
5140 },
5141 )
5142 .await
5143 .map(|module_source_and_info| {
5144 PendingInfoResponse::Module {
5145 specifier: requested_specifier.clone(),
5146 module_source_and_info,
5147 pending_load: Some(Box::new((checksum, module_info))),
5148 is_root: options.is_root,
5149 }
5150 })
5151 }
5152 Ok(Some(response)) => match response {
5153 LoadResponse::External { specifier } => {
5154 Ok(PendingInfoResponse::External {
5155 specifier,
5156 is_root: options.is_root,
5157 is_asset: false,
5158 })
5159 }
5160 LoadResponse::Redirect { specifier } => Err(
5161 ModuleErrorKind::Load {
5162 specifier: requested_specifier.clone(),
5163 maybe_referrer: maybe_range.clone(),
5164 err: JsrLoadError::RedirectInPackage(specifier).into(),
5165 }
5166 .into_box(),
5167 ),
5168 LoadResponse::Module {
5169 content,
5170 specifier,
5171 mtime,
5172 maybe_headers,
5173 } => parse_module_source_and_info(
5174 module_analyzer,
5175 ParseModuleAndSourceInfoOptions {
5176 specifier: specifier.clone(),
5177 maybe_headers,
5178 mtime,
5179 content,
5180 maybe_attribute_type: options.maybe_attribute_type.as_ref(),
5181 maybe_referrer: maybe_range.as_ref(),
5182 is_root: options.is_root,
5183 is_dynamic_branch,
5184 },
5185 )
5186 .await
5187 .map(|module_source_and_info| {
5188 PendingInfoResponse::Module {
5189 specifier: specifier.clone(),
5190 module_source_and_info,
5191 pending_load: None,
5192 is_root: options.is_root,
5193 }
5194 }),
5195 },
5196 Err(err) => Err(
5197 ModuleErrorKind::Load {
5198 specifier: requested_specifier.clone(),
5199 maybe_referrer: maybe_range.clone(),
5200 err: ModuleLoadError::Loader(Arc::new(err)),
5201 }
5202 .into_box(),
5203 ),
5204 };
5205 PendingInfo {
5206 requested_specifier,
5207 maybe_range,
5208 result,
5209 maybe_version_info: Some(version_info),
5210 loaded_package_via_https_url: None,
5211 }
5212 }
5213 .boxed_local()
5214 });
5215 self
5216 .graph
5217 .module_slots
5218 .insert(specifier.clone(), ModuleSlot::Pending { is_asset: false });
5219 } else {
5220 self.load_pending_module(PendingModuleLoadItem {
5221 redirect_count,
5222 requested_specifier: specifier.clone(),
5223 maybe_attribute_type: options.maybe_attribute_type,
5224 maybe_range: options.maybe_range.cloned(),
5225 load_specifier: specifier.clone(),
5226 is_asset: options.is_asset,
5227 in_dynamic_branch: options.in_dynamic_branch,
5228 is_root: options.is_root,
5229 maybe_checksum: Some(checksum),
5230 maybe_version_info: Some(version_info.clone()),
5231 });
5232 }
5233 }
5234
5235 #[allow(clippy::too_many_arguments)]
5236 fn load_jsr_specifier(
5237 &mut self,
5238 specifier: Url,
5239 package_ref: JsrPackageReqReference,
5240 maybe_attribute_type: Option<AttributeTypeWithRange>,
5241 maybe_range: Option<&Range>,
5242 is_asset: bool,
5243 in_dynamic_branch: bool,
5244 is_root: bool,
5245 ) {
5246 let package_name = &package_ref.req().name;
5247 let specifier = specifier.clone();
5248 self.queue_load_package_info(package_name);
5249 self
5250 .state
5251 .jsr
5252 .pending_resolutions
5253 .push_back(PendingJsrReqResolutionItem {
5254 specifier,
5255 package_ref,
5256 maybe_attribute_type,
5257 maybe_range: maybe_range.cloned(),
5258 in_dynamic_branch,
5259 is_asset,
5260 is_root,
5261 });
5262 }
5263
5264 fn load_npm_specifier(
5265 &mut self,
5266 npm_resolver: &dyn NpmResolver,
5267 specifier: Url,
5268 package_ref: NpmPackageReqReference,
5269 maybe_range: Option<&Range>,
5270 in_dynamic_branch: bool,
5271 ) {
5272 if self
5273 .state
5274 .npm
5275 .requested_registry_info_loads
5276 .insert(package_ref.req().name.clone())
5277 {
5278 npm_resolver.load_and_cache_npm_package_info(&package_ref.req().name);
5280 }
5281
5282 self
5283 .state
5284 .npm
5285 .pending_resolutions
5286 .push(PendingNpmResolutionItem {
5287 specifier,
5288 package_ref,
5289 maybe_range: maybe_range.cloned(),
5290 in_dynamic_branch,
5291 });
5292 }
5293
5294 fn parse_load_specifier_kind(
5295 &self,
5296 specifier: &Url,
5297 maybe_range: Option<&Range>,
5298 ) -> Result<LoadSpecifierKind, ModuleError> {
5299 match specifier.scheme() {
5300 "jsr" => validate_jsr_specifier(specifier)
5301 .map(LoadSpecifierKind::Jsr)
5302 .map_err(|err| {
5303 ModuleErrorKind::Load {
5304 specifier: specifier.clone(),
5305 maybe_referrer: maybe_range.cloned(),
5306 err: JsrLoadError::PackageFormat(err).into(),
5307 }
5308 .into_box()
5309 }),
5310 "npm" => NpmPackageReqReference::from_specifier(specifier)
5311 .map(LoadSpecifierKind::Npm)
5312 .map_err(|err| {
5313 ModuleErrorKind::Load {
5314 specifier: specifier.clone(),
5315 maybe_referrer: maybe_range.cloned(),
5316 err: NpmLoadError::PackageReqReferenceParse(err).into(),
5317 }
5318 .into_box()
5319 }),
5320 "node" => Ok(LoadSpecifierKind::Node(specifier.path().to_string())),
5321 _ => Ok(LoadSpecifierKind::Url),
5322 }
5323 }
5324
5325 fn maybe_mark_dep(
5326 &mut self,
5327 load_specifier: &LoadSpecifierKind,
5328 maybe_range: Option<&Range>,
5329 ) {
5330 match load_specifier {
5331 LoadSpecifierKind::Jsr(package_ref) => {
5332 self.mark_jsr_dep(package_ref, maybe_range);
5333 }
5334 LoadSpecifierKind::Npm(package_ref) => {
5335 self.mark_npm_dep(package_ref, maybe_range);
5336 }
5337 LoadSpecifierKind::Node(_) | LoadSpecifierKind::Url => {
5338 }
5340 }
5341 }
5342
5343 fn mark_jsr_dep(
5344 &mut self,
5345 package_ref: &JsrPackageReqReference,
5346 maybe_range: Option<&Range>,
5347 ) {
5348 if let Some(range) = &maybe_range {
5349 if let Some(nv) =
5350 self.jsr_url_provider.package_url_to_nv(&range.specifier)
5351 {
5352 self.graph.packages.add_dependency(
5353 &nv,
5354 JsrDepPackageReq::jsr(package_ref.req().clone()),
5355 );
5356 }
5357 }
5358 }
5359
5360 fn mark_npm_dep(
5361 &mut self,
5362 package_ref: &NpmPackageReqReference,
5363 maybe_range: Option<&Range>,
5364 ) {
5365 if let Some(range) = &maybe_range {
5366 if let Some(nv) =
5367 self.jsr_url_provider.package_url_to_nv(&range.specifier)
5368 {
5369 self.graph.packages.add_dependency(
5370 &nv,
5371 JsrDepPackageReq::npm(package_ref.req().clone()),
5372 );
5373 }
5374 }
5375 }
5376
5377 fn load_pending_module(&mut self, item: PendingModuleLoadItem) {
5378 let PendingModuleLoadItem {
5379 redirect_count,
5380 requested_specifier,
5381 maybe_attribute_type,
5382 maybe_range,
5383 load_specifier,
5384 in_dynamic_branch,
5385 is_asset,
5386 is_root,
5387 mut maybe_checksum,
5388 mut maybe_version_info,
5389 } = item;
5390 self.graph.module_slots.insert(
5391 requested_specifier.clone(),
5392 ModuleSlot::Pending { is_asset },
5393 );
5394 let loader = self.loader;
5395 let module_analyzer = self.module_analyzer;
5396 let jsr_url_provider = self.jsr_url_provider;
5397 let was_dynamic_root = self.was_dynamic_root;
5398 let maybe_nv_when_no_version_info = if maybe_version_info.is_none() {
5399 self
5400 .jsr_url_provider
5401 .package_url_to_nv(&requested_specifier)
5402 } else {
5403 None
5404 };
5405 let maybe_version_load_fut =
5406 maybe_nv_when_no_version_info.map(|package_nv| {
5407 self.queue_load_package_version_info(&package_nv);
5408 let fut = self
5409 .state
5410 .jsr
5411 .metadata
5412 .get_package_version_metadata(&package_nv)
5413 .unwrap();
5414 (package_nv, fut)
5415 });
5416 if maybe_checksum.is_none() {
5417 maybe_checksum = self
5418 .locker
5419 .as_ref()
5420 .and_then(|l| l.get_remote_checksum(&requested_specifier));
5421 }
5422 let fut = async move {
5423 #[allow(clippy::too_many_arguments)]
5424 async fn try_load(
5425 is_root: bool,
5426 redirect_count: usize,
5427 load_specifier: ModuleSpecifier,
5428 mut maybe_checksum: Option<LoaderChecksum>,
5429 maybe_range: Option<&Range>,
5430 maybe_version_info: &mut Option<JsrPackageVersionInfoExt>,
5431 maybe_attribute_type: Option<AttributeTypeWithRange>,
5432 loaded_package_via_https_url: &mut Option<LoadedJsrPackageViaHttpsUrl>,
5433 maybe_version_load_fut: Option<(
5434 PackageNv,
5435 PendingResult<PendingJsrPackageVersionInfoLoadItem>,
5436 )>,
5437 is_asset: bool,
5438 in_dynamic_branch: bool,
5439 was_dynamic_root: bool,
5440 loader: &dyn Loader,
5441 jsr_url_provider: &dyn JsrUrlProvider,
5442 module_analyzer: &dyn ModuleAnalyzer,
5443 ) -> Result<PendingInfoResponse, ModuleError> {
5444 async fn handle_success(
5445 module_analyzer: &dyn ModuleAnalyzer,
5446 specifier: Url,
5447 options: ParseModuleAndSourceInfoOptions<'_>,
5448 ) -> Result<PendingInfoResponse, ModuleError> {
5449 let is_root = options.is_root;
5450 parse_module_source_and_info(module_analyzer, options)
5451 .await
5452 .map(|module_source_and_info| PendingInfoResponse::Module {
5453 specifier: specifier.clone(),
5454 module_source_and_info,
5455 pending_load: None,
5456 is_root,
5457 })
5458 }
5459
5460 if let Some((package_nv, fut)) = maybe_version_load_fut {
5461 let inner = fut.await.map_err(|err| {
5462 ModuleErrorKind::Load {
5463 specifier: jsr_url_provider.package_url(&package_nv),
5464 maybe_referrer: maybe_range.cloned(),
5465 err: err.into(),
5466 }
5467 .into_box()
5468 })?;
5469 let info = JsrPackageVersionInfoExt {
5470 base_url: jsr_url_provider.package_url(&package_nv),
5471 inner: inner.info,
5472 };
5473 if let Some(sub_path) = info.get_subpath(&load_specifier) {
5474 maybe_checksum = Some(LoaderChecksum::new(
5475 info
5476 .get_checksum(sub_path)
5477 .map_err(|err| {
5478 ModuleErrorKind::Load {
5479 specifier: load_specifier.clone(),
5480 maybe_referrer: maybe_range.cloned(),
5481 err,
5482 }
5483 .into_box()
5484 })?
5485 .to_string(),
5486 ));
5487 }
5488 maybe_version_info.replace(info);
5489 loaded_package_via_https_url.replace(LoadedJsrPackageViaHttpsUrl {
5490 nv: package_nv,
5491 manifest_checksum_for_locker: inner.checksum_for_locker,
5492 });
5493 }
5494
5495 let load_options = LoadOptions {
5496 in_dynamic_branch,
5497 was_dynamic_root,
5498 cache_setting: CacheSetting::Use,
5499 maybe_checksum: maybe_checksum.clone(),
5500 };
5501
5502 let handle_redirect =
5503 |specifier: Url,
5504 maybe_attribute_type: Option<AttributeTypeWithRange>,
5505 maybe_checksum: Option<LoaderChecksum>| {
5506 if maybe_version_info.is_some() {
5507 Err(
5512 ModuleErrorKind::Load {
5513 specifier: load_specifier.clone(),
5514 maybe_referrer: maybe_range.cloned(),
5515 err: JsrLoadError::RedirectInPackage(specifier.clone())
5516 .into(),
5517 }
5518 .into_box(),
5519 )
5520 } else if let Some(expected_checksum) = maybe_checksum {
5521 Err(
5522 ModuleErrorKind::Load {
5523 specifier: load_specifier.clone(),
5524 maybe_referrer: maybe_range.cloned(),
5525 err: ModuleLoadError::HttpsChecksumIntegrity(
5526 ChecksumIntegrityError {
5527 actual: format!("Redirect to {}", specifier),
5528 expected: expected_checksum.into_string(),
5529 },
5530 ),
5531 }
5532 .into_box(),
5533 )
5534 } else if redirect_count >= loader.max_redirects() {
5535 Err(
5536 ModuleErrorKind::Load {
5537 specifier: load_specifier.clone(),
5538 maybe_referrer: maybe_range.cloned(),
5539 err: ModuleLoadError::TooManyRedirects,
5540 }
5541 .into_box(),
5542 )
5543 } else {
5544 Ok(PendingInfoResponse::Redirect {
5545 count: redirect_count + 1,
5546 specifier,
5547 maybe_attribute_type,
5548 is_asset,
5549 is_dynamic: in_dynamic_branch,
5550 is_root,
5551 })
5552 }
5553 };
5554
5555 if is_asset {
5556 let result = loader.ensure_cached(&load_specifier, load_options);
5557 return match result.await {
5558 Ok(Some(CacheResponse::Cached)) => {
5559 Ok(PendingInfoResponse::External {
5560 specifier: load_specifier,
5561 is_root,
5562 is_asset,
5563 })
5564 }
5565 Ok(Some(CacheResponse::Redirect { specifier })) => {
5566 handle_redirect(specifier, maybe_attribute_type, maybe_checksum)
5567 }
5568 Ok(None) => Err(
5569 ModuleErrorKind::Missing {
5570 specifier: load_specifier.clone(),
5571 maybe_referrer: maybe_range.cloned(),
5572 }
5573 .into_box(),
5574 ),
5575 Err(LoadError::ChecksumIntegrity(err)) => {
5576 if maybe_version_info.is_none() {
5577 let result = loader
5579 .ensure_cached(
5580 &load_specifier,
5581 LoadOptions {
5582 in_dynamic_branch,
5583 was_dynamic_root,
5584 cache_setting: CacheSetting::Reload,
5585 maybe_checksum: maybe_checksum.clone(),
5586 },
5587 )
5588 .await;
5589 if let Ok(Some(CacheResponse::Cached)) = result {
5590 return Ok(PendingInfoResponse::External {
5591 specifier: load_specifier,
5592 is_root,
5593 is_asset,
5594 });
5595 }
5596 }
5597
5598 Err(
5599 ModuleErrorKind::Load {
5600 specifier: load_specifier.clone(),
5601 maybe_referrer: maybe_range.cloned(),
5602 err: if maybe_version_info.is_some() {
5603 ModuleLoadError::Jsr(
5604 JsrLoadError::ContentChecksumIntegrity(err),
5605 )
5606 } else {
5607 ModuleLoadError::HttpsChecksumIntegrity(err)
5608 },
5609 }
5610 .into_box(),
5611 )
5612 }
5613 Err(err) => Err(
5614 ModuleErrorKind::Load {
5615 specifier: load_specifier.clone(),
5616 maybe_referrer: maybe_range.cloned(),
5617 err: ModuleLoadError::Loader(Arc::new(err)),
5618 }
5619 .into_box(),
5620 ),
5621 };
5622 }
5623
5624 let result = loader.load(&load_specifier, load_options);
5625 match result.await {
5626 Ok(Some(response)) => match response {
5627 LoadResponse::Redirect { specifier } => {
5628 handle_redirect(specifier, maybe_attribute_type, maybe_checksum)
5629 }
5630 LoadResponse::External { specifier } => {
5631 Ok(PendingInfoResponse::External {
5632 specifier,
5633 is_root,
5634 is_asset,
5635 })
5636 }
5637 LoadResponse::Module {
5638 content,
5639 mtime,
5640 specifier,
5641 maybe_headers,
5642 } => {
5643 handle_success(
5644 module_analyzer,
5645 specifier.clone(),
5646 ParseModuleAndSourceInfoOptions {
5647 specifier,
5648 maybe_headers,
5649 mtime,
5650 content,
5651 maybe_attribute_type: maybe_attribute_type.as_ref(),
5652 maybe_referrer: maybe_range,
5653 is_root,
5654 is_dynamic_branch: in_dynamic_branch,
5655 },
5656 )
5657 .await
5658 }
5659 },
5660 Ok(None) => Err(
5661 ModuleErrorKind::Missing {
5662 specifier: load_specifier.clone(),
5663 maybe_referrer: maybe_range.cloned(),
5664 }
5665 .into_box(),
5666 ),
5667 Err(LoadError::ChecksumIntegrity(err)) => {
5668 if maybe_version_info.is_none() {
5669 let result = loader
5671 .load(
5672 &load_specifier,
5673 LoadOptions {
5674 in_dynamic_branch,
5675 was_dynamic_root,
5676 cache_setting: CacheSetting::Reload,
5677 maybe_checksum: maybe_checksum.clone(),
5678 },
5679 )
5680 .await;
5681 if let Ok(Some(LoadResponse::Module {
5682 specifier,
5683 maybe_headers,
5684 mtime,
5685 content,
5686 })) = result
5687 {
5688 return handle_success(
5689 module_analyzer,
5690 specifier.clone(),
5691 ParseModuleAndSourceInfoOptions {
5692 specifier,
5693 maybe_headers,
5694 mtime,
5695 content,
5696 maybe_attribute_type: maybe_attribute_type.as_ref(),
5697 maybe_referrer: maybe_range,
5698 is_root,
5699 is_dynamic_branch: in_dynamic_branch,
5700 },
5701 )
5702 .await;
5703 }
5704 }
5705
5706 Err(
5707 ModuleErrorKind::Load {
5708 specifier: load_specifier.clone(),
5709 maybe_referrer: maybe_range.cloned(),
5710 err: if maybe_version_info.is_some() {
5711 ModuleLoadError::Jsr(JsrLoadError::ContentChecksumIntegrity(
5712 err,
5713 ))
5714 } else {
5715 ModuleLoadError::HttpsChecksumIntegrity(err)
5716 },
5717 }
5718 .into_box(),
5719 )
5720 }
5721 Err(err) => Err(
5722 ModuleErrorKind::Load {
5723 specifier: load_specifier.clone(),
5724 maybe_referrer: maybe_range.cloned(),
5725 err: ModuleLoadError::Loader(Arc::new(err)),
5726 }
5727 .into_box(),
5728 ),
5729 }
5730 }
5731
5732 let mut loaded_package_via_https_url = None;
5733 let result = try_load(
5734 is_root,
5735 redirect_count,
5736 load_specifier,
5737 maybe_checksum,
5738 maybe_range.as_ref(),
5739 &mut maybe_version_info,
5740 maybe_attribute_type,
5741 &mut loaded_package_via_https_url,
5742 maybe_version_load_fut,
5743 is_asset,
5744 in_dynamic_branch,
5745 was_dynamic_root,
5746 loader,
5747 jsr_url_provider,
5748 module_analyzer,
5749 )
5750 .await;
5751
5752 PendingInfo {
5753 result,
5754 requested_specifier,
5755 maybe_range,
5756 maybe_version_info,
5757 loaded_package_via_https_url,
5758 }
5759 }
5760 .boxed_local();
5761 self.state.pending.push_back(fut);
5762 }
5763
5764 fn queue_load_package_info(&mut self, package_name: &str) {
5765 self.state.jsr.metadata.queue_load_package_info(
5766 package_name,
5767 self.fill_pass_mode.to_cache_setting(),
5768 JsrMetadataStoreServices {
5769 executor: self.executor,
5770 jsr_url_provider: self.jsr_url_provider,
5771 loader: self.loader,
5772 },
5773 );
5774 }
5775
5776 fn queue_load_package_version_info(&mut self, package_nv: &PackageNv) {
5777 self.state.jsr.metadata.queue_load_package_version_info(
5778 package_nv,
5779 self.fill_pass_mode.to_cache_setting(),
5780 self.locker.as_deref(),
5781 JsrMetadataStoreServices {
5782 executor: self.executor,
5783 jsr_url_provider: self.jsr_url_provider,
5784 loader: self.loader,
5785 },
5786 );
5787 }
5788
5789 fn visit(
5790 &mut self,
5791 response: PendingInfoResponse,
5792 maybe_referrer: Option<Range>,
5793 maybe_version_info: Option<&JsrPackageVersionInfoExt>,
5794 ) {
5795 match response {
5796 PendingInfoResponse::External {
5797 specifier,
5798 is_root,
5799 is_asset,
5800 } => {
5801 if is_root {
5802 self.resolved_roots.insert(specifier.clone());
5803 }
5804 if let Some(entry) = self.graph.module_slots.get_mut(&specifier) {
5805 if entry.is_pending() {
5806 *entry = ModuleSlot::Module(Module::External(ExternalModule {
5807 maybe_cache_info: None,
5808 specifier,
5809 was_asset_load: is_asset,
5810 }));
5811 }
5812 } else {
5813 self.graph.module_slots.insert(
5814 specifier.clone(),
5815 ModuleSlot::Module(Module::External(ExternalModule {
5816 maybe_cache_info: None,
5817 specifier,
5818 was_asset_load: is_asset,
5819 })),
5820 );
5821 }
5822 }
5823 PendingInfoResponse::Module {
5824 specifier,
5825 pending_load,
5826 module_source_and_info,
5827 is_root,
5828 } => {
5829 debug_assert_eq!(
5831 maybe_version_info.is_none(),
5832 self
5833 .jsr_url_provider
5834 .package_url_to_nv(&specifier)
5835 .is_none(),
5836 "{}",
5837 specifier
5838 );
5839
5840 if is_root {
5841 self.resolved_roots.insert(specifier.clone());
5842 }
5843
5844 if let Some((checksum, module_info)) = pending_load.map(|v| *v) {
5845 self.state.jsr.pending_content_loads.push({
5846 let specifier = specifier.clone();
5847 let maybe_range = maybe_referrer.clone();
5848 let fut = self.loader.load(
5849 &specifier,
5850 LoadOptions {
5851 in_dynamic_branch: self.in_dynamic_branch,
5852 was_dynamic_root: self.was_dynamic_root,
5853 cache_setting: CacheSetting::Use,
5854 maybe_checksum: Some(checksum.clone()),
5855 },
5856 );
5857 async move {
5858 let result = fut.await;
5859 PendingContentLoadItem {
5860 specifier,
5861 maybe_range,
5862 result,
5863 module_info,
5864 }
5865 }
5866 .boxed_local()
5867 });
5868 } else if maybe_version_info.is_none()
5869 && !module_source_and_info.media_type().is_declaration()
5871 && matches!(specifier.scheme(), "https" | "http")
5872 {
5873 if let Some(locker) = &mut self.locker {
5874 if !locker.has_remote_checksum(&specifier) {
5875 locker.set_remote_checksum(
5876 &specifier,
5877 LoaderChecksum::new(LoaderChecksum::gen(
5878 module_source_and_info.source_bytes(),
5879 )),
5880 );
5881 }
5882 }
5883 }
5884
5885 let module_slot =
5886 self.visit_module(module_source_and_info, maybe_version_info);
5887 self.graph.module_slots.insert(specifier, module_slot);
5888 }
5889 PendingInfoResponse::Redirect {
5890 count,
5891 specifier,
5892 is_asset,
5893 is_dynamic,
5894 is_root,
5895 maybe_attribute_type,
5896 } => {
5897 self.load_with_redirect_count(
5898 count,
5899 LoadOptionsRef {
5900 specifier: &specifier,
5901 maybe_range: maybe_referrer.as_ref(),
5902 is_asset,
5903 in_dynamic_branch: is_dynamic,
5904 is_root,
5905 maybe_attribute_type,
5906 maybe_version_info: None,
5907 },
5908 );
5909 }
5910 }
5911 }
5912
5913 fn visit_module(
5915 &mut self,
5916 module_source_and_info: ModuleSourceAndInfo,
5917 maybe_version_info: Option<&JsrPackageVersionInfoExt>,
5918 ) -> ModuleSlot {
5919 let module = parse_module(
5920 self.file_system,
5921 self.jsr_url_provider,
5922 self.resolver,
5923 ParseModuleOptions {
5924 graph_kind: self.graph.graph_kind,
5925 module_source_and_info,
5926 },
5927 );
5928
5929 let mut module_slot = ModuleSlot::Module(module);
5930
5931 match &mut module_slot {
5932 ModuleSlot::Module(Module::Js(module)) => {
5933 if matches!(self.graph.graph_kind, GraphKind::All | GraphKind::CodeOnly)
5934 || module.maybe_types_dependency.is_none()
5935 {
5936 self.visit_module_dependencies(
5937 &mut module.dependencies,
5938 maybe_version_info,
5939 );
5940 } else {
5941 module.dependencies.clear();
5942 }
5943
5944 if self.graph.graph_kind.include_types() {
5945 if let Some(Resolution::Ok(resolved)) = module
5946 .maybe_types_dependency
5947 .as_ref()
5948 .map(|d| &d.dependency)
5949 {
5950 self.load(LoadOptionsRef {
5951 specifier: &resolved.specifier,
5952 maybe_range: Some(&resolved.range),
5953 is_asset: false, in_dynamic_branch: false,
5955 is_root: self.resolved_roots.contains(&resolved.specifier),
5956 maybe_attribute_type: None,
5957 maybe_version_info,
5958 });
5959 }
5960 } else {
5961 module.maybe_types_dependency = None;
5962 }
5963 }
5964 ModuleSlot::Module(Module::Wasm(module)) => {
5965 self.visit_module_dependencies(
5966 &mut module.dependencies,
5967 maybe_version_info,
5968 );
5969 }
5970 _ => {}
5971 }
5972
5973 module_slot
5974 }
5975
5976 fn visit_module_dependencies(
5977 &mut self,
5978 dependencies: &mut IndexMap<String, Dependency>,
5979 maybe_version_info: Option<&JsrPackageVersionInfoExt>,
5980 ) {
5981 for dep in dependencies.values_mut() {
5982 if dep.is_dynamic && self.skip_dynamic_deps {
5983 continue;
5984 }
5985 let is_asset = dep.imports.iter().all(|i| i.attributes.has_asset());
5986
5987 if self.graph.graph_kind.include_code() || dep.maybe_type.is_none() {
5988 if let Resolution::Ok(resolved) = &dep.maybe_code {
5989 let specifier = &resolved.specifier;
5990 let range = &resolved.range;
5991 let maybe_attribute_type =
5992 dep.maybe_attribute_type.as_ref().map(|attribute_type| {
5993 AttributeTypeWithRange {
5994 range: range.clone(),
5995 kind: attribute_type.clone(),
5996 }
5997 });
5998 if dep.is_dynamic && !self.in_dynamic_branch {
5999 let value = self
6000 .state
6001 .dynamic_branches
6002 .entry(specifier.clone())
6003 .or_insert_with(|| PendingDynamicBranch {
6004 range: range.clone(),
6005 is_asset,
6006 maybe_attribute_type,
6007 maybe_version_info: maybe_version_info.map(ToOwned::to_owned),
6008 });
6009 if !is_asset {
6010 value.is_asset = false;
6012 }
6013 } else {
6014 self.load(LoadOptionsRef {
6015 specifier,
6016 maybe_range: Some(range),
6017 is_asset,
6018 in_dynamic_branch: self.in_dynamic_branch,
6019 is_root: self.resolved_roots.contains(specifier),
6020 maybe_attribute_type,
6021 maybe_version_info,
6022 });
6023 }
6024 }
6025 } else {
6026 dep.maybe_code = Resolution::None;
6027 }
6028
6029 if self.graph.graph_kind.include_types() {
6030 if let Resolution::Ok(resolved) = &dep.maybe_type {
6031 let specifier = &resolved.specifier;
6032 let range = &resolved.range;
6033 let maybe_attribute_type =
6034 dep.maybe_attribute_type.as_ref().map(|assert_type| {
6035 AttributeTypeWithRange {
6036 range: range.clone(),
6037 kind: assert_type.clone(),
6038 }
6039 });
6040 if dep.is_dynamic && !self.in_dynamic_branch {
6041 self.state.dynamic_branches.insert(
6042 specifier.clone(),
6043 PendingDynamicBranch {
6044 is_asset,
6045 range: range.clone(),
6046 maybe_attribute_type,
6047 maybe_version_info: maybe_version_info.map(ToOwned::to_owned),
6048 },
6049 );
6050 } else {
6051 self.load(LoadOptionsRef {
6052 specifier,
6053 maybe_range: Some(range),
6054 is_asset,
6055 in_dynamic_branch: self.in_dynamic_branch,
6056 is_root: self.resolved_roots.contains(specifier),
6057 maybe_attribute_type,
6058 maybe_version_info,
6059 });
6060 }
6061 }
6062 } else {
6063 dep.maybe_type = Resolution::None;
6064 }
6065 }
6066 }
6067}
6068
6069fn validate_jsr_specifier(
6070 specifier: &Url,
6071) -> Result<JsrPackageReqReference, JsrPackageFormatError> {
6072 let package_ref = JsrPackageReqReference::from_specifier(specifier)
6073 .map_err(JsrPackageFormatError::JsrPackageParseError)?;
6074 match package_ref.req().version_req.inner() {
6075 RangeSetOrTag::Tag(tag) => {
6076 Err(JsrPackageFormatError::VersionTagNotSupported { tag: tag.clone() })
6077 }
6078 RangeSetOrTag::RangeSet(_) => Ok(package_ref),
6079 }
6080}
6081
6082struct NpmSpecifierBuildPendingInfo {
6085 found_pkg_nvs: IndexSet<PackageNv>,
6086 module_slots: HashMap<ModuleSpecifier, ModuleSlot>,
6087 dependencies_resolution: Option<Result<(), Arc<dyn JsErrorClass>>>,
6088 redirects: HashMap<ModuleSpecifier, ModuleSpecifier>,
6089}
6090
6091impl NpmSpecifierBuildPendingInfo {
6092 pub fn with_capacity(capacity: usize) -> Self {
6093 Self {
6094 found_pkg_nvs: IndexSet::with_capacity(capacity),
6095 module_slots: HashMap::with_capacity(capacity),
6096 dependencies_resolution: None,
6097 redirects: HashMap::with_capacity(capacity),
6098 }
6099 }
6100}
6101
6102#[derive(Debug)]
6103struct PendingNpmResolutionItem {
6104 specifier: ModuleSpecifier,
6105 package_ref: NpmPackageReqReference,
6106 maybe_range: Option<Range>,
6107 in_dynamic_branch: bool,
6108}
6109
6110struct NpmSpecifierResolver<'a> {
6111 npm_resolver: Option<&'a dyn NpmResolver>,
6112 pending_info: NpmSpecifierBuildPendingInfo,
6113 pending_npm_specifiers: Vec<PendingNpmResolutionItem>,
6114}
6115
6116impl<'a> NpmSpecifierResolver<'a> {
6117 pub async fn fill_builder(builder: &mut Builder<'a, '_>) {
6118 let mut npm_specifier_resolver = NpmSpecifierResolver::new(
6119 builder.npm_resolver,
6120 std::mem::take(&mut builder.state.npm.pending_resolutions),
6121 );
6122
6123 npm_specifier_resolver.resolve().await;
6124
6125 npm_specifier_resolver.fill_graph(builder.graph);
6126 }
6127
6128 fn new(
6129 npm_resolver: Option<&'a dyn NpmResolver>,
6130 pending_npm_specifiers: Vec<PendingNpmResolutionItem>,
6131 ) -> Self {
6132 Self {
6133 npm_resolver,
6134 pending_info: NpmSpecifierBuildPendingInfo::with_capacity(
6135 pending_npm_specifiers.len(),
6136 ),
6137 pending_npm_specifiers,
6138 }
6139 }
6140
6141 async fn resolve(&mut self) {
6142 let Some(npm_resolver) = self.npm_resolver else {
6143 for item in self.pending_npm_specifiers.drain(..) {
6144 self.pending_info.module_slots.insert(
6145 item.specifier.clone(),
6146 ModuleSlot::Err(
6147 ModuleErrorKind::Load {
6148 specifier: item.specifier.clone(),
6149 maybe_referrer: item.maybe_range.clone(),
6150 err: NpmLoadError::NotSupportedEnvironment.into(),
6151 }
6152 .into_box(),
6153 ),
6154 );
6155 }
6156 return;
6157 };
6158
6159 let (main_items, dynamic_items) = self
6160 .pending_npm_specifiers
6161 .drain(..)
6162 .partition::<Vec<_>, _>(|item| !item.in_dynamic_branch);
6163
6164 if !main_items.is_empty() || dynamic_items.is_empty() {
6168 let items_by_req: IndexMap<_, Vec<_>> =
6169 IndexMap::with_capacity(main_items.len());
6170 let items_by_req =
6171 main_items
6172 .into_iter()
6173 .fold(items_by_req, |mut items_by_req, item| {
6174 items_by_req
6175 .entry(item.package_ref.req().clone())
6176 .or_default()
6177 .push(item);
6178 items_by_req
6179 });
6180 let all_package_reqs = items_by_req.keys().cloned().collect::<Vec<_>>();
6181 let result = npm_resolver.resolve_pkg_reqs(&all_package_reqs).await;
6182
6183 self.pending_info.dependencies_resolution = Some(result.dep_graph_result);
6184
6185 assert_eq!(all_package_reqs.len(), result.results.len());
6186 for (req, resolution) in
6187 all_package_reqs.into_iter().zip(result.results.into_iter())
6188 {
6189 let items = items_by_req.get(&req).unwrap();
6190 for item in items {
6191 match &resolution {
6192 Ok(pkg_nv) => {
6193 self.add_nv_for_item(
6194 item.specifier.clone(),
6195 pkg_nv.clone(),
6196 item.package_ref.sub_path().map(PackageSubPath::from_str),
6197 );
6198 }
6199 Err(err) => {
6200 self.pending_info.module_slots.insert(
6201 item.specifier.clone(),
6202 ModuleSlot::Err(
6203 ModuleErrorKind::Load {
6204 specifier: item.specifier.clone(),
6205 maybe_referrer: item.maybe_range.clone(),
6206 err: err.clone().into(),
6207 }
6208 .into_box(),
6209 ),
6210 );
6211 }
6212 }
6213 }
6214 }
6215 }
6216
6217 for item in dynamic_items {
6218 let mut result = npm_resolver
6220 .resolve_pkg_reqs(&[item.package_ref.req().clone()])
6221 .await;
6222 assert_eq!(result.results.len(), 1);
6223 match result.results.remove(0) {
6224 Ok(pkg_nv) => {
6225 if let Err(err) = result.dep_graph_result {
6226 self.pending_info.module_slots.insert(
6227 item.specifier.clone(),
6228 ModuleSlot::Err(
6229 ModuleErrorKind::Load {
6230 specifier: item.specifier,
6231 maybe_referrer: item.maybe_range,
6232 err: NpmLoadError::PackageReqResolution(err).into(),
6233 }
6234 .into_box(),
6235 ),
6236 );
6237 } else {
6238 self.add_nv_for_item(
6239 item.specifier,
6240 pkg_nv,
6241 item.package_ref.into_inner().sub_path,
6242 );
6243 }
6244 }
6245 Err(err) => {
6246 self.pending_info.module_slots.insert(
6247 item.specifier.clone(),
6248 ModuleSlot::Err(
6249 ModuleErrorKind::Load {
6250 specifier: item.specifier,
6251 maybe_referrer: item.maybe_range,
6252 err: err.into(),
6253 }
6254 .into_box(),
6255 ),
6256 );
6257 }
6258 }
6259 }
6260 }
6261
6262 fn add_nv_for_item(
6263 &mut self,
6264 specifier: ModuleSpecifier,
6265 pkg_nv: PackageNv,
6266 sub_path: Option<SmallStackString>,
6267 ) {
6268 let pkg_id_ref = NpmPackageNvReference::new(PackageNvReference {
6269 nv: pkg_nv.clone(),
6270 sub_path,
6271 });
6272 let resolved_specifier = pkg_id_ref.as_specifier();
6273 if resolved_specifier != specifier {
6274 self
6275 .pending_info
6276 .redirects
6277 .insert(specifier.clone(), resolved_specifier.clone());
6278 }
6279 self.pending_info.found_pkg_nvs.insert(pkg_nv);
6280 self.pending_info.module_slots.insert(
6281 resolved_specifier.clone(),
6282 ModuleSlot::Module(Module::Npm(NpmModule {
6283 specifier: resolved_specifier,
6284 nv_reference: pkg_id_ref,
6285 })),
6286 );
6287 }
6288
6289 fn fill_graph(self, graph: &mut ModuleGraph) {
6290 let pending_info = self.pending_info;
6291
6292 for (key, value) in pending_info.module_slots {
6294 graph.module_slots.entry(key).or_insert(value);
6297 }
6298 for (key, value) in pending_info.redirects {
6299 graph.redirects.entry(key).or_insert(value);
6300 }
6301
6302 graph.npm_packages.extend(pending_info.found_pkg_nvs);
6303 if let Some(result) = pending_info.dependencies_resolution {
6304 graph.npm_dep_graph_result = result;
6305 }
6306 }
6307}
6308
6309fn new_source_with_text(
6310 specifier: &ModuleSpecifier,
6311 bytes: Arc<[u8]>,
6312 maybe_charset: Option<&str>,
6313 mtime: Option<SystemTime>,
6314) -> Result<ModuleTextSource, ModuleError> {
6315 let charset = maybe_charset.unwrap_or_else(|| {
6316 deno_media_type::encoding::detect_charset(specifier, bytes.as_ref())
6317 });
6318 deno_media_type::encoding::decode_arc_source_detail(charset, bytes)
6319 .map(|detail| ModuleTextSource {
6320 text: detail.text,
6321 decoded_kind: detail.kind,
6322 })
6323 .map_err(|err| {
6324 ModuleErrorKind::Load {
6325 specifier: specifier.clone(),
6326 maybe_referrer: None,
6327 err: ModuleLoadError::Decode(Arc::new(DecodeError { mtime, err })),
6328 }
6329 .into_box()
6330 })
6331}
6332
6333impl Serialize for Resolution {
6334 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
6335 where
6336 S: Serializer,
6337 {
6338 match self {
6339 Resolution::Ok(resolved) => {
6340 let mut state = serializer.serialize_struct("ResolvedSpecifier", 3)?;
6341 state.serialize_field("specifier", &resolved.specifier)?;
6342 if resolved.range.resolution_mode.is_some() {
6343 state.serialize_field(
6344 "resolutionMode",
6345 &resolved.range.resolution_mode,
6346 )?;
6347 }
6348 state.serialize_field("span", &resolved.range)?;
6349 state.end()
6350 }
6351 Resolution::Err(err) => {
6352 let mut state = serializer.serialize_struct("ResolvedError", 3)?;
6353 state.serialize_field("error", &err.to_string())?;
6354 let range = err.range();
6355 if range.resolution_mode.is_some() {
6356 state.serialize_field("resolutionMode", &range.resolution_mode)?;
6357 }
6358 state.serialize_field("span", range)?;
6359 state.end()
6360 }
6361 Resolution::None => {
6362 Serialize::serialize(&serde_json::Value::Null, serializer)
6363 }
6364 }
6365 }
6366}
6367
6368fn serialize_dependencies<S>(
6369 dependencies: &IndexMap<String, Dependency>,
6370 serializer: S,
6371) -> Result<S::Ok, S::Error>
6372where
6373 S: Serializer,
6374{
6375 #[derive(Serialize)]
6376 struct DependencyWithSpecifier<'a> {
6377 specifier: &'a str,
6378 #[serde(flatten)]
6379 dependency: &'a Dependency,
6380 }
6381 let mut seq = serializer.serialize_seq(Some(dependencies.len()))?;
6382 for (specifier, dependency) in dependencies {
6383 seq.serialize_element(&DependencyWithSpecifier {
6384 specifier,
6385 dependency,
6386 })?
6387 }
6388 seq.end()
6389}
6390
6391fn serialize_source<S>(
6392 source: &ModuleTextSource,
6393 serializer: S,
6394) -> Result<S::Ok, S::Error>
6395where
6396 S: Serializer,
6397{
6398 serializer.serialize_u32(source.text.len() as u32)
6399}
6400
6401fn serialize_source_bytes<S>(
6402 source: &Arc<[u8]>,
6403 serializer: S,
6404) -> Result<S::Ok, S::Error>
6405where
6406 S: Serializer,
6407{
6408 serializer.serialize_u32(source.len() as u32)
6409}
6410
6411#[cfg(test)]
6412mod tests {
6413 use crate::analysis::ImportAttribute;
6414 use crate::packages::JsrPackageInfoVersion;
6415 use deno_ast::emit;
6416 use deno_ast::EmitOptions;
6417 use deno_ast::SourceMap;
6418 use pretty_assertions::assert_eq;
6419 use serde_json::json;
6420
6421 use super::*;
6422 use url::Url;
6423
6424 #[test]
6425 fn test_range_includes() {
6426 let range = PositionRange {
6427 start: Position {
6428 line: 1,
6429 character: 20,
6430 },
6431 end: Position {
6432 line: 1,
6433 character: 30,
6434 },
6435 };
6436 assert!(range.includes(Position {
6437 line: 1,
6438 character: 20
6439 }));
6440 assert!(range.includes(Position {
6441 line: 1,
6442 character: 25
6443 }));
6444 assert!(range.includes(Position {
6445 line: 1,
6446 character: 30
6447 }));
6448 assert!(!range.includes(Position {
6449 line: 0,
6450 character: 25
6451 }));
6452 assert!(!range.includes(Position {
6453 line: 2,
6454 character: 25
6455 }));
6456 }
6457
6458 #[test]
6459 fn test_jsr_import_format() {
6460 assert!(
6461 validate_jsr_specifier(&Url::parse("jsr:@scope/mod@tag").unwrap())
6462 .is_err(),
6463 "jsr import specifier with tag should be an error"
6464 );
6465
6466 assert!(
6467 validate_jsr_specifier(&Url::parse("jsr:@scope/mod@").unwrap()).is_err()
6468 );
6469
6470 assert!(validate_jsr_specifier(
6471 &Url::parse("jsr:@scope/mod@1.2.3").unwrap()
6472 )
6473 .is_ok());
6474
6475 assert!(
6476 validate_jsr_specifier(&Url::parse("jsr:@scope/mod").unwrap()).is_ok()
6477 );
6478 }
6479
6480 #[test]
6481 fn test_module_dependency_includes() {
6482 let specifier = ModuleSpecifier::parse("file:///a.ts").unwrap();
6483 let dependency = Dependency {
6484 maybe_code: Resolution::Ok(Box::new(ResolutionResolved {
6485 specifier: ModuleSpecifier::parse("file:///b.ts").unwrap(),
6486 range: Range {
6487 specifier: specifier.clone(),
6488 range: PositionRange {
6489 start: Position {
6490 line: 0,
6491 character: 19,
6492 },
6493 end: Position {
6494 line: 0,
6495 character: 27,
6496 },
6497 },
6498 resolution_mode: None,
6499 },
6500 })),
6501 imports: vec![
6502 Import {
6503 specifier: "./b.ts".to_string(),
6504 kind: ImportKind::Es,
6505 specifier_range: Range {
6506 specifier: specifier.clone(),
6507 range: PositionRange {
6508 start: Position {
6509 line: 0,
6510 character: 19,
6511 },
6512 end: Position {
6513 line: 0,
6514 character: 27,
6515 },
6516 },
6517 resolution_mode: None,
6518 },
6519 is_dynamic: false,
6520 attributes: Default::default(),
6521 is_side_effect: false,
6522 },
6523 Import {
6524 specifier: "./b.ts".to_string(),
6525 kind: ImportKind::Es,
6526 specifier_range: Range {
6527 specifier: specifier.clone(),
6528 range: PositionRange {
6529 start: Position {
6530 line: 1,
6531 character: 19,
6532 },
6533 end: Position {
6534 line: 1,
6535 character: 27,
6536 },
6537 },
6538 resolution_mode: None,
6539 },
6540 is_dynamic: false,
6541 attributes: Default::default(),
6542 is_side_effect: false,
6543 },
6544 ],
6545 ..Default::default()
6546 };
6547 assert_eq!(
6548 dependency.includes(Position {
6549 line: 0,
6550 character: 21,
6551 }),
6552 Some(&Range {
6553 specifier: specifier.clone(),
6554 range: PositionRange {
6555 start: Position {
6556 line: 0,
6557 character: 19
6558 },
6559 end: Position {
6560 line: 0,
6561 character: 27
6562 },
6563 },
6564 resolution_mode: None,
6565 })
6566 );
6567 assert_eq!(
6568 dependency.includes(Position {
6569 line: 1,
6570 character: 21,
6571 }),
6572 Some(&Range {
6573 specifier,
6574 range: PositionRange {
6575 start: Position {
6576 line: 1,
6577 character: 19
6578 },
6579 end: Position {
6580 line: 1,
6581 character: 27
6582 },
6583 },
6584 resolution_mode: None,
6585 })
6586 );
6587 assert_eq!(
6588 dependency.includes(Position {
6589 line: 0,
6590 character: 18,
6591 }),
6592 None,
6593 );
6594 }
6595
6596 #[test]
6597 fn dependency_with_new_resolver() {
6598 let referrer = ModuleSpecifier::parse("file:///a/main.ts").unwrap();
6599 let dependency = Dependency {
6600 maybe_code: Resolution::Ok(Box::new(ResolutionResolved {
6601 specifier: ModuleSpecifier::parse("file:///wrong.ts").unwrap(),
6602 range: Range {
6603 specifier: referrer.clone(),
6604 range: PositionRange::zeroed(),
6605 resolution_mode: None,
6606 },
6607 })),
6608 maybe_type: Resolution::Ok(Box::new(ResolutionResolved {
6609 specifier: ModuleSpecifier::parse("file:///wrong.ts").unwrap(),
6610 range: Range {
6611 specifier: referrer.clone(),
6612 range: PositionRange::zeroed(),
6613 resolution_mode: None,
6614 },
6615 })),
6616 maybe_deno_types_specifier: Some("./b.d.ts".to_string()),
6617 ..Default::default()
6618 };
6619 let new_dependency =
6620 dependency.with_new_resolver("./b.ts", Default::default(), None);
6621 assert_eq!(
6622 new_dependency,
6623 Dependency {
6624 maybe_code: Resolution::Ok(Box::new(ResolutionResolved {
6625 specifier: ModuleSpecifier::parse("file:///a/b.ts").unwrap(),
6626 range: Range {
6627 specifier: referrer.clone(),
6628 range: PositionRange::zeroed(),
6629 resolution_mode: None,
6630 },
6631 })),
6632 maybe_type: Resolution::Ok(Box::new(ResolutionResolved {
6633 specifier: ModuleSpecifier::parse("file:///a/b.d.ts").unwrap(),
6634 range: Range {
6635 specifier: referrer.clone(),
6636 range: PositionRange::zeroed(),
6637 resolution_mode: None,
6638 },
6639 })),
6640 maybe_deno_types_specifier: Some("./b.d.ts".to_string()),
6641 ..Default::default()
6642 }
6643 );
6644 }
6645
6646 #[test]
6647 fn types_dependency_with_new_resolver() {
6648 let referrer = ModuleSpecifier::parse("file:///a/main.ts").unwrap();
6649 let types_dependency = TypesDependency {
6650 specifier: "./main.d.ts".to_string(),
6651 dependency: Resolution::Ok(Box::new(ResolutionResolved {
6652 specifier: ModuleSpecifier::parse("file:///wrong.ts").unwrap(),
6653 range: Range {
6654 specifier: referrer.clone(),
6655 range: PositionRange::zeroed(),
6656 resolution_mode: None,
6657 },
6658 })),
6659 };
6660 let new_types_dependency =
6661 types_dependency.with_new_resolver(Default::default(), None);
6662 assert_eq!(
6663 new_types_dependency,
6664 TypesDependency {
6665 specifier: "./main.d.ts".to_string(),
6666 dependency: Resolution::Ok(Box::new(ResolutionResolved {
6667 specifier: ModuleSpecifier::parse("file:///a/main.d.ts").unwrap(),
6668 range: Range {
6669 specifier: referrer.clone(),
6670 range: PositionRange::zeroed(),
6671 resolution_mode: None,
6672 },
6673 })),
6674 }
6675 );
6676 }
6677
6678 #[tokio::test]
6679 async fn static_dep_of_dynamic_dep_is_dynamic() {
6680 #[derive(Default)]
6681 struct TestLoader {
6682 loaded_foo: RefCell<bool>,
6683 loaded_bar: RefCell<bool>,
6684 loaded_baz: RefCell<bool>,
6685 loaded_dynamic_root: RefCell<bool>,
6686 }
6687 impl Loader for TestLoader {
6688 fn load(
6689 &self,
6690 specifier: &ModuleSpecifier,
6691 options: LoadOptions,
6692 ) -> LoadFuture {
6693 let specifier = specifier.clone();
6694 match specifier.as_str() {
6695 "file:///foo.js" => {
6696 assert!(!options.in_dynamic_branch);
6697 assert!(!options.was_dynamic_root);
6698 *self.loaded_foo.borrow_mut() = true;
6699 Box::pin(async move {
6700 Ok(Some(LoadResponse::Module {
6701 specifier: specifier.clone(),
6702 maybe_headers: None,
6703 mtime: None,
6704 content: b"await import('file:///bar.js')".to_vec().into(),
6705 }))
6706 })
6707 }
6708 "file:///bar.js" => {
6709 assert!(options.in_dynamic_branch);
6710 assert!(!options.was_dynamic_root);
6711 *self.loaded_bar.borrow_mut() = true;
6712 Box::pin(async move {
6713 Ok(Some(LoadResponse::Module {
6714 specifier: specifier.clone(),
6715 maybe_headers: None,
6716 mtime: None,
6717 content: b"import 'file:///baz.js'".to_vec().into(),
6718 }))
6719 })
6720 }
6721 "file:///baz.js" => {
6722 assert!(options.in_dynamic_branch);
6723 assert!(!options.was_dynamic_root);
6724 *self.loaded_baz.borrow_mut() = true;
6725 Box::pin(async move {
6726 Ok(Some(LoadResponse::Module {
6727 specifier: specifier.clone(),
6728 maybe_headers: None,
6729 mtime: None,
6730 content: b"console.log('Hello, world!')".to_vec().into(),
6731 }))
6732 })
6733 }
6734 "file:///dynamic_root.js" => {
6735 assert!(options.in_dynamic_branch);
6736 assert!(options.was_dynamic_root);
6737 *self.loaded_dynamic_root.borrow_mut() = true;
6738 Box::pin(async move {
6739 Ok(Some(LoadResponse::Module {
6740 specifier: specifier.clone(),
6741 maybe_headers: None,
6742 mtime: None,
6743 content: b"console.log('Hello, world!')".to_vec().into(),
6744 }))
6745 })
6746 }
6747 _ => unreachable!(),
6748 }
6749 }
6750 }
6751
6752 let loader = TestLoader::default();
6753 let mut graph = ModuleGraph::new(GraphKind::All);
6754 graph
6755 .build(
6756 vec![Url::parse("file:///foo.js").unwrap()],
6757 Vec::new(),
6758 &loader,
6759 Default::default(),
6760 )
6761 .await;
6762 assert!(*loader.loaded_foo.borrow());
6763 assert!(*loader.loaded_bar.borrow());
6764 assert!(*loader.loaded_baz.borrow());
6765 assert!(!*loader.loaded_dynamic_root.borrow());
6766 assert_eq!(graph.specifiers_count(), 3);
6767
6768 graph
6769 .build(
6770 vec![Url::parse("file:///dynamic_root.js").unwrap()],
6771 Vec::new(),
6772 &loader,
6773 BuildOptions {
6774 is_dynamic: true,
6775 ..Default::default()
6776 },
6777 )
6778 .await;
6779 assert!(*loader.loaded_dynamic_root.borrow());
6780 assert_eq!(graph.specifiers_count(), 4);
6781 }
6782
6783 #[tokio::test]
6784 async fn missing_module_is_error() {
6785 struct TestLoader;
6786 impl Loader for TestLoader {
6787 fn load(
6788 &self,
6789 specifier: &ModuleSpecifier,
6790 _options: LoadOptions,
6791 ) -> LoadFuture {
6792 let specifier = specifier.clone();
6793 match specifier.as_str() {
6794 "file:///foo.js" => Box::pin(async move {
6795 Ok(Some(LoadResponse::Module {
6796 specifier: specifier.clone(),
6797 maybe_headers: None,
6798 mtime: None,
6799 content: b"await import('file:///bar.js')".to_vec().into(),
6800 }))
6801 }),
6802 "file:///bar.js" => Box::pin(async move { Ok(None) }),
6803 _ => unreachable!(),
6804 }
6805 }
6806 }
6807 let loader = TestLoader;
6808 let mut graph = ModuleGraph::new(GraphKind::All);
6809 let roots = vec![Url::parse("file:///foo.js").unwrap()];
6810 graph
6811 .build(roots.clone(), Vec::new(), &loader, Default::default())
6812 .await;
6813 assert!(graph
6814 .try_get(&Url::parse("file:///foo.js").unwrap())
6815 .is_ok());
6816 assert!(matches!(
6817 graph
6818 .try_get(&Url::parse("file:///bar.js").unwrap())
6819 .unwrap_err()
6820 .as_kind(),
6821 ModuleErrorKind::Missing { .. }
6822 ));
6823 let specifiers = graph.specifiers().collect::<HashMap<_, _>>();
6824 assert_eq!(specifiers.len(), 2);
6825 assert!(specifiers
6826 .get(&Url::parse("file:///foo.js").unwrap())
6827 .unwrap()
6828 .is_ok());
6829 assert!(matches!(
6830 specifiers
6831 .get(&Url::parse("file:///bar.js").unwrap())
6832 .unwrap()
6833 .as_ref()
6834 .unwrap_err()
6835 .as_kind(),
6836 ModuleErrorKind::Missing { .. }
6837 ));
6838
6839 let error_count = graph
6841 .walk(
6842 roots.iter(),
6843 WalkOptions {
6844 follow_dynamic: false,
6845 kind: GraphKind::All,
6846 check_js: CheckJsOption::True,
6847 prefer_fast_check_graph: false,
6848 },
6849 )
6850 .errors()
6851 .count();
6852 assert_eq!(error_count, 0);
6853
6854 let errors = graph
6856 .walk(
6857 roots.iter(),
6858 WalkOptions {
6859 follow_dynamic: true,
6860 kind: GraphKind::All,
6861 check_js: CheckJsOption::True,
6862 prefer_fast_check_graph: false,
6863 },
6864 )
6865 .errors()
6866 .collect::<Vec<_>>();
6867 assert_eq!(errors.len(), 1);
6868 let err = &errors[0];
6869 match err {
6870 ModuleGraphError::ModuleError(err) => {
6871 assert!(matches!(
6872 err.as_kind(),
6873 ModuleErrorKind::MissingDynamic { .. }
6874 ));
6875 }
6876 _ => unreachable!(),
6877 }
6878 }
6879
6880 #[tokio::test]
6881 async fn missing_wasm_module_dep_is_error() {
6882 struct TestLoader;
6883 impl Loader for TestLoader {
6884 fn load(
6885 &self,
6886 specifier: &ModuleSpecifier,
6887 _options: LoadOptions,
6888 ) -> LoadFuture {
6889 let specifier = specifier.clone();
6890 match specifier.as_str() {
6891 "file:///foo.js" => Box::pin(async move {
6892 Ok(Some(LoadResponse::Module {
6893 specifier: specifier.clone(),
6894 maybe_headers: None,
6895 mtime: None,
6896 content: b"import './math_with_import.wasm';".to_vec().into(),
6897 }))
6898 }),
6899 "file:///math_with_import.wasm" => Box::pin(async move {
6900 Ok(Some(LoadResponse::Module {
6901 specifier: specifier.clone(),
6902 maybe_headers: None,
6903 mtime: None,
6904 content: include_bytes!(
6905 "../tests/testdata/math_with_import.wasm"
6906 )
6907 .to_vec()
6908 .into(),
6909 }))
6910 }),
6911 "file:///math.ts" => Box::pin(async move { Ok(None) }),
6912 _ => unreachable!(),
6913 }
6914 }
6915 }
6916 let loader = TestLoader;
6917 let mut graph = ModuleGraph::new(GraphKind::All);
6918 let roots = vec![Url::parse("file:///foo.js").unwrap()];
6919 graph
6920 .build(roots.clone(), Vec::new(), &loader, Default::default())
6921 .await;
6922
6923 let errors = graph
6925 .walk(
6926 roots.iter(),
6927 WalkOptions {
6928 follow_dynamic: false,
6929 kind: GraphKind::All,
6930 check_js: CheckJsOption::True,
6931 prefer_fast_check_graph: false,
6932 },
6933 )
6934 .errors()
6935 .collect::<Vec<_>>();
6936 assert_eq!(errors.len(), 1);
6937 match &errors[0] {
6938 ModuleGraphError::ModuleError(err) => {
6939 match err.as_kind() {
6940 ModuleErrorKind::Missing {
6941 specifier,
6942 maybe_referrer,
6943 } => {
6944 assert_eq!(specifier.as_str(), "file:///math.ts");
6945 assert_eq!(
6946 maybe_referrer.clone(),
6947 Some(Range {
6948 specifier: Url::parse("file:///math_with_import.wasm").unwrap(),
6949 range: PositionRange {
6951 start: Position {
6952 line: 0,
6953 character: 92,
6954 },
6955 end: Position {
6956 line: 0,
6957 character: 103,
6958 }
6959 },
6960 resolution_mode: Some(ResolutionMode::Import),
6961 })
6962 );
6963 }
6964 _ => unreachable!(),
6965 }
6966 }
6967 _ => unreachable!(),
6968 }
6969 }
6970
6971 #[tokio::test]
6972 async fn redirected_specifiers() {
6973 struct TestLoader;
6974 impl Loader for TestLoader {
6975 fn load(
6976 &self,
6977 specifier: &ModuleSpecifier,
6978 _options: LoadOptions,
6979 ) -> LoadFuture {
6980 let specifier = specifier.clone();
6981 match specifier.as_str() {
6982 "file:///foo.js" => Box::pin(async move {
6983 Ok(Some(LoadResponse::Module {
6984 specifier: Url::parse("file:///foo_actual.js").unwrap(),
6985 maybe_headers: None,
6986 mtime: None,
6987 content: b"import 'file:///bar.js'".to_vec().into(),
6988 }))
6989 }),
6990 "file:///bar.js" => Box::pin(async move {
6991 Ok(Some(LoadResponse::Module {
6992 specifier: Url::parse("file:///bar_actual.js").unwrap(),
6993 maybe_headers: None,
6994 mtime: None,
6995 content: b"(".to_vec().into(),
6996 }))
6997 }),
6998 _ => unreachable!(),
6999 }
7000 }
7001 }
7002 let loader = TestLoader;
7003 let mut graph = ModuleGraph::new(GraphKind::All);
7004 graph
7005 .build(
7006 vec![Url::parse("file:///foo.js").unwrap()],
7007 Vec::new(),
7008 &loader,
7009 Default::default(),
7010 )
7011 .await;
7012 let specifiers = graph.specifiers().collect::<HashMap<_, _>>();
7013 assert_eq!(specifiers.len(), 4);
7014 assert!(specifiers
7015 .get(&Url::parse("file:///foo.js").unwrap())
7016 .unwrap()
7017 .is_ok());
7018 assert!(specifiers
7019 .get(&Url::parse("file:///foo_actual.js").unwrap())
7020 .unwrap()
7021 .is_ok());
7022 assert!(matches!(
7023 specifiers
7024 .get(&Url::parse("file:///bar.js").unwrap())
7025 .unwrap()
7026 .as_ref()
7027 .unwrap_err()
7028 .as_kind(),
7029 ModuleErrorKind::Parse { .. }
7030 ));
7031 assert!(matches!(
7032 specifiers
7033 .get(&Url::parse("file:///bar_actual.js").unwrap())
7034 .unwrap()
7035 .as_ref()
7036 .unwrap_err()
7037 .as_kind(),
7038 ModuleErrorKind::Parse { .. }
7039 ));
7040 }
7041
7042 #[tokio::test]
7043 async fn local_import_remote_module() {
7044 struct TestLoader;
7045 impl Loader for TestLoader {
7046 fn load(
7047 &self,
7048 specifier: &ModuleSpecifier,
7049 _options: LoadOptions,
7050 ) -> LoadFuture {
7051 let specifier = specifier.clone();
7052 match specifier.as_str() {
7053 "https://deno.land/foo.js" => Box::pin(async move {
7054 Ok(Some(LoadResponse::Module {
7055 specifier: specifier.clone(),
7056 maybe_headers: None,
7057 mtime: None,
7058 content:
7059 b"import 'FILE:///baz.js'; import 'file:///bar.js'; import 'http://deno.land/foo.js';"
7060 .to_vec().into(),
7061 }))
7062 }),
7063 "http://deno.land/foo.js" => Box::pin(async move {
7064 Ok(Some(LoadResponse::Module {
7065 specifier: specifier.clone(),
7066 maybe_headers: None,
7067 mtime: None,
7068 content: b"export {}".to_vec().into(),
7069 }))
7070 }),
7071 "file:///bar.js" => Box::pin(async move {
7072 Ok(Some(LoadResponse::Module {
7073 specifier: specifier.clone(),
7074 maybe_headers: None,
7075 mtime: None,
7076 content: b"console.log('Hello, world!')".to_vec().into(),
7077 }))
7078 }),
7079 "file:///baz.js" => Box::pin(async move {
7080 Ok(Some(LoadResponse::Module {
7081 specifier: specifier.clone(),
7082 maybe_headers: None,
7083 mtime: None,
7084 content: b"console.log('Hello, world 2!')".to_vec().into(),
7085 }))
7086 }),
7087 _ => unreachable!(),
7088 }
7089 }
7090 }
7091 let loader = TestLoader;
7092 let mut graph = ModuleGraph::new(GraphKind::All);
7093 let roots = vec![Url::parse("https://deno.land/foo.js").unwrap()];
7094 graph
7095 .build(roots.clone(), Vec::new(), &loader, Default::default())
7096 .await;
7097 assert_eq!(graph.specifiers_count(), 4);
7098 let errors = graph
7099 .walk(
7100 roots.iter(),
7101 WalkOptions {
7102 check_js: CheckJsOption::True,
7103 follow_dynamic: false,
7104 kind: GraphKind::All,
7105 prefer_fast_check_graph: false,
7106 },
7107 )
7108 .errors()
7109 .collect::<Vec<_>>();
7110 assert_eq!(errors.len(), 3);
7111 let errors = errors
7112 .into_iter()
7113 .map(|err| match err {
7114 ModuleGraphError::ResolutionError(err) => err,
7115 _ => unreachable!(),
7116 })
7117 .collect::<Vec<_>>();
7118
7119 assert_eq!(
7120 errors[0],
7121 ResolutionError::InvalidDowngrade {
7122 range: Range {
7123 specifier: ModuleSpecifier::parse("https://deno.land/foo.js")
7124 .unwrap(),
7125 range: PositionRange {
7126 start: Position {
7127 line: 0,
7128 character: 57,
7129 },
7130 end: Position {
7131 line: 0,
7132 character: 82,
7133 },
7134 },
7135 resolution_mode: Some(ResolutionMode::Import),
7136 },
7137 specifier: ModuleSpecifier::parse("http://deno.land/foo.js").unwrap(),
7138 },
7139 );
7140 assert_eq!(
7141 errors[1],
7142 ResolutionError::InvalidLocalImport {
7143 range: Range {
7144 specifier: ModuleSpecifier::parse("https://deno.land/foo.js")
7145 .unwrap(),
7146 range: PositionRange {
7147 start: Position {
7148 line: 0,
7149 character: 32,
7150 },
7151 end: Position {
7152 line: 0,
7153 character: 48,
7154 },
7155 },
7156 resolution_mode: Some(ResolutionMode::Import),
7157 },
7158 specifier: ModuleSpecifier::parse("file:///bar.js").unwrap(),
7159 },
7160 );
7161
7162 assert_eq!(
7163 errors[2],
7164 ResolutionError::InvalidLocalImport {
7165 range: Range {
7166 specifier: ModuleSpecifier::parse("https://deno.land/foo.js")
7167 .unwrap(),
7168 range: PositionRange {
7169 start: Position {
7170 line: 0,
7171 character: 7,
7172 },
7173 end: Position {
7174 line: 0,
7175 character: 23,
7176 },
7177 },
7178 resolution_mode: Some(ResolutionMode::Import),
7179 },
7180 specifier: ModuleSpecifier::parse("file:///baz.js").unwrap(),
7181 },
7182 );
7183 }
7184
7185 #[tokio::test]
7186 async fn static_and_dynamic_dep_is_static() {
7187 struct TestLoader {
7188 loaded_bar: RefCell<bool>,
7189 }
7190 impl Loader for TestLoader {
7191 fn load(
7192 &self,
7193 specifier: &ModuleSpecifier,
7194 options: LoadOptions,
7195 ) -> LoadFuture {
7196 let specifier = specifier.clone();
7197 match specifier.as_str() {
7198 "file:///foo.js" => Box::pin(async move {
7199 Ok(Some(LoadResponse::Module {
7200 specifier: specifier.clone(),
7201 maybe_headers: None,
7202 mtime: None,
7203 content:
7204 b"import 'file:///bar.js'; await import('file:///bar.js')"
7205 .to_vec()
7206 .into(),
7207 }))
7208 }),
7209 "file:///bar.js" => {
7210 assert!(!options.in_dynamic_branch);
7211 *self.loaded_bar.borrow_mut() = true;
7212 Box::pin(async move {
7213 Ok(Some(LoadResponse::Module {
7214 specifier: specifier.clone(),
7215 maybe_headers: None,
7216 mtime: None,
7217 content: b"console.log('Hello, world!')".to_vec().into(),
7218 }))
7219 })
7220 }
7221 _ => unreachable!(),
7222 }
7223 }
7224 }
7225 let loader = TestLoader {
7226 loaded_bar: RefCell::new(false),
7227 };
7228 let mut graph = ModuleGraph::new(GraphKind::All);
7229 graph
7230 .build(
7231 vec![Url::parse("file:///foo.js").unwrap()],
7232 Default::default(),
7233 &loader,
7234 Default::default(),
7235 )
7236 .await;
7237 assert!(*loader.loaded_bar.borrow());
7238 }
7239
7240 #[tokio::test]
7241 async fn dependency_imports() {
7242 struct TestLoader;
7243 impl Loader for TestLoader {
7244 fn load(
7245 &self,
7246 specifier: &ModuleSpecifier,
7247 options: LoadOptions,
7248 ) -> LoadFuture {
7249 let specifier = specifier.clone();
7250 match specifier.as_str() {
7251 "file:///foo.ts" => Box::pin(async move {
7252 Ok(Some(LoadResponse::Module {
7253 specifier: specifier.clone(),
7254 maybe_headers: None,
7255 mtime: None,
7256 content: b"
7257 /// <reference path='file:///bar.ts' />
7258 /// <reference types='file:///bar.ts' />
7259 /* @jsxImportSource file:///bar.ts */
7260 import 'file:///bar.ts';
7261 await import('file:///bar.ts');
7262 await import('file:///bar.ts', { assert: eval('') });
7263 import 'file:///baz.json' assert { type: 'json' };
7264 import type {} from 'file:///bar.ts';
7265 /** @typedef { import('file:///bar.ts') } bar */
7266 "
7267 .to_vec()
7268 .into(),
7269 }))
7270 }),
7271 "file:///bar.ts" => {
7272 assert!(!options.in_dynamic_branch);
7273 Box::pin(async move {
7274 Ok(Some(LoadResponse::Module {
7275 specifier: specifier.clone(),
7276 maybe_headers: None,
7277 mtime: None,
7278 content: b"".to_vec().into(),
7279 }))
7280 })
7281 }
7282 "file:///baz.json" => {
7283 assert!(!options.in_dynamic_branch);
7284 Box::pin(async move {
7285 Ok(Some(LoadResponse::Module {
7286 specifier: specifier.clone(),
7287 maybe_headers: None,
7288 mtime: None,
7289 content: b"{}".to_vec().into(),
7290 }))
7291 })
7292 }
7293 _ => unreachable!(),
7294 }
7295 }
7296 }
7297 let mut graph = ModuleGraph::new(GraphKind::All);
7298 graph
7299 .build(
7300 vec![Url::parse("file:///foo.ts").unwrap()],
7301 Vec::new(),
7302 &TestLoader,
7303 Default::default(),
7304 )
7305 .await;
7306 graph.valid().unwrap();
7307 let module = graph.get(&Url::parse("file:///foo.ts").unwrap()).unwrap();
7308 let module = module.js().unwrap();
7309 let dependency_a = module.dependencies.get("file:///bar.ts").unwrap();
7310 let dependency_b = module.dependencies.get("file:///baz.json").unwrap();
7311 assert_eq!(
7312 dependency_a.imports,
7313 vec![
7314 Import {
7315 specifier: "file:///bar.ts".to_string(),
7316 kind: ImportKind::TsReferencePath,
7317 specifier_range: Range {
7318 specifier: Url::parse("file:///foo.ts").unwrap(),
7319 range: PositionRange {
7320 start: Position {
7321 line: 1,
7322 character: 36
7323 },
7324 end: Position {
7325 line: 1,
7326 character: 52,
7327 },
7328 },
7329 resolution_mode: None,
7330 },
7331 is_dynamic: false,
7332 attributes: ImportAttributes::None,
7333 is_side_effect: false,
7334 },
7335 Import {
7336 specifier: "file:///bar.ts".to_string(),
7337 kind: ImportKind::TsReferenceTypes,
7338 specifier_range: Range {
7339 specifier: Url::parse("file:///foo.ts").unwrap(),
7340 range: PositionRange {
7341 start: Position {
7342 line: 2,
7343 character: 37,
7344 },
7345 end: Position {
7346 line: 2,
7347 character: 53,
7348 },
7349 },
7350 resolution_mode: None,
7351 },
7352 is_dynamic: false,
7353 attributes: ImportAttributes::None,
7354 is_side_effect: false,
7355 },
7356 Import {
7357 specifier: "file:///bar.ts".to_string(),
7358 kind: ImportKind::Es,
7359 specifier_range: Range {
7360 specifier: Url::parse("file:///foo.ts").unwrap(),
7361 range: PositionRange {
7362 start: Position {
7363 line: 4,
7364 character: 23,
7365 },
7366 end: Position {
7367 line: 4,
7368 character: 39,
7369 },
7370 },
7371 resolution_mode: Some(ResolutionMode::Import),
7372 },
7373 is_dynamic: false,
7374 attributes: ImportAttributes::None,
7375 is_side_effect: true,
7376 },
7377 Import {
7378 specifier: "file:///bar.ts".to_string(),
7379 kind: ImportKind::Es,
7380 specifier_range: Range {
7381 specifier: Url::parse("file:///foo.ts").unwrap(),
7382 range: PositionRange {
7383 start: Position {
7384 line: 5,
7385 character: 29,
7386 },
7387 end: Position {
7388 line: 5,
7389 character: 45,
7390 },
7391 },
7392 resolution_mode: Some(ResolutionMode::Import),
7393 },
7394 is_dynamic: true,
7395 attributes: ImportAttributes::None,
7396 is_side_effect: false,
7397 },
7398 Import {
7399 specifier: "file:///bar.ts".to_string(),
7400 kind: ImportKind::Es,
7401 specifier_range: Range {
7402 specifier: Url::parse("file:///foo.ts").unwrap(),
7403 range: PositionRange {
7404 start: Position {
7405 line: 6,
7406 character: 29,
7407 },
7408 end: Position {
7409 line: 6,
7410 character: 45,
7411 },
7412 },
7413 resolution_mode: Some(ResolutionMode::Import),
7414 },
7415 is_dynamic: true,
7416 attributes: ImportAttributes::Unknown,
7417 is_side_effect: false,
7418 },
7419 Import {
7420 specifier: "file:///bar.ts".to_string(),
7421 kind: ImportKind::TsType,
7422 specifier_range: Range {
7423 specifier: Url::parse("file:///foo.ts").unwrap(),
7424 range: PositionRange {
7425 start: Position {
7426 line: 8,
7427 character: 36,
7428 },
7429 end: Position {
7430 line: 8,
7431 character: 52,
7432 },
7433 },
7434 resolution_mode: Some(ResolutionMode::Import),
7435 },
7436 is_dynamic: false,
7437 attributes: ImportAttributes::None,
7438 is_side_effect: true,
7439 },
7440 ]
7441 );
7442 assert_eq!(
7443 dependency_b.imports,
7444 vec![Import {
7445 specifier: "file:///baz.json".to_string(),
7446 kind: ImportKind::Es,
7447 specifier_range: Range {
7448 specifier: Url::parse("file:///foo.ts").unwrap(),
7449 range: PositionRange {
7450 start: Position {
7451 line: 7,
7452 character: 23,
7453 },
7454 end: Position {
7455 line: 7,
7456 character: 41,
7457 },
7458 },
7459 resolution_mode: Some(ResolutionMode::Import),
7460 },
7461 is_dynamic: false,
7462 attributes: ImportAttributes::Known(HashMap::from_iter(vec![(
7463 "type".to_string(),
7464 ImportAttribute::Known("json".to_string())
7465 )])),
7466 is_side_effect: true,
7467 }]
7468 );
7469 }
7470
7471 #[tokio::test]
7472 async fn dependency_ts_type_import_with_deno_types() {
7473 struct TestLoader;
7474 impl Loader for TestLoader {
7475 fn load(
7476 &self,
7477 specifier: &ModuleSpecifier,
7478 options: LoadOptions,
7479 ) -> LoadFuture {
7480 let specifier = specifier.clone();
7481 match specifier.as_str() {
7482 "file:///foo.ts" => Box::pin(async move {
7483 Ok(Some(LoadResponse::Module {
7484 specifier: specifier.clone(),
7485 maybe_headers: None,
7486 mtime: None,
7487 content: b"
7488 // @deno-types='file:///bar.d.ts'
7489 import type { Bar as _ } from 'bar';
7490 "
7491 .to_vec()
7492 .into(),
7493 }))
7494 }),
7495 "file:///bar.d.ts" => {
7496 assert!(!options.in_dynamic_branch);
7497 Box::pin(async move {
7498 Ok(Some(LoadResponse::Module {
7499 specifier: specifier.clone(),
7500 maybe_headers: None,
7501 mtime: None,
7502 content: b"export type Bar = null;\n".to_vec().into(),
7503 }))
7504 })
7505 }
7506 _ => unreachable!(),
7507 }
7508 }
7509 }
7510 let mut graph = ModuleGraph::new(GraphKind::TypesOnly);
7511 graph
7512 .build(
7513 vec![Url::parse("file:///foo.ts").unwrap()],
7514 Vec::new(),
7515 &TestLoader,
7516 Default::default(),
7517 )
7518 .await;
7519 graph.valid().unwrap();
7520 let module = graph.get(&Url::parse("file:///foo.ts").unwrap()).unwrap();
7521 let module = module.js().unwrap();
7522 let dependency = module.dependencies.get("bar").unwrap();
7523 assert_eq!(dependency.maybe_code, Resolution::None);
7524 assert_eq!(
7525 dependency.maybe_type.maybe_specifier().unwrap().as_str(),
7526 "file:///bar.d.ts",
7527 );
7528 }
7529
7530 #[tokio::test]
7531 async fn dependency_declare_module() {
7532 #[derive(Debug)]
7533 struct TestLoader;
7534 impl Loader for TestLoader {
7535 fn load(
7536 &self,
7537 specifier: &ModuleSpecifier,
7538 _options: LoadOptions,
7539 ) -> LoadFuture {
7540 let specifier = specifier.clone();
7541 match specifier.as_str() {
7542 "file:///main.ts" => Box::pin(async move {
7543 Ok(Some(LoadResponse::Module {
7544 specifier: specifier.clone(),
7545 maybe_headers: None,
7546 mtime: None,
7547 content: b"
7548 declare module 'foo' {}
7549 "
7550 .to_vec()
7551 .into(),
7552 }))
7553 }),
7554 "file:///foo.d.ts" => Box::pin(async move {
7555 Ok(Some(LoadResponse::Module {
7556 specifier: specifier.clone(),
7557 maybe_headers: None,
7558 mtime: None,
7559 content: vec![].into(),
7560 }))
7561 }),
7562 s => unreachable!("{s}"),
7563 }
7564 }
7565 }
7566 impl Resolver for TestLoader {
7567 fn resolve(
7568 &self,
7569 specifier_text: &str,
7570 _referrer_range: &Range,
7571 kind: ResolutionKind,
7572 ) -> Result<ModuleSpecifier, ResolveError> {
7573 match (specifier_text, kind) {
7574 ("foo", ResolutionKind::Types) => {
7575 Ok(ModuleSpecifier::parse("file:///foo.d.ts").unwrap())
7576 }
7577 e => unreachable!("{e:?}"),
7578 }
7579 }
7580 }
7581 let mut graph = ModuleGraph::new(GraphKind::TypesOnly);
7582 graph
7583 .build(
7584 vec![Url::parse("file:///main.ts").unwrap()],
7585 Vec::new(),
7586 &TestLoader,
7587 BuildOptions {
7588 resolver: Some(&TestLoader),
7589 ..Default::default()
7590 },
7591 )
7592 .await;
7593 graph.valid().unwrap();
7594 let module = graph.get(&Url::parse("file:///main.ts").unwrap()).unwrap();
7595 let module = module.js().unwrap();
7596 let dependency = module.dependencies.get("foo").unwrap();
7597 assert_eq!(dependency.maybe_code, Resolution::None);
7598 assert_eq!(
7599 dependency.maybe_type.maybe_specifier().unwrap().as_str(),
7600 "file:///foo.d.ts",
7601 );
7602 }
7603
7604 #[tokio::test]
7605 async fn dependency_jsx_import_source_types_resolution() {
7606 let mut mem_loader = MemoryLoader::default();
7607 mem_loader.add_source_with_text(
7608 "file:///foo.tsx",
7609 "
7610/* @jsxImportSource foo */
7611",
7612 );
7613 mem_loader.add_source(
7614 "file:///foo/jsx-runtime",
7615 Source::Module {
7616 specifier: "file:///foo/jsx-runtime",
7617 maybe_headers: Some(vec![("content-type", "application/javascript")]),
7618 content: "",
7619 },
7620 );
7621 mem_loader.add_source(
7622 "file:///foo/types/jsx-runtime",
7623 Source::Module {
7624 specifier: "file:///foo/types/jsx-runtime",
7625 maybe_headers: Some(vec![("content-type", "application/typescript")]),
7626 content: "",
7627 },
7628 );
7629 let mut graph = ModuleGraph::new(GraphKind::All);
7630
7631 #[derive(Debug)]
7632 struct TestResolver;
7633 impl Resolver for TestResolver {
7634 fn resolve(
7635 &self,
7636 specifier_text: &str,
7637 referrer_range: &Range,
7638 resolution_kind: ResolutionKind,
7639 ) -> Result<ModuleSpecifier, ResolveError> {
7640 if specifier_text == "foo/jsx-runtime" {
7641 match resolution_kind {
7642 ResolutionKind::Execution => {
7643 Ok(ModuleSpecifier::parse("file:///foo/jsx-runtime").unwrap())
7644 }
7645 ResolutionKind::Types => Ok(
7646 ModuleSpecifier::parse("file:///foo/types/jsx-runtime").unwrap(),
7647 ),
7648 }
7649 } else {
7650 Ok(resolve_import(specifier_text, &referrer_range.specifier)?)
7651 }
7652 }
7653 }
7654
7655 let resolver = TestResolver;
7656 graph
7657 .build(
7658 vec![Url::parse("file:///foo.tsx").unwrap()],
7659 Vec::new(),
7660 &mem_loader,
7661 BuildOptions {
7662 resolver: Some(&resolver),
7663 ..Default::default()
7664 },
7665 )
7666 .await;
7667 graph.valid().unwrap();
7668 let module = graph.get(&Url::parse("file:///foo.tsx").unwrap()).unwrap();
7669 let module = module.js().unwrap();
7670 let dependency_a = module.dependencies.get("foo/jsx-runtime").unwrap();
7671 assert_eq!(
7672 dependency_a.maybe_code.maybe_specifier().unwrap().as_str(),
7673 "file:///foo/jsx-runtime"
7674 );
7675 assert_eq!(
7676 dependency_a.maybe_type.maybe_specifier().unwrap().as_str(),
7677 "file:///foo/types/jsx-runtime"
7678 );
7679 }
7680
7681 #[tokio::test]
7682 async fn dependency_jsx_import_source_types_pragma() {
7683 let mut mem_loader = MemoryLoader::default();
7684 mem_loader.add_source_with_text(
7685 "file:///foo.tsx",
7686 "
7687/* @jsxImportSource http://localhost */
7688/* @jsxImportSourceTypes http://localhost/types */
7689",
7690 );
7691 mem_loader.add_source(
7692 "http://localhost/jsx-runtime",
7693 Source::Module {
7694 specifier: "http://localhost/jsx-runtime",
7695 maybe_headers: Some(vec![("content-type", "application/javascript")]),
7696 content: "",
7697 },
7698 );
7699 mem_loader.add_source(
7700 "http://localhost/types/jsx-runtime",
7701 Source::Module {
7702 specifier: "http://localhost/types/jsx-runtime",
7703 maybe_headers: Some(vec![("content-type", "application/typescript")]),
7704 content: "",
7705 },
7706 );
7707 let mut graph = ModuleGraph::new(GraphKind::All);
7708 graph
7709 .build(
7710 vec![Url::parse("file:///foo.tsx").unwrap()],
7711 Vec::new(),
7712 &mem_loader,
7713 Default::default(),
7714 )
7715 .await;
7716 graph.valid().unwrap();
7717 let module = graph.get(&Url::parse("file:///foo.tsx").unwrap()).unwrap();
7718 let module = module.js().unwrap();
7719 let dependency_a = module
7720 .dependencies
7721 .get("http://localhost/jsx-runtime")
7722 .unwrap();
7723 assert_eq!(
7724 dependency_a.maybe_type.maybe_specifier().unwrap().as_str(),
7725 "http://localhost/types/jsx-runtime"
7726 );
7727 assert_eq!(
7728 dependency_a.maybe_deno_types_specifier.as_ref().unwrap(),
7729 "http://localhost/types/jsx-runtime"
7730 );
7731 }
7732
7733 #[cfg(feature = "fast_check")]
7734 #[tokio::test]
7735 async fn fast_check_dts() {
7736 use deno_ast::EmittedSourceText;
7737
7738 let mut exports = IndexMap::new();
7739 exports.insert(".".to_string(), "./foo.ts".to_string());
7740
7741 let workspace_members = vec![WorkspaceMember {
7742 base: Url::parse("file:///").unwrap(),
7743 exports: exports.clone(),
7744 name: "@foo/bar".into(),
7745 version: Some(Version::parse_standard("1.0.0").unwrap()),
7746 }];
7747 let mut test_loader = MemoryLoader::default();
7748 test_loader.add_source_with_text(
7749 "file:///foo.ts",
7750 "
7751 export function add(a: number, b: number): number {
7752 return a + b;
7753 }
7754 ",
7755 );
7756 let mut graph = ModuleGraph::new(GraphKind::All);
7757 graph
7758 .build(
7759 vec![Url::parse("file:///foo.ts").unwrap()],
7760 Vec::new(),
7761 &test_loader,
7762 BuildOptions {
7763 ..Default::default()
7764 },
7765 )
7766 .await;
7767 graph.build_fast_check_type_graph(BuildFastCheckTypeGraphOptions {
7768 fast_check_cache: None,
7769 fast_check_dts: true,
7770 workspace_fast_check: WorkspaceFastCheckOption::Enabled(
7771 &workspace_members,
7772 ),
7773 ..Default::default()
7774 });
7775 graph.valid().unwrap();
7776 let module = graph.get(&Url::parse("file:///foo.ts").unwrap()).unwrap();
7777 let module = module.js().unwrap();
7778 let FastCheckTypeModuleSlot::Module(fsm) =
7779 module.fast_check.clone().unwrap()
7780 else {
7781 unreachable!();
7782 };
7783 let dts = fsm.dts.unwrap();
7784 let source_map = SourceMap::single(
7785 module.specifier.clone(),
7786 module.source.text.to_string(),
7787 );
7788 let EmittedSourceText { text, .. } = emit(
7789 (&dts.program).into(),
7790 &dts.comments.as_single_threaded(),
7791 &source_map,
7792 &EmitOptions {
7793 remove_comments: false,
7794 source_map: deno_ast::SourceMapOption::None,
7795 ..Default::default()
7796 },
7797 )
7798 .unwrap();
7799 assert_eq!(
7800 text.trim(),
7801 "export declare function add(a: number, b: number): number;"
7802 );
7803 assert!(dts.diagnostics.is_empty());
7804 }
7805
7806 #[cfg(feature = "fast_check")]
7807 #[tokio::test]
7808 async fn fast_check_external() {
7809 use deno_ast::EmittedSourceText;
7810
7811 let mut exports = IndexMap::new();
7812 exports.insert(".".to_string(), "./foo.ts".to_string());
7813
7814 let workspace_members = vec![WorkspaceMember {
7815 base: Url::parse("file:///").unwrap(),
7816 exports: exports.clone(),
7817 name: "@foo/bar".into(),
7818 version: Some(Version::parse_standard("1.0.0").unwrap()),
7819 }];
7820 let mut test_loader = MemoryLoader::default();
7821 test_loader.add_source_with_text(
7822 "file:///foo.ts",
7823 "export * from 'jsr:@package/foo';",
7824 );
7825 test_loader.add_jsr_package_info(
7826 "@package/foo",
7827 &JsrPackageInfo {
7828 versions: HashMap::from([(
7829 Version::parse_standard("1.0.0").unwrap(),
7830 JsrPackageInfoVersion::default(),
7831 )]),
7832 },
7833 );
7834 test_loader.add_jsr_version_info(
7835 "@package/foo",
7836 "1.0.0",
7837 &JsrPackageVersionInfo {
7838 exports: json!({ ".": "./mod.ts" }),
7839 module_graph_1: None,
7840 module_graph_2: None,
7841 manifest: Default::default(),
7842 lockfile_checksum: None,
7843 },
7844 );
7845 test_loader.add_external_source("https://jsr.io/@package/foo/1.0.0/mod.ts");
7846 let mut graph = ModuleGraph::new(GraphKind::All);
7847 graph
7848 .build(
7849 vec![Url::parse("file:///foo.ts").unwrap()],
7850 Vec::new(),
7851 &test_loader,
7852 BuildOptions::default(),
7853 )
7854 .await;
7855 graph.build_fast_check_type_graph(BuildFastCheckTypeGraphOptions {
7856 fast_check_cache: None,
7857 fast_check_dts: true,
7858 workspace_fast_check: WorkspaceFastCheckOption::Enabled(
7859 &workspace_members,
7860 ),
7861 ..Default::default()
7862 });
7863 graph.valid().unwrap();
7864 {
7865 let module = graph.get(&Url::parse("file:///foo.ts").unwrap()).unwrap();
7866 let FastCheckTypeModuleSlot::Module(fsm) =
7867 module.js().unwrap().fast_check.clone().unwrap()
7868 else {
7869 unreachable!();
7870 };
7871 let dts = fsm.dts.unwrap();
7872 let source_map = SourceMap::single(
7873 module.specifier().clone(),
7874 module.source().unwrap().to_string(),
7875 );
7876 let EmittedSourceText { text, .. } = emit(
7877 (&dts.program).into(),
7878 &dts.comments.as_single_threaded(),
7879 &source_map,
7880 &EmitOptions {
7881 remove_comments: false,
7882 source_map: deno_ast::SourceMapOption::None,
7883 ..Default::default()
7884 },
7885 )
7886 .unwrap();
7887 assert_eq!(text.trim(), "export * from 'jsr:@package/foo';");
7888 assert!(dts.diagnostics.is_empty());
7889 }
7890
7891 let module = graph
7892 .get(&Url::parse("https://jsr.io/@package/foo/1.0.0/mod.ts").unwrap())
7893 .unwrap();
7894 assert!(module.external().is_some());
7895 }
7896
7897 #[test]
7898 fn leading_v_version_tag_err() {
7899 {
7900 let err =
7901 JsrPackageFormatError::VersionTagNotSupported { tag: "v1.2".into() };
7902 assert_eq!(err.to_string(), "Version tag not supported in jsr specifiers ('v1.2'). Remove leading 'v' before version.");
7903 }
7904 {
7905 let err = JsrPackageFormatError::VersionTagNotSupported {
7906 tag: "latest".into(),
7907 };
7908 assert_eq!(
7909 err.to_string(),
7910 "Version tag not supported in jsr specifiers ('latest')."
7911 );
7912 }
7913 {
7914 let err = JsrPackageFormatError::VersionTagNotSupported {
7915 tag: "version".into(), };
7917 assert_eq!(
7918 err.to_string(),
7919 "Version tag not supported in jsr specifiers ('version')."
7920 );
7921 }
7922 }
7923
7924 #[tokio::test]
7925 async fn check_js_option_custom() {
7926 #[derive(Debug)]
7927 struct CustomResolver;
7928
7929 impl CheckJsResolver for CustomResolver {
7930 fn resolve(&self, specifier: &ModuleSpecifier) -> bool {
7931 specifier.as_str() == "file:///true.js"
7932 }
7933 }
7934
7935 struct TestLoader;
7936 impl Loader for TestLoader {
7937 fn load(
7938 &self,
7939 specifier: &ModuleSpecifier,
7940 _options: LoadOptions,
7941 ) -> LoadFuture {
7942 let specifier = specifier.clone();
7943 match specifier.as_str() {
7944 "file:///valid.js" => Box::pin(async move {
7945 Ok(Some(LoadResponse::Module {
7946 specifier: specifier.clone(),
7947 maybe_headers: None,
7948 mtime: None,
7949 content: b"export {}".to_vec().into(),
7950 }))
7951 }),
7952 "file:///true.js" => Box::pin(async move {
7953 Ok(Some(LoadResponse::Module {
7954 specifier: specifier.clone(),
7955 maybe_headers: None,
7956 mtime: None,
7957 content: b"// @ts-types='invalid'\nimport {} from './valid.js';"
7958 .to_vec()
7959 .into(),
7960 }))
7961 }),
7962 "file:///false.js" => Box::pin(async move {
7963 Ok(Some(LoadResponse::Module {
7964 specifier: specifier.clone(),
7965 maybe_headers: None,
7966 mtime: None,
7967 content: b"// @ts-types='invalid'\nimport {} from './valid.js';"
7969 .to_vec()
7970 .into(),
7971 }))
7972 }),
7973 "file:///main.ts" => Box::pin(async move {
7974 Ok(Some(LoadResponse::Module {
7975 specifier: specifier.clone(),
7976 maybe_headers: None,
7977 mtime: None,
7978 content: b"import './true.js'; import './false.js'"
7979 .to_vec()
7980 .into(),
7981 }))
7982 }),
7983 _ => unreachable!(),
7984 }
7985 }
7986 }
7987 let loader = TestLoader;
7988 let mut graph = ModuleGraph::new(GraphKind::All);
7989 let roots = vec![Url::parse("file:///main.ts").unwrap()];
7990 graph
7991 .build(roots.clone(), Vec::new(), &loader, Default::default())
7992 .await;
7993 assert_eq!(graph.specifiers_count(), 4);
7994 let errors = graph
7995 .walk(
7996 roots.iter(),
7997 WalkOptions {
7998 check_js: CheckJsOption::Custom(&CustomResolver),
7999 follow_dynamic: false,
8000 kind: GraphKind::All,
8001 prefer_fast_check_graph: false,
8002 },
8003 )
8004 .errors()
8005 .collect::<Vec<_>>();
8006
8007 assert_eq!(errors.len(), 1);
8009 }
8010
8011 #[test]
8012 fn module_text_source_bom() {
8013 let module_text_source = ModuleTextSource {
8014 text: "test".into(),
8015 decoded_kind: DecodedArcSourceDetailKind::OnlyUtf8Bom,
8016 };
8017 assert_eq!(
8018 module_text_source
8019 .try_get_original_bytes()
8020 .unwrap()
8021 .to_vec(),
8022 vec![0xEF, 0xBB, 0xBF, b't', b'e', b's', b't']
8023 )
8024 }
8025}