Skip to content
Merged
Show file tree
Hide file tree
Changes from 61 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
9b9298d
add check suppression accounting
jonathanong Aug 9, 2026
7cf8c53
cover check suppression adapters
jonathanong Aug 9, 2026
eaa51cb
defer adapter suppression to check runner
jonathanong Aug 9, 2026
f842f2e
fix: preserve suppression audit parity
jonathanong Aug 10, 2026
fa57f52
fix: satisfy suppression audit quality gates
jonathanong Aug 10, 2026
2e06912
fix: preserve standalone unique export suppression
jonathanong Aug 10, 2026
8b70a74
test: cover line suppression accounting
jonathanong Aug 10, 2026
5c2da05
fix: defer suppression accounting at rule boundaries
jonathanong Aug 10, 2026
fcf06dc
fix: defer remaining aggregate rule suppressions
jonathanong Aug 10, 2026
748c98c
fix: defer storybook suppression in aggregate checks
jonathanong Aug 10, 2026
f842b4e
test: cover aggregate API route deferral
jonathanong Aug 10, 2026
85b7036
test: cover aggregate suppression contracts
jonathanong Aug 10, 2026
667e8b6
fix: satisfy strict clippy for suppression pipeline
jonathanong Aug 10, 2026
bde2a85
fix: close suppression review gaps
jonathanong Aug 10, 2026
94be44f
fix: preserve suppressed parse failures
jonathanong Aug 10, 2026
22d923f
fix: close suppression review gaps
jonathanong Aug 10, 2026
94355e4
refactor: split deferred reachable analysis
jonathanong Aug 10, 2026
24f80c6
fix: account for advisory suppressions
jonathanong Aug 10, 2026
b7e02ea
fix: complete suppression review regressions
jonathanong Aug 10, 2026
a330fa5
fix: preserve duplicate suppression audit entries
jonathanong Aug 10, 2026
98417e4
fix: preserve prepared suppression and export representatives
jonathanong Aug 10, 2026
c6da8d1
fix: authorize explicit storybook config sources
jonathanong Aug 10, 2026
9fc7c7b
fix: preserve batched check options and source reuse
jonathanong Aug 10, 2026
6a4cd5e
fix: preserve deferred dynamic suppression provenance
jonathanong Aug 10, 2026
13e7c43
fix: keep React suppression metadata internal
jonathanong Aug 11, 2026
2851277
test: track aggregate React task entrypoint
jonathanong Aug 11, 2026
bcdfe5a
fix: keep aggregate React metadata internal
jonathanong Aug 11, 2026
86be383
test: cover React aggregate compatibility branches
jonathanong Aug 11, 2026
ff3d7cd
fix: honor parent React suppression directives
jonathanong Aug 11, 2026
c6aafb7
test: cover React check analysis errors
jonathanong Aug 11, 2026
09e0647
test: cover prepared React usages branches
jonathanong Aug 11, 2026
4c31eef
test: cover Storybook suppression adapters
jonathanong Aug 11, 2026
3d3fead
test: satisfy Storybook coverage lint
jonathanong Aug 11, 2026
1409c4d
test: cover prepared dynamic import facts
jonathanong Aug 11, 2026
d4017ee
test: cover general suppression paths
jonathanong Aug 11, 2026
1b5318b
test: cover unique export suppression origins
jonathanong Aug 11, 2026
b58a2b9
test: split suppression coverage modules
jonathanong Aug 11, 2026
4768f2d
fix: account only hidden React findings
jonathanong Aug 11, 2026
1fefd6f
fix: preserve prepared check compatibility
jonathanong Aug 11, 2026
c035c5e
fix: honor aggregate suppression parse errors
jonathanong Aug 11, 2026
f773acb
fix: retain suppression directive provenance
jonathanong Aug 11, 2026
ad553d6
test: follow split filesystem dispatch modules
jonathanong Aug 11, 2026
dd76170
fix: defer suppression only for audit reports
jonathanong Aug 11, 2026
fcea1da
refactor: group aggregate codebase inputs
jonathanong Aug 11, 2026
1b1e420
fix: preserve early React suppression
jonathanong Aug 11, 2026
93a9ea5
refactor: split domain check inputs
jonathanong Aug 11, 2026
e69aba0
fix: preserve ordinary suppression contracts
jonathanong Aug 11, 2026
833f798
perf: account for Rust suppression source reads
jonathanong Aug 11, 2026
35c9dab
test: cover prepared suppression contracts
jonathanong Aug 11, 2026
b22e561
fix: preserve ordinary react suppression alignment
jonathanong Aug 11, 2026
822a4c3
fix: preserve suppression parity in audit mode
jonathanong Aug 11, 2026
a2560d3
test: cover lexical named re-export suppression
jonathanong Aug 11, 2026
0faeae0
fix: avoid React check fact clones
jonathanong Aug 11, 2026
78cb781
refactor: reduce unique export collector complexity
jonathanong Aug 11, 2026
a04f85e
fix: retain unique export suppression provenance
jonathanong Aug 11, 2026
cdbaebe
fix: scope suppressed findings to check options
jonathanong Aug 11, 2026
41d7939
style: format analyze project check options
jonathanong Aug 11, 2026
f10fdc2
fix: preserve unique export suppression lines
jonathanong Aug 11, 2026
1f751e8
fix: preserve active unique export parity
jonathanong Aug 11, 2026
729fff7
fix: preserve origin suppression parity
jonathanong Aug 11, 2026
530b01f
fix: preserve public unique export findings
jonathanong Aug 11, 2026
5d2329e
fix: preserve suppression provenance
jonathanong Aug 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/no-mistakes/benches/core_analysis/fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ pub(super) const EXPECTED_SOURCE_FILES: usize = 14;
pub(super) const EXPECTED_IMPACTED_CHECKS: usize = 1;
// Nine graph-scope keys plus two session-scoped legacy-symbol keys.
pub(super) const EXPECTED_MULTI_REPORT_RESOLVER_KEYS: u64 = 11;
// Fourteen source files plus four configuration and manifest reads.
pub(super) const EXPECTED_CHECK_SOURCE_READS: u64 = 18;
// Fourteen TS sources, one Rust rule source, and four config/manifest reads.
pub(super) const EXPECTED_CHECK_SOURCE_READS: u64 = 19;
pub(super) const EXPECTED_CHECK_MANIFEST_PARSES: u64 = 4;
pub(super) const EXPECTED_CHECK_RESOLVER_KEYS: u64 = 14;

Expand Down
3 changes: 2 additions & 1 deletion crates/no-mistakes/src/benchmark_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ pub fn high_fanout_finalization_signature(
/// Run every configured `check` domain and serialize the stable public report.
pub fn check_json(root: &Path) -> Result<String> {
crate::ast::with_request_parse_cache(|| {
let results = crate::check_runner::run_all(root.to_path_buf(), None, None)?;
let results =
crate::check_runner::run_all_with_suppressed(root.to_path_buf(), None, None, false)?;
Ok(serde_json::to_string(&crate::check_runner::json_value(
&results,
))?)
Expand Down
76 changes: 47 additions & 29 deletions crates/no-mistakes/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ pub(crate) struct CheckArgs {
/// Shorthand for --format json.
#[arg(long, global = true, conflicts_with = "format")]
json: bool,
/// Include deterministic accounting for findings hidden by no-mistakes
/// suppression directives. Disabled by default to preserve existing output.
#[arg(long, global = true)]
include_suppressed: bool,
/// Legacy programmatic timing switch. CLI timing flags are root-global.
#[arg(skip)]
timings: bool,
Expand All @@ -48,7 +52,12 @@ pub(crate) fn run(args: CheckArgs) -> Result<ExitCode> {
);
let cwd = std::env::current_dir().context("cwd must be accessible")?;
let root = resolve_root(&args.root, &cwd);
let results = check_runner::run_all(root, args.config, args.tsconfig)?;
let results = check_runner::run_all_with_suppressed(
root,
args.config,
args.tsconfig,
args.include_suppressed,
)?;
record_missing_check_timings(&results);
no_mistakes::invocation::commit_timeout()?;
for warning in &results.warnings {
Expand Down Expand Up @@ -77,41 +86,47 @@ fn record_missing_check_timings(results: &check_runner::CheckResults) {
.map(|entry| entry.label)
.collect::<std::collections::HashSet<_>>();
for (label, duration) in &results.timings {
let (label, kind) = match *label {
"discover" => ("discovery", no_mistakes::diagnostics::TimingKind::Serial),
"parse_extract" => ("parse", no_mistakes::diagnostics::TimingKind::Serial),
"react" => (
"analysis.react",
no_mistakes::diagnostics::TimingKind::Parallel,
),
"queues" => (
"analysis.queues",
no_mistakes::diagnostics::TimingKind::Parallel,
),
"rules" => (
"analysis.rules",
no_mistakes::diagnostics::TimingKind::Parallel,
),
"integration" => (
"analysis.integration",
no_mistakes::diagnostics::TimingKind::Parallel,
),
"codebase" => (
"analysis.codebase",
no_mistakes::diagnostics::TimingKind::Parallel,
),
"filesystem_rules" => (
"analysis.filesystem_rules",
no_mistakes::diagnostics::TimingKind::Parallel,
),
_ => continue,
let Some((label, kind)) = timing_metadata(label) else {
continue;
};
if !existing.contains(label) {
observer.record_duration(label, *duration, kind);
}
}
}

fn timing_metadata(label: &str) -> Option<(&'static str, no_mistakes::diagnostics::TimingKind)> {
Some(match label {
"discover" => ("discovery", no_mistakes::diagnostics::TimingKind::Serial),
"parse_extract" => ("parse", no_mistakes::diagnostics::TimingKind::Serial),
"react" => (
"analysis.react",
no_mistakes::diagnostics::TimingKind::Parallel,
),
"queues" => (
"analysis.queues",
no_mistakes::diagnostics::TimingKind::Parallel,
),
"rules" => (
"analysis.rules",
no_mistakes::diagnostics::TimingKind::Parallel,
),
"integration" => (
"analysis.integration",
no_mistakes::diagnostics::TimingKind::Parallel,
),
"codebase" => (
"analysis.codebase",
no_mistakes::diagnostics::TimingKind::Parallel,
),
"filesystem_rules" => (
"analysis.filesystem_rules",
no_mistakes::diagnostics::TimingKind::Parallel,
),
_ => return None,
})
}

fn has_failures(results: &check_runner::CheckResults) -> bool {
!results.react.is_empty()
|| !results.queues.is_empty()
Expand All @@ -120,3 +135,6 @@ fn has_failures(results: &check_runner::CheckResults) -> bool {
|| !results.codebase.is_empty()
|| !results.warnings.is_empty()
}

#[cfg(test)]
mod tests;
13 changes: 13 additions & 0 deletions crates/no-mistakes/src/check/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use super::*;

#[test]
fn timing_metadata_omits_unknown_check_runner_labels() {
assert_eq!(
timing_metadata("queues"),
Some((
"analysis.queues",
no_mistakes::diagnostics::TimingKind::Parallel,
))
);
assert_eq!(timing_metadata("not-a-check-timing"), None);
}
88 changes: 25 additions & 63 deletions crates/no-mistakes/src/check_parallel.rs
Original file line number Diff line number Diff line change
@@ -1,58 +1,10 @@
use crate::check_tasks::{
run_codebase_check_with_catalog, run_filesystem_rules_check_with_facts, run_integration_check,
run_queue_check, run_react_check, run_rules_check, CheckTask,
run_queue_check, run_rules_check, CodebaseCheckInputs,
};
use no_mistakes::codebase::check_facts::CheckFactMap;
use no_mistakes::codebase::rules::RuleFinding;
use no_mistakes::codebase::unique_exports::UniqueExportFinding;
use no_mistakes::integration_tests::IntegrationFinding;
use no_mistakes::queue::CheckFinding;
use no_mistakes::react_traits;
use std::path::{Path, PathBuf};

pub(crate) type DomainResults = (
anyhow::Result<CheckTask<Vec<react_traits::Violation>>>,
anyhow::Result<CheckTask<Vec<CheckFinding>>>,
anyhow::Result<CheckTask<Vec<RuleFinding>>>,
anyhow::Result<CheckTask<Vec<IntegrationFinding>>>,
anyhow::Result<CheckTask<Vec<UniqueExportFinding>>>,
anyhow::Result<CheckTask<Vec<RuleFinding>>>,
);

pub(crate) struct DomainCheckInputs<'a> {
pub(crate) session: std::sync::Arc<no_mistakes::codebase::analysis_session::AnalysisSession>,
pub(crate) root: &'a Path,
pub(crate) config_path: &'a Option<PathBuf>,
pub(crate) tsconfig_path: &'a Option<PathBuf>,
pub(crate) react_enabled: bool,
pub(crate) queues_enabled: bool,
pub(crate) integration_enabled: bool,
pub(crate) unique_exports_enabled: bool,
pub(crate) filesystem_rules_enabled: bool,
pub(crate) discovered_files: &'a [PathBuf],
pub(crate) facts: &'a CheckFactMap,
pub(crate) prepared_playwright:
Option<&'a no_mistakes::playwright::rules::PreparedPlaywrightRules>,
pub(crate) prepared_react: &'a no_mistakes::react_traits::PreparedReactCheck,
pub(crate) prepared_graph:
Option<&'a no_mistakes::codebase::dependencies::graph::PreparedGraphConfig>,
pub(crate) dependency_graph:
Option<std::sync::Arc<no_mistakes::codebase::dependencies::graph::DepGraph>>,
pub(crate) prepared_tsconfig: &'a no_mistakes::codebase::ts_resolver::TsConfig,
pub(crate) prepared_tsconfig_catalog:
&'a std::sync::Arc<no_mistakes::codebase::ts_resolver::TsConfigCatalog>,
pub(crate) visible_paths: &'a no_mistakes::codebase::ts_source::VisiblePathSnapshot,
pub(crate) sources: std::sync::Arc<no_mistakes::codebase::ts_source::SourceStore>,
pub(crate) inferred_roots: &'a no_mistakes::codebase::config::InferredRoots,
pub(crate) config: &'a no_mistakes::config::v2::NoMistakesConfig,
pub(crate) codebase_config: &'a no_mistakes::codebase::config::Config,
pub(crate) vitest_projects:
Option<&'a no_mistakes::codebase::rules::PreparedVitestProjectCatalog>,
pub(crate) workflow_documents:
Option<&'a no_mistakes::codebase::ci_workflows::ParsedWorkflowSet>,
pub(crate) tsconfig_gate_project_inputs:
Option<&'a no_mistakes::codebase::rules::tsconfig_gate_coverage::ProjectSourceInputs>,
}
mod inputs;
mod react_dispatch;
pub(crate) use inputs::{DomainCheckInputs, DomainResults};

pub(crate) fn run_domain_checks(inputs: DomainCheckInputs<'_>) -> DomainResults {
let observer = no_mistakes::diagnostics::current();
Expand All @@ -75,20 +27,26 @@ pub(crate) fn run_domain_checks(inputs: DomainCheckInputs<'_>) -> DomainResults
let prepared_tsconfig_catalog = inputs.prepared_tsconfig_catalog;
let visible_paths = inputs.visible_paths;
let sources = inputs.sources;
let rule_sources = std::sync::Arc::clone(&sources);
let inferred_roots = inputs.inferred_roots;
let config = inputs.config;
let codebase_config = inputs.codebase_config;
let vitest_projects = inputs.vitest_projects;
let (codebase_config, vitest_projects) = (inputs.codebase_config, inputs.vitest_projects);
let workflow_documents = inputs.workflow_documents;
let tsconfig_gate_project_inputs = inputs.tsconfig_gate_project_inputs;
let defer_suppression = inputs.defer_suppression;

let ((react, queues), (rules, (integration, (codebase, filesystem_rules)))) = rayon::join(
|| {
rayon::join(
|| {
no_mistakes::diagnostics::with_observer(observer.clone(), || {
run_react_check(root, react_enabled, facts, prepared_react)
react_dispatch::run(react_dispatch::Inputs {
root,
enabled: react_enabled,
facts,
prepared: prepared_react,
sources: sources.as_ref(),
defer_suppression,
})
})
},
|| {
Expand Down Expand Up @@ -121,9 +79,11 @@ pub(crate) fn run_domain_checks(inputs: DomainCheckInputs<'_>) -> DomainResults
prepared_tsconfig,
prepared_tsconfig_catalog,
inferred_roots: Some(inferred_roots),
sources: Some(&rule_sources),
sources: Some(sources.as_ref()),
},
dependency_graph.as_deref(),
sources.as_ref(),
defer_suppression,
)
})
},
Expand All @@ -148,15 +108,16 @@ pub(crate) fn run_domain_checks(inputs: DomainCheckInputs<'_>) -> DomainResults
no_mistakes::diagnostics::with_observer(
observer.clone(),
|| {
run_codebase_check_with_catalog(
&session,
run_codebase_check_with_catalog(CodebaseCheckInputs {
session: &session,
root,
codebase_config,
config: codebase_config,
prepared_tsconfig_catalog,
unique_exports_enabled,
enabled: unique_exports_enabled,
facts,
inferred_roots,
)
defer_suppression,
})
},
)
},
Expand All @@ -171,13 +132,14 @@ pub(crate) fn run_domain_checks(inputs: DomainCheckInputs<'_>) -> DomainResults
discovered_files,
no_mistakes::codebase::rules::filesystem_dispatch::PreparedFilesystemRuleInputs {
snapshot: visible_paths,
sources,
sources: std::sync::Arc::clone(&sources),
vitest_catalog: vitest_projects,
workflow_documents,
tsconfig_gate_project_inputs,
config_path: config_path.as_deref(),
},
Some(facts),
defer_suppression,
)
},
)
Expand Down
53 changes: 53 additions & 0 deletions crates/no-mistakes/src/check_parallel/inputs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use crate::check_tasks::CheckTask;
use no_mistakes::codebase::check_facts::CheckFactMap;
use no_mistakes::codebase::rules::RuleFinding;
use no_mistakes::codebase::unique_exports::PreparedUniqueExportFinding;
use no_mistakes::integration_tests::IntegrationFinding;
use no_mistakes::queue::CheckFinding;
use no_mistakes::react_traits;
use std::path::{Path, PathBuf};

pub(crate) type DomainResults = (
anyhow::Result<CheckTask<Vec<react_traits::Violation>>>,
anyhow::Result<CheckTask<Vec<CheckFinding>>>,
anyhow::Result<CheckTask<Vec<RuleFinding>>>,
anyhow::Result<CheckTask<Vec<IntegrationFinding>>>,
anyhow::Result<CheckTask<Vec<PreparedUniqueExportFinding>>>,
anyhow::Result<CheckTask<Vec<RuleFinding>>>,
);

pub(crate) struct DomainCheckInputs<'a> {
pub(crate) session: std::sync::Arc<no_mistakes::codebase::analysis_session::AnalysisSession>,
pub(crate) root: &'a Path,
pub(crate) config_path: &'a Option<PathBuf>,
pub(crate) tsconfig_path: &'a Option<PathBuf>,
pub(crate) react_enabled: bool,
pub(crate) queues_enabled: bool,
pub(crate) integration_enabled: bool,
pub(crate) unique_exports_enabled: bool,
pub(crate) filesystem_rules_enabled: bool,
pub(crate) discovered_files: &'a [PathBuf],
pub(crate) facts: &'a CheckFactMap,
pub(crate) prepared_playwright:
Option<&'a no_mistakes::playwright::rules::PreparedPlaywrightRules>,
pub(crate) prepared_react: &'a no_mistakes::react_traits::PreparedReactCheck,
pub(crate) prepared_graph:
Option<&'a no_mistakes::codebase::dependencies::graph::PreparedGraphConfig>,
pub(crate) dependency_graph:
Option<std::sync::Arc<no_mistakes::codebase::dependencies::graph::DepGraph>>,
pub(crate) prepared_tsconfig: &'a no_mistakes::codebase::ts_resolver::TsConfig,
pub(crate) prepared_tsconfig_catalog:
&'a std::sync::Arc<no_mistakes::codebase::ts_resolver::TsConfigCatalog>,
pub(crate) visible_paths: &'a no_mistakes::codebase::ts_source::VisiblePathSnapshot,
pub(crate) sources: std::sync::Arc<no_mistakes::codebase::ts_source::SourceStore>,
pub(crate) inferred_roots: &'a no_mistakes::codebase::config::InferredRoots,
pub(crate) config: &'a no_mistakes::config::v2::NoMistakesConfig,
pub(crate) codebase_config: &'a no_mistakes::codebase::config::Config,
pub(crate) vitest_projects:
Option<&'a no_mistakes::codebase::rules::PreparedVitestProjectCatalog>,
pub(crate) workflow_documents:
Option<&'a no_mistakes::codebase::ci_workflows::ParsedWorkflowSet>,
pub(crate) tsconfig_gate_project_inputs:
Option<&'a no_mistakes::codebase::rules::tsconfig_gate_coverage::ProjectSourceInputs>,
pub(crate) defer_suppression: bool,
}
24 changes: 24 additions & 0 deletions crates/no-mistakes/src/check_parallel/react_dispatch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use crate::check_tasks::{run_react_check, CheckTask};
use no_mistakes::codebase::check_facts::CheckFactMap;
use no_mistakes::react_traits;

pub(super) struct Inputs<'a> {
pub(super) root: &'a std::path::Path,
pub(super) enabled: bool,
pub(super) facts: &'a CheckFactMap,
pub(super) prepared: &'a react_traits::PreparedReactCheck,
pub(super) sources: &'a no_mistakes::codebase::ts_source::SourceStore,
pub(super) defer_suppression: bool,
}

pub(super) fn run(inputs: Inputs<'_>) -> anyhow::Result<CheckTask<Vec<react_traits::Violation>>> {
let Inputs {
root,
enabled,
facts,
prepared,
sources,
defer_suppression,
} = inputs;
run_react_check(root, enabled, facts, prepared, sources, defer_suppression)
}
4 changes: 2 additions & 2 deletions crates/no-mistakes/src/check_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ mod fact_collection;
pub(crate) mod finite_set_plan;
mod graph_plan;
pub(crate) mod prepared;
mod results;
pub(crate) mod results;
mod run_all;

pub(crate) use results::{complete_domain_checks, empty_results, json_value, CheckResults};
pub(crate) use run_all::run_all;
pub(crate) use run_all::run_all_with_suppressed;

#[cfg(test)]
mod tests;
5 changes: 5 additions & 0 deletions crates/no-mistakes/src/check_runner/prepared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ pub(crate) fn prepare_from_shared(
let codebase_config =
no_mistakes::codebase::config::config_from_loaded_v2(root, config_path, &config);
let sources = visible_paths.source_store_for(root);
if config.rule_configured(no_mistakes::codebase::rules::REQUIRE_STORYBOOK_STORIES) {
no_mistakes::codebase::rules::require_storybook_stories::authorize_configured_sources(
root, &config, &sources,
);
}
let tsconfig_catalog = Arc::new(if let Some(path) = tsconfig_path {
let path = if path.is_absolute() {
path.to_path_buf()
Expand Down
Loading
Loading