diff --git a/README.md b/README.md index 54f0edd92c..9c5c1c58ea 100644 --- a/README.md +++ b/README.md @@ -353,6 +353,8 @@ Options: Respect file exclusions via `.gitignore` and other standard ignore files --force-exclude Enforce exclusions, even for paths passed to Ruff directly on the command-line + --update-check + Enable or disable automatic update checks --show-files See the files Ruff will be run against with the current settings --show-settings @@ -2198,6 +2200,24 @@ unfixable = ["F401"] --- +#### [`update-check`](#update-check) + +Enable or disable automatic update checks (overridden by the +`--update-check` and `--no-update-check` command-line flags). + +**Default value**: `true` + +**Type**: `bool` + +**Example usage**: + +```toml +[tool.ruff] +update-check = false +``` + +--- + ### `flake8-annotations` #### [`allow-star-arg-any`](#allow-star-arg-any) diff --git a/flake8_to_ruff/src/converter.rs b/flake8_to_ruff/src/converter.rs index 079a31a084..1f552089b0 100644 --- a/flake8_to_ruff/src/converter.rs +++ b/flake8_to_ruff/src/converter.rs @@ -296,6 +296,7 @@ mod tests { )?; let expected = Pyproject::new(Options { allowed_confusables: None, + cache_dir: None, dummy_variable_rgx: None, exclude: None, extend: None, @@ -323,7 +324,7 @@ mod tests { src: None, target_version: None, unfixable: None, - cache_dir: None, + update_check: None, flake8_annotations: None, flake8_bugbear: None, flake8_errmsg: None, @@ -354,6 +355,7 @@ mod tests { )?; let expected = Pyproject::new(Options { allowed_confusables: None, + cache_dir: None, dummy_variable_rgx: None, exclude: None, extend: None, @@ -381,7 +383,7 @@ mod tests { src: None, target_version: None, unfixable: None, - cache_dir: None, + update_check: None, flake8_annotations: None, flake8_bugbear: None, flake8_errmsg: None, @@ -412,6 +414,7 @@ mod tests { )?; let expected = Pyproject::new(Options { allowed_confusables: None, + cache_dir: None, dummy_variable_rgx: None, exclude: None, extend: None, @@ -439,7 +442,7 @@ mod tests { src: None, target_version: None, unfixable: None, - cache_dir: None, + update_check: None, flake8_annotations: None, flake8_bugbear: None, flake8_errmsg: None, @@ -470,6 +473,7 @@ mod tests { )?; let expected = Pyproject::new(Options { allowed_confusables: None, + cache_dir: None, dummy_variable_rgx: None, exclude: None, extend: None, @@ -497,7 +501,7 @@ mod tests { src: None, target_version: None, unfixable: None, - cache_dir: None, + update_check: None, flake8_annotations: None, flake8_bugbear: None, flake8_errmsg: None, @@ -528,6 +532,7 @@ mod tests { )?; let expected = Pyproject::new(Options { allowed_confusables: None, + cache_dir: None, dummy_variable_rgx: None, exclude: None, extend: None, @@ -555,7 +560,7 @@ mod tests { src: None, target_version: None, unfixable: None, - cache_dir: None, + update_check: None, flake8_annotations: None, flake8_bugbear: None, flake8_errmsg: None, @@ -594,6 +599,7 @@ mod tests { )?; let expected = Pyproject::new(Options { allowed_confusables: None, + cache_dir: None, dummy_variable_rgx: None, exclude: None, extend: None, @@ -657,7 +663,7 @@ mod tests { src: None, target_version: None, unfixable: None, - cache_dir: None, + update_check: None, flake8_annotations: None, flake8_bugbear: None, flake8_errmsg: None, @@ -690,6 +696,7 @@ mod tests { )?; let expected = Pyproject::new(Options { allowed_confusables: None, + cache_dir: None, dummy_variable_rgx: None, exclude: None, extend: None, @@ -718,7 +725,7 @@ mod tests { src: None, target_version: None, unfixable: None, - cache_dir: None, + update_check: None, flake8_annotations: None, flake8_bugbear: None, flake8_errmsg: None, diff --git a/playground/src/ruff_options.ts b/playground/src/ruff_options.ts index be50567516..181736f5b9 100644 --- a/playground/src/ruff_options.ts +++ b/playground/src/ruff_options.ts @@ -71,6 +71,11 @@ export const AVAILABLE_OPTIONS: OptionGroup[] = [ "default": '[]', "type": 'Vec', }, + { + "name": "update-check", + "default": 'true', + "type": 'bool', + }, ]}, {"name": "flake8-annotations", "fields": [ { diff --git a/pyproject.toml b/pyproject.toml index 85da64240f..fa9374d48f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ bindings = "bin" strip = true [tool.ruff] +update-check = true [tool.ruff.isort] force-wrap-aliases = true diff --git a/ruff.schema.json b/ruff.schema.json index c8e2cd08c9..963426068e 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -364,6 +364,13 @@ "items": { "$ref": "#/definitions/CheckCodePrefix" } + }, + "update-check": { + "description": "Enable or disable automatic update checks (overridden by the `--update-check` and `--no-update-check` command-line flags).", + "type": [ + "boolean", + "null" + ] } }, "additionalProperties": false, diff --git a/src/cli.rs b/src/cli.rs index 498a0ef694..1b001c9547 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -105,10 +105,15 @@ pub struct Cli { no_respect_gitignore: bool, /// Enforce exclusions, even for paths passed to Ruff directly on the /// command-line. - #[arg(long, overrides_with("no_show_source"))] + #[arg(long, overrides_with("no_force_exclude"))] force_exclude: bool, #[clap(long, overrides_with("force_exclude"), hide = true)] no_force_exclude: bool, + /// Enable or disable automatic update checks. + #[arg(long, overrides_with("no_update_check"))] + update_check: bool, + #[clap(long, overrides_with("update_check"), hide = true)] + no_update_check: bool, /// See the files Ruff will be run against with the current settings. #[arg(long)] pub show_files: bool, @@ -192,11 +197,12 @@ impl Cli { target_version: self.target_version, unfixable: self.unfixable, // TODO(charlie): Included in `pyproject.toml`, but not inherited. + cache_dir: self.cache_dir, fix: resolve_bool_arg(self.fix, self.no_fix), fix_only: resolve_bool_arg(self.fix_only, self.no_fix_only), - format: self.format, force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude), - cache_dir: self.cache_dir, + format: self.format, + update_check: resolve_bool_arg(self.update_check, self.no_update_check), }, ) } @@ -253,11 +259,12 @@ pub struct Overrides { pub target_version: Option, pub unfixable: Option>, // TODO(charlie): Captured in pyproject.toml as a default, but not part of `Settings`. + pub cache_dir: Option, pub fix: Option, pub fix_only: Option, - pub format: Option, pub force_exclude: Option, - pub cache_dir: Option, + pub format: Option, + pub update_check: Option, } /// Map the CLI settings to a `LogLevel`. diff --git a/src/main_native.rs b/src/main_native.rs index 3e66b0a338..bfd4d84773 100644 --- a/src/main_native.rs +++ b/src/main_native.rs @@ -117,11 +117,19 @@ pub(crate) fn inner_main() -> Result { PyprojectDiscovery::Hierarchical(settings) => settings.respect_gitignore, }, }; - let (fix, fix_only, format) = match &pyproject_strategy { - PyprojectDiscovery::Fixed(settings) => (settings.fix, settings.fix_only, settings.format), - PyprojectDiscovery::Hierarchical(settings) => { - (settings.fix, settings.fix_only, settings.format) - } + let (fix, fix_only, format, update_check) = match &pyproject_strategy { + PyprojectDiscovery::Fixed(settings) => ( + settings.fix, + settings.fix_only, + settings.format, + settings.update_check, + ), + PyprojectDiscovery::Hierarchical(settings) => ( + settings.fix, + settings.fix_only, + settings.format, + settings.update_check, + ), }; if let Some(code) = cli.explain { @@ -270,7 +278,11 @@ pub(crate) fn inner_main() -> Result { // Check for updates if we're in a non-silent log level. #[cfg(feature = "update-informer")] - if !is_stdin && log_level >= LogLevel::Default && atty::is(atty::Stream::Stdout) { + if update_check + && !is_stdin + && log_level >= LogLevel::Default + && atty::is(atty::Stream::Stdout) + { drop(updates::check_for_updates()); } diff --git a/src/settings/configuration.rs b/src/settings/configuration.rs index e9dcf73f94..bb6406131b 100644 --- a/src/settings/configuration.rs +++ b/src/settings/configuration.rs @@ -28,6 +28,7 @@ use crate::{ #[derive(Debug, Default)] pub struct Configuration { pub allowed_confusables: Option>, + pub cache_dir: Option, pub dummy_variable_rgx: Option, pub exclude: Option>, pub extend: Option, @@ -38,8 +39,8 @@ pub struct Configuration { pub fix: Option, pub fix_only: Option, pub fixable: Option>, - pub format: Option, pub force_exclude: Option, + pub format: Option, pub ignore: Option>, pub ignore_init_module_imports: Option, pub line_length: Option, @@ -51,7 +52,7 @@ pub struct Configuration { pub src: Option>, pub target_version: Option, pub unfixable: Option>, - pub cache_dir: Option, + pub update_check: Option, // Plugins pub flake8_annotations: Option, pub flake8_bugbear: Option, @@ -75,6 +76,14 @@ impl Configuration { pub fn from_options(options: Options, project_root: &Path) -> Result { Ok(Configuration { allowed_confusables: options.allowed_confusables, + cache_dir: options + .cache_dir + .map(|dir| { + let dir = shellexpand::full(&dir); + dir.map(|dir| PathBuf::from(dir.as_ref())) + }) + .transpose() + .map_err(|e| anyhow!("Invalid `cache-dir` value: {e}"))?, dummy_variable_rgx: options .dummy_variable_rgx .map(|pattern| Regex::new(&pattern)) @@ -139,14 +148,7 @@ impl Configuration { .transpose()?, target_version: options.target_version, unfixable: options.unfixable, - cache_dir: options - .cache_dir - .map(|dir| { - let dir = shellexpand::full(&dir); - dir.map(|dir| PathBuf::from(dir.as_ref())) - }) - .transpose() - .map_err(|e| anyhow!("Invalid `cache-dir` value: {e}"))?, + update_check: options.update_check, // Plugins flake8_annotations: options.flake8_annotations, flake8_bugbear: options.flake8_bugbear, @@ -167,6 +169,7 @@ impl Configuration { pub fn combine(self, config: Configuration) -> Self { Self { allowed_confusables: self.allowed_confusables.or(config.allowed_confusables), + cache_dir: self.cache_dir.or(config.cache_dir), dummy_variable_rgx: self.dummy_variable_rgx.or(config.dummy_variable_rgx), exclude: self.exclude.or(config.exclude), extend: self.extend.or(config.extend), @@ -204,7 +207,7 @@ impl Configuration { src: self.src.or(config.src), target_version: self.target_version.or(config.target_version), unfixable: self.unfixable.or(config.unfixable), - cache_dir: self.cache_dir.or(config.cache_dir), + update_check: self.update_check.or(config.update_check), // Plugins flake8_annotations: self.flake8_annotations.or(config.flake8_annotations), flake8_bugbear: self.flake8_bugbear.or(config.flake8_bugbear), @@ -226,6 +229,9 @@ impl Configuration { } pub fn apply(&mut self, overrides: Overrides) { + if let Some(cache_dir) = overrides.cache_dir { + self.cache_dir = Some(cache_dir); + } if let Some(dummy_variable_rgx) = overrides.dummy_variable_rgx { self.dummy_variable_rgx = Some(dummy_variable_rgx); } @@ -279,8 +285,8 @@ impl Configuration { if let Some(unfixable) = overrides.unfixable { self.unfixable = Some(unfixable); } - if let Some(cache_dir) = overrides.cache_dir { - self.cache_dir = Some(cache_dir); + if let Some(update_check) = overrides.update_check { + self.update_check = Some(update_check); } // Special-case: `extend_ignore` and `extend_select` are parallel arrays, so // push an empty array if only one of the two is provided. diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 5dedc20cb8..9257831a49 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -39,6 +39,7 @@ const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); #[allow(clippy::struct_excessive_bools)] pub struct Settings { pub allowed_confusables: FxHashSet, + pub cache_dir: PathBuf, pub dummy_variable_rgx: Regex, pub enabled: FxHashSet, pub exclude: GlobSet, @@ -47,8 +48,8 @@ pub struct Settings { pub fix: bool, pub fix_only: bool, pub fixable: FxHashSet, - pub format: SerializationFormat, pub force_exclude: bool, + pub format: SerializationFormat, pub ignore_init_module_imports: bool, pub line_length: usize, pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, FxHashSet)>, @@ -57,7 +58,7 @@ pub struct Settings { pub show_source: bool, pub src: Vec, pub target_version: PythonVersion, - pub cache_dir: PathBuf, + pub update_check: bool, // Plugins pub flake8_annotations: flake8_annotations::settings::Settings, pub flake8_bugbear: flake8_bugbear::settings::Settings, @@ -107,6 +108,7 @@ impl Settings { .allowed_confusables .map(FxHashSet::from_iter) .unwrap_or_default(), + cache_dir: config.cache_dir.unwrap_or_else(|| cache_dir(project_root)), dummy_variable_rgx: config .dummy_variable_rgx .unwrap_or_else(|| DEFAULT_DUMMY_VARIABLE_RGX.clone()), @@ -147,12 +149,12 @@ impl Settings { )?, respect_gitignore: config.respect_gitignore.unwrap_or(true), required_version: config.required_version, + show_source: config.show_source.unwrap_or_default(), src: config .src .unwrap_or_else(|| vec![project_root.to_path_buf()]), target_version: config.target_version.unwrap_or(PythonVersion::Py310), - show_source: config.show_source.unwrap_or_default(), - cache_dir: config.cache_dir.unwrap_or_else(|| cache_dir(project_root)), + update_check: config.update_check.unwrap_or(true), // Plugins flake8_annotations: config .flake8_annotations @@ -210,6 +212,7 @@ impl Settings { pub fn for_rule(check_code: CheckCode) -> Self { Self { allowed_confusables: FxHashSet::from_iter([]), + cache_dir: cache_dir(path_dedot::CWD.as_path()), dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(), enabled: FxHashSet::from_iter([check_code.clone()]), exclude: GlobSet::empty(), @@ -218,8 +221,8 @@ impl Settings { fix: false, fix_only: false, fixable: FxHashSet::from_iter([check_code]), - format: SerializationFormat::Text, force_exclude: false, + format: SerializationFormat::Text, ignore_init_module_imports: false, line_length: 88, per_file_ignores: vec![], @@ -228,7 +231,7 @@ impl Settings { show_source: false, src: vec![path_dedot::CWD.clone()], target_version: PythonVersion::Py310, - cache_dir: cache_dir(path_dedot::CWD.as_path()), + update_check: false, flake8_annotations: flake8_annotations::settings::Settings::default(), flake8_bugbear: flake8_bugbear::settings::Settings::default(), flake8_errmsg: flake8_errmsg::settings::Settings::default(), @@ -247,6 +250,7 @@ impl Settings { pub fn for_rules(check_codes: Vec) -> Self { Self { allowed_confusables: FxHashSet::from_iter([]), + cache_dir: cache_dir(path_dedot::CWD.as_path()), dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(), enabled: FxHashSet::from_iter(check_codes.clone()), exclude: GlobSet::empty(), @@ -255,8 +259,8 @@ impl Settings { fix: false, fix_only: false, fixable: FxHashSet::from_iter(check_codes), - format: SerializationFormat::Text, force_exclude: false, + format: SerializationFormat::Text, ignore_init_module_imports: false, line_length: 88, per_file_ignores: vec![], @@ -265,7 +269,7 @@ impl Settings { show_source: false, src: vec![path_dedot::CWD.clone()], target_version: PythonVersion::Py310, - cache_dir: cache_dir(path_dedot::CWD.as_path()), + update_check: false, flake8_annotations: flake8_annotations::settings::Settings::default(), flake8_bugbear: flake8_bugbear::settings::Settings::default(), flake8_errmsg: flake8_errmsg::settings::Settings::default(), diff --git a/src/settings/options.rs b/src/settings/options.rs index 52bba29fe9..487f6fc433 100644 --- a/src/settings/options.rs +++ b/src/settings/options.rs @@ -30,6 +30,22 @@ pub struct Options { /// A list of allowed "confusable" Unicode characters to ignore when /// enforcing `RUF001`, `RUF002`, and `RUF003`. pub allowed_confusables: Option>, + #[option( + default = ".ruff_cache", + value_type = "PathBuf", + example = r#"cache-dir = "~/.cache/ruff""# + )] + /// A path to the cache directory. + /// + /// By default, Ruff stores cache results in a `.ruff_cache` directory in + /// the current project root. + /// + /// However, Ruff will also respect the `RUFF_CACHE_DIR` environment + /// variable, which takes precedence over that default. + /// + /// This setting will override even the `RUFF_CACHE_DIR` environment + /// variable, if set. + pub cache_dir: Option, #[option( default = r#""^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$""#, value_type = "Regex", @@ -324,21 +340,13 @@ pub struct Options { /// A list of check code prefixes to consider un-autofix-able. pub unfixable: Option>, #[option( - default = ".ruff_cache", - value_type = "PathBuf", - example = r#"cache-dir = "~/.cache/ruff""# + default = "true", + value_type = "bool", + example = "update-check = false" )] - /// A path to the cache directory. - /// - /// By default, Ruff stores cache results in a `.ruff_cache` directory in - /// the current project root. - /// - /// However, Ruff will also respect the `RUFF_CACHE_DIR` environment - /// variable, which takes precedence over that default. - /// - /// This setting will override even the `RUFF_CACHE_DIR` environment - /// variable, if set. - pub cache_dir: Option, + /// Enable or disable automatic update checks (overridden by the + /// `--update-check` and `--no-update-check` command-line flags). + pub update_check: Option, #[option_group] /// Options for the `flake8-annotations` plugin. pub flake8_annotations: Option, diff --git a/src/settings/pyproject.rs b/src/settings/pyproject.rs index 3e7a14bcde..2ccbfecf78 100644 --- a/src/settings/pyproject.rs +++ b/src/settings/pyproject.rs @@ -164,6 +164,7 @@ mod tests { Some(Tools { ruff: Some(Options { allowed_confusables: None, + cache_dir: None, dummy_variable_rgx: None, exclude: None, extend: None, @@ -174,20 +175,20 @@ mod tests { fix: None, fix_only: None, fixable: None, - format: None, force_exclude: None, + format: None, ignore: None, ignore_init_module_imports: None, line_length: None, per_file_ignores: None, - respect_gitignore: None, required_version: None, + respect_gitignore: None, select: None, show_source: None, src: None, target_version: None, unfixable: None, - cache_dir: None, + update_check: None, flake8_annotations: None, flake8_bugbear: None, flake8_errmsg: None, @@ -239,6 +240,7 @@ line-length = 79 src: None, target_version: None, unfixable: None, + update_check: None, cache_dir: None, flake8_annotations: None, flake8_bugbear: None, @@ -268,6 +270,7 @@ exclude = ["foo.py"] Some(Tools { ruff: Some(Options { allowed_confusables: None, + cache_dir: None, dummy_variable_rgx: None, exclude: Some(vec!["foo.py".to_string()]), extend: None, @@ -284,14 +287,14 @@ exclude = ["foo.py"] ignore_init_module_imports: None, line_length: None, per_file_ignores: None, - respect_gitignore: None, required_version: None, + respect_gitignore: None, select: None, show_source: None, src: None, target_version: None, unfixable: None, - cache_dir: None, + update_check: None, flake8_annotations: None, flake8_errmsg: None, flake8_bugbear: None, @@ -320,6 +323,7 @@ select = ["E501"] Some(Tools { ruff: Some(Options { allowed_confusables: None, + cache_dir: None, dummy_variable_rgx: None, exclude: None, extend: None, @@ -336,14 +340,14 @@ select = ["E501"] ignore_init_module_imports: None, line_length: None, per_file_ignores: None, - respect_gitignore: None, required_version: None, + respect_gitignore: None, select: Some(vec![CheckCodePrefix::E501]), show_source: None, src: None, target_version: None, unfixable: None, - cache_dir: None, + update_check: None, flake8_annotations: None, flake8_bugbear: None, flake8_errmsg: None, @@ -373,6 +377,7 @@ ignore = ["E501"] Some(Tools { ruff: Some(Options { allowed_confusables: None, + cache_dir: None, dummy_variable_rgx: None, exclude: None, extend: None, @@ -389,14 +394,14 @@ ignore = ["E501"] ignore_init_module_imports: None, line_length: None, per_file_ignores: None, - respect_gitignore: None, required_version: None, + respect_gitignore: None, select: None, show_source: None, src: None, target_version: None, unfixable: None, - cache_dir: None, + update_check: None, flake8_annotations: None, flake8_bugbear: None, flake8_errmsg: None, @@ -480,6 +485,7 @@ other-attribute = 1 format: None, force_exclude: None, unfixable: None, + update_check: None, cache_dir: None, per_file_ignores: Some(FxHashMap::from_iter([( "__init__.py".to_string(),