diff --git a/crates/next-api/src/project.rs b/crates/next-api/src/project.rs index 56cded9abe4b..7278976366e3 100644 --- a/crates/next-api/src/project.rs +++ b/crates/next-api/src/project.rs @@ -1186,7 +1186,7 @@ impl Project { pub async fn issue_filter(self: Vc) -> Result> { let ignore_rules = self.next_config().turbopack_ignore_issue_rules().await?; Ok(IssueFilter::warnings_and_foreign_errors() - .with_ignore_rules(ignore_rules.to_vec()) + .with_ignore_rules(ReadRef::into_owned(ignore_rules)) .cell()) } diff --git a/crates/next-core/src/next_config.rs b/crates/next-core/src/next_config.rs index c409d0331a6a..9109cda43cfd 100644 --- a/crates/next-core/src/next_config.rs +++ b/crates/next-core/src/next_config.rs @@ -1605,7 +1605,7 @@ pub struct OptionSubResourceIntegrity(Option); pub struct OptionFileSystemPath(Option); #[turbo_tasks::value(transparent)] -pub struct IgnoreIssues(Vec); +pub struct IgnoreIssues(Box<[IgnoreIssue]>); #[turbo_tasks::value(transparent)] pub struct OptionJsonValue( @@ -2531,7 +2531,7 @@ impl NextConfig { .transpose()?, }) }) - .collect::>>()?; + .collect::>()?; Ok(Vc::cell(rules)) } diff --git a/crates/next-napi-bindings/src/next_api/analyze.rs b/crates/next-napi-bindings/src/next_api/analyze.rs index 2e63c5fa1c5e..fc1901c49c09 100644 --- a/crates/next-napi-bindings/src/next_api/analyze.rs +++ b/crates/next-napi-bindings/src/next_api/analyze.rs @@ -26,10 +26,10 @@ pub async fn write_analyze_data_with_issues_operation( app_dir_only: bool, ) -> Result> { let analyze_data_op = write_analyze_data_with_issues_operation_inner(project, app_dir_only); - let filter = project.project().issue_filter(); + let filter = project.project().issue_filter().await?; let (_analyze_data, issues, effects) = - strongly_consistent_catch_collectables(analyze_data_op, filter).await?; + strongly_consistent_catch_collectables(analyze_data_op, &*filter).await?; Ok(WriteAnalyzeResult { issues, effects }.cell()) } diff --git a/crates/next-napi-bindings/src/next_api/endpoint.rs b/crates/next-napi-bindings/src/next_api/endpoint.rs index b07f1c4385f0..50abe69fb0db 100644 --- a/crates/next-napi-bindings/src/next_api/endpoint.rs +++ b/crates/next-napi-bindings/src/next_api/endpoint.rs @@ -107,16 +107,16 @@ impl Deref for ExternalEndpoint { /// `node_modules` reshuffle), this falls back to a default filter rather than /// propagating the error. In this scenario we believe the caller will already be observing the /// same error -async fn issue_filter_from_endpoint(endpoint_op: OperationVc) -> Vc { - match endpoint_op.connect().await { - Ok(endpoint_option) => { - if let Some(ep) = &*endpoint_option { - ep.project().issue_filter() - } else { - IssueFilter::warnings_and_foreign_errors().cell() - } - } - Err(_) => IssueFilter::warnings_and_foreign_errors().cell(), +async fn issue_filter_from_endpoint( + endpoint_op: OperationVc, +) -> ReadRef { + if let Ok(ep_option) = endpoint_op.connect().await + && let Some(ep) = &*ep_option + && let Ok(filter) = ep.project().issue_filter().await + { + filter + } else { + ReadRef::new_owned(IssueFilter::warnings_and_foreign_errors()) } } @@ -134,7 +134,7 @@ async fn get_written_endpoint_with_issues_operation( let write_to_disk_op = endpoint_write_to_disk_operation(endpoint_op); let filter = issue_filter_from_endpoint(endpoint_op).await; let (written, issues, effects) = - strongly_consistent_catch_collectables(write_to_disk_op, filter).await?; + strongly_consistent_catch_collectables(write_to_disk_op, &*filter).await?; Ok(WrittenEndpointWithIssues { written, issues, @@ -244,7 +244,7 @@ async fn subscribe_issues_and_diags_operation( // payload, but we still need the catch path to avoid the FATAL. let filter = issue_filter_from_endpoint(endpoint_op).await; let (changed_value, issues, effects) = - strongly_consistent_catch_collectables(changed_op, filter).await?; + strongly_consistent_catch_collectables(changed_op, &*filter).await?; Ok(EndpointIssuesAndDiags { changed: changed_value, issues: if should_include_issues { diff --git a/crates/next-napi-bindings/src/next_api/project.rs b/crates/next-napi-bindings/src/next_api/project.rs index 6d4466ee4719..5b1d898d4fe9 100644 --- a/crates/next-napi-bindings/src/next_api/project.rs +++ b/crates/next-napi-bindings/src/next_api/project.rs @@ -62,7 +62,7 @@ use turbo_tasks_fs::{ use turbo_unix_path::{get_relative_path_to, sys_to_unix, unix_to_sys}; use turbopack_core::{ PROJECT_FILESYSTEM_NAME, SOURCE_URL_PROTOCOL, - issue::{IssueFilter, PlainIssue}, + issue::PlainIssue, output::{OutputAsset, OutputAssets}, source_map::{SourceMap, Token}, version::{PartialUpdate, TotalUpdate, Update, VersionState}, @@ -99,11 +99,6 @@ static SOURCE_MAP_PREFIX: LazyLock = LazyLock::new(|| format!("{SOURCE_U static SOURCE_MAP_PREFIX_PROJECT: LazyLock = LazyLock::new(|| format!("{SOURCE_URL_PROTOCOL}///[{PROJECT_FILESYSTEM_NAME}]/")); -/// Get the `Vc` for a `ProjectContainer`. -fn issue_filter_from_container(container: ResolvedVc) -> Vc { - container.project().issue_filter() -} - #[napi(object)] #[derive(Clone, Debug)] pub struct NapiEnvVar { @@ -1001,9 +996,9 @@ async fn get_entrypoints_with_issues_operation( ) -> Result> { let entrypoints_operation = EntrypointsOperation::new(project_container_entrypoints_operation(container)); - let filter = issue_filter_from_container(container); + let filter = container.project().issue_filter().await?; let (entrypoints, issues, effects) = - strongly_consistent_catch_collectables(entrypoints_operation, filter).await?; + strongly_consistent_catch_collectables(entrypoints_operation, &*filter).await?; Ok(EntrypointsWithIssues { entrypoints, issues, @@ -1557,9 +1552,9 @@ async fn get_all_written_entrypoints_with_issues_operation( app_dir_only, write_phase, )); - let filter = issue_filter_from_container(container); + let filter = container.project().issue_filter().await?; let (entrypoints, issues, effects) = - strongly_consistent_catch_collectables(entrypoints_operation, filter).await?; + strongly_consistent_catch_collectables(entrypoints_operation, &*filter).await?; Ok(AllWrittenEntrypointsWithIssues { entrypoints, issues, @@ -1640,9 +1635,9 @@ async fn emit_all_output_assets_once_with_issues_operation( app_dir_only, has_deferred_entrypoints, )); - let filter = issue_filter_from_container(container); + let filter = container.project().issue_filter().await?; let (_, issues, effects) = - strongly_consistent_catch_collectables(entrypoints_operation, filter).await?; + strongly_consistent_catch_collectables(entrypoints_operation, &*filter).await?; Ok(OperationResult { issues, effects }.cell()) } @@ -1826,8 +1821,8 @@ async fn hmr_update_with_issues_operation( // `subscribeToClientHmrEvents`) rely on this read *throwing* on build-graph // failures to trigger their recovery paths let update = update_op.read_strongly_consistent().await?; - let filter = project.issue_filter(); - let issues = get_issues(update_op, filter).await?; + let filter = project.issue_filter().await?; + let issues = get_issues(update_op, &*filter).await?; let effects = Arc::new(take_effects(update_op).await?); Ok(HmrUpdateWithIssues { update, @@ -1968,8 +1963,8 @@ async fn get_hmr_chunk_names_with_issues_operation( // list keeps the loop running but with stale state, and obscures the real // failure from the dev server log. let hmr_chunk_names = hmr_chunk_names_op.read_strongly_consistent().await?; - let filter = issue_filter_from_container(container); - let issues = get_issues(hmr_chunk_names_op, filter).await?; + let filter = container.project().issue_filter().await?; + let issues = get_issues(hmr_chunk_names_op, &*filter).await?; let effects = Arc::new(take_effects(hmr_chunk_names_op).await?); Ok(HmrChunkNamesWithIssues { chunk_names: hmr_chunk_names, @@ -2512,8 +2507,8 @@ async fn get_all_compilation_issues_operation( container: ResolvedVc, ) -> Result> { let inner_op = get_all_compilation_issues_inner_operation(container); - let filter = issue_filter_from_container(container); - let (_, issues, effects) = strongly_consistent_catch_collectables(inner_op, filter).await?; + let filter = container.project().issue_filter().await?; + let (_, issues, effects) = strongly_consistent_catch_collectables(inner_op, &*filter).await?; Ok(OperationResult { issues, effects }.cell()) } diff --git a/crates/next-napi-bindings/src/next_api/utils.rs b/crates/next-napi-bindings/src/next_api/utils.rs index 96f56976e0b7..0315a7102715 100644 --- a/crates/next-napi-bindings/src/next_api/utils.rs +++ b/crates/next-napi-bindings/src/next_api/utils.rs @@ -107,7 +107,7 @@ pub fn root_task_dispose( /// [consume]: turbo_tasks::CollectiblesSource::take_collectibles pub async fn get_issues( source: OperationVc, - filter: Vc, + filter: &IssueFilter, ) -> Result>>> { Ok(Arc::new( source.peek_issues().get_plain_issues(filter).await?, @@ -447,7 +447,7 @@ pub fn subscribe> + Send, // propagate any actual error results. pub async fn strongly_consistent_catch_collectables( source_op: OperationVc, - filter: Vc, + filter: &IssueFilter, ) -> Result<( Option>, Arc>>, diff --git a/turbopack/crates/turbopack-cli-utils/src/issue.rs b/turbopack/crates/turbopack-cli-utils/src/issue.rs index 233d6a556268..3e17810ed118 100644 --- a/turbopack/crates/turbopack-cli-utils/src/issue.rs +++ b/turbopack/crates/turbopack-cli-utils/src/issue.rs @@ -395,7 +395,7 @@ impl IssueReporter for ConsoleUi { } = self.options; let mut grouped_issues: GroupedIssues = FxHashMap::default(); - let plain_issues = issues.get_plain_issues(IssueFilter::everything()).await?; + let plain_issues = issues.get_plain_issues(&IssueFilter::everything()).await?; let issues = plain_issues .iter() .map(|plain_issue| { diff --git a/turbopack/crates/turbopack-core/src/issue/mod.rs b/turbopack/crates/turbopack-core/src/issue/mod.rs index fac44de3b6e4..3d1affd95e3c 100644 --- a/turbopack/crates/turbopack-core/src/issue/mod.rs +++ b/turbopack/crates/turbopack-core/src/issue/mod.rs @@ -282,43 +282,60 @@ pub struct IssueFilter { /// The minimum severity for issues in node_modules foreign_severity: IssueSeverity, /// Issues matching any of these rules are ignored (dropped from results). - ignore_rules: Vec, + ignore_rules: Box<[IgnoreIssue]>, } -#[turbo_tasks::value_impl] impl IssueFilter { /// A filter that lets everything through. - #[turbo_tasks::function] - pub fn everything() -> Vc { + pub fn everything() -> Self { IssueFilter { severity: IssueSeverity::Info, foreign_severity: IssueSeverity::Info, - ignore_rules: Vec::new(), + ignore_rules: Box::from([]), + } + } + + /// Construct a filter with the standard warning/foreign-error severities. + pub fn warnings_and_foreign_errors() -> Self { + IssueFilter { + severity: IssueSeverity::Warning, + foreign_severity: IssueSeverity::Error, + ignore_rules: Box::from([]), } - .cell() + } + + /// Set the ignore rules for this filter. + pub fn with_ignore_rules(mut self, rules: Box<[IgnoreIssue]>) -> Self { + self.ignore_rules = rules; + self } /// Returns true if the issue is allowed by this filter. - #[turbo_tasks::function] - pub async fn matches(&self, issue: ResolvedVc>) -> Result> { - let has_no_ignore_rules = self.ignore_rules.is_empty(); - let is_everything = self.severity == IssueSeverity::Info - && self.foreign_severity == IssueSeverity::Info - && has_no_ignore_rules; + pub async fn matches(&self, issue: ResolvedVc>) -> Result { + Ok(self.matches_all_fast_path() + || self + .matches_ref_slow_path(&*issue.into_trait_ref().await?) + .await?) + } - if is_everything { - return Ok(Vc::cell(true)); - } + pub async fn matches_ref(&self, issue: &dyn Issue) -> Result { + Ok(self.matches_all_fast_path() || self.matches_ref_slow_path(issue).await?) + } - let trait_ref = issue.into_trait_ref().await?; + fn matches_all_fast_path(&self) -> bool { + self.severity == IssueSeverity::Info + && self.foreign_severity == IssueSeverity::Info + && self.ignore_rules.is_empty() + } + async fn matches_ref_slow_path(&self, issue: &dyn Issue) -> Result { // Fetch the file path once — it's used by both severity and ignore-rule // checks. - let file_path = trait_ref.file_path().await?; + let file_path = issue.file_path().await?; // Check severity first — this is cheap and avoids fetching // title/description for issues that would be filtered out anyway. - let severity = trait_ref.severity(); + let severity = issue.severity(); // NOTE: Lower severities are _more_ severe let severity_allowed = if severity <= self.severity || severity <= self.foreign_severity { // we need to check the path to see if it is foreign or not. Only await the @@ -337,13 +354,13 @@ impl IssueFilter { }; if !severity_allowed { - return Ok(Vc::cell(false)); + return Ok(false); } // Check ignore rules — if any rule matches, the issue is dropped. // Title and description are fetched lazily: only when a rule's path // matches and the rule also specifies a title/description pattern. - if !has_no_ignore_rules { + if !self.ignore_rules.is_empty() { let file_path_str = file_path.to_string(); let mut title_str: Option = None; let mut description_text: Option> = None; @@ -354,7 +371,7 @@ impl IssueFilter { } if let Some(ref title_pat) = rule.title { if title_str.is_none() { - title_str = Some(trait_ref.title().await?.to_unstyled_string()); + title_str = Some(issue.title().await?.to_unstyled_string()); } if !title_pat.matches(title_str.as_deref().unwrap()) { continue; @@ -362,12 +379,8 @@ impl IssueFilter { } if let Some(ref desc_pat) = rule.description { if description_text.is_none() { - description_text = Some( - trait_ref - .description() - .await? - .map(|s| s.to_unstyled_string()), - ); + description_text = + Some(issue.description().await?.map(|s| s.to_unstyled_string())); } match description_text.as_ref().unwrap().as_deref() { Some(desc) if desc_pat.matches(desc) => {} @@ -375,28 +388,11 @@ impl IssueFilter { } } // All specified fields matched — ignore this issue. - return Ok(Vc::cell(false)); + return Ok(false); } } - Ok(Vc::cell(true)) - } -} - -impl IssueFilter { - /// Construct a filter with the standard warning/foreign-error severities. - pub fn warnings_and_foreign_errors() -> Self { - IssueFilter { - severity: IssueSeverity::Warning, - foreign_severity: IssueSeverity::Error, - ignore_rules: Vec::new(), - } - } - - /// Set the ignore rules for this filter. - pub fn with_ignore_rules(mut self, rules: Vec) -> Self { - self.ignore_rules = rules; - self + Ok(true) } } @@ -415,15 +411,12 @@ impl CapturedIssues { } // Returns all the issues as formatted `PlainIssues`. - pub async fn get_plain_issues( - &self, - filter: Vc, - ) -> Result>> { + pub async fn get_plain_issues(&self, filter: &IssueFilter) -> Result>> { let mut list = self .issues .iter() .map(async |issue| { - if *filter.matches(**issue).await? { + if filter.matches(*issue).await? { Ok(Some( PlainIssue::from_issue(**issue, Some(*self.tracer)).await?, )) @@ -975,12 +968,24 @@ impl PlainIssue { issue: ResolvedVc>, import_tracer: Option>, ) -> Result> { - let trait_ref = issue.into_trait_ref().await?; + Ok( + Self::from_issue_ref(&*issue.into_trait_ref().await?, import_tracer) + .await? + .cell(), + ) + } +} + +impl PlainIssue { + pub async fn from_issue_ref( + trait_ref: &dyn Issue, + import_tracer: Option>, + ) -> Result { let severity = trait_ref.severity(); let file_path = trait_ref.file_path().await?; let file_path_str = file_path.to_string_ref().await?; - Ok(Self::cell(Self { + Ok(Self { severity, file_path: file_path_str, stage: trait_ref.stage(), @@ -1015,7 +1020,7 @@ impl PlainIssue { } None => vec![], }, - })) + }) } } diff --git a/turbopack/crates/turbopack-dev-server/src/update/stream.rs b/turbopack/crates/turbopack-dev-server/src/update/stream.rs index e6af47a4c8ee..be21ace81ea9 100644 --- a/turbopack/crates/turbopack-dev-server/src/update/stream.rs +++ b/turbopack/crates/turbopack-dev-server/src/update/stream.rs @@ -90,7 +90,7 @@ impl GetContentFn { async fn peek_issues(source: OperationVc) -> Result>> { let captured = source.peek_issues(); - captured.get_plain_issues(IssueFilter::everything()).await + captured.get_plain_issues(&IssueFilter::everything()).await } fn extend_issues(issues: &mut Vec>, new_issues: Vec>) { diff --git a/turbopack/crates/turbopack-tests/tests/execution.rs b/turbopack/crates/turbopack-tests/tests/execution.rs index 098625168615..a53f74e2f0f7 100644 --- a/turbopack/crates/turbopack-tests/tests/execution.rs +++ b/turbopack/crates/turbopack-tests/tests/execution.rs @@ -638,7 +638,7 @@ async fn snapshot_issues( let plain_issues = run_result_op .peek_issues() - .get_plain_issues(IssueFilter::everything()) + .get_plain_issues(&IssueFilter::everything()) .await?; turbopack_test_utils::snapshot::snapshot_issues(plain_issues, path.join("issues")?, &REPO_ROOT) diff --git a/turbopack/crates/turbopack-tests/tests/snapshot.rs b/turbopack/crates/turbopack-tests/tests/snapshot.rs index adea92a631de..afcedcdb83da 100644 --- a/turbopack/crates/turbopack-tests/tests/snapshot.rs +++ b/turbopack/crates/turbopack-tests/tests/snapshot.rs @@ -234,7 +234,7 @@ async fn run(resource: PathBuf) -> Result<()> { let plain_issues = out_op .peek_issues() - .get_plain_issues(IssueFilter::everything()) + .get_plain_issues(&IssueFilter::everything()) .await?; snapshot_issues(plain_issues, out_vc.join("issues")?, &REPO_ROOT)