Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add options to exclude files #238

Merged
merged 11 commits into from
Dec 20, 2024
47 changes: 35 additions & 12 deletions fortitude/src/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use crate::rule_selector::{
use crate::rules::Rule;
use crate::rules::{error::ioerror::IoError, AstRuleEnum, PathRuleEnum, TextRuleEnum};
use crate::settings::{
FixMode, OutputFormat, PatternPrefixPair, PreviewMode, ProgressBar, Settings, UnsafeFixes,
DEFAULT_SELECTORS,
ExcludeMode, FilePattern, FilePatternSet, FixMode, OutputFormat, PatternPrefixPair,
PreviewMode, ProgressBar, Settings, UnsafeFixes, DEFAULT_SELECTORS,
};

use anyhow::{anyhow, Context, Result};
Expand Down Expand Up @@ -154,6 +154,9 @@ pub struct CheckSettings {
pub output_format: OutputFormat,
pub progress_bar: ProgressBar,
pub preview: PreviewMode,
pub exclude: Option<Vec<FilePattern>>,
pub extend_exclude: Vec<FilePattern>,
pub exclude_mode: ExcludeMode,
}

/// Read either fpm.toml or fortitude.toml into our "known good" file
Expand Down Expand Up @@ -192,6 +195,11 @@ fn parse_config_file(config_file: &Option<PathBuf>) -> Result<CheckSettings> {
preview: resolve_bool_arg(value.preview, value.no_preview)
.map(PreviewMode::from)
.unwrap_or_default(),
exclude: value.exclude,
extend_exclude: value.extend_exclude.unwrap_or_default(),
exclude_mode: resolve_bool_arg(value.force_exclude, value.no_force_exclude)
.map(ExcludeMode::from)
.unwrap_or_default(),
},
None => CheckSettings::default(),
};
Expand Down Expand Up @@ -241,9 +249,8 @@ fn ruleset(args: RuleSelection, preview: &PreviewMode) -> anyhow::Result<Vec<Rul
Ok(rules)
}

/// Helper function used with `filter` to select only paths that end in a Fortran extension.
/// Includes non-standard extensions, as these should be reported.
fn filter_fortran_extensions<S: AsRef<str>>(path: &Path, extensions: &[S]) -> bool {
/// Helper function used with `get_files` to select only paths that end in a Fortran extension.
fn is_valid_extension<S: AsRef<str>>(path: &Path, extensions: &[S]) -> bool {
if let Some(ext) = path.extension() {
// Can't use '&[&str].contains()', as extensions are of type OsStr
extensions.iter().any(|x| x.as_ref() == ext)
Expand All @@ -256,7 +263,9 @@ fn filter_fortran_extensions<S: AsRef<str>>(path: &Path, extensions: &[S]) -> bo
fn get_files<P: AsRef<Path>, S: AsRef<str>>(
paths: &[P],
extensions: &[S],
) -> anyhow::Result<Vec<PathBuf>> {
excludes: &FilePatternSet,
exclude_mode: ExcludeMode,
) -> Vec<PathBuf> {
paths
.iter()
.flat_map(|path| {
Expand All @@ -265,15 +274,18 @@ fn get_files<P: AsRef<Path>, S: AsRef<str>>(
.min_depth(1)
.into_iter()
.filter_map(|x| x.ok()) // skip dirs if user doesn't have permission
.filter(|x| filter_fortran_extensions(x.path(), extensions))
.map(|x| std::path::absolute(x.path()))
.filter(|x| {
is_valid_extension(x.path(), extensions) && !excludes.matches(x.path())
})
.map(|x| fs::normalize_path(x.path()))
.collect::<Vec<_>>()
} else if matches!(exclude_mode, ExcludeMode::Force) && excludes.matches(path) {
vec![]
} else {
vec![std::path::absolute(path)]
vec![fs::normalize_path(path)]
}
})
.collect::<Result<Vec<_>, _>>()
.map_err(anyhow::Error::new)
.collect()
}

/// Parse a file, check it for issues, and return the report.
Expand Down Expand Up @@ -718,6 +730,17 @@ pub fn check(args: CheckArgs, global_options: &GlobalConfigArgs) -> Result<ExitC
.collect::<Vec<_>>(),
))?;

let file_excludes = FilePatternSet::try_from_iter(
args.exclude
.unwrap_or(file_settings.exclude.unwrap_or_default())
.into_iter()
.chain(args.extend_exclude.unwrap_or_default().into_iter())
.chain(file_settings.extend_exclude.into_iter()),
)?;
let exclude_mode = resolve_bool_arg(args.force_exclude, args.no_force_exclude)
.map(ExcludeMode::from)
.unwrap_or(file_settings.exclude_mode);

let output_format = args.output_format.unwrap_or(file_settings.output_format);
let preview_mode = resolve_bool_arg(args.preview, args.no_preview)
.map(PreviewMode::from)
Expand Down Expand Up @@ -762,7 +785,7 @@ pub fn check(args: CheckArgs, global_options: &GlobalConfigArgs) -> Result<ExitC
let text_rules = rules_to_text_rules(&rules);
let ast_entrypoints = ast_entrypoint_map(&rules);

let files = get_files(files, file_extensions)?;
let files = get_files(files, file_extensions, &file_excludes, exclude_mode);
let file_digits = files.len().to_string().len();
let progress_bar_style = match progress_bar {
ProgressBar::Fancy => {
Expand Down
143 changes: 98 additions & 45 deletions fortitude/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use serde::Deserialize;
use std::path::PathBuf;

use crate::{
build, rule_selector::RuleSelector, settings::OutputFormat, settings::PatternPrefixPair,
settings::ProgressBar, RuleSelectorParser,
build, rule_selector::RuleSelector, settings::FilePattern, settings::OutputFormat,
settings::PatternPrefixPair, settings::ProgressBar, RuleSelectorParser,
};

/// Default extensions to check
Expand All @@ -31,6 +31,7 @@ pub struct GlobalConfigArgs {
pub config_file: Option<PathBuf>,
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand, Clone, PartialEq)]
pub enum SubCommands {
Check(CheckArgs),
Expand Down Expand Up @@ -60,68 +61,28 @@ pub struct CheckArgs {
/// are included in the search.
#[arg(default_value = ".")]
pub files: Option<Vec<PathBuf>>,
/// Comma-separated list of rules to ignore.
#[arg(
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
help_heading = "Rule selection",
hide_possible_values = true
)]
pub ignore: Option<Vec<RuleSelector>>,
/// Comma-separated list of rule codes to enable (or ALL, to enable all rules).
#[arg(
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
help_heading = "Rule selection",
hide_possible_values = true
)]
pub select: Option<Vec<RuleSelector>>,
/// Like --select, but adds additional rule codes on top of those already specified.
#[arg(
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
help_heading = "Rule selection",
hide_possible_values = true
)]
pub extend_select: Option<Vec<RuleSelector>>,
/// List of mappings from file pattern to code to exclude.
#[arg(long, value_delimiter = ',', help_heading = "Rule selection")]
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,
/// Like `--per-file-ignores`, but adds additional ignores on top of those already specified.
#[arg(long, value_delimiter = ',', help_heading = "Rule selection")]
pub extend_per_file_ignores: Option<Vec<PatternPrefixPair>>,

/// Set the maximum allowable line length.
#[arg(long, default_value = "100")]
pub line_length: Option<usize>,
/// File extensions to check
#[arg(long, value_delimiter = ',', default_values = FORTRAN_EXTS)]
pub file_extensions: Option<Vec<String>>,

/// Apply fixes to resolve lint violations.
/// Use `--no-fix` to disable or `--unsafe-fixes` to include unsafe fixes.
#[arg(long, overrides_with("no_fix"), action = clap::ArgAction::SetTrue)]
pub fix: Option<bool>,
#[clap(long, overrides_with("fix"), hide = true, action = SetTrue)]
pub no_fix: Option<bool>,

/// Include fixes that may not retain the original intent of the code.
/// Use `--no-unsafe-fixes` to disable.
#[arg(long, overrides_with("no_unsafe_fixes"), action = SetTrue)]
pub unsafe_fixes: Option<bool>,
#[arg(long, overrides_with("unsafe_fixes"), hide = true, action = SetTrue)]
pub no_unsafe_fixes: Option<bool>,

/// Show an enumeration of all fixed lint violations.
/// Use `--no-show-fixes` to disable.
#[arg(long, overrides_with("no_show_fixes"), action = SetTrue)]
pub show_fixes: Option<bool>,
#[clap(long, overrides_with("show_fixes"), hide = true, action = SetTrue)]
pub no_show_fixes: Option<bool>,

/// Apply fixes to resolve lint violations, but don't report on, or exit non-zero for, leftover violations. Implies `--fix`.
/// Use `--no-fix-only` to disable or `--unsafe-fixes` to include unsafe fixes.
#[arg(long, overrides_with("no_fix_only"), action = SetTrue)]
Expand All @@ -145,4 +106,96 @@ pub struct CheckArgs {
/// Options are "off" (default), "ascii", and "fancy"
#[arg(long, value_enum)]
pub progress_bar: Option<ProgressBar>,

// Rule selection
/// Comma-separated list of rules to ignore.
#[arg(
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
help_heading = "Rule selection",
hide_possible_values = true
)]
pub ignore: Option<Vec<RuleSelector>>,

/// Comma-separated list of rule codes to enable (or ALL, to enable all rules).
#[arg(
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
help_heading = "Rule selection",
hide_possible_values = true
)]
pub select: Option<Vec<RuleSelector>>,

/// Like --select, but adds additional rule codes on top of those already specified.
#[arg(
long,
value_delimiter = ',',
value_name = "RULE_CODE",
value_parser = RuleSelectorParser,
help_heading = "Rule selection",
hide_possible_values = true
)]
pub extend_select: Option<Vec<RuleSelector>>,

/// List of mappings from file pattern to code to exclude.
#[arg(
long,
value_delimiter = ',',
value_name = "FILE_PATTERN:RULE_CODE",
help_heading = "Rule selection"
)]
pub per_file_ignores: Option<Vec<PatternPrefixPair>>,

/// Like `--per-file-ignores`, but adds additional ignores on top of those already specified.
#[arg(
long,
value_delimiter = ',',
value_name = "FILE_PATTERN:RULE_CODE",
help_heading = "Rule selection"
)]
pub extend_per_file_ignores: Option<Vec<PatternPrefixPair>>,

// File selection
/// File extensions to check
#[arg(
long,
value_delimiter = ',',
default_values = FORTRAN_EXTS,
help_heading = "File selection"
)]
pub file_extensions: Option<Vec<String>>,

/// List of paths, used to omit files and/or directories from analysis.
#[arg(
long,
value_delimiter = ',',
value_name = "FILE_PATTERN",
help_heading = "File selection"
)]
pub exclude: Option<Vec<FilePattern>>,

/// Like --exclude, but adds additional files and directories on top of those already excluded.
#[arg(
long,
value_delimiter = ',',
value_name = "FILE_PATTERN",
help_heading = "File selection"
)]
pub extend_exclude: Option<Vec<FilePattern>>,

/// Enforce exclusions, even for paths passed to Ruff directly on the command-line.
LiamPattinson marked this conversation as resolved.
Show resolved Hide resolved
/// Use `--no-force_exclude` to disable.
#[arg(long, overrides_with("no_force_exclude"), action = SetTrue)]
pub force_exclude: Option<bool>,
#[clap(long, overrides_with("force_exclude"), hide = true, action = SetTrue)]
pub no_force_exclude: Option<bool>,

// Options for individual rules
/// Set the maximum allowable line length.
#[arg(long, help_heading = "Per-Rule Options", default_value = "100")]
pub line_length: Option<usize>,
}
Loading
Loading