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