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