Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 14 additions & 3 deletions crates/no-mistakes/src/check_parallel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ pub(crate) struct DomainCheckInputs<'a> {
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) fn run_domain_checks(inputs: DomainCheckInputs<'_>) -> DomainResults {
Expand Down Expand Up @@ -76,6 +80,8 @@ pub(crate) fn run_domain_checks(inputs: DomainCheckInputs<'_>) -> DomainResults
let config = inputs.config;
let codebase_config = inputs.codebase_config;
let vitest_projects = inputs.vitest_projects;
let workflow_documents = inputs.workflow_documents;
let tsconfig_gate_project_inputs = inputs.tsconfig_gate_project_inputs;

let ((react, queues), (rules, (integration, (codebase, filesystem_rules)))) = rayon::join(
|| {
Expand Down Expand Up @@ -163,9 +169,14 @@ pub(crate) fn run_domain_checks(inputs: DomainCheckInputs<'_>) -> DomainResults
config,
filesystem_rules_enabled,
discovered_files,
visible_paths,
sources,
vitest_projects,
no_mistakes::codebase::rules::filesystem_dispatch::PreparedFilesystemRuleInputs {
snapshot: visible_paths,
sources,
vitest_catalog: vitest_projects,
workflow_documents,
tsconfig_gate_project_inputs,
config_path: config_path.as_deref(),
},
)
},
)
Expand Down
204 changes: 2 additions & 202 deletions crates/no-mistakes/src/check_runner.rs
Original file line number Diff line number Diff line change
@@ -1,211 +1,11 @@
use crate::check_parallel::{run_domain_checks, DomainCheckInputs};
use crate::check_tasks;
use anyhow::{Context, Result};
use enabled::{fact_plan, integration_configured, plan_requests_facts};
use no_mistakes::codebase::check_facts::collect_check_facts_with_graph_files_playwright_sources_and_session;
use std::path::PathBuf;

pub(crate) mod enabled;
mod forbidden_plan;
pub(crate) mod prepared;
mod results;
mod run_all;

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

pub(crate) fn run_all(
root: PathBuf,
config_path: Option<PathBuf>,
tsconfig_path: Option<PathBuf>,
) -> Result<CheckResults> {
let root = root.canonicalize().unwrap_or(root);
let session = no_mistakes::codebase::analysis_session::AnalysisSession::new(
no_mistakes::diagnostics::current(),
);
let prepared = prepared::prepare_with_session(
&session,
&root,
config_path.as_deref(),
tsconfig_path.as_deref(),
)?;
let config = &prepared.config;
let queues_enabled = check_tasks::queues_configured(config);
let unique_exports_enabled = check_tasks::unique_exports_configured(config);
let enabled = enabled::ConfiguredChecks::from_config(config);
let filesystem_rules_enabled = check_tasks::filesystem_rules_configured(config);
let forbidden_deps_enabled = check_tasks::forbidden_dependencies_configured(config);
let forbidden_graph_plan = if forbidden_deps_enabled {
no_mistakes::codebase::rules::forbidden_dependencies::graph_plan(config)
} else {
None
};
let playwright_consumers = forbidden_graph_plan
.map(
|plan| no_mistakes::playwright::rules::PlaywrightFactConsumers {
graph_selectors: plan.playwright_selectors,
graph_routes: plan.playwright_routes,
},
)
.unwrap_or_default();
let mut playwright_fact_plan = match prepared.playwright.as_ref() {
Some(prepared) => Some(prepared.fact_plan()),
None => no_mistakes::playwright::rules::fact_plan_for_consumers(
&root,
config_path.as_deref(),
config,
playwright_consumers,
)
.context("failed to prepare Playwright shared facts")?,
};
let integration_enabled = integration_configured(config);
let react_enabled = prepared.react.enabled();
let mut plan = fact_plan(enabled::EnabledChecks {
react: react_enabled,
queue: queues_enabled,
queue_factory_names: config.queues.factories.clone(),
dynamic_import_rules: enabled.dynamic_import_rules,
boundary_rules: enabled.boundary_rules,
nextjs_api_routes: enabled.nextjs_api_routes,
nextjs_caching: enabled.nextjs_caching,
storybook_stories: enabled.storybook_stories,
integration: integration_enabled,
unique_exports: unique_exports_enabled,
});
if integration_enabled {
plan.integration_runner_configs = Some(std::sync::Arc::new(
no_mistakes::integration_tests::prepare_runner_configs_with_catalog(
&root,
config,
prepared.visible_paths.paths_for(&root).as_ref(),
std::sync::Arc::clone(&prepared.tsconfig_catalog),
prepared.visible_paths.source_store_for(&root),
),
));
}
let prepared_graph = forbidden_plan::prepare(
&root,
config,
forbidden_plan::PreparedInputs {
codebase_config: &prepared.codebase_config,
tsconfig: &prepared.tsconfig,
visible_paths: prepared.visible_paths.as_ref(),
},
forbidden_graph_plan,
&mut playwright_fact_plan,
&mut plan,
)?;
let needs_shared_facts =
forbidden_deps_enabled || playwright_fact_plan.is_some() || plan_requests_facts(&plan);
if !needs_shared_facts
&& !filesystem_rules_enabled
&& !no_mistakes::playwright::rules::configured(config)
{
return Ok(empty_results([None]));
}
let skip_directories = config.filesystem.skip_directories.clone();
let needs_full_graph_files = forbidden_graph_plan.is_some() || playwright_fact_plan.is_some();
let needs_graph_files =
needs_shared_facts && (needs_full_graph_files || enabled.dynamic_import_rules);
let (views, discover_duration) = no_mistakes::diagnostics::measure_if_enabled(
"discovery",
no_mistakes::diagnostics::TimingKind::Serial,
|| {
crate::check_discovery::discover_check_file_views_from_snapshot(
&root,
config,
&skip_directories,
unique_exports_enabled,
prepared.visible_paths.as_ref(),
)
},
);
let (discovered, graph_files) = if needs_full_graph_files {
(views.filesystem, views.graph)
} else if needs_graph_files {
// The dynamic-import rule traverses the same filesystem-scoped
// visible universe it analyzes. Supplying that universe explicitly
// keeps prepared graph construction strict without a fallback parse.
let graph_files = views.filesystem.clone();
(views.filesystem, graph_files)
} else {
(views.filesystem, Vec::new())
};
// When only filesystem rules are enabled, no TS/JS parsing is needed.
let sources = prepared.visible_paths.source_store_for(&root);
let ((fs_files, facts), facts_duration) = no_mistakes::diagnostics::measure_if_enabled(
"parse",
no_mistakes::diagnostics::TimingKind::Serial,
|| {
if needs_shared_facts {
let fs = if filesystem_rules_enabled {
discovered.clone()
} else {
Vec::new()
};
let f = collect_check_facts_with_graph_files_playwright_sources_and_session(
&session,
&root,
(discovered, graph_files),
plan,
playwright_fact_plan,
std::sync::Arc::clone(&sources),
);
(fs, f)
} else {
(discovered, Default::default())
}
},
);
// Fact collectors stop scheduling work at the deadline. Reject their
// partial maps before rules can turn missing facts into incomplete output.
no_mistakes::invocation::check_timeout()?;

let (react, queues, rules, integration, codebase, filesystem_rules) =
run_domain_checks(DomainCheckInputs {
session: session.clone(),
root: &root,
config_path: &config_path,
tsconfig_path: &tsconfig_path,
react_enabled,
queues_enabled,
integration_enabled,
unique_exports_enabled,
filesystem_rules_enabled,
discovered_files: &fs_files,
facts: &facts,
prepared_playwright: prepared.playwright.as_ref(),
prepared_react: &prepared.react,
prepared_graph: prepared_graph.as_ref(),
dependency_graph: None,
prepared_tsconfig: &prepared.tsconfig,
prepared_tsconfig_catalog: &prepared.tsconfig_catalog,
visible_paths: prepared.visible_paths.as_ref(),
sources: std::sync::Arc::clone(&sources),
inferred_roots: &prepared.inferred_roots,
config,
codebase_config: &prepared.codebase_config,
vitest_projects: prepared.vitest_projects.as_ref(),
});
no_mistakes::invocation::check_timeout()?;

results::finalize_domain_checks(results::FinalizeInput {
root: &root,
config,
filesystem_files: &fs_files,
sources: &sources,
filesystem_rules_enabled,
react_warning: None,
discover_duration,
facts_duration,
completed: complete_domain_checks((
react,
queues,
rules,
integration,
codebase,
filesystem_rules,
))?,
})
}
pub(crate) use run_all::run_all;

#[cfg(test)]
mod tests;
7 changes: 6 additions & 1 deletion crates/no-mistakes/src/check_runner/forbidden_plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub(super) struct PreparedInputs<'a> {
pub(super) codebase_config: &'a no_mistakes::codebase::config::Config,
pub(super) tsconfig: &'a TsConfig,
pub(super) visible_paths: &'a VisiblePathSnapshot,
pub(super) workflow_documents:
Option<&'a std::sync::Arc<no_mistakes::codebase::ci_workflows::ParsedWorkflowSet>>,
}

pub(super) fn prepare(
Expand All @@ -20,7 +22,7 @@ pub(super) fn prepare(
playwright_fact_plan: &mut Option<PlaywrightFactPlan>,
plan: &mut CheckFactPlan,
) -> Result<Option<PreparedGraphConfig>> {
let prepared_graph = graph_plan
let mut prepared_graph = graph_plan
.map(|graph_plan| {
no_mistakes::codebase::dependencies::graph::prepare_graph_config(
root,
Expand All @@ -31,6 +33,9 @@ pub(super) fn prepare(
)
})
.transpose()?;
if let Some(prepared) = prepared_graph.as_mut() {
prepared.set_workflow_documents(inputs.workflow_documents.cloned());
}
if let Some(graph_playwright) = prepared_graph
.as_ref()
.map(|graph| graph.playwright_fact_plan(root, inputs.tsconfig, inputs.visible_paths))
Expand Down
41 changes: 38 additions & 3 deletions crates/no-mistakes/src/check_runner/prepared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ pub(crate) struct PreparedCheckInputs {
pub(crate) visible_paths: Arc<VisiblePathSnapshot>,
pub(crate) inferred_roots: no_mistakes::codebase::config::InferredRoots,
pub(crate) config: NoMistakesConfig,
pub(crate) config_path: Option<std::path::PathBuf>,
pub(crate) codebase_config: no_mistakes::codebase::config::Config,
pub(crate) playwright: Option<no_mistakes::playwright::rules::PreparedPlaywrightRules>,
pub(crate) react: no_mistakes::react_traits::PreparedReactCheck,
pub(crate) tsconfig: no_mistakes::codebase::ts_resolver::TsConfig,
pub(crate) tsconfig_catalog: Arc<no_mistakes::codebase::ts_resolver::TsConfigCatalog>,
pub(crate) vitest_projects: Option<no_mistakes::codebase::rules::PreparedVitestProjectCatalog>,
pub(crate) workflow_documents:
Option<Arc<no_mistakes::codebase::ci_workflows::ParsedWorkflowSet>>,
pub(crate) tsconfig_gate_project_inputs:
Option<no_mistakes::codebase::rules::tsconfig_gate_coverage::ProjectSourceInputs>,
}

pub(super) fn prepare_with_session(
Expand All @@ -27,14 +32,15 @@ pub(super) fn prepare_with_session(
no_mistakes::diagnostics::TimingKind::Serial,
|| session.visible_paths(root),
);
let config = session.config(root, config_path)?;
let (config, effective_config_path) = session.config_with_path(root, config_path)?;
let tsconfig = session.tsconfig(root, tsconfig_path)?;
let workspace = (tsconfig_path.is_none()
|| no_mistakes::playwright::rules::configured(&config))
|| no_mistakes::playwright::rules::configured(&config)
|| config.rule_configured(no_mistakes::codebase::rules::TSCONFIG_GATE_COVERAGE))
.then(|| session.workspace(root));
prepare_from_shared(
root,
config_path,
effective_config_path.as_deref(),
tsconfig_path,
visible_paths,
config.as_ref().clone(),
Expand Down Expand Up @@ -119,15 +125,44 @@ pub(crate) fn prepare_from_shared(
&tsconfig_catalog,
)
});
let workflow_documents = (config
.rule_configured(no_mistakes::codebase::rules::VITEST_CI_PATH_COVERAGE)
|| config.rule_configured(no_mistakes::codebase::rules::TSCONFIG_GATE_COVERAGE)
|| config.rule_configured(no_mistakes::codebase::rules::FORBIDDEN_DEPENDENCIES))
.then(|| {
Arc::new(
no_mistakes::codebase::ci_workflows::ParsedWorkflowSet::load_from_snapshot_and_sources(
root,
&config.ci,
visible_paths.as_ref(),
&sources,
),
)
});
let tsconfig_gate_project_inputs = config
.rule_configured(no_mistakes::codebase::rules::TSCONFIG_GATE_COVERAGE)
.then(|| {
no_mistakes::codebase::rules::tsconfig_gate_coverage::prepare_project_source_inputs(
root,
root_paths.as_ref(),
&sources,
workspace
.as_deref()
.expect("tsconfig gate coverage requires a workspace projection"),
)
});
Ok(PreparedCheckInputs {
visible_paths,
inferred_roots,
config,
config_path: config_path.map(Path::to_path_buf),
codebase_config,
playwright,
react,
tsconfig,
tsconfig_catalog,
vitest_projects,
workflow_documents,
tsconfig_gate_project_inputs,
})
}
Loading
Loading