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