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