Derive Hash instead of implementing it by hand

The caching mechanism of the CLI (ruff_cli::cache) relies on
ruff::settings::Settings implementing the Hash trait.

The ruff::settings::Settings struct previously couldn't automatically
derive the Hash implementation via the #[derive(Hash)] macro attribute
since some of its field types intentionally[1][2] don't implement Hash
(namely regex::Regex, globset::GlobMatcher and globset::GlobSet and
HashMap and HashSet from the standard library).

The code therefore previously implemented the Hash trait by hand for the
whole struct. Implementing Hash by hand for structs that are subject to
change is a bad idea since it's very easy to forget to update the Hash
implementation when adding a new field to the struct. And the Hash
implementation indeed was already incorrect by omitting several fields
from the hash.

This commit introduces wrapper types for Regex, GlobMatcher, GlobSet,
HashSet & HashMap that implement Hash so that we can still add
#[derive(Hash)] to the Settings struct, guaranteeing a correct hash
implementation.

[1]: https://github.com/rust-lang/regex/issues/364#issuecomment-301082076
[2]: The standard library doesn't impl<T: Hash + Ord> Hash for HashSet<T>
     presumably since sorted() requires an allocation and Hash
     implementations are generally expected to work without allocations.
This commit is contained in:
Martin Fischer
2023-01-15 08:21:06 +01:00
committed by Charlie Marsh
parent 42031b8574
commit 8c18b28bc4
11 changed files with 279 additions and 148 deletions

View File

@@ -4,11 +4,11 @@ use std::io::{BufReader, Read};
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Result};
use globset::GlobMatcher;
use path_absolutize::{path_dedot, Absolutize};
use rustc_hash::FxHashSet;
use crate::registry::RuleCode;
use crate::settings::hashable::{HashableGlobMatcher, HashableHashSet};
/// Extract the absolute path and basename (as strings) from a Path.
pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
@@ -26,7 +26,11 @@ pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
/// Create a set with codes matching the pattern/code pairs.
pub(crate) fn ignores_from_path<'a>(
path: &Path,
pattern_code_pairs: &'a [(GlobMatcher, GlobMatcher, FxHashSet<RuleCode>)],
pattern_code_pairs: &'a [(
HashableGlobMatcher,
HashableGlobMatcher,
HashableHashSet<RuleCode>,
)],
) -> Result<FxHashSet<&'a RuleCode>> {
let (file_path, file_basename) = extract_path_names(path)?;
Ok(pattern_code_pairs
@@ -34,7 +38,7 @@ pub(crate) fn ignores_from_path<'a>(
.filter(|(absolute, basename, _)| {
basename.is_match(file_basename) || absolute.is_match(file_path)
})
.flat_map(|(_, _, codes)| codes)
.flat_map(|(_, _, codes)| codes.iter())
.collect())
}

View File

@@ -9,6 +9,7 @@ use regex::Regex;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::registry::{Diagnostic, RuleCode, CODE_REDIRECTS};
use crate::settings::hashable::HashableHashSet;
use crate::source_code::LineEnding;
static NOQA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
@@ -84,7 +85,7 @@ pub fn add_noqa(
diagnostics: &[Diagnostic],
contents: &str,
noqa_line_for: &IntMap<usize, usize>,
external: &FxHashSet<String>,
external: &HashableHashSet<String>,
line_ending: &LineEnding,
) -> Result<usize> {
let (count, output) =
@@ -97,7 +98,7 @@ fn add_noqa_inner(
diagnostics: &[Diagnostic],
contents: &str,
noqa_line_for: &IntMap<usize, usize>,
external: &FxHashSet<String>,
external: &HashableHashSet<String>,
line_ending: &LineEnding,
) -> (usize, String) {
let mut matches_by_line: FxHashMap<usize, FxHashSet<&RuleCode>> = FxHashMap::default();
@@ -208,12 +209,12 @@ fn add_noqa_inner(
#[cfg(test)]
mod tests {
use nohash_hasher::IntMap;
use rustc_hash::FxHashSet;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::noqa::{add_noqa_inner, NOQA_LINE_REGEX};
use crate::registry::Diagnostic;
use crate::settings::hashable::HashableHashSet;
use crate::source_code::LineEnding;
use crate::violations;
@@ -236,7 +237,7 @@ mod tests {
let diagnostics = vec![];
let contents = "x = 1";
let noqa_line_for = IntMap::default();
let external = FxHashSet::default();
let external = HashableHashSet::default();
let (count, output) = add_noqa_inner(
&diagnostics,
contents,
@@ -253,7 +254,7 @@ mod tests {
)];
let contents = "x = 1";
let noqa_line_for = IntMap::default();
let external = FxHashSet::default();
let external = HashableHashSet::default();
let (count, output) = add_noqa_inner(
&diagnostics,
contents,
@@ -276,7 +277,7 @@ mod tests {
];
let contents = "x = 1 # noqa: E741\n";
let noqa_line_for = IntMap::default();
let external = FxHashSet::default();
let external = HashableHashSet::default();
let (count, output) = add_noqa_inner(
&diagnostics,
contents,
@@ -299,7 +300,7 @@ mod tests {
];
let contents = "x = 1 # noqa";
let noqa_line_for = IntMap::default();
let external = FxHashSet::default();
let external = HashableHashSet::default();
let (count, output) = add_noqa_inner(
&diagnostics,
contents,

View File

@@ -1,13 +1,14 @@
//! Settings for import conventions.
use std::hash::{Hash, Hasher};
use std::hash::Hash;
use itertools::Itertools;
use ruff_macros::ConfigurationOptions;
use rustc_hash::FxHashMap;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::settings::hashable::HashableHashMap;
const CONVENTIONAL_ALIASES: &[(&str, &str)] = &[
("altair", "alt"),
("matplotlib.pyplot", "plt"),
@@ -55,17 +56,9 @@ pub struct Options {
pub extend_aliases: Option<FxHashMap<String, String>>,
}
#[derive(Debug)]
#[derive(Debug, Hash)]
pub struct Settings {
pub aliases: FxHashMap<String, String>,
}
impl Hash for Settings {
fn hash<H: Hasher>(&self, state: &mut H) {
for value in self.aliases.iter().sorted() {
value.hash(state);
}
}
pub aliases: HashableHashMap<String, String>,
}
fn default_aliases() -> FxHashMap<String, String> {
@@ -89,7 +82,7 @@ fn resolve_aliases(options: Options) -> FxHashMap<String, String> {
impl Default for Settings {
fn default() -> Self {
Self {
aliases: default_aliases(),
aliases: default_aliases().into(),
}
}
}
@@ -97,7 +90,7 @@ impl Default for Settings {
impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
aliases: resolve_aliases(options),
aliases: resolve_aliases(options).into(),
}
}
}
@@ -105,7 +98,7 @@ impl From<Options> for Settings {
impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
aliases: Some(settings.aliases),
aliases: Some(settings.aliases.into()),
extend_aliases: None,
}
}

View File

@@ -64,7 +64,8 @@ mod tests {
msg: "Use typing_extensions.TypedDict instead.".to_string(),
},
),
]),
])
.into(),
..Default::default()
},
..Settings::for_rules(vec![RuleCode::TID251])

View File

@@ -1,10 +1,10 @@
use rustc_hash::FxHashMap;
use rustpython_ast::{Alias, Expr, Located, Stmt};
use super::settings::{BannedApi, Strictness};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::registry::Diagnostic;
use crate::settings::hashable::HashableHashMap;
use crate::violations;
/// TID252
@@ -31,7 +31,7 @@ pub fn banned_relative_import(
pub fn name_is_banned(
module: &str,
name: &Alias,
banned_apis: &FxHashMap<String, BannedApi>,
banned_apis: &HashableHashMap<String, BannedApi>,
) -> Option<Diagnostic> {
let full_name = format!("{module}.{}", &name.node.name);
if let Some(ban) = banned_apis.get(&full_name) {
@@ -50,7 +50,7 @@ pub fn name_is_banned(
pub fn name_or_parent_is_banned<T>(
located: &Located<T>,
name: &str,
banned_apis: &FxHashMap<String, BannedApi>,
banned_apis: &HashableHashMap<String, BannedApi>,
) -> Option<Diagnostic> {
let mut name = name;
loop {
@@ -75,7 +75,7 @@ pub fn name_or_parent_is_banned<T>(
/// TID251
pub fn banned_attribute_access(checker: &mut Checker, expr: &Expr) {
if let Some(call_path) = checker.resolve_call_path(expr) {
for (banned_path, ban) in &checker.settings.flake8_tidy_imports.banned_api {
for (banned_path, ban) in checker.settings.flake8_tidy_imports.banned_api.iter() {
if call_path == banned_path.split('.').collect::<Vec<_>>() {
checker.diagnostics.push(Diagnostic::new(
violations::BannedApi {

View File

@@ -1,13 +1,14 @@
//! Settings for the `flake8-tidy-imports` plugin.
use std::hash::{Hash, Hasher};
use std::hash::Hash;
use itertools::Itertools;
use ruff_macros::ConfigurationOptions;
use rustc_hash::FxHashMap;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::settings::hashable::HashableHashMap;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub enum Strictness {
@@ -59,17 +60,17 @@ pub struct Options {
pub banned_api: Option<FxHashMap<String, BannedApi>>,
}
#[derive(Debug)]
#[derive(Debug, Hash)]
pub struct Settings {
pub ban_relative_imports: Strictness,
pub banned_api: FxHashMap<String, BannedApi>,
pub banned_api: HashableHashMap<String, BannedApi>,
}
impl Default for Settings {
fn default() -> Self {
Self {
ban_relative_imports: Strictness::Parents,
banned_api: FxHashMap::default(),
banned_api: HashableHashMap::default(),
}
}
}
@@ -78,7 +79,7 @@ impl From<Options> for Settings {
fn from(options: Options) -> Self {
Self {
ban_relative_imports: options.ban_relative_imports.unwrap_or(Strictness::Parents),
banned_api: options.banned_api.unwrap_or_default(),
banned_api: options.banned_api.unwrap_or_default().into(),
}
}
}
@@ -87,17 +88,7 @@ impl From<Settings> for Options {
fn from(settings: Settings) -> Self {
Self {
ban_relative_imports: Some(settings.ban_relative_imports),
banned_api: Some(settings.banned_api),
}
}
}
impl Hash for Settings {
fn hash<H: Hasher>(&self, state: &mut H) {
self.ban_relative_imports.hash(state);
for key in self.banned_api.keys().sorted() {
key.hash(state);
self.banned_api[key].hash(state);
banned_api: Some(settings.banned_api.into()),
}
}
}

View File

@@ -121,7 +121,7 @@ mod tests {
let diagnostics = test_path(
Path::new("./resources/test/fixtures/pyflakes/F841_0.py"),
&settings::Settings {
dummy_variable_rgx: Regex::new(r"^z$").unwrap(),
dummy_variable_rgx: Regex::new(r"^z$").unwrap().into(),
..settings::Settings::for_rule(RuleCode::F841)
},
)?;

View File

@@ -31,7 +31,7 @@ mod tests {
let diagnostics = test_path(
Path::new("./resources/test/fixtures/ruff/confusables.py"),
&settings::Settings {
allowed_confusables: FxHashSet::from_iter(['−', 'ρ', '∗']),
allowed_confusables: FxHashSet::from_iter(['−', 'ρ', '∗']).into(),
..settings::Settings::for_rules(vec![
RuleCode::RUF001,
RuleCode::RUF002,

176
src/settings/hashable.rs Normal file
View File

@@ -0,0 +1,176 @@
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
use globset::{GlobMatcher, GlobSet};
use itertools::Itertools;
use regex::Regex;
use rustc_hash::{FxHashMap, FxHashSet};
use super::types::FilePattern;
#[derive(Debug)]
pub struct HashableRegex(Regex);
impl Hash for HashableRegex {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.as_str().hash(state);
}
}
impl From<Regex> for HashableRegex {
fn from(regex: Regex) -> Self {
Self(regex)
}
}
impl Deref for HashableRegex {
type Target = Regex;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug)]
pub struct HashableGlobMatcher(GlobMatcher);
impl From<GlobMatcher> for HashableGlobMatcher {
fn from(matcher: GlobMatcher) -> Self {
Self(matcher)
}
}
impl Deref for HashableGlobMatcher {
type Target = GlobMatcher;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Hash for HashableGlobMatcher {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.glob().hash(state);
}
}
#[derive(Debug)]
pub struct HashableGlobSet {
patterns: Vec<FilePattern>,
globset: GlobSet,
}
impl HashableGlobSet {
pub fn new(patterns: Vec<FilePattern>) -> anyhow::Result<Self> {
let mut builder = globset::GlobSetBuilder::new();
for pattern in &patterns {
pattern.clone().add_to(&mut builder)?;
}
let globset = builder.build()?;
Ok(HashableGlobSet { patterns, globset })
}
pub fn empty() -> Self {
Self {
patterns: Vec::new(),
globset: GlobSet::empty(),
}
}
}
impl Deref for HashableGlobSet {
type Target = GlobSet;
fn deref(&self) -> &Self::Target {
&self.globset
}
}
impl Hash for HashableGlobSet {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
for pattern in self.patterns.iter().sorted() {
pattern.hash(state);
}
}
}
#[derive(Debug, Clone)]
pub struct HashableHashSet<T>(FxHashSet<T>);
impl<T: Hash + Ord> Hash for HashableHashSet<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
for v in self.0.iter().sorted() {
v.hash(state);
}
}
}
impl<T> Default for HashableHashSet<T> {
fn default() -> Self {
Self(FxHashSet::default())
}
}
impl<T> From<FxHashSet<T>> for HashableHashSet<T> {
fn from(set: FxHashSet<T>) -> Self {
Self(set)
}
}
impl<T> From<HashableHashSet<T>> for FxHashSet<T> {
fn from(set: HashableHashSet<T>) -> Self {
set.0
}
}
impl<T> Deref for HashableHashSet<T> {
type Target = FxHashSet<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Clone)]
pub struct HashableHashMap<K, V>(FxHashMap<K, V>);
impl<K: Hash + Ord, V: Hash> Hash for HashableHashMap<K, V> {
fn hash<H: Hasher>(&self, state: &mut H) {
for key in self.0.keys().sorted() {
key.hash(state);
self.0[key].hash(state);
}
}
}
impl<K, V> Default for HashableHashMap<K, V> {
fn default() -> Self {
Self(FxHashMap::default())
}
}
impl<K, V> From<FxHashMap<K, V>> for HashableHashMap<K, V> {
fn from(map: FxHashMap<K, V>) -> Self {
Self(map)
}
}
impl<K, V> From<HashableHashMap<K, V>> for FxHashMap<K, V> {
fn from(map: HashableHashMap<K, V>) -> Self {
map.0
}
}
impl<K, V> Deref for HashableHashMap<K, V> {
type Target = FxHashMap<K, V>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<K, V> DerefMut for HashableHashMap<K, V> {
fn deref_mut(&mut self) -> &mut <Self as Deref>::Target {
&mut self.0
}
}

View File

@@ -2,21 +2,20 @@
//! command-line options. Structure is optimized for internal usage, as opposed
//! to external visibility or parsing.
use std::hash::{Hash, Hasher};
use std::iter;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, Result};
use colored::Colorize;
use globset::{Glob, GlobMatcher, GlobSet};
use globset::Glob;
use itertools::Either::{Left, Right};
use itertools::Itertools;
use once_cell::sync::Lazy;
#[cfg(test)]
use path_absolutize::path_dedot;
use regex::Regex;
use rustc_hash::FxHashSet;
use self::hashable::{HashableGlobMatcher, HashableGlobSet, HashableHashSet, HashableRegex};
use crate::cache::cache_dir;
use crate::registry::{RuleCode, RuleCodePrefix, SuffixLength, CATEGORIES, INCOMPATIBLE_CODES};
use crate::rules::{
@@ -32,6 +31,7 @@ use crate::warn_user_once;
pub mod configuration;
pub mod flags;
pub mod hashable;
pub mod options;
pub mod options_base;
pub mod pyproject;
@@ -74,22 +74,26 @@ pub struct CliSettings {
pub update_check: bool,
}
#[derive(Debug)]
#[derive(Debug, Hash)]
#[allow(clippy::struct_excessive_bools)]
pub struct Settings {
pub allowed_confusables: FxHashSet<char>,
pub allowed_confusables: HashableHashSet<char>,
pub builtins: Vec<String>,
pub dummy_variable_rgx: Regex,
pub enabled: FxHashSet<RuleCode>,
pub exclude: GlobSet,
pub extend_exclude: GlobSet,
pub external: FxHashSet<String>,
pub fixable: FxHashSet<RuleCode>,
pub dummy_variable_rgx: HashableRegex,
pub enabled: HashableHashSet<RuleCode>,
pub exclude: HashableGlobSet,
pub extend_exclude: HashableGlobSet,
pub external: HashableHashSet<String>,
pub fixable: HashableHashSet<RuleCode>,
pub force_exclude: bool,
pub ignore_init_module_imports: bool,
pub line_length: usize,
pub namespace_packages: Vec<PathBuf>,
pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, FxHashSet<RuleCode>)>,
pub per_file_ignores: Vec<(
HashableGlobMatcher,
HashableGlobMatcher,
HashableHashSet<RuleCode>,
)>,
pub required_version: Option<Version>,
pub respect_gitignore: bool,
pub show_source: bool,
@@ -148,11 +152,13 @@ impl Settings {
allowed_confusables: config
.allowed_confusables
.map(FxHashSet::from_iter)
.unwrap_or_default(),
.unwrap_or_default()
.into(),
builtins: config.builtins.unwrap_or_default(),
dummy_variable_rgx: config
.dummy_variable_rgx
.unwrap_or_else(|| DEFAULT_DUMMY_VARIABLE_RGX.clone()),
.unwrap_or_else(|| DEFAULT_DUMMY_VARIABLE_RGX.clone())
.into(),
enabled: validate_enabled(resolve_codes(
[RuleCodeSpec {
select: &config
@@ -184,17 +190,21 @@ impl Settings {
Right(iter::empty())
},
),
)),
exclude: resolve_globset(config.exclude.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()))?,
extend_exclude: resolve_globset(config.extend_exclude)?,
external: FxHashSet::from_iter(config.external.unwrap_or_default()),
))
.into(),
exclude: HashableGlobSet::new(
config.exclude.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()),
)?,
extend_exclude: HashableGlobSet::new(config.extend_exclude)?,
external: FxHashSet::from_iter(config.external.unwrap_or_default()).into(),
fixable: resolve_codes(
[RuleCodeSpec {
select: &config.fixable.unwrap_or_else(|| CATEGORIES.to_vec()),
ignore: &config.unfixable.unwrap_or_default(),
}]
.into_iter(),
),
)
.into(),
force_exclude: config.force_exclude.unwrap_or(false),
ignore_init_module_imports: config.ignore_init_module_imports.unwrap_or_default(),
line_length: config.line_length.unwrap_or(88),
@@ -250,14 +260,16 @@ impl Settings {
#[cfg(test)]
pub fn for_rule(rule_code: RuleCode) -> Self {
Self {
allowed_confusables: FxHashSet::from_iter([]),
allowed_confusables: FxHashSet::from_iter([]).into(),
builtins: vec![],
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
enabled: FxHashSet::from_iter([rule_code.clone()]),
exclude: GlobSet::empty(),
extend_exclude: GlobSet::empty(),
external: FxHashSet::default(),
fixable: FxHashSet::from_iter([rule_code]),
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$")
.unwrap()
.into(),
enabled: FxHashSet::from_iter([rule_code.clone()]).into(),
exclude: HashableGlobSet::empty(),
extend_exclude: HashableGlobSet::empty(),
external: HashableHashSet::default(),
fixable: FxHashSet::from_iter([rule_code]).into(),
force_exclude: false,
ignore_init_module_imports: false,
line_length: 88,
@@ -291,14 +303,16 @@ impl Settings {
#[cfg(test)]
pub fn for_rules(rule_codes: Vec<RuleCode>) -> Self {
Self {
allowed_confusables: FxHashSet::from_iter([]),
allowed_confusables: HashableHashSet::default(),
builtins: vec![],
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(),
enabled: FxHashSet::from_iter(rule_codes.clone()),
exclude: GlobSet::empty(),
extend_exclude: GlobSet::empty(),
external: FxHashSet::default(),
fixable: FxHashSet::from_iter(rule_codes),
dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$")
.unwrap()
.into(),
enabled: FxHashSet::from_iter(rule_codes.clone()).into(),
exclude: HashableGlobSet::empty(),
extend_exclude: HashableGlobSet::empty(),
external: HashableHashSet::default(),
fixable: FxHashSet::from_iter(rule_codes).into(),
force_exclude: false,
ignore_init_module_imports: false,
line_length: 88,
@@ -343,68 +357,16 @@ impl Settings {
}
}
impl Hash for Settings {
fn hash<H: Hasher>(&self, state: &mut H) {
// Add base properties in alphabetical order.
for confusable in &self.allowed_confusables {
confusable.hash(state);
}
self.builtins.hash(state);
self.dummy_variable_rgx.as_str().hash(state);
for value in self.enabled.iter().sorted() {
value.hash(state);
}
for value in self.external.iter().sorted() {
value.hash(state);
}
for value in self.fixable.iter().sorted() {
value.hash(state);
}
self.ignore_init_module_imports.hash(state);
self.line_length.hash(state);
for (absolute, basename, codes) in &self.per_file_ignores {
absolute.glob().hash(state);
basename.glob().hash(state);
for value in codes.iter().sorted() {
value.hash(state);
}
}
self.show_source.hash(state);
self.src.hash(state);
self.target_version.hash(state);
self.task_tags.hash(state);
self.typing_modules.hash(state);
// Add plugin properties in alphabetical order.
self.flake8_annotations.hash(state);
self.flake8_bandit.hash(state);
self.flake8_bugbear.hash(state);
self.flake8_errmsg.hash(state);
self.flake8_import_conventions.hash(state);
self.flake8_pytest_style.hash(state);
self.flake8_quotes.hash(state);
self.flake8_tidy_imports.hash(state);
self.flake8_unused_arguments.hash(state);
self.isort.hash(state);
self.mccabe.hash(state);
self.pep8_naming.hash(state);
self.pydocstyle.hash(state);
self.pyupgrade.hash(state);
}
}
/// Given a list of patterns, create a `GlobSet`.
pub fn resolve_globset(patterns: Vec<FilePattern>) -> Result<GlobSet> {
let mut builder = globset::GlobSetBuilder::new();
for pattern in patterns {
pattern.add_to(&mut builder)?;
}
builder.build().map_err(Into::into)
}
/// Given a list of patterns, create a `GlobSet`.
pub fn resolve_per_file_ignores(
per_file_ignores: Vec<PerFileIgnore>,
) -> Result<Vec<(GlobMatcher, GlobMatcher, FxHashSet<RuleCode>)>> {
) -> Result<
Vec<(
HashableGlobMatcher,
HashableGlobMatcher,
HashableHashSet<RuleCode>,
)>,
> {
per_file_ignores
.into_iter()
.map(|per_file_ignore| {
@@ -415,7 +377,7 @@ pub fn resolve_per_file_ignores(
// Construct basename matcher.
let basename = Glob::new(&per_file_ignore.basename)?.compile_matcher();
Ok((absolute, basename, per_file_ignore.codes))
Ok((absolute.into(), basename.into(), per_file_ignore.codes))
})
.collect()
}

View File

@@ -10,6 +10,7 @@ use rustc_hash::FxHashSet;
use schemars::JsonSchema;
use serde::{de, Deserialize, Deserializer, Serialize};
use super::hashable::HashableHashSet;
use crate::fs;
use crate::registry::{RuleCode, RuleCodePrefix};
@@ -54,7 +55,7 @@ impl FromStr for PythonVersion {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)]
pub enum FilePattern {
Builtin(&'static str),
User(String, PathBuf),
@@ -94,16 +95,16 @@ impl FromStr for FilePattern {
pub struct PerFileIgnore {
pub basename: String,
pub absolute: PathBuf,
pub codes: FxHashSet<RuleCode>,
pub codes: HashableHashSet<RuleCode>,
}
impl PerFileIgnore {
pub fn new(basename: String, absolute: PathBuf, prefixes: &[RuleCodePrefix]) -> Self {
let codes = prefixes.iter().flat_map(RuleCodePrefix::codes).collect();
let codes: FxHashSet<_> = prefixes.iter().flat_map(RuleCodePrefix::codes).collect();
Self {
basename,
absolute,
codes,
codes: codes.into(),
}
}
}
@@ -150,7 +151,9 @@ impl FromStr for PatternPrefixPair {
}
}
#[derive(Clone, Copy, ValueEnum, PartialEq, Eq, Serialize, Deserialize, Debug, JsonSchema)]
#[derive(
Clone, Copy, ValueEnum, PartialEq, Eq, Serialize, Deserialize, Debug, JsonSchema, Hash,
)]
#[serde(rename_all = "kebab-case")]
pub enum SerializationFormat {
Text,
@@ -167,7 +170,7 @@ impl Default for SerializationFormat {
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Hash)]
#[serde(try_from = "String")]
pub struct Version(String);