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