use anyhow::{Context, Result};
use toml::{Table, Value};
use crate::make_reader::{RecipeCommand, make_assignment};
pub fn disallowed_method_paths(configuration: &str) -> Result<Vec<String>> {
let configuration: Table = configuration
.parse()
.context("parse the Clippy configuration")?;
let methods = configuration
.get("disallowed-methods")
.and_then(Value::as_array)
.context("clippy.toml should declare disallowed-methods")?;
Ok(methods
.iter()
.filter_map(|method| method.get("path").and_then(Value::as_str))
.map(str::to_owned)
.collect())
}
pub fn clippy_lint_level(
manifest: &str,
workspace_manifest: &str,
lint: &str,
) -> Result<Option<String>> {
let manifest: Table = manifest.parse().context("parse the package manifest")?;
let inherits = manifest
.get("lints")
.and_then(|lints| lints.get("workspace"))
.and_then(Value::as_bool)
.unwrap_or(false);
let table = if inherits {
let workspace: Table = workspace_manifest
.parse()
.context("parse the workspace manifest")?;
workspace
.get("workspace")
.and_then(|workspace| workspace.get("lints"))
.and_then(|lints| lints.get("clippy"))
.cloned()
} else {
manifest
.get("lints")
.and_then(|lints| lints.get("clippy"))
.cloned()
};
Ok(table
.as_ref()
.and_then(|table| table.get(lint))
.and_then(|entry| {
entry
.as_str()
.or_else(|| entry.get("level").and_then(Value::as_str))
})
.map(str::to_owned))
}
pub fn status_masking_construct(command: &RecipeCommand) -> Option<&'static str> {
if command.ignores_errors {
return Some("carries Make's `-` prefix, so its failure is ignored");
}
if command.text.contains(';') {
return Some("chains another command with `;`, so only the last status is reported");
}
if command
.text
.split("||")
.skip(1)
.any(|fallback| fallback.trim() != "exit 1")
{
return Some("has a `||` fallback other than `exit 1`, which substitutes a success");
}
if command.text.replace("||", "").contains('|') {
return Some("is piped, so the reported status is the last stage's");
}
None
}
pub fn covers_package(command: &str, manifest: &str) -> bool {
if command.contains("--workspace") {
return true;
}
match manifest {
"Cargo.toml" => !command.contains("--manifest-path"),
_ => command.contains(&format!("--manifest-path {manifest}")),
}
}
fn is_cargo_path(text: &str) -> bool { text == "cargo" || text.ends_with("/cargo") }
fn value_names_cargo(value: &str) -> bool {
value
.split([' ', '\t', ',', '(', ')'])
.any(|piece| is_cargo_path(piece.trim()))
}
pub fn is_cargo_clippy_invocation(makefile: &str, command: &str) -> bool {
let mut words = command.split_whitespace();
let Some(executable) = words.next() else {
return false;
};
let names_cargo = match executable
.strip_prefix("$(")
.and_then(|r| r.strip_suffix(')'))
{
Some(variable) => make_assignment(makefile, variable).is_some_and(value_names_cargo),
None => is_cargo_path(executable),
};
names_cargo && words.find(|word| !word.starts_with('+')) == Some("clippy")
}