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
2 changes: 1 addition & 1 deletion crates/fensu-cli/src/catalogue/data/catalogue.json

Large diffs are not rendered by default.

33 changes: 25 additions & 8 deletions crates/fensu-cli/src/check/_helpers/evaluation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ use crate::check::_helpers::policy::{
};
use crate::check::_helpers::project::{observe, project_plane};
use crate::check::_helpers::rule_policy::{
applicable, display_codes_by_implementation, resolved_thresholds, selected_rules,
validate_config_tiers, validate_unique_implementations,
applicable, display_codes_by_implementation, required_thresholds, resolved_thresholds,
selected_rules, validate_config_tiers, validate_unique_implementations,
};
use crate::check::models::WebCustomRulePayload;
use crate::check::models::{CheckResult, EvaluationRequest, TargetEvaluation};
use crate::check::models::{CustomRuleSubject, RustCustomRulePayload};
use crate::check::models::{CustomRuleSubject, RustCustomRulePayload, RustThresholds};
use crate::check::repository_custom_facts::python_repository_fact_payload;
use crate::check::web_custom_facts::web_fact_payload;
use crate::check::web_policy::{self, WebPolicyRequest};
Expand Down Expand Up @@ -340,20 +340,37 @@ fn evaluate_rust_target(request: EvaluationRequest<'_>) -> Result<TargetEvaluati
.filter(|(code, _)| !code.starts_with('X'))
.map(|(code, options)| (code.clone(), options.clone()))
.collect::<toml::map::Map<_, _>>();
let thresholds = RustThresholds {
config,
project_root,
required: required_thresholds(
&all_rules
.iter()
.map(|rule| rule.code.clone())
.collect::<Vec<_>>(),
)?,
uses: std::cell::RefCell::new(Ok(Vec::new())),
};
let analysis = if config.ownership_roots.is_empty() {
fensu_rust::engine::main::analyze_repository::analyze_repository(
project_root,
Some(&native_options),
&config.tooling,
&thresholds,
)?
} else {
fensu_rust::engine::main::analyze_repository_with_ownership_roots::analyze_repository_with_ownership_roots(
project_root,
Some(&native_options),
&config.tooling,
&config.resolved_ownership_roots.iter().map(|root| root.path.clone()).collect::<Vec<_>>(),
fensu_rust::models::RepositoryAnalysisRequest {
repository_root: project_root,
options: Some(&native_options),
tooling: &config.tooling,
ownership_roots: &config.resolved_ownership_roots.iter().map(|root| root.path.clone()).collect::<Vec<_>>(),
thresholds: &thresholds,
},
)?
};
let mut threshold_uses = thresholds.uses.into_inner()?;
threshold_uses.sort();
if let Some(diagnostic) = analysis
.diagnostics
.iter()
Expand Down Expand Up @@ -509,7 +526,7 @@ fn evaluate_rust_target(request: EvaluationRequest<'_>) -> Result<TargetEvaluati
.count(),
excluded,
applied_exceptions: applied,
threshold_uses: Vec::new(),
threshold_uses,
cacheable: Some(cacheable),
},
repository_facts,
Expand Down
30 changes: 22 additions & 8 deletions crates/fensu-cli/src/check/_helpers/policy.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::{BTreeMap, HashSet};
use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::fs;
use std::path::{Component, Path, PathBuf};

Expand Down Expand Up @@ -210,12 +210,11 @@ fn wildcard_matches(path: &str, pattern: &str) -> bool {

pub(crate) fn check_identity(request: CheckIdentityRequest<'_>) -> Result<String, String> {
let CheckIdentityRequest {
root,
project_root,
config,
sources,
project_inputs,
warnings,
..
} = request;
let mut digest = Sha256::new();
digest.update(b"fensu-native-check-v6\0");
Expand Down Expand Up @@ -265,7 +264,7 @@ pub(crate) fn check_identity(request: CheckIdentityRequest<'_>) -> Result<String
digest_text(&mut digest, &input.fingerprint);
digest.update([u8::from(input.present)]);
}
digest_project_observations(&mut digest, root, project_root, config)?;
digest_project_observations(&mut digest, request)?;
Ok(format!("{:x}", digest.finalize()))
}

Expand All @@ -276,10 +275,19 @@ fn digest_text(digest: &mut Sha256, value: &str) {

fn digest_project_observations(
digest: &mut Sha256,
root: &Path,
project_root: &Path,
config: &Config,
request: CheckIdentityRequest<'_>,
) -> Result<(), String> {
let CheckIdentityRequest {
root,
project_root,
config,
sources,
..
} = request;
let selected: BTreeSet<&str> = sources
.iter()
.map(|source| source.repository_path.as_str())
.collect();
let mut entries: BTreeMap<String, (u8, PathBuf)> = BTreeMap::new();
for configured_root in config
.roots
Expand Down Expand Up @@ -335,7 +343,13 @@ fn digest_project_observations(
for (path, (kind, filesystem_path)) in entries {
digest.update(path.as_bytes());
digest.update([kind]);
if filesystem_path.extension().and_then(|value| value.to_str()) == Some("pyi") {
let unselected_python = kind == b'f'
&& !selected.contains(path.as_str())
&& matches!(
filesystem_path.extension().and_then(|value| value.to_str()),
Some("py" | "pyi")
);
if unselected_python {
let content = fs::read(&filesystem_path).map_err(|error| {
format!(
"Could not fingerprint Python support file {}: {error}",
Expand Down
15 changes: 15 additions & 0 deletions crates/fensu-cli/src/check/_helpers/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ pub(crate) fn observe(
"directory_entries" => directory_entries(&path, root)?,
"glob" => glob_answers(&path, root, &query.argument)?,
"python_anchor" => python_anchor(&path, root)?.into_iter().collect(),
"code_lines" => code_lines(&path, programs.get(query.path.as_str()).copied()),
"public_facade" => {
vec![bool_text(programs.get(query.path.as_str()).is_some_and(
|program| program.is_public_facade(&query.argument),
Expand All @@ -271,6 +272,20 @@ pub(crate) fn observe(
Ok(answers)
}

fn code_lines(path: &Path, program: Option<&ProgramHandle>) -> Vec<String> {
if let Some(program) = program {
return vec![program.code_line_count().to_string()];
}
let Ok(source) = fs::read_to_string(path) else {
return Vec::new();
};
let version = python_version();
match ProgramHandle::parse_source(&source, version.major, version.minor) {
Ok(program) => vec![program.code_line_count().to_string()],
Err(_) => Vec::new(),
}
}

pub(crate) fn directory_entries(path: &Path, root: &Path) -> Result<Vec<String>, String> {
let mut entries: Vec<String> = Vec::new();
if !path.exists() {
Expand Down
104 changes: 80 additions & 24 deletions crates/fensu-cli/src/check/_helpers/rule_policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use crate::catalogue::main::rule_metadata::rule_metadata;
use crate::catalogue::main::validate_config_selectors::validate_config_selectors;
use crate::catalogue::models::RuleMetadata;
use crate::check::_helpers::policy::{path_matches, role};
use crate::check::models::RustThresholds;
use crate::configuration::main::configuration_directory::configuration_directory;
use crate::configuration::main::expand_path_pattern::expand_path_pattern;
use crate::models::{Config, ScopedSource, ThresholdUse};

Expand Down Expand Up @@ -143,39 +145,93 @@ pub(crate) fn resolved_thresholds(
}
let mut uses: Vec<ThresholdUse> = Vec::new();
for name in required_thresholds(codes)? {
let mut winner = None;
for (order, override_) in config.threshold_overrides.iter().enumerate() {
let Some(value) = override_.thresholds.get(name) else {
continue;
};
for (pattern_order, pattern) in override_.paths.iter().enumerate() {
let Some(specificity) = matching_path_specificity(&source.target_path, pattern)?
else {
continue;
};
let rank = (specificity, order, pattern_order);
if winner
.as_ref()
.is_none_or(|(current, _, _, _, _)| rank >= *current)
{
winner = Some((rank, order, pattern, &override_.reason, *value));
}
let Some(use_) =
path_threshold_use(config, &source.target_path, &source.repository_path, name)?
else {
continue;
};
values.insert(name.to_owned(), use_.effective_value);
uses.push(use_);
}
Ok((values, uses))
}

impl fensu_rust::types::ThresholdLookup for RustThresholds<'_> {
/// Resolve a threshold, recording an applied override only for selected rules.
fn threshold(&self, name: &str, path: &str) -> Option<usize> {
rust_threshold(self, name, path)
}
}

fn rust_threshold(thresholds: &RustThresholds<'_>, name: &str, path: &str) -> Option<usize> {
let RustThresholds {
config,
project_root,
required,
uses,
} = thresholds;
let configured = config.thresholds.get(name).map(|value| *value as usize);
if !required.contains(name) {
return configured;
}
let directory = configuration_directory(project_root, &config.target_root);
let repository_path = project_root
.join(path)
.strip_prefix(&directory)
.map_or_else(
|_| path.to_owned(),
|relative| relative.to_string_lossy().replace('\\', "/"),
);
match path_threshold_use(config, path, &repository_path, name) {
Ok(Some(use_)) => {
let value = use_.effective_value as usize;
if let Ok(recorded) = uses.borrow_mut().as_mut() {
recorded.push(use_);
}
Some(value)
}
Ok(None) => configured,
Err(error) => {
*uses.borrow_mut() = Err(error);
configured
}
let Some((_, order, pattern, reason, value)) = winner else {
}
}

fn path_threshold_use(
config: &Config,
target_path: &str,
repository_path: &str,
name: &str,
) -> Result<Option<ThresholdUse>, String> {
let mut winner = None;
for (order, override_) in config.threshold_overrides.iter().enumerate() {
let Some(value) = override_.thresholds.get(name) else {
continue;
};
values.insert(name.to_owned(), value);
uses.push(ThresholdUse {
repository_path: source.repository_path.clone(),
for (pattern_order, pattern) in override_.paths.iter().enumerate() {
let Some(specificity) = matching_path_specificity(target_path, pattern)? else {
continue;
};
let rank = (specificity, order, pattern_order);
if winner
.as_ref()
.is_none_or(|(current, _, _, _, _)| rank >= *current)
{
winner = Some((rank, order, pattern, &override_.reason, *value));
}
}
}
Ok(
winner.map(|(_, order, pattern, reason, value)| ThresholdUse {
repository_path: repository_path.to_owned(),
threshold: name.to_owned(),
override_order: order,
matched_pattern: pattern.clone(),
reason: reason.clone(),
effective_value: value,
});
}
Ok((values, uses))
}),
)
}

pub(crate) fn applicable(rule: &RuleMetadata, config: &Config) -> bool {
Expand Down
13 changes: 12 additions & 1 deletion crates/fensu-cli/src/check/models.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Prepared check state and cache cleanup plans.

use std::cell::RefCell;
use std::collections::HashSet;
use std::path::Path;
use std::path::PathBuf;

Expand All @@ -11,7 +13,7 @@ use serde::{Deserialize, Serialize};
use crate::analyzer::AnalyzerId;
use crate::models::{Config, Fault, ProjectInput, ScopedSource, ThresholdUse};

#[derive(Debug)]
#[derive(Clone, Copy, Debug)]
pub(crate) struct CheckIdentityRequest<'a> {
pub(crate) root: &'a Path,
pub(crate) project_root: &'a Path,
Expand Down Expand Up @@ -141,6 +143,15 @@ pub(crate) struct EvaluationRequest<'a> {
pub(crate) collect_repository_facts: bool,
}

/// Rust analyzer threshold resolution and the overrides it applied for selected rules.
#[derive(Debug)]
pub(crate) struct RustThresholds<'a> {
pub(crate) config: &'a Config,
pub(crate) project_root: &'a Path,
pub(crate) required: HashSet<&'static str>,
pub(crate) uses: RefCell<Result<Vec<ThresholdUse>, String>>,
}

#[derive(Debug)]
pub(crate) struct TargetEvaluation {
pub(crate) result: CheckResult,
Expand Down
2 changes: 2 additions & 0 deletions crates/fensu-cli/src/configuration/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub(crate) const DEFAULT_THRESHOLDS: &[(&str, u32)] = &[
("max_file_lines", 2000),
("max_helpers_container_modules", 10),
("max_main_container_modules", 20),
("max_helpers_container_buckets", 15),
("max_crate_code_lines", 75000),
("max_role_depth", 1),
("max_positional_args", 1),
("max_arguments", 10),
Expand Down
19 changes: 18 additions & 1 deletion crates/fensu-cli/src/skills/_helpers/content/rendering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,15 @@ fn repository_lines(context: &SkillContext) -> Result<Vec<String>, String> {
.get("max_role_depth")
.copied()
.ok_or_else(|| "Effective configuration has no max_role_depth threshold.".to_owned())?;
let bucket_limit = role_threshold(context, "helpers", "max_helpers_container_buckets")?;
let code_line_limit = context
.config
.thresholds
.get("max_crate_code_lines")
.copied()
.ok_or_else(|| {
"Effective configuration has no max_crate_code_lines threshold.".to_owned()
})?;
let import_root = context
.config
.roots
Expand All @@ -385,7 +394,7 @@ fn repository_lines(context: &SkillContext) -> Result<Vec<String>, String> {
.and_then(|name| name.to_str())
.unwrap_or(".");
let mut profile = profile_lines("repository")?;
for code in ["FFR403", "FFR310"] {
for code in ["FFR403", "FFR310", "FFR312", "FFR602"] {
if !active.contains(code) {
let prefix = format!("Enforced by {code}:");
profile.retain(|line| !line.starts_with(&prefix));
Expand Down Expand Up @@ -464,6 +473,14 @@ fn repository_lines(context: &SkillContext) -> Result<Vec<String>, String> {
"Configured base `max_role_depth` is 1.",
&format!("Configured base `max_role_depth` is {role_depth}."),
)
.replace(
"configured base `max_helpers_container_buckets` is 15.",
&format!("configured base `max_helpers_container_buckets` is {bucket_limit}."),
)
.replace(
"configured base `max_crate_code_lines` is 75000.",
&format!("configured base `max_crate_code_lines` is {code_line_limit}."),
)
})
.collect())
}
Expand Down
2 changes: 1 addition & 1 deletion crates/fensu-cli/src/skills/data/skills_invariant.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion crates/fensu-cli/src/tests/test_rule_catalogue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::tests::test_types::{
fn given_every_core_rule_when_rendering_then_complete_authored_metadata_is_visible() {
let test_cases = [CoreRuleRenderingTestCase {
description: "every native core registration renders the complete stable metadata frame",
expected_core_count: 116,
expected_core_count: 118,
expected_labels: &[
"Authored metadata:",
"Family:",
Expand Down
2 changes: 2 additions & 0 deletions crates/fensu-cli/tests/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ mod test_check_repository_custom;
mod test_check_rust;
#[path = "command/test_check_rust_exceptions.rs"]
mod test_check_rust_exceptions;
#[path = "command/test_check_size_limits.rs"]
mod test_check_size_limits;
#[path = "command/test_check_validation.rs"]
mod test_check_validation;
#[path = "command/test_check_web.rs"]
Expand Down
Loading
Loading