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 --required-version #1376

Merged
merged 1 commit into from Dec 26, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -50,6 +50,7 @@ rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "1b6cb170e925a43d605b3fed9f6b878e63e47744" }
rustpython-parser = { features = ["lalrpop"], git = "https://github.com/RustPython/RustPython.git", rev = "1b6cb170e925a43d605b3fed9f6b878e63e47744" }
schemars = { version = "0.8.11" }
semver = { version = "1.0.16" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }
shellexpand = { version = "3.0.0" }
Expand Down
19 changes: 19 additions & 0 deletions README.md
Expand Up @@ -1965,6 +1965,25 @@ when considering any matching files.

---

#### [`required-version`](#required-version)

Require a specific version of Ruff to be running (useful for unifying
results across many environments, e.g., with a `pyproject.toml`
file).

**Default value**: `None`

**Type**: `String`

**Example usage**:

```toml
[tool.ruff]
required-version = "0.0.193"
```

---

#### [`respect-gitignore`](#respect-gitignore)

Whether to automatically exclude files that are ignored by `.ignore`,
Expand Down
7 changes: 7 additions & 0 deletions flake8_to_ruff/src/converter.rs
Expand Up @@ -303,6 +303,7 @@ mod tests {
ignore_init_module_imports: None,
line_length: None,
per_file_ignores: None,
required_version: None,
respect_gitignore: None,
select: Some(vec![
CheckCodePrefix::E,
Expand Down Expand Up @@ -359,6 +360,7 @@ mod tests {
ignore_init_module_imports: None,
line_length: Some(100),
per_file_ignores: None,
required_version: None,
respect_gitignore: None,
select: Some(vec![
CheckCodePrefix::E,
Expand Down Expand Up @@ -415,6 +417,7 @@ mod tests {
ignore_init_module_imports: None,
line_length: Some(100),
per_file_ignores: None,
required_version: None,
respect_gitignore: None,
select: Some(vec![
CheckCodePrefix::E,
Expand Down Expand Up @@ -471,6 +474,7 @@ mod tests {
ignore_init_module_imports: None,
line_length: None,
per_file_ignores: None,
required_version: None,
respect_gitignore: None,
select: Some(vec![
CheckCodePrefix::E,
Expand Down Expand Up @@ -527,6 +531,7 @@ mod tests {
ignore_init_module_imports: None,
line_length: None,
per_file_ignores: None,
required_version: None,
respect_gitignore: None,
select: Some(vec![
CheckCodePrefix::E,
Expand Down Expand Up @@ -591,6 +596,7 @@ mod tests {
ignore_init_module_imports: None,
line_length: None,
per_file_ignores: None,
required_version: None,
respect_gitignore: None,
select: Some(vec![
CheckCodePrefix::D100,
Expand Down Expand Up @@ -683,6 +689,7 @@ mod tests {
ignore_init_module_imports: None,
line_length: None,
per_file_ignores: None,
required_version: None,
respect_gitignore: None,
select: Some(vec![
CheckCodePrefix::E,
Expand Down
4 changes: 4 additions & 0 deletions pyproject.toml
Expand Up @@ -34,7 +34,11 @@ bindings = "bin"
strip = true

[tool.ruff]
#required-version = "0.0.192"

[tool.ruff.isort]
force-wrap-aliases = true
combine-as-imports = true

[tool.black]
required-version = "22.12.1"
14 changes: 14 additions & 0 deletions ruff.schema.json
Expand Up @@ -288,6 +288,17 @@
}
]
},
"required-version": {
"description": "Require a specific version of Ruff to be running (useful for unifying results across many environments, e.g., with a `pyproject.toml` file).",
"anyOf": [
{
"$ref": "#/definitions/Version"
},
{
"type": "null"
}
]
},
"respect-gitignore": {
"description": "Whether to automatically exclude files that are ignored by `.ignore`, `.gitignore`, `.git/info/exclude`, and global `gitignore` files. Enabled by default.",
"type": [
Expand Down Expand Up @@ -1196,6 +1207,9 @@
"parents",
"all"
]
},
"Version": {
"type": "string"
}
}
}
33 changes: 24 additions & 9 deletions src/commands.rs
Expand Up @@ -38,14 +38,8 @@ pub fn run(
let duration = start.elapsed();
debug!("Identified files to lint in: {:?}", duration);

// Discover the package root for each Python file.
let package_roots = packages::detect_package_roots(
&paths
.iter()
.flatten()
.map(ignore::DirEntry::path)
.collect::<Vec<_>>(),
);
// Validate the `Settings` and return any errors.
resolver.validate(pyproject_strategy)?;

// Initialize the cache.
if matches!(cache, flags::Cache::Enabled) {
Expand All @@ -71,6 +65,15 @@ pub fn run(
}
};

// Discover the package root for each Python file.
let package_roots = packages::detect_package_roots(
&paths
.iter()
.flatten()
.map(ignore::DirEntry::path)
.collect::<Vec<_>>(),
);

let start = Instant::now();
let mut diagnostics: Diagnostics = par_iter(&paths)
.map(|entry| {
Expand Down Expand Up @@ -176,6 +179,9 @@ pub fn add_noqa(
let duration = start.elapsed();
debug!("Identified files to lint in: {:?}", duration);

// Validate the `Settings` and return any errors.
resolver.validate(pyproject_strategy)?;

let start = Instant::now();
let modifications: usize = par_iter(&paths)
.flatten()
Expand Down Expand Up @@ -212,6 +218,9 @@ pub fn autoformat(
let duration = start.elapsed();
debug!("Identified files to lint in: {:?}", duration);

// Validate the `Settings` and return any errors.
resolver.validate(pyproject_strategy)?;

let start = Instant::now();
let modifications = par_iter(&paths)
.flatten()
Expand Down Expand Up @@ -245,6 +254,9 @@ pub fn show_settings(
let (paths, resolver) =
resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?;

// Validate the `Settings` and return any errors.
resolver.validate(pyproject_strategy)?;

// Print the list of files.
let Some(entry) = paths
.iter()
Expand All @@ -268,9 +280,12 @@ pub fn show_files(
overrides: &Overrides,
) -> Result<()> {
// Collect all files in the hierarchy.
let (paths, _resolver) =
let (paths, resolver) =
resolver::python_files_in_path(files, pyproject_strategy, file_strategy, overrides)?;

// Validate the `Settings` and return any errors.
resolver.validate(pyproject_strategy)?;

// Print the list of files.
for entry in paths
.iter()
Expand Down
17 changes: 16 additions & 1 deletion src/linter.rs
Expand Up @@ -59,6 +59,9 @@ pub(crate) fn check_path(
autofix: flags::Autofix,
noqa: flags::Noqa,
) -> Result<Vec<Check>> {
// Validate the `Settings` and return any errors.
settings.validate()?;

// Aggregate all checks.
let mut checks: Vec<Check> = vec![];

Expand Down Expand Up @@ -175,6 +178,9 @@ pub fn lint_path(
cache: flags::Cache,
autofix: fixer::Mode,
) -> Result<Diagnostics> {
// Validate the `Settings` and return any errors.
settings.validate()?;

let metadata = path.metadata()?;

// Check the cache.
Expand Down Expand Up @@ -202,6 +208,9 @@ pub fn lint_path(

/// Add any missing `#noqa` pragmas to the source code at the given `Path`.
pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
// Validate the `Settings` and return any errors.
settings.validate()?;

// Read the file from disk.
let contents = fs::read_file(path)?;

Expand Down Expand Up @@ -241,7 +250,10 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
}

/// Apply autoformatting to the source code at the given `Path`.
pub fn autoformat_path(path: &Path, _settings: &Settings) -> Result<()> {
pub fn autoformat_path(path: &Path, settings: &Settings) -> Result<()> {
// Validate the `Settings` and return any errors.
settings.validate()?;

// Read the file from disk.
let contents = fs::read_file(path)?;

Expand All @@ -266,6 +278,9 @@ pub fn lint_stdin(
settings: &Settings,
autofix: fixer::Mode,
) -> Result<Diagnostics> {
// Validate the `Settings` and return any errors.
settings.validate()?;

// Read the file from disk.
let contents = stdin.to_string();

Expand Down
6 changes: 6 additions & 0 deletions src/main.rs
Expand Up @@ -100,6 +100,12 @@ fn inner_main() -> Result<ExitCode> {
cli.stdin_filename.as_deref(),
)?;

// Validate the `Settings` and return any errors.
match &pyproject_strategy {
PyprojectDiscovery::Fixed(settings) => settings.validate()?,
PyprojectDiscovery::Hierarchical(settings) => settings.validate()?,
};

// Extract options that are included in `Settings`, but only apply at the top
// level.
let file_strategy = FileDiscovery {
Expand Down
20 changes: 20 additions & 0 deletions src/resolver.rs
Expand Up @@ -91,6 +91,26 @@ impl Resolver {
pub fn iter(&self) -> impl Iterator<Item = &Settings> {
self.settings.values()
}

/// Validate all resolved `Settings` in this `Resolver`.
pub fn validate(&self, strategy: &PyprojectDiscovery) -> Result<()> {
// TODO(charlie): This risks false positives (but not false negatives), since
// some of the `Settings` in the path may ultimately be unused (or, e.g., they
// could have their `required_version` overridden by other `Settings` in
// the path). It'd be preferable to validate once we've determined the
// `Settings` for each path, but that's more expensive.
match &strategy {
PyprojectDiscovery::Fixed(settings) => {
settings.validate()?;
}
PyprojectDiscovery::Hierarchical(default) => {
for settings in std::iter::once(default).chain(self.iter()) {
settings.validate()?;
}
}
}
Ok(())
}
}

/// Recursively resolve a `Configuration` from a `pyproject.toml` file at the
Expand Down
9 changes: 7 additions & 2 deletions src/settings/configuration.rs
Expand Up @@ -16,7 +16,9 @@ use crate::checks_gen::CheckCodePrefix;
use crate::cli::{collect_per_file_ignores, Overrides};
use crate::settings::options::Options;
use crate::settings::pyproject::load_options;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion, SerializationFormat};
use crate::settings::types::{
FilePattern, PerFileIgnore, PythonVersion, SerializationFormat, Version,
};
use crate::{
flake8_annotations, flake8_bugbear, flake8_errmsg, flake8_import_conventions, flake8_quotes,
flake8_tidy_imports, flake8_unused_arguments, fs, isort, mccabe, pep8_naming, pyupgrade,
Expand All @@ -41,6 +43,7 @@ pub struct Configuration {
pub ignore_init_module_imports: Option<bool>,
pub line_length: Option<usize>,
pub per_file_ignores: Option<Vec<PerFileIgnore>>,
pub required_version: Option<Version>,
pub respect_gitignore: Option<bool>,
pub select: Option<Vec<CheckCodePrefix>>,
pub show_source: Option<bool>,
Expand Down Expand Up @@ -124,6 +127,7 @@ impl Configuration {
})
.collect()
}),
required_version: options.required_version,
respect_gitignore: options.respect_gitignore,
select: options.select,
show_source: options.show_source,
Expand Down Expand Up @@ -162,7 +166,6 @@ impl Configuration {
allowed_confusables: self.allowed_confusables.or(config.allowed_confusables),
dummy_variable_rgx: self.dummy_variable_rgx.or(config.dummy_variable_rgx),
exclude: self.exclude.or(config.exclude),
respect_gitignore: self.respect_gitignore.or(config.respect_gitignore),
extend: self.extend.or(config.extend),
extend_exclude: config
.extend_exclude
Expand Down Expand Up @@ -191,6 +194,8 @@ impl Configuration {
.or(config.ignore_init_module_imports),
line_length: self.line_length.or(config.line_length),
per_file_ignores: self.per_file_ignores.or(config.per_file_ignores),
required_version: self.required_version.or(config.required_version),
respect_gitignore: self.respect_gitignore.or(config.respect_gitignore),
select: self.select.or(config.select),
show_source: self.show_source.or(config.show_source),
src: self.src.or(config.src),
Expand Down