diff --git a/crates/next-core/src/app_source.rs b/crates/next-core/src/app_source.rs index 6c9f1e94101cc4..81fe7793e75019 100644 --- a/crates/next-core/src/app_source.rs +++ b/crates/next-core/src/app_source.rs @@ -1,6 +1,7 @@ use std::{ collections::{BTreeMap, HashMap}, io::Write, + iter::once, }; use anyhow::{anyhow, Result}; @@ -25,7 +26,8 @@ use turbopack_core::{ use turbopack_dev_server::{ html::DevHtmlAssetVc, source::{ - combined::CombinedContentSource, ContentSourceData, ContentSourceVc, NoContentSourceVc, + combined::CombinedContentSource, issue_context::IssueContextSourceVc, ContentSourceData, + ContentSourceVc, NoContentSourceVc, }, }; use turbopack_ecmascript::{ @@ -478,21 +480,30 @@ async fn create_app_source_for_directory( return Ok(NoContentSourceVc::new().into()); } } - for child in children.iter() { - sources.push(create_app_source_for_directory( - *child, - context_ssr, - context, - project_path, - env, - server_root, - runtime_entries, - fallback_page, - intermediate_output_path_root, - )); - } - Ok(CombinedContentSource { sources }.cell().into()) + let source = CombinedContentSource { sources }.cell().into(); + let source = + IssueContextSourceVc::new_context(directory, "Next.js app directory", source).into(); + + Ok(CombinedContentSource { + sources: once(source) + .chain(children.iter().map(|child| { + create_app_source_for_directory( + *child, + context_ssr, + context, + project_path, + env, + server_root, + runtime_entries, + fallback_page, + intermediate_output_path_root, + ) + })) + .collect(), + } + .cell() + .into()) } /// The renderer for pages in app directory diff --git a/crates/next-core/src/next_config.rs b/crates/next-core/src/next_config.rs index 2afbaefc07c1aa..607299b553d9f7 100644 --- a/crates/next-core/src/next_config.rs +++ b/crates/next-core/src/next_config.rs @@ -15,6 +15,7 @@ use turbopack_core::{ changed::any_content_changed, context::AssetContext, ident::AssetIdentVc, + issue::IssueVc, reference_type::{EntryReferenceSubType, ReferenceType}, resolve::{ find_context_file, @@ -539,6 +540,25 @@ fn next_configs() -> StringsVc { #[turbo_tasks::function] pub async fn load_next_config(execution_context: ExecutionContextVc) -> Result { + let ExecutionContext { project_path, .. } = *execution_context.await?; + let find_config_result = find_context_file(project_path, next_configs()); + let config_file = match &*find_config_result.await? { + FindContextFileResult::Found(config_path, _) => Some(*config_path), + FindContextFileResult::NotFound(_) => None, + }; + IssueVc::attach_context_or_description( + config_file, + "Loading Next.js config", + load_next_config_internal(execution_context, config_file), + ) + .await +} + +#[turbo_tasks::function] +pub async fn load_next_config_internal( + execution_context: ExecutionContextVc, + config_file: Option, +) -> Result { let ExecutionContext { project_path, intermediate_output_path, @@ -552,11 +572,7 @@ pub async fn load_next_config(execution_context: ExecutionContextVc) -> Result Some(SourceAssetVc::new(*config_path)), - FindContextFileResult::NotFound(_) => None, - }; + let config_asset = config_file.map(SourceAssetVc::new); let config_changed = config_asset.map_or_else(CompletionVc::immutable, |config_asset| { // This invalidates the execution when anything referenced by the config file diff --git a/crates/next-core/src/page_source.rs b/crates/next-core/src/page_source.rs index 1758ebb8ed6628..f04887af3e007d 100644 --- a/crates/next-core/src/page_source.rs +++ b/crates/next-core/src/page_source.rs @@ -22,6 +22,7 @@ use turbopack_dev_server::{ source::{ asset_graph::AssetGraphContentSourceVc, combined::{CombinedContentSource, CombinedContentSourceVc}, + issue_context::IssueContextSourceVc, specificity::SpecificityVc, ContentSourceData, ContentSourceVc, NoContentSourceVc, }, @@ -276,17 +277,33 @@ pub async fn create_page_source( let fallback_source = AssetGraphContentSourceVc::new_eager(server_root, fallback_page.as_asset()); - Ok(CombinedContentSource { + let source = CombinedContentSource { sources: vec![ // Match _next/404 first to ensure rewrites work properly. - force_not_found_source, + IssueContextSourceVc::new_context( + pages_dir, + "Next.js pages directory not found", + force_not_found_source, + ) + .into(), page_source, - fallback_source.into(), - fallback_not_found_source, + IssueContextSourceVc::new_context( + pages_dir, + "Next.js pages directory fallback", + fallback_source.into(), + ) + .into(), + IssueContextSourceVc::new_context( + pages_dir, + "Next.js pages directory not found fallback", + fallback_not_found_source, + ) + .into(), ], } .cell() - .into()) + .into(); + Ok(source) } /// Handles a single page file in the pages directory @@ -554,13 +571,15 @@ async fn create_page_source_for_directory( let mut sources = vec![]; for item in items.iter() { - match *item.await? { + let source = match *item.await? { PagesStructureItem::Page { page, specificity, url, - } => { - sources.push(create_page_source_for_file( + } => IssueContextSourceVc::new_context( + page, + "Next.js pages directory", + create_page_source_for_file( project_path, env, server_context, @@ -576,14 +595,17 @@ async fn create_page_source_for_directory( false, rebase(page, project_path, output_root), output_root, - )); - } + ), + ) + .into(), PagesStructureItem::Api { api, specificity, url, - } => { - sources.push(create_page_source_for_file( + } => IssueContextSourceVc::new_context( + api, + "Next.js pages api directory", + create_page_source_for_file( project_path, env, server_context, @@ -599,9 +621,11 @@ async fn create_page_source_for_directory( true, rebase(api, project_path, output_root), output_root, - )); - } - } + ), + ) + .into(), + }; + sources.push(source); } for child in children.iter() { diff --git a/crates/next-core/src/router.rs b/crates/next-core/src/router.rs index d5b50dc01f2eca..fa115b1bd18ee8 100644 --- a/crates/next-core/src/router.rs +++ b/crates/next-core/src/router.rs @@ -17,6 +17,7 @@ use turbopack_core::{ context::{AssetContext, AssetContextVc}, environment::{EnvironmentIntention::Middleware, ServerAddrVc}, ident::AssetIdentVc, + issue::IssueVc, reference_type::{EcmaScriptModulesReferenceSubType, ReferenceType}, resolve::{find_context_file, FindContextFileResult}, source_asset::SourceAssetVc, @@ -306,6 +307,32 @@ pub async fn route( next_config: NextConfigVc, server_addr: ServerAddrVc, routes_changed: CompletionVc, +) -> Result { + let RouterRequest { + ref method, + ref pathname, + .. + } = *request.await?; + IssueVc::attach_description( + format!("Next.js Routing for {} {}", method, pathname), + route_internal( + execution_context, + request, + next_config, + server_addr, + routes_changed, + ), + ) + .await +} + +#[turbo_tasks::function] +async fn route_internal( + execution_context: ExecutionContextVc, + request: RouterRequestVc, + next_config: NextConfigVc, + server_addr: ServerAddrVc, + routes_changed: CompletionVc, ) -> Result { let ExecutionContext { project_path, diff --git a/crates/next-dev-tests/tests/integration.rs b/crates/next-dev-tests/tests/integration.rs index 9eed0bcd845bd0..afb784d7ab5c67 100644 --- a/crates/next-dev-tests/tests/integration.rs +++ b/crates/next-dev-tests/tests/integration.rs @@ -494,8 +494,8 @@ impl IssueReporter for TestIssueReporter { _source: TransientValue, ) -> Result { let issue_tx = self.issue_tx.get_untracked().clone(); - for issue in captured_issues.iter() { - let plain = issue.into_plain(); + for (issue, path) in captured_issues.iter_with_shortest_path() { + let plain = issue.into_plain(path); issue_tx.send((plain.await?, plain.dbg().await?)).await?; } diff --git a/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.existsSync(__q____q____q____star__0__star__) is very dynamic-5efef4.txt b/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.existsSync(__q____q____q____star__0__star__) is very dynamic-5efef4.txt index 8eea1e7d940591..52708253ed0785 100644 --- a/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.existsSync(__q____q____q____star__0__star__) is very dynamic-5efef4.txt +++ b/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.existsSync(__q____q____q____star__0__star__) is very dynamic-5efef4.txt @@ -22,4 +22,20 @@ PlainIssue { }, ), sub_issues: [], + processing_path: Some( + [ + PlainIssueProcessingPathItem { + context: Some( + "[project]/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/pages/index.jsx", + ), + description: "Next.js pages directory", + }, + PlainIssueProcessingPathItem { + context: Some( + "[project]/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/styles/globals.css", + ), + description: "PostCSS processing", + }, + ], + ), } \ No newline at end of file diff --git a/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.readFileSync(__q____q____q____star__0__star__, __quo__utf-8__quo__) is very dynamic-f84517.txt b/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.readFileSync(__q____q____q____star__0__star__, __quo__utf-8__quo__) is very dynamic-f84517.txt index 6dc6703ee92a33..5648e1f03d9d08 100644 --- a/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.readFileSync(__q____q____q____star__0__star__, __quo__utf-8__quo__) is very dynamic-f84517.txt +++ b/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1004 fs.readFileSync(__q____q____q____star__0__star__, __quo__utf-8__quo__) is very dynamic-f84517.txt @@ -22,4 +22,20 @@ PlainIssue { }, ), sub_issues: [], + processing_path: Some( + [ + PlainIssueProcessingPathItem { + context: Some( + "[project]/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/pages/index.jsx", + ), + description: "Next.js pages directory", + }, + PlainIssueProcessingPathItem { + context: Some( + "[project]/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/styles/globals.css", + ), + description: "PostCSS processing", + }, + ], + ), } \ No newline at end of file diff --git a/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1006 path.resolve(__q____q____q____star__0__star__) is very dynamic-dd45bc.txt b/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1006 path.resolve(__q____q____q____star__0__star__) is very dynamic-dd45bc.txt index d5b11a86837252..161e29a15fb28c 100644 --- a/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1006 path.resolve(__q____q____q____star__0__star__) is very dynamic-dd45bc.txt +++ b/crates/next-dev-tests/tests/integration/next/tailwind/basic/issues/lint TP1006 path.resolve(__q____q____q____star__0__star__) is very dynamic-dd45bc.txt @@ -22,4 +22,20 @@ PlainIssue { }, ), sub_issues: [], + processing_path: Some( + [ + PlainIssueProcessingPathItem { + context: Some( + "[project]/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/pages/index.jsx", + ), + description: "Next.js pages directory", + }, + PlainIssueProcessingPathItem { + context: Some( + "[project]/crates/next-dev-tests/tests/integration/next/tailwind/basic/input/styles/globals.css", + ), + description: "PostCSS processing", + }, + ], + ), } \ No newline at end of file diff --git a/crates/next-dev-tests/tests/integration/turbopack/basic/comptime/input/next.config.js b/crates/next-dev-tests/tests/integration/turbopack/basic/comptime/input/next.config.js index 70bc8091057ed5..343cc2aa42b49f 100644 --- a/crates/next-dev-tests/tests/integration/turbopack/basic/comptime/input/next.config.js +++ b/crates/next-dev-tests/tests/integration/turbopack/basic/comptime/input/next.config.js @@ -7,7 +7,6 @@ function f() { if (process.env.NODE_ENV !== "development") { throw new Error("NODE_ENV is not development"); } - console.log(process.env); } f(); diff --git a/crates/next-dev-tests/tests/integration/turbopack/basic/comptime/issues/Error evaluating Node.js code-f68ab4.txt b/crates/next-dev-tests/tests/integration/turbopack/basic/comptime/issues/Error evaluating Node.js code-f68ab4.txt index 5f09ddd6d51892..05d21e529ea880 100644 --- a/crates/next-dev-tests/tests/integration/turbopack/basic/comptime/issues/Error evaluating Node.js code-f68ab4.txt +++ b/crates/next-dev-tests/tests/integration/turbopack/basic/comptime/issues/Error evaluating Node.js code-f68ab4.txt @@ -8,4 +8,12 @@ PlainIssue { documentation_link: "", source: None, sub_issues: [], + processing_path: Some( + [ + PlainIssueProcessingPathItem { + context: None, + description: "Next.js Routing for GET /_chunks/index_e7cdb3.js", + }, + ], + ), } \ No newline at end of file diff --git a/crates/next-dev-tests/tests/integration/turbopack/basic/comptime/issues/Error resolving commonjs request-e56948.txt b/crates/next-dev-tests/tests/integration/turbopack/basic/comptime/issues/Error resolving commonjs request-e56948.txt index d16398e3da94bc..ce6c02a75e1b78 100644 --- a/crates/next-dev-tests/tests/integration/turbopack/basic/comptime/issues/Error resolving commonjs request-e56948.txt +++ b/crates/next-dev-tests/tests/integration/turbopack/basic/comptime/issues/Error resolving commonjs request-e56948.txt @@ -8,4 +8,7 @@ PlainIssue { documentation_link: "", source: None, sub_issues: [], + processing_path: Some( + [], + ), } \ No newline at end of file diff --git a/crates/next-dev/src/lib.rs b/crates/next-dev/src/lib.rs index 2ad23d9c35c9da..f03385255809ae 100644 --- a/crates/next-dev/src/lib.rs +++ b/crates/next-dev/src/lib.rs @@ -10,7 +10,7 @@ use std::{ future::{join, Future}, io::{stdout, Write}, net::{IpAddr, SocketAddr}, - path::MAIN_SEPARATOR, + path::{PathBuf, MAIN_SEPARATOR}, sync::Arc, time::{Duration, Instant}, }; @@ -206,6 +206,7 @@ impl NextDevServerBuilder { let browserslist_query = self.browserslist_query; let log_options = Arc::new(LogOptions { current_dir: current_dir().unwrap(), + project_dir: PathBuf::from(project_dir.clone()), show_all, log_detail, log_level: self.log_level, diff --git a/crates/node-file-trace/src/lib.rs b/crates/node-file-trace/src/lib.rs index 75291e0d6fd723..9e363d97a4c9a1 100644 --- a/crates/node-file-trace/src/lib.rs +++ b/crates/node-file-trace/src/lib.rs @@ -459,6 +459,7 @@ async fn run>( let resolve_options = TransientInstance::new(resolve_options.unwrap_or_default()); let log_options = TransientInstance::new(LogOptions { current_dir: dir.clone(), + project_dir: dir.clone(), show_all, log_detail, log_level: log_level.map_or_else(|| IssueSeverity::Error, |l| l.0), @@ -543,12 +544,8 @@ async fn main_operation( .await?; for module in modules.iter() { let set = all_assets(*module); - IssueVc::attach_context( - module.ident().path(), - "gathering list of assets".to_string(), - set, - ) - .await?; + IssueVc::attach_context(module.ident().path(), "gathering list of assets", set) + .await?; for asset in set.await?.iter() { let path = asset.ident().path().await?; result.insert(path.path.to_string()); @@ -629,7 +626,7 @@ async fn create_module_asset( let env = EnvironmentVc::new( Value::new(ExecutionEnvironment::NodeJsLambda( NodeJsEnvironment { - cwd: OptionStringVc::cell(process_cwd.clone()), + cwd: OptionStringVc::cell(process_cwd), ..Default::default() } .into(), diff --git a/crates/turbopack-cli-utils/src/issue.rs b/crates/turbopack-cli-utils/src/issue.rs index 9fed3d3a04369a..837fe42f8d55bf 100644 --- a/crates/turbopack-cli-utils/src/issue.rs +++ b/crates/turbopack-cli-utils/src/issue.rs @@ -1,8 +1,9 @@ use std::{ + borrow::Cow, cmp::min, collections::{hash_map::Entry, HashMap, HashSet}, fmt::Write as _, - path::PathBuf, + path::{Path, PathBuf}, str::FromStr, sync::{Arc, Mutex}, }; @@ -12,16 +13,14 @@ use crossterm::style::{StyledContent, Stylize}; use owo_colors::{OwoColorize as _, Style}; use turbo_tasks::{ primitives::BoolVc, RawVc, ReadRef, TransientInstance, TransientValue, TryJoinIterExt, - ValueToString, }; use turbo_tasks_fs::{ - attach::AttachedFileSystemVc, source_context::{get_source_context, SourceContextLine}, - to_sys_path, FileLinesContent, FileSystemPathVc, + FileLinesContent, }; use turbopack_core::issue::{ - CapturedIssues, Issue, IssueProcessingPathItem, IssueReporter, IssueReporterVc, IssueSeverity, - OptionIssueProcessingPathItemsVc, PlainIssue, PlainIssueSource, + CapturedIssues, IssueReporter, IssueReporterVc, IssueSeverity, PlainIssue, + PlainIssueProcessingPathItem, PlainIssueProcessingPathItemReadRef, PlainIssueSource, }; #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -173,32 +172,32 @@ fn format_source_content(source: &PlainIssueSource, formatted_issue: &mut String } } -async fn format_optional_path( - path: &OptionIssueProcessingPathItemsVc, +fn format_optional_path( + path: &Option>, formatted_issue: &mut String, ) -> Result<()> { - if let Some(path) = &*path.await? { + if let Some(path) = path { let mut last_context = None; for item in path.iter().rev() { - let IssueProcessingPathItem { - context, - description, - } = &*item.await?; + let PlainIssueProcessingPathItem { + ref context, + ref description, + } = **item; if let Some(context) = context { - let context = context.resolve().await?; - if last_context == Some(context) { - writeln!(formatted_issue, " at {}", &*description.await?)?; + let option_context = Some(context.clone()); + if last_context == option_context { + writeln!(formatted_issue, " at {}", description)?; } else { writeln!( formatted_issue, " at {} ({})", - context.to_string().await?.bright_blue(), - &*description.await? + context.to_string().bright_blue(), + description )?; - last_context = Some(context); + last_context = option_context; } } else { - writeln!(formatted_issue, " at {}", &*description.await?)?; + writeln!(formatted_issue, " at {}", description)?; last_context = None; } } @@ -301,6 +300,7 @@ const ORDERED_GROUPS: &[IssueSeverity] = &[ #[derive(Debug, Clone)] pub struct LogOptions { pub current_dir: PathBuf, + pub project_dir: PathBuf, pub show_all: bool, pub log_detail: bool, pub log_level: IssueSeverity, @@ -438,6 +438,7 @@ impl IssueReporter for ConsoleUi { let issues = &*issues; let LogOptions { ref current_dir, + ref project_dir, show_all, log_detail, log_level, @@ -448,18 +449,14 @@ impl IssueReporter for ConsoleUi { let issues = issues .iter_with_shortest_path() .map(|(issue, path)| async move { - // (issue.) - let plain_issue = issue.into_plain(); + let plain_issue = issue.into_plain(path); let id = plain_issue.internal_hash().await?; - Ok((plain_issue.await?, path, issue.context(), *id)) + Ok((plain_issue.await?, *id)) }) .try_join() .await?; - let issue_ids = issues - .iter() - .map(|(_, _, _, id)| *id) - .collect::>(); + let issue_ids = issues.iter().map(|(_, id)| *id).collect::>(); let mut new_ids = self .seen .lock() @@ -467,7 +464,7 @@ impl IssueReporter for ConsoleUi { .new_ids(source.into_value(), issue_ids); let mut has_fatal = false; - for (plain_issue, path, context, id) in issues { + for (plain_issue, id) in issues { if !new_ids.remove(&id) { continue; } @@ -477,9 +474,10 @@ impl IssueReporter for ConsoleUi { has_fatal = true; } - let context_path = make_relative_to_cwd(context, current_dir).await?; + let context_path = make_relative_to_cwd(&plain_issue.context, project_dir, current_dir); let category = &plain_issue.category; let title = &plain_issue.title; + let processing_path = &*plain_issue.processing_path; let severity_map = grouped_issues .entry(severity) .or_insert_with(Default::default); @@ -487,7 +485,7 @@ impl IssueReporter for ConsoleUi { .entry(category.clone()) .or_insert_with(Default::default); let issues = category_map - .entry(context_path.clone()) + .entry(context_path.to_string()) .or_insert_with(Default::default); let mut styled_issue = if let Some(source) = &plain_issue.source { @@ -522,7 +520,7 @@ impl IssueReporter for ConsoleUi { if !documentation_link.is_empty() { writeln!(&mut styled_issue, "\ndocumentation: {documentation_link}")?; } - format_optional_path(&path, &mut styled_issue).await?; + format_optional_path(processing_path, &mut styled_issue)?; } issues.push(styled_issue); } @@ -616,21 +614,21 @@ impl IssueReporter for ConsoleUi { } } -async fn make_relative_to_cwd(path: FileSystemPathVc, cwd: &PathBuf) -> Result { - let path = if let Some(fs) = AttachedFileSystemVc::resolve_from(path.fs()).await? { - fs.get_inner_fs_path(path) - } else { - path - }; - if let Some(sys_path) = to_sys_path(path).await? { - let relative = sys_path +fn make_relative_to_cwd<'a>(path: &'a str, project_dir: &Path, cwd: &Path) -> Cow<'a, str> { + if let Some(path_in_project) = path.strip_prefix("[project]/") { + let abs_path = if std::path::MAIN_SEPARATOR != '/' { + project_dir.join(path_in_project.replace('/', std::path::MAIN_SEPARATOR_STR)) + } else { + project_dir.join(path_in_project) + }; + let relative = abs_path .strip_prefix(cwd) - .unwrap_or(&sys_path) + .unwrap_or(&abs_path) .to_string_lossy() .to_string(); - Ok(relative) + relative.into() } else { - Ok(path.to_string().await?.clone_value()) + path.into() } } diff --git a/crates/turbopack-cli-utils/src/lib.rs b/crates/turbopack-cli-utils/src/lib.rs index ddddfe14531dbd..b185c55cb1a7a9 100644 --- a/crates/turbopack-cli-utils/src/lib.rs +++ b/crates/turbopack-cli-utils/src/lib.rs @@ -1,6 +1,7 @@ #![feature(async_closure)] #![feature(min_specialization)] #![feature(round_char_boundary)] +#![feature(main_separator_str)] pub mod issue; diff --git a/crates/turbopack-core/Cargo.toml b/crates/turbopack-core/Cargo.toml index 4957fcfb01d527..d2e6bfa581e7a4 100644 --- a/crates/turbopack-core/Cargo.toml +++ b/crates/turbopack-core/Cargo.toml @@ -36,4 +36,5 @@ turbo-tasks-build = { path = "../turbo-tasks-build" } rstest = "0.12.0" [features] +default = ["issue_path"] issue_path = [] diff --git a/crates/turbopack-core/src/issue/mod.rs b/crates/turbopack-core/src/issue/mod.rs index 295e7d95b1da97..7d8379968a4c8b 100644 --- a/crates/turbopack-core/src/issue/mod.rs +++ b/crates/turbopack-core/src/issue/mod.rs @@ -7,13 +7,11 @@ pub mod unsupported_module; use std::{ cmp::Ordering, fmt::{Display, Formatter}, - future::IntoFuture, sync::Arc, }; use anyhow::Result; use auto_hash_map::AutoSet; -use futures::FutureExt; use turbo_tasks::{ emit, primitives::{BoolVc, StringReadRef, StringVc, U64Vc}, @@ -159,9 +157,51 @@ impl ValueToString for IssueProcessingPathItem { } } +#[turbo_tasks::value_impl] +impl IssueProcessingPathItemVc { + #[turbo_tasks::function] + pub async fn into_plain(self) -> Result { + let this = self.await?; + Ok(PlainIssueProcessingPathItem { + context: if let Some(context) = this.context { + Some(context.to_string().await?) + } else { + None + }, + description: this.description.await?, + } + .cell()) + } +} + #[turbo_tasks::value(transparent)] pub struct OptionIssueProcessingPathItems(Option>); +#[turbo_tasks::value_impl] +impl OptionIssueProcessingPathItemsVc { + #[turbo_tasks::function] + pub fn none() -> Self { + OptionIssueProcessingPathItemsVc::cell(None) + } + + #[turbo_tasks::function] + pub async fn into_plain(self) -> Result { + Ok(PlainIssueProcessingPathVc::cell( + if let Some(items) = &*self.await? { + Some( + items + .iter() + .map(|item| item.into_plain()) + .try_join() + .await?, + ) + } else { + None + }, + )) + } +} + #[turbo_tasks::value] struct RootIssueProcessingPath(IssueVc); @@ -198,7 +238,7 @@ impl IssueProcessingPath for ItemIssueProcessingPath { let mut shortest: Option<&Vec<_>> = None; for path in paths.iter().filter_map(|p| p.as_ref()) { if let Some(old) = shortest { - match old.cmp(path) { + match old.len().cmp(&path.len()) { Ordering::Greater => { shortest = Some(path); } @@ -246,9 +286,9 @@ impl IssueVc { impl IssueVc { #[allow(unused_variables, reason = "behind feature flag")] - pub async fn attach_context( - context: FileSystemPathVc, - description: String, + pub async fn attach_context_or_description( + context: Option, + description: impl Into, source: T, ) -> Result { #[cfg(feature = "issue_path")] @@ -258,8 +298,8 @@ impl IssueVc { emit( ItemIssueProcessingPathVc::cell(ItemIssueProcessingPath( Some(IssueProcessingPathItemVc::cell(IssueProcessingPathItem { - context: Some(context), - description: StringVc::cell(description), + context, + description: StringVc::cell(description.into()), })), children, )) @@ -270,28 +310,21 @@ impl IssueVc { Ok(source) } + #[allow(unused_variables, reason = "behind feature flag")] + pub async fn attach_context( + context: FileSystemPathVc, + description: impl Into, + source: T, + ) -> Result { + Self::attach_context_or_description(Some(context), description, source).await + } + #[allow(unused_variables, reason = "behind feature flag")] pub async fn attach_description( - description: String, + description: impl Into, source: T, ) -> Result { - #[cfg(feature = "issue_path")] - { - let children = source.take_collectibles().await?; - if !children.is_empty() { - emit( - ItemIssueProcessingPathVc::cell(ItemIssueProcessingPath( - Some(IssueProcessingPathItemVc::cell(IssueProcessingPathItem { - context: None, - description: StringVc::cell(description), - })), - children, - )) - .as_issue_processing_path(), - ); - } - } - Ok(source) + Self::attach_context_or_description(None, description, source).await } /// Returns all issues from `source` in a list with their associated @@ -373,7 +406,7 @@ impl CapturedIssues { #[cfg(feature = "issue_path")] let path = self.processing_path.shortest_path(*issue); #[cfg(not(feature = "issue_path"))] - let path = OptionIssueProcessingPathItemsVc::cell(None); + let path = OptionIssueProcessingPathItemsVc::none(); (*issue, path) }) } @@ -382,7 +415,16 @@ impl CapturedIssues { let mut list = self .issues .iter() - .map(|issue| issue.into_plain().into_future()) + .map(|&issue| async move { + #[cfg(feature = "issue_path")] + return issue + .into_plain(self.processing_path.shortest_path(issue)) + .await; + #[cfg(not(feature = "issue_path"))] + return issue + .into_plain(OptionIssueProcessingPathItemsVc::none()) + .await; + }) .try_join() .await?; list.sort_by(|a, b| ReadRef::ptr_cmp(a, b)); @@ -453,6 +495,7 @@ pub struct PlainIssue { pub source: Option, pub sub_issues: Vec, + pub processing_path: PlainIssueProcessingPathReadRef, } impl PlainIssue { @@ -497,7 +540,10 @@ impl PlainIssueVc { #[turbo_tasks::value_impl] impl IssueVc { #[turbo_tasks::function] - pub async fn into_plain(self) -> Result { + pub async fn into_plain( + self, + processing_path: OptionIssueProcessingPathItemsVc, + ) -> Result { Ok(PlainIssue { severity: *self.severity().await?, context: self.context().to_string().await?.clone_value(), @@ -506,24 +552,26 @@ impl IssueVc { description: self.description().await?.clone_value(), detail: self.detail().await?.clone_value(), documentation_link: self.documentation_link().await?.clone_value(), - source: self - .source() - .into_future() - .then(|s| async { - if let Some(s) = *s? { - return Ok(Some(s.into_plain().await?)); - } - - anyhow::Ok(None) - }) - .await?, + source: { + if let Some(s) = *self.source().await? { + Some(s.into_plain().await?) + } else { + None + } + }, sub_issues: self .sub_issues() .await? .iter() - .map(|i| async move { anyhow::Ok(i.into_plain().await?) }) + .map(|i| async move { + anyhow::Ok( + i.into_plain(OptionIssueProcessingPathItemsVc::none()) + .await?, + ) + }) .try_join() .await?, + processing_path: processing_path.into_plain().await?, } .cell()) } @@ -577,6 +625,17 @@ impl PlainAssetVc { } } +#[turbo_tasks::value(transparent, serialization = "none")] +#[derive(Clone, Debug)] +pub struct PlainIssueProcessingPath(Option>); + +#[turbo_tasks::value(serialization = "none")] +#[derive(Clone, Debug)] +pub struct PlainIssueProcessingPathItem { + pub context: Option, + pub description: StringReadRef, +} + #[turbo_tasks::value_trait] pub trait IssueReporter { fn report_issues( diff --git a/crates/turbopack-core/src/reference/mod.rs b/crates/turbopack-core/src/reference/mod.rs index bc44c45790ecf0..57699048802861 100644 --- a/crates/turbopack-core/src/reference/mod.rs +++ b/crates/turbopack-core/src/reference/mod.rs @@ -130,7 +130,7 @@ pub async fn all_assets(asset: AssetVc) -> Result { while let Some((parent, references)) = queue.pop_front() { IssueVc::attach_context( parent.ident().path(), - "expanding references of asset".to_string(), + "expanding references of asset", references, ) .await?; diff --git a/crates/turbopack-dev-server/src/source/issue_context.rs b/crates/turbopack-dev-server/src/source/issue_context.rs new file mode 100644 index 00000000000000..f073853c902122 --- /dev/null +++ b/crates/turbopack-dev-server/src/source/issue_context.rs @@ -0,0 +1,110 @@ +use anyhow::Result; +use turbo_tasks::{CollectiblesSource, Value}; +use turbo_tasks_fs::FileSystemPathVc; +use turbopack_core::issue::IssueVc; + +use super::{ + ContentSource, ContentSourceContentVc, ContentSourceData, ContentSourceDataVaryVc, + ContentSourceResult, ContentSourceResultVc, ContentSourceVc, ContentSourcesVc, + GetContentSourceContent, GetContentSourceContentVc, +}; + +#[turbo_tasks::value] +pub struct IssueContextSource { + context: Option, + description: String, + source: ContentSourceVc, +} + +impl IssueContextSource { + async fn attach(&self, source: T) -> Result { + IssueVc::attach_context_or_description(self.context, &self.description, source).await + } +} + +#[turbo_tasks::value_impl] +impl IssueContextSourceVc { + #[turbo_tasks::function] + pub fn new_context( + context: FileSystemPathVc, + description: &str, + source: ContentSourceVc, + ) -> Self { + IssueContextSource { + context: Some(context), + description: description.to_string(), + source, + } + .cell() + } + + #[turbo_tasks::function] + pub fn new_description(description: &str, source: ContentSourceVc) -> Self { + IssueContextSource { + context: None, + description: description.to_string(), + source, + } + .cell() + } +} + +#[turbo_tasks::value_impl] +impl ContentSource for IssueContextSource { + #[turbo_tasks::function] + async fn get( + self_vc: IssueContextSourceVc, + path: &str, + data: Value, + ) -> Result { + let this = self_vc.await?; + let result = this.source.get(path, data); + let result = this.attach(result).await?; + if let ContentSourceResult::Result { + get_content, + specificity, + } = *result.await? + { + Ok(ContentSourceResult::Result { + get_content: IssueContextGetContentSourceContent { + get_content, + source: self_vc, + } + .cell() + .into(), + specificity, + } + .cell()) + } else { + Ok(result) + } + } + + #[turbo_tasks::function] + fn get_children(&self) -> ContentSourcesVc { + ContentSourcesVc::cell(vec![self.source]) + } +} + +#[turbo_tasks::value] +struct IssueContextGetContentSourceContent { + get_content: GetContentSourceContentVc, + source: IssueContextSourceVc, +} + +#[turbo_tasks::value_impl] +impl GetContentSourceContent for IssueContextGetContentSourceContent { + #[turbo_tasks::function] + async fn vary(&self) -> Result { + let result = self.get_content.vary(); + let result = self.source.await?.attach(result).await?; + Ok(result) + } + + #[turbo_tasks::function] + async fn get(&self, data: Value) -> Result { + let result = self.get_content.get(data); + let result = self.source.await?.attach(result).await?; + Ok(result) + } +} diff --git a/crates/turbopack-dev-server/src/source/mod.rs b/crates/turbopack-dev-server/src/source/mod.rs index 51fb977af057dc..bebc901ad73de5 100644 --- a/crates/turbopack-dev-server/src/source/mod.rs +++ b/crates/turbopack-dev-server/src/source/mod.rs @@ -2,6 +2,7 @@ pub mod asset_graph; pub mod combined; pub mod conditional; pub mod headers; +pub mod issue_context; pub mod lazy_instantiated; pub mod query; pub mod request; diff --git a/crates/turbopack-dev-server/src/update/protocol.rs b/crates/turbopack-dev-server/src/update/protocol.rs index a19144262fdd9d..66af64f26207d3 100644 --- a/crates/turbopack-dev-server/src/update/protocol.rs +++ b/crates/turbopack-dev-server/src/update/protocol.rs @@ -145,11 +145,15 @@ impl<'a> From<&'a PlainIssue> for Issue<'a> { detail: &plain.detail, source, sub_issues: plain.sub_issues.iter().map(|p| p.deref().into()).collect(), + // TODO formatting the issue should be handled by the error overlay. + // The browser could handle error formatting in a better way than the text only + // formatting here formatted: format_issue( plain, None, &LogOptions { current_dir: PathBuf::new(), + project_dir: PathBuf::new(), show_all: true, log_detail: true, log_level: IssueSeverity::Info, diff --git a/crates/turbopack-node/src/render/rendered_source.rs b/crates/turbopack-node/src/render/rendered_source.rs index f59e194d1c6586..6c4b7720bcb430 100644 --- a/crates/turbopack-node/src/render/rendered_source.rs +++ b/crates/turbopack-node/src/render/rendered_source.rs @@ -8,6 +8,7 @@ use turbopack_core::{ introspect::{ asset::IntrospectableAssetVc, Introspectable, IntrospectableChildrenVc, IntrospectableVc, }, + issue::IssueVc, reference::AssetReference, resolve::PrimaryResolveResult, }; @@ -224,6 +225,12 @@ impl GetContentSourceContent for NodeRenderGetContentResult { } .cell(), ); + IssueVc::attach_context( + entry.module.ident().path(), + format!("server-side rendering /{}", source.pathname.await?), + result, + ) + .await?; Ok(match *result.await? { StaticResult::Content { content, diff --git a/crates/turbopack-node/src/transforms/postcss.rs b/crates/turbopack-node/src/transforms/postcss.rs index ee0d3e6cc3e8a2..66220753a38844 100644 --- a/crates/turbopack-node/src/transforms/postcss.rs +++ b/crates/turbopack-node/src/transforms/postcss.rs @@ -14,6 +14,7 @@ use turbopack_core::{ changed::any_content_changed, context::{AssetContext, AssetContextVc}, ident::AssetIdentVc, + issue::IssueVc, reference_type::{EntryReferenceSubType, ReferenceType}, resolve::{find_context_file, FindContextFileResult}, source_asset::SourceAssetVc, @@ -118,7 +119,15 @@ impl Asset for PostCssTransformedAsset { #[turbo_tasks::function] async fn content(self_vc: PostCssTransformedAssetVc) -> Result { - Ok(self_vc.process().await?.content) + let this = self_vc.await?; + Ok(IssueVc::attach_context( + this.source.ident().path(), + "PostCSS processing", + self_vc.process(), + ) + .await? + .await? + .content) } } diff --git a/crates/turbopack-tests/tests/snapshot.rs b/crates/turbopack-tests/tests/snapshot.rs index 4f0d87616428b9..c84fd5594c3e2e 100644 --- a/crates/turbopack-tests/tests/snapshot.rs +++ b/crates/turbopack-tests/tests/snapshot.rs @@ -104,11 +104,11 @@ async fn run(resource: &'static str) -> Result<()> { .await?; let plain_issues = captured_issues - .iter() - .map(|issue_vc| async move { + .iter_with_shortest_path() + .map(|(issue_vc, path)| async move { Ok(( - issue_vc.into_plain().await?, - issue_vc.into_plain().dbg().await?, + issue_vc.into_plain(path).await?, + issue_vc.into_plain(path).dbg().await?, )) }) .try_join() diff --git a/crates/turbopack-tests/tests/snapshot/export-alls/cjs-2/issues/unexpected export __star__-b4c42d.txt b/crates/turbopack-tests/tests/snapshot/export-alls/cjs-2/issues/unexpected export __star__-b4c42d.txt index 4249b8d2851356..4d82202bd482f7 100644 --- a/crates/turbopack-tests/tests/snapshot/export-alls/cjs-2/issues/unexpected export __star__-b4c42d.txt +++ b/crates/turbopack-tests/tests/snapshot/export-alls/cjs-2/issues/unexpected export __star__-b4c42d.txt @@ -8,4 +8,7 @@ PlainIssue { documentation_link: "", source: None, sub_issues: [], + processing_path: Some( + [], + ), } \ No newline at end of file diff --git a/crates/turbopack-tests/tests/snapshot/export-alls/cjs-script/issues/unexpected export __star__-30c177.txt b/crates/turbopack-tests/tests/snapshot/export-alls/cjs-script/issues/unexpected export __star__-30c177.txt index 499d725706229d..2e59a9c5169416 100644 --- a/crates/turbopack-tests/tests/snapshot/export-alls/cjs-script/issues/unexpected export __star__-30c177.txt +++ b/crates/turbopack-tests/tests/snapshot/export-alls/cjs-script/issues/unexpected export __star__-30c177.txt @@ -8,4 +8,7 @@ PlainIssue { documentation_link: "", source: None, sub_issues: [], + processing_path: Some( + [], + ), } \ No newline at end of file diff --git a/crates/turbopack-tests/tests/snapshot/imports/json/issues/Code generation for chunk item errored-798a9e.txt b/crates/turbopack-tests/tests/snapshot/imports/json/issues/Code generation for chunk item errored-798a9e.txt index a14c644d4e4769..36533d45dd1c10 100644 --- a/crates/turbopack-tests/tests/snapshot/imports/json/issues/Code generation for chunk item errored-798a9e.txt +++ b/crates/turbopack-tests/tests/snapshot/imports/json/issues/Code generation for chunk item errored-798a9e.txt @@ -8,4 +8,7 @@ PlainIssue { documentation_link: "", source: None, sub_issues: [], + processing_path: Some( + [], + ), } \ No newline at end of file diff --git a/crates/turbopack-tests/tests/snapshot/imports/resolve_error_cjs/issues/Error resolving commonjs request-69aa17.txt b/crates/turbopack-tests/tests/snapshot/imports/resolve_error_cjs/issues/Error resolving commonjs request-69aa17.txt index 867bc251184535..5046e04f280008 100644 --- a/crates/turbopack-tests/tests/snapshot/imports/resolve_error_cjs/issues/Error resolving commonjs request-69aa17.txt +++ b/crates/turbopack-tests/tests/snapshot/imports/resolve_error_cjs/issues/Error resolving commonjs request-69aa17.txt @@ -8,4 +8,7 @@ PlainIssue { documentation_link: "", source: None, sub_issues: [], + processing_path: Some( + [], + ), } \ No newline at end of file diff --git a/crates/turbopack-tests/tests/snapshot/imports/resolve_error_esm/issues/Error resolving EcmaScript Modules request-cd3585.txt b/crates/turbopack-tests/tests/snapshot/imports/resolve_error_esm/issues/Error resolving EcmaScript Modules request-cd3585.txt index f797c849313c73..4258654d1eba2f 100644 --- a/crates/turbopack-tests/tests/snapshot/imports/resolve_error_esm/issues/Error resolving EcmaScript Modules request-cd3585.txt +++ b/crates/turbopack-tests/tests/snapshot/imports/resolve_error_esm/issues/Error resolving EcmaScript Modules request-cd3585.txt @@ -8,4 +8,7 @@ PlainIssue { documentation_link: "", source: None, sub_issues: [], + processing_path: Some( + [], + ), } \ No newline at end of file