1use std::mem::take;
2
3use anyhow::{Context, Result, bail};
4use base64::Engine;
5use either::Either;
6use futures::try_join;
7use serde::{Deserialize, Serialize};
8use serde_json::{Map as JsonMap, Value as JsonValue, json};
9use serde_with::serde_as;
10use turbo_rcstr::{RcStr, rcstr};
11use turbo_tasks::{
12 Completion, NonLocalValue, OperationValue, OperationVc, ResolvedVc, TaskInput, TryJoinIterExt,
13 ValueToString, Vc, trace::TraceRawVcs,
14};
15use turbo_tasks_bytes::stream::SingleValue;
16use turbo_tasks_env::ProcessEnv;
17use turbo_tasks_fs::{
18 File, FileContent, FileSystemPath,
19 glob::{Glob, GlobOptions},
20 json::parse_json_with_source_context,
21 rope::Rope,
22};
23use turbopack_core::{
24 asset::{Asset, AssetContent},
25 chunk::ChunkingContext,
26 context::{AssetContext, ProcessResult},
27 file_source::FileSource,
28 ident::AssetIdent,
29 issue::{
30 Issue, IssueExt, IssueSeverity, IssueSource, IssueStage, OptionIssueSource,
31 OptionStyledString, StyledString,
32 },
33 module::Module,
34 reference_type::{InnerAssets, ReferenceType},
35 resolve::{
36 options::{ConditionValue, ResolveInPackage, ResolveIntoPackage, ResolveOptions},
37 parse::Request,
38 pattern::Pattern,
39 resolve,
40 },
41 source::Source,
42 source_map::{
43 GenerateSourceMap, OptionStringifiedSourceMap, utils::resolve_source_map_sources,
44 },
45 source_transform::SourceTransform,
46 virtual_source::VirtualSource,
47};
48use turbopack_resolve::{
49 ecmascript::get_condition_maps, resolve::resolve_options,
50 resolve_options_context::ResolveOptionsContext,
51};
52
53use super::util::{EmittedAsset, emitted_assets_to_virtual_sources};
54use crate::{
55 AssetsForSourceMapping,
56 debug::should_debug,
57 embed_js::embed_file_path,
58 evaluate::{
59 EnvVarTracking, EvaluateContext, EvaluationIssue, JavaScriptEvaluation,
60 JavaScriptStreamSender, compute, custom_evaluate, get_evaluate_pool,
61 },
62 execution_context::ExecutionContext,
63 pool::{FormattingMode, NodeJsPool},
64 source_map::{StackFrame, StructuredError},
65};
66
67#[serde_as]
68#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
69struct BytesBase64 {
70 #[serde_as(as = "serde_with::base64::Base64")]
71 binary: Vec<u8>,
72}
73
74#[derive(Debug, Serialize, Deserialize, Clone)]
75#[serde(rename_all = "camelCase")]
76#[turbo_tasks::value(serialization = "custom")]
77struct WebpackLoadersProcessingResult {
78 #[serde(with = "either::serde_untagged")]
79 #[turbo_tasks(debug_ignore, trace_ignore)]
80 source: Either<RcStr, BytesBase64>,
81 map: Option<RcStr>,
82 #[turbo_tasks(trace_ignore)]
83 assets: Option<Vec<EmittedAsset>>,
84}
85
86#[derive(
87 Clone, PartialEq, Eq, Debug, TraceRawVcs, Serialize, Deserialize, NonLocalValue, OperationValue,
88)]
89pub struct WebpackLoaderItem {
90 pub loader: RcStr,
91 #[serde(default)]
92 pub options: serde_json::Map<String, serde_json::Value>,
93}
94
95#[derive(Debug, Clone)]
96#[turbo_tasks::value(shared, transparent)]
97pub struct WebpackLoaderItems(pub Vec<WebpackLoaderItem>);
98
99#[turbo_tasks::value]
100pub struct WebpackLoaders {
101 evaluate_context: ResolvedVc<Box<dyn AssetContext>>,
102 execution_context: ResolvedVc<ExecutionContext>,
103 loaders: ResolvedVc<WebpackLoaderItems>,
104 rename_as: Option<RcStr>,
105 resolve_options_context: ResolvedVc<ResolveOptionsContext>,
106 source_maps: bool,
107}
108
109#[turbo_tasks::value_impl]
110impl WebpackLoaders {
111 #[turbo_tasks::function]
112 pub fn new(
113 evaluate_context: ResolvedVc<Box<dyn AssetContext>>,
114 execution_context: ResolvedVc<ExecutionContext>,
115 loaders: ResolvedVc<WebpackLoaderItems>,
116 rename_as: Option<RcStr>,
117 resolve_options_context: ResolvedVc<ResolveOptionsContext>,
118 source_maps: bool,
119 ) -> Vc<Self> {
120 WebpackLoaders {
121 evaluate_context,
122 execution_context,
123 loaders,
124 rename_as,
125 resolve_options_context,
126 source_maps,
127 }
128 .cell()
129 }
130}
131
132#[turbo_tasks::value_impl]
133impl SourceTransform for WebpackLoaders {
134 #[turbo_tasks::function]
135 fn transform(
136 self: ResolvedVc<Self>,
137 source: ResolvedVc<Box<dyn Source>>,
138 ) -> Vc<Box<dyn Source>> {
139 Vc::upcast(
140 WebpackLoadersProcessedAsset {
141 transform: self,
142 source,
143 }
144 .cell(),
145 )
146 }
147}
148
149#[turbo_tasks::value]
150struct WebpackLoadersProcessedAsset {
151 transform: ResolvedVc<WebpackLoaders>,
152 source: ResolvedVc<Box<dyn Source>>,
153}
154
155#[turbo_tasks::value_impl]
156impl Source for WebpackLoadersProcessedAsset {
157 #[turbo_tasks::function]
158 async fn ident(&self) -> Result<Vc<AssetIdent>> {
159 Ok(
160 if let Some(rename_as) = self.transform.await?.rename_as.as_deref() {
161 self.source.ident().rename_as(rename_as.into())
162 } else {
163 self.source.ident()
164 },
165 )
166 }
167}
168
169#[turbo_tasks::value_impl]
170impl Asset for WebpackLoadersProcessedAsset {
171 #[turbo_tasks::function]
172 async fn content(self: Vc<Self>) -> Result<Vc<AssetContent>> {
173 Ok(*self.process().await?.content)
174 }
175}
176
177#[turbo_tasks::value_impl]
178impl GenerateSourceMap for WebpackLoadersProcessedAsset {
179 #[turbo_tasks::function]
180 async fn generate_source_map(self: Vc<Self>) -> Result<Vc<OptionStringifiedSourceMap>> {
181 Ok(*self.process().await?.source_map)
182 }
183}
184
185#[turbo_tasks::value]
186struct ProcessWebpackLoadersResult {
187 content: ResolvedVc<AssetContent>,
188 source_map: ResolvedVc<OptionStringifiedSourceMap>,
189 assets: Vec<ResolvedVc<VirtualSource>>,
190}
191
192#[turbo_tasks::function]
193async fn webpack_loaders_executor(
194 evaluate_context: Vc<Box<dyn AssetContext>>,
195) -> Result<Vc<ProcessResult>> {
196 Ok(evaluate_context.process(
197 Vc::upcast(FileSource::new(
198 embed_file_path(rcstr!("transforms/webpack-loaders.ts"))
199 .owned()
200 .await?,
201 )),
202 ReferenceType::Internal(InnerAssets::empty().to_resolved().await?),
203 ))
204}
205
206#[turbo_tasks::value_impl]
207impl WebpackLoadersProcessedAsset {
208 #[turbo_tasks::function]
209 async fn process(self: Vc<Self>) -> Result<Vc<ProcessWebpackLoadersResult>> {
210 let this = self.await?;
211 let transform = this.transform.await?;
212
213 let ExecutionContext {
214 project_path,
215 chunking_context,
216 env,
217 } = &*transform.execution_context.await?;
218 let source_content = this.source.content();
219 let AssetContent::File(file) = *source_content.await? else {
220 bail!("Webpack Loaders transform only support transforming files");
221 };
222 let FileContent::Content(file_content) = &*file.await? else {
223 return Ok(ProcessWebpackLoadersResult {
224 content: AssetContent::File(FileContent::NotFound.resolved_cell()).resolved_cell(),
225 assets: Vec::new(),
226 source_map: ResolvedVc::cell(None),
227 }
228 .cell());
229 };
230
231 let content: JsonValue = match file_content.content().to_str() {
234 Ok(utf8_str) => utf8_str.to_string().into(),
235 Err(_) => JsonValue::Object(JsonMap::from_iter(std::iter::once((
236 "binary".to_string(),
237 JsonValue::from(
238 base64::engine::general_purpose::STANDARD
239 .encode(file_content.content().to_bytes()),
240 ),
241 )))),
242 };
243 let evaluate_context = transform.evaluate_context;
244
245 let webpack_loaders_executor = webpack_loaders_executor(*evaluate_context)
246 .module()
247 .to_resolved()
248 .await?;
249
250 let resource_fs_path = this.source.ident().path().owned().await?;
251 let resource_fs_path_ref = resource_fs_path.clone();
252 let Some(resource_path) = project_path.get_relative_path_to(&resource_fs_path_ref) else {
253 bail!(format!(
254 "Resource path \"{}\" need to be on project filesystem \"{}\"",
255 resource_fs_path_ref, project_path
256 ));
257 };
258 let loaders = transform.loaders.await?;
259 let config_value = evaluate_webpack_loader(WebpackLoaderContext {
260 module_asset: webpack_loaders_executor,
261 cwd: project_path.clone(),
262 env: *env,
263 context_source_for_issue: this.source,
264 asset_context: evaluate_context,
265 chunking_context: *chunking_context,
266 resolve_options_context: Some(transform.resolve_options_context),
267 args: vec![
268 ResolvedVc::cell(content),
269 ResolvedVc::cell(resource_path.to_string().into()),
271 ResolvedVc::cell(this.source.ident().await?.query.to_string().into()),
272 ResolvedVc::cell(json!(*loaders)),
273 ResolvedVc::cell(transform.source_maps.into()),
274 ],
275 additional_invalidation: Completion::immutable().to_resolved().await?,
276 })
277 .await?;
278
279 let SingleValue::Single(val) = config_value.try_into_single().await? else {
280 return Ok(ProcessWebpackLoadersResult {
282 content: AssetContent::File(FileContent::NotFound.resolved_cell()).resolved_cell(),
283 assets: Vec::new(),
284 source_map: ResolvedVc::cell(None),
285 }
286 .cell());
287 };
288 let processed: WebpackLoadersProcessingResult = parse_json_with_source_context(
289 val.to_str()?,
290 )
291 .context("Unable to deserializate response from webpack loaders transform operation")?;
292
293 let source_map = if !transform.source_maps {
295 None
296 } else {
297 processed
298 .map
299 .map(|source_map| Rope::from(source_map.into_owned()))
300 };
301 let source_map = resolve_source_map_sources(source_map.as_ref(), resource_fs_path).await?;
302
303 let file = match processed.source {
304 Either::Left(str) => File::from(str),
305 Either::Right(bytes) => File::from(bytes.binary),
306 };
307 let assets = emitted_assets_to_virtual_sources(processed.assets).await?;
308
309 let content =
310 AssetContent::File(FileContent::Content(file).resolved_cell()).resolved_cell();
311 Ok(ProcessWebpackLoadersResult {
312 content,
313 assets,
314 source_map: ResolvedVc::cell(source_map),
315 }
316 .cell())
317 }
318}
319
320#[turbo_tasks::function]
321pub(crate) async fn evaluate_webpack_loader(
322 webpack_loader_context: WebpackLoaderContext,
323) -> Result<Vc<JavaScriptEvaluation>> {
324 custom_evaluate(webpack_loader_context).await
325}
326
327#[turbo_tasks::function]
328async fn compute_webpack_loader_evaluation(
329 webpack_loader_context: WebpackLoaderContext,
330 sender: Vc<JavaScriptStreamSender>,
331) -> Result<Vc<()>> {
332 compute(webpack_loader_context, sender).await
333}
334
335#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
336#[serde(rename_all = "camelCase")]
337enum LogType {
338 Error,
339 Warn,
340 Info,
341 Log,
342 Debug,
343 Trace,
344 Group,
345 GroupCollapsed,
346 GroupEnd,
347 Profile,
348 ProfileEnd,
349 Time,
350 Clear,
351 Status,
352}
353
354#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
355#[serde(rename_all = "camelCase")]
356pub struct LogInfo {
357 time: u64,
358 log_type: LogType,
359 args: Vec<JsonValue>,
360 trace: Option<Vec<StackFrame<'static>>>,
361}
362
363#[derive(Deserialize, Debug)]
364#[serde(tag = "type", rename_all = "camelCase")]
365pub enum InfoMessage {
366 #[serde(rename_all = "camelCase")]
370 Dependencies {
371 #[serde(default)]
372 env_variables: Vec<RcStr>,
373 #[serde(default)]
374 file_paths: Vec<RcStr>,
375 #[serde(default)]
376 directories: Vec<(RcStr, RcStr)>,
377 #[serde(default)]
378 build_file_paths: Vec<RcStr>,
379 },
380 EmittedError {
381 severity: IssueSeverity,
382 error: StructuredError,
383 },
384 Log {
385 logs: Vec<LogInfo>,
386 },
387}
388
389#[derive(Debug, Clone, TaskInput, Hash, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs)]
390#[serde(rename_all = "camelCase")]
391pub struct WebpackResolveOptions {
392 alias_fields: Option<Vec<RcStr>>,
393 condition_names: Option<Vec<RcStr>>,
394 no_package_json: bool,
395 extensions: Option<Vec<RcStr>>,
396 main_fields: Option<Vec<RcStr>>,
397 no_exports_field: bool,
398 main_files: Option<Vec<RcStr>>,
399 no_modules: bool,
400 prefer_relative: bool,
401}
402
403#[derive(Deserialize, Debug)]
404#[serde(tag = "type", rename_all = "camelCase")]
405pub enum RequestMessage {
406 #[serde(rename_all = "camelCase")]
407 Resolve {
408 options: WebpackResolveOptions,
409 lookup_path: RcStr,
410 request: RcStr,
411 },
412}
413
414#[derive(Serialize, Debug)]
415#[serde(untagged)]
416pub enum ResponseMessage {
417 Resolve { path: RcStr },
418}
419
420#[derive(Clone, PartialEq, Eq, Hash, TaskInput, Serialize, Deserialize, Debug, TraceRawVcs)]
421pub struct WebpackLoaderContext {
422 pub module_asset: ResolvedVc<Box<dyn Module>>,
423 pub cwd: FileSystemPath,
424 pub env: ResolvedVc<Box<dyn ProcessEnv>>,
425 pub context_source_for_issue: ResolvedVc<Box<dyn Source>>,
426 pub asset_context: ResolvedVc<Box<dyn AssetContext>>,
427 pub chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
428 pub resolve_options_context: Option<ResolvedVc<ResolveOptionsContext>>,
429 pub args: Vec<ResolvedVc<JsonValue>>,
430 pub additional_invalidation: ResolvedVc<Completion>,
431}
432
433impl EvaluateContext for WebpackLoaderContext {
434 type InfoMessage = InfoMessage;
435 type RequestMessage = RequestMessage;
436 type ResponseMessage = ResponseMessage;
437 type State = Vec<LogInfo>;
438
439 async fn compute(self, sender: Vc<JavaScriptStreamSender>) -> Result<()> {
440 compute_webpack_loader_evaluation(self, sender)
441 .as_side_effect()
442 .await
443 }
444
445 fn pool(&self) -> OperationVc<crate::pool::NodeJsPool> {
446 get_evaluate_pool(
447 self.module_asset,
448 self.cwd.clone(),
449 self.env,
450 self.asset_context,
451 self.chunking_context,
452 None,
453 self.additional_invalidation,
454 should_debug("webpack_loader"),
455 EnvVarTracking::Untracked,
459 )
460 }
461
462 fn args(&self) -> &[ResolvedVc<serde_json::Value>] {
463 &self.args
464 }
465
466 fn cwd(&self) -> Vc<turbo_tasks_fs::FileSystemPath> {
467 self.cwd.clone().cell()
468 }
469
470 fn keep_alive(&self) -> bool {
471 true
472 }
473
474 async fn emit_error(&self, error: StructuredError, pool: &NodeJsPool) -> Result<()> {
475 EvaluationIssue {
476 error,
477 source: IssueSource::from_source_only(self.context_source_for_issue),
478 assets_for_source_mapping: pool.assets_for_source_mapping,
479 assets_root: pool.assets_root.clone(),
480 root_path: self.chunking_context.root_path().owned().await?,
481 }
482 .resolved_cell()
483 .emit();
484 Ok(())
485 }
486
487 async fn info(
488 &self,
489 state: &mut Self::State,
490 data: Self::InfoMessage,
491 pool: &NodeJsPool,
492 ) -> Result<()> {
493 match data {
494 InfoMessage::Dependencies {
495 env_variables,
496 file_paths,
497 directories,
498 build_file_paths,
499 } => {
500 let env_subscriptions = env_variables
508 .iter()
509 .map(|e| self.env.read(e.clone()))
510 .try_join();
511 let file_subscriptions = file_paths
512 .iter()
513 .map(|p| async move { self.cwd.join(p)?.read().await })
514 .try_join();
515 let directory_subscriptions = directories
516 .iter()
517 .map(|(dir, glob)| async move {
518 self.cwd
519 .join(dir)?
520 .track_glob(Glob::new(glob.clone(), GlobOptions::default()), false)
521 .await
522 })
523 .try_join();
524 let build_paths = build_file_paths
525 .iter()
526 .map(|path| async move { self.cwd.join(path) })
527 .try_join();
528 let (resolved_build_paths, ..) = try_join!(
529 build_paths,
530 env_subscriptions,
531 file_subscriptions,
532 directory_subscriptions
533 )?;
534
535 for build_path in resolved_build_paths {
536 BuildDependencyIssue {
537 source: IssueSource::from_source_only(self.context_source_for_issue),
538 path: build_path,
539 }
540 .resolved_cell()
541 .emit();
542 }
543 }
544 InfoMessage::EmittedError { error, severity } => {
545 EvaluateEmittedErrorIssue {
546 source: IssueSource::from_source_only(self.context_source_for_issue),
547 error,
548 severity,
549 assets_for_source_mapping: pool.assets_for_source_mapping,
550 assets_root: pool.assets_root.clone(),
551 project_dir: self.chunking_context.root_path().owned().await?,
552 }
553 .resolved_cell()
554 .emit();
555 }
556 InfoMessage::Log { logs } => {
557 state.extend(logs);
558 }
559 }
560 Ok(())
561 }
562
563 async fn request(
564 &self,
565 _state: &mut Self::State,
566 data: Self::RequestMessage,
567 _pool: &NodeJsPool,
568 ) -> Result<Self::ResponseMessage> {
569 match data {
570 RequestMessage::Resolve {
571 options: webpack_options,
572 lookup_path,
573 request,
574 } => {
575 let Some(resolve_options_context) = self.resolve_options_context else {
576 bail!("Resolve options are not available in this context");
577 };
578 let lookup_path = self.cwd.join(&lookup_path)?;
579 let request = Request::parse(Pattern::Constant(request));
580 let options = resolve_options(lookup_path.clone(), *resolve_options_context);
581
582 let options = apply_webpack_resolve_options(options, webpack_options);
583
584 let resolved = resolve(
585 lookup_path.clone(),
586 ReferenceType::Undefined,
587 request,
588 options,
589 );
590
591 if let Some(source) = *resolved.first_source().await? {
592 if let Some(path) = self
593 .cwd
594 .get_relative_path_to(&*source.ident().path().await?)
595 {
596 Ok(ResponseMessage::Resolve { path })
597 } else {
598 bail!(
599 "Resolving {} in {} ends up on a different filesystem",
600 request.to_string().await?,
601 lookup_path.value_to_string().await?
602 );
603 }
604 } else {
605 bail!(
606 "Unable to resolve {} in {}",
607 request.to_string().await?,
608 lookup_path.value_to_string().await?
609 );
610 }
611 }
612 }
613 }
614
615 async fn finish(&self, state: Self::State, pool: &NodeJsPool) -> Result<()> {
616 let has_errors = state.iter().any(|log| log.log_type == LogType::Error);
617 let has_warnings = state.iter().any(|log| log.log_type == LogType::Warn);
618 if has_errors || has_warnings {
619 let logs = state
620 .into_iter()
621 .filter(|log| {
622 matches!(
623 log.log_type,
624 LogType::Error
625 | LogType::Warn
626 | LogType::Info
627 | LogType::Log
628 | LogType::Clear,
629 )
630 })
631 .collect();
632
633 EvaluateErrorLoggingIssue {
634 source: IssueSource::from_source_only(self.context_source_for_issue),
635 logging: logs,
636 severity: if has_errors {
637 IssueSeverity::Error
638 } else {
639 IssueSeverity::Warning
640 },
641 assets_for_source_mapping: pool.assets_for_source_mapping,
642 assets_root: pool.assets_root.clone(),
643 project_dir: self.chunking_context.root_path().owned().await?,
644 }
645 .resolved_cell()
646 .emit();
647 }
648 Ok(())
649 }
650}
651
652#[turbo_tasks::function]
653async fn apply_webpack_resolve_options(
654 resolve_options: Vc<ResolveOptions>,
655 webpack_resolve_options: WebpackResolveOptions,
656) -> Result<Vc<ResolveOptions>> {
657 let mut resolve_options = resolve_options.owned().await?;
658 if let Some(alias_fields) = webpack_resolve_options.alias_fields {
659 let mut old = resolve_options
660 .in_package
661 .extract_if(0.., |field| {
662 matches!(field, ResolveInPackage::AliasField(..))
663 })
664 .collect::<Vec<_>>();
665 for field in alias_fields {
666 if &*field == "..." {
667 resolve_options.in_package.extend(take(&mut old));
668 } else {
669 resolve_options
670 .in_package
671 .push(ResolveInPackage::AliasField(field));
672 }
673 }
674 }
675 if let Some(condition_names) = webpack_resolve_options.condition_names {
676 for conditions in get_condition_maps(&mut resolve_options) {
677 let mut old = take(conditions);
678 for name in &condition_names {
679 if name == "..." {
680 conditions.extend(take(&mut old));
681 } else {
682 conditions.insert(name.clone(), ConditionValue::Set);
683 }
684 }
685 }
686 }
687 if webpack_resolve_options.no_package_json {
688 resolve_options.into_package.retain(|item| {
689 !matches!(
690 item,
691 ResolveIntoPackage::ExportsField { .. } | ResolveIntoPackage::MainField { .. }
692 )
693 });
694 }
695 if let Some(mut extensions) = webpack_resolve_options.extensions {
696 if let Some(pos) = extensions.iter().position(|ext| ext == "...") {
697 extensions.splice(pos..=pos, take(&mut resolve_options.extensions));
698 }
699 resolve_options.extensions = extensions;
700 }
701 if let Some(main_fields) = webpack_resolve_options.main_fields {
702 let mut old = resolve_options
703 .into_package
704 .extract_if(0.., |field| {
705 matches!(field, ResolveIntoPackage::MainField { .. })
706 })
707 .collect::<Vec<_>>();
708 for field in main_fields {
709 if &*field == "..." {
710 resolve_options.into_package.extend(take(&mut old));
711 } else {
712 resolve_options
713 .into_package
714 .push(ResolveIntoPackage::MainField { field });
715 }
716 }
717 }
718 if webpack_resolve_options.no_exports_field {
719 resolve_options
720 .into_package
721 .retain(|field| !matches!(field, ResolveIntoPackage::ExportsField { .. }));
722 }
723 if let Some(main_files) = webpack_resolve_options.main_files {
724 resolve_options.default_files = main_files;
725 }
726 if webpack_resolve_options.no_modules {
727 resolve_options.modules.clear();
728 }
729 if webpack_resolve_options.prefer_relative {
730 resolve_options.prefer_relative = true;
731 }
732 Ok(resolve_options.cell())
733}
734
735#[turbo_tasks::value(shared)]
737pub struct BuildDependencyIssue {
738 pub path: FileSystemPath,
739 pub source: IssueSource,
740}
741
742#[turbo_tasks::value_impl]
743impl Issue for BuildDependencyIssue {
744 fn severity(&self) -> IssueSeverity {
745 IssueSeverity::Warning
746 }
747
748 #[turbo_tasks::function]
749 fn title(&self) -> Vc<StyledString> {
750 StyledString::Text(rcstr!("Build dependencies are not yet supported")).cell()
751 }
752
753 #[turbo_tasks::function]
754 fn stage(&self) -> Vc<IssueStage> {
755 IssueStage::Unsupported.cell()
756 }
757
758 #[turbo_tasks::function]
759 fn file_path(&self) -> Vc<FileSystemPath> {
760 self.source.file_path()
761 }
762
763 #[turbo_tasks::function]
764 async fn description(&self) -> Result<Vc<OptionStyledString>> {
765 Ok(Vc::cell(Some(
766 StyledString::Line(vec![
767 StyledString::Text(rcstr!("The file at ")),
768 StyledString::Code(self.path.to_string().into()),
769 StyledString::Text(
770 " is a build dependency, which is not yet implemented.
771 Changing this file or any dependency will not be recognized and might require restarting the \
772 server"
773 .into(),
774 ),
775 ])
776 .resolved_cell(),
777 )))
778 }
779
780 #[turbo_tasks::function]
781 fn source(&self) -> Vc<OptionIssueSource> {
782 Vc::cell(Some(self.source))
783 }
784}
785
786#[turbo_tasks::value(shared)]
787pub struct EvaluateEmittedErrorIssue {
788 pub source: IssueSource,
789 pub severity: IssueSeverity,
790 pub error: StructuredError,
791 pub assets_for_source_mapping: ResolvedVc<AssetsForSourceMapping>,
792 pub assets_root: FileSystemPath,
793 pub project_dir: FileSystemPath,
794}
795
796#[turbo_tasks::value_impl]
797impl Issue for EvaluateEmittedErrorIssue {
798 #[turbo_tasks::function]
799 fn file_path(&self) -> Vc<FileSystemPath> {
800 self.source.file_path()
801 }
802
803 #[turbo_tasks::function]
804 fn stage(&self) -> Vc<IssueStage> {
805 IssueStage::Transform.cell()
806 }
807
808 fn severity(&self) -> IssueSeverity {
809 self.severity
810 }
811
812 #[turbo_tasks::function]
813 fn title(&self) -> Vc<StyledString> {
814 StyledString::Text(rcstr!("Issue while running loader")).cell()
815 }
816
817 #[turbo_tasks::function]
818 async fn description(&self) -> Result<Vc<OptionStyledString>> {
819 Ok(Vc::cell(Some(
820 StyledString::Text(
821 self.error
822 .print(
823 *self.assets_for_source_mapping,
824 self.assets_root.clone(),
825 self.project_dir.clone(),
826 FormattingMode::Plain,
827 )
828 .await?
829 .into(),
830 )
831 .resolved_cell(),
832 )))
833 }
834
835 #[turbo_tasks::function]
836 fn source(&self) -> Vc<OptionIssueSource> {
837 Vc::cell(Some(self.source))
838 }
839}
840
841#[turbo_tasks::value(shared)]
842pub struct EvaluateErrorLoggingIssue {
843 pub source: IssueSource,
844 pub severity: IssueSeverity,
845 #[turbo_tasks(trace_ignore)]
846 pub logging: Vec<LogInfo>,
847 pub assets_for_source_mapping: ResolvedVc<AssetsForSourceMapping>,
848 pub assets_root: FileSystemPath,
849 pub project_dir: FileSystemPath,
850}
851
852#[turbo_tasks::value_impl]
853impl Issue for EvaluateErrorLoggingIssue {
854 #[turbo_tasks::function]
855 fn file_path(&self) -> Vc<FileSystemPath> {
856 self.source.file_path()
857 }
858
859 #[turbo_tasks::function]
860 fn stage(&self) -> Vc<IssueStage> {
861 IssueStage::Transform.cell()
862 }
863
864 fn severity(&self) -> IssueSeverity {
865 self.severity
866 }
867
868 #[turbo_tasks::function]
869 fn title(&self) -> Vc<StyledString> {
870 StyledString::Text(rcstr!("Error logging while running loader")).cell()
871 }
872
873 #[turbo_tasks::function]
874 fn description(&self) -> Vc<OptionStyledString> {
875 fn fmt_args(prefix: String, args: &[JsonValue]) -> String {
876 let mut iter = args.iter();
877 let Some(first) = iter.next() else {
878 return "".to_string();
879 };
880 let mut result = prefix;
881 if let JsonValue::String(s) = first {
882 result.push_str(s);
883 } else {
884 result.push_str(&first.to_string());
885 }
886 for arg in iter {
887 result.push(' ');
888 result.push_str(&arg.to_string());
889 }
890 result
891 }
892 let lines = self
893 .logging
894 .iter()
895 .map(|log| match log.log_type {
896 LogType::Error => {
897 StyledString::Strong(fmt_args("<e> ".to_string(), &log.args).into())
898 }
899 LogType::Warn => StyledString::Text(fmt_args("<w> ".to_string(), &log.args).into()),
900 LogType::Info => StyledString::Text(fmt_args("<i> ".to_string(), &log.args).into()),
901 LogType::Log => StyledString::Text(fmt_args("<l> ".to_string(), &log.args).into()),
902 LogType::Clear => StyledString::Strong(rcstr!("---")),
903 _ => {
904 unimplemented!("{:?} is not implemented", log.log_type)
905 }
906 })
907 .collect::<Vec<_>>();
908 Vc::cell(Some(StyledString::Stack(lines).resolved_cell()))
909 }
910
911 #[turbo_tasks::function]
912 fn source(&self) -> Vc<OptionIssueSource> {
913 Vc::cell(Some(self.source))
914 }
915}