Compare commits

..

5 Commits

Author SHA1 Message Date
Charlie Marsh
c7349b69c1 Bump version to 0.0.47 2022-09-28 22:30:48 -04:00
Charlie Marsh
7f84753f3c Improve rendering of --show-settings 2022-09-28 22:30:20 -04:00
Charlie Marsh
e2ec62cf33 Misc. follow-up changes to #272 (#278) 2022-09-28 22:15:58 -04:00
Charlie Marsh
1d5592d937 Use take-while to terminate on parse errors (#279) 2022-09-28 22:06:35 -04:00
Anders Kaseorg
886def13bd Upgrade to clap 4 (#272) 2022-09-28 17:11:57 -04:00
12 changed files with 213 additions and 117 deletions

22
Cargo.lock generated
View File

@@ -382,26 +382,24 @@ checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
[[package]]
name = "clap"
version = "3.2.16"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9"
checksum = "dd03107d0f87139c1774a15f3db2165b0652b5460c58c27e561f89c20c599eaf"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"indexmap",
"once_cell",
"strsim",
"termcolor",
"textwrap",
]
[[package]]
name = "clap_derive"
version = "3.2.15"
version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4"
checksum = "ca689d7434ce44517a12a89456b2be4d1ea1cafcd8f581978c03d45f5a5c12a7"
dependencies = [
"heck",
"proc-macro-error",
@@ -412,9 +410,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.2.4"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
dependencies = [
"os_str_bytes",
]
@@ -1801,7 +1799,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.46"
version = "0.0.47"
dependencies = [
"anyhow",
"bincode",
@@ -2187,12 +2185,6 @@ dependencies = [
"phf_codegen 0.8.0",
]
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "thiserror"
version = "1.0.32"

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.46"
version = "0.0.47"
edition = "2021"
[lib]
@@ -11,7 +11,7 @@ anyhow = { version = "1.0.60" }
bincode = { version = "1.3.3" }
cacache = { version = "10.0.1" }
chrono = { version = "0.4.21" }
clap = { version = "3.2.16", features = ["derive"] }
clap = { version = "4.0.1", features = ["derive"] }
clearscreen = { version = "1.0.10" }
colored = { version = "2.0.0" }
common-path = { version = "1.0.0" }

View File

@@ -80,61 +80,60 @@ select = [
Alternatively, on the command-line:
```shell
ruff path/to/code/ --select F401 F403
ruff path/to/code/ --select F401 --select F403
```
See `ruff --help` for more:
```shell
ruff (v0.0.46)
An extremely fast Python linter.
ruff: An extremely fast Python linter.
USAGE:
ruff [OPTIONS] <FILES>...
Usage: ruff [OPTIONS] <FILES>...
ARGS:
<FILES>...
Arguments:
<FILES>...
OPTIONS:
--select <SELECT>...
List of error codes to enable
--extend-select <EXTEND_SELECT>...
Like --select, but adds additional error codes on top of the selected ones
--ignore <IGNORE>...
List of error codes to ignore
--extend-ignore <EXTEND_IGNORE>...
Like --ignore, but adds additional error codes on top of the ignored ones
--exclude <EXCLUDE>...
List of paths, used to exclude files and/or directories from checks
--extend-exclude <EXTEND_EXCLUDE>...
Like --exclude, but adds additional files and directories on top of the excluded ones
-e, --exit-zero
Exit with status code "0", even upon detecting errors
-f, --fix
Attempt to automatically fix lint errors
--format <FORMAT>
Output serialization format for error messages [default: text] [possible values: text,
json]
-h, --help
Print help information
-n, --no-cache
Disable cache reads
--per-file-ignores <PER_FILE_IGNORES>...
List of mappings from file pattern to code to exclude
-q, --quiet
Disable all logging (but still exit with status code "1" upon detecting errors)
--add-noqa
Enable automatic additions of noqa directives to failing lines
--show-files
See the files ruff will be run against with the current settings
--show-settings
See ruff's settings
-v, --verbose
Enable verbose logging
-V, --version
Print version information
-w, --watch
Run in watch mode by re-running whenever files change
Options:
-v, --verbose
Enable verbose logging
-q, --quiet
Disable all logging (but still exit with status code "1" upon detecting errors)
-e, --exit-zero
Exit with status code "0", even upon detecting errors
-w, --watch
Run in watch mode by re-running whenever files change
-f, --fix
Attempt to automatically fix lint errors
-n, --no-cache
Disable cache reads
--select <SELECT>
List of error codes to enable
--extend-select <EXTEND_SELECT>
Like --select, but adds additional error codes on top of the selected ones
--ignore <IGNORE>
List of error codes to ignore
--extend-ignore <EXTEND_IGNORE>
Like --ignore, but adds additional error codes on top of the ignored ones
--exclude <EXCLUDE>
List of paths, used to exclude files and/or directories from checks
--extend-exclude <EXTEND_EXCLUDE>
Like --exclude, but adds additional files and directories on top of the excluded ones
--per-file-ignores <PER_FILE_IGNORES>
List of mappings from file pattern to code to exclude
--format <FORMAT>
Output serialization format for error messages [default: text] [possible values: text, json]
--show-files
See the files ruff will be run against with the current settings
--show-settings
See ruff's settings
--add-noqa
Enable automatic additions of noqa directives to failing lines
--dummy-variable-rgx <DUMMY_VARIABLE_RGX>
Regular expression matching the name of dummy variables
-h, --help
Print help information
-V, --version
Print version information
```
### Excluding files

View File

@@ -2,14 +2,14 @@
use std::path::PathBuf;
use anyhow::Result;
use clap::{Parser, ValueHint};
use clap::Parser;
use rustpython_parser::parser;
use ruff::fs;
#[derive(Debug, Parser)]
struct Cli {
#[clap(parse(from_os_str), value_hint = ValueHint::FilePath, required = true)]
#[arg(required = true)]
file: PathBuf,
}

View File

@@ -2,14 +2,14 @@
use std::path::PathBuf;
use anyhow::Result;
use clap::{Parser, ValueHint};
use clap::Parser;
use rustpython_parser::lexer;
use ruff::fs;
#[derive(Debug, Parser)]
struct Cli {
#[clap(parse(from_os_str), value_hint = ValueHint::FilePath, required = true)]
#[arg(required = true)]
file: PathBuf,
}

View File

@@ -256,7 +256,7 @@ impl CheckCode {
pub fn lint_source(&self) -> &'static LintSource {
match self {
CheckCode::E501 | CheckCode::M001 => &LintSource::Lines,
CheckCode::E902 | CheckCode::E999 => &LintSource::FileSystem,
CheckCode::E902 => &LintSource::FileSystem,
_ => &LintSource::AST,
}
}

View File

@@ -15,19 +15,30 @@ use crate::noqa::add_noqa;
use crate::settings::Settings;
use crate::{cache, fs, noqa};
/// Collect tokens up to and including the first error.
fn tokenize(contents: &str) -> Vec<LexResult> {
let mut tokens: Vec<LexResult> = vec![];
for tok in lexer::make_tokenizer(contents) {
let is_err = tok.is_err();
tokens.push(tok);
if is_err {
break;
}
}
tokens
}
fn check_path(
path: &Path,
contents: &str,
tokens: Vec<LexResult>,
noqa_line_for: &[usize],
settings: &Settings,
autofix: &fixer::Mode,
) -> Result<Vec<Check>> {
// Aggregate all checks.
let mut checks: Vec<Check> = vec![];
// Determine the noqa line for every line in the source.
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
// Run the AST-based checks.
if settings
.select
@@ -50,7 +61,7 @@ fn check_path(
}
// Run the lines-based checks.
check_lines(&mut checks, contents, &noqa_line_for, settings, autofix);
check_lines(&mut checks, contents, noqa_line_for, settings, autofix);
// Create path ignores.
if !checks.is_empty() && !settings.per_file_ignores.is_empty() {
@@ -84,10 +95,13 @@ pub fn lint_path(
let contents = fs::read_file(path)?;
// Tokenize once.
let tokens: Vec<LexResult> = lexer::make_tokenizer(&contents).collect();
let tokens: Vec<LexResult> = tokenize(&contents);
// Determine the noqa line for every line in the source.
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
// Generate checks.
let mut checks = check_path(path, &contents, tokens, settings, autofix)?;
let mut checks = check_path(path, &contents, tokens, &noqa_line_for, settings, autofix)?;
// Apply autofix.
if matches!(autofix, fixer::Mode::Apply) {
@@ -114,13 +128,20 @@ pub fn add_noqa_to_path(path: &Path, settings: &Settings) -> Result<usize> {
let contents = fs::read_file(path)?;
// Tokenize once.
let tokens: Vec<LexResult> = lexer::make_tokenizer(&contents).collect();
let tokens: Vec<LexResult> = tokenize(&contents);
// Determine the noqa line for every line in the source.
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
// Generate checks.
let checks = check_path(path, &contents, tokens, settings, &fixer::Mode::None)?;
let checks = check_path(
path,
&contents,
tokens,
&noqa_line_for,
settings,
&fixer::Mode::None,
)?;
add_noqa(&checks, &contents, &noqa_line_for, path)
}
@@ -131,14 +152,14 @@ mod tests {
use anyhow::Result;
use regex::Regex;
use rustpython_parser::lexer;
use rustpython_parser::lexer::LexResult;
use crate::autofix::fixer;
use crate::checks::{Check, CheckCode};
use crate::fs;
use crate::linter;
use crate::linter::tokenize;
use crate::settings;
use crate::{fs, noqa};
fn check_path(
path: &Path,
@@ -146,8 +167,9 @@ mod tests {
autofix: &fixer::Mode,
) -> Result<Vec<Check>> {
let contents = fs::read_file(path)?;
let tokens: Vec<LexResult> = lexer::make_tokenizer(&contents).collect();
linter::check_path(path, &contents, tokens, settings, autofix)
let tokens: Vec<LexResult> = tokenize(&contents);
let noqa_line_for = noqa::extract_noqa_line_for(&tokens);
linter::check_path(path, &contents, tokens, &noqa_line_for, settings, autofix)
}
#[test]
@@ -692,4 +714,16 @@ mod tests {
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn e999() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/E999.py"),
&settings::Settings::for_rule(CheckCode::E999),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View File

@@ -1,6 +1,5 @@
extern crate core;
use regex::Regex;
use std::io;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
@@ -8,11 +7,12 @@ use std::sync::mpsc::channel;
use std::time::Instant;
use anyhow::Result;
use clap::{Parser, ValueHint};
use clap::{command, Parser};
use colored::Colorize;
use log::{debug, error};
use notify::{raw_watcher, RecursiveMode, Watcher};
use rayon::prelude::*;
use regex::Regex;
use walkdir::DirEntry;
use ::ruff::cache;
@@ -27,70 +27,70 @@ use ::ruff::printer::{Printer, SerializationFormat};
use ::ruff::pyproject::{self, StrCheckCodePair};
use ::ruff::settings::{FilePattern, PerFileIgnore, Settings};
use ::ruff::tell_user;
use ruff::settings::CurrentSettings;
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Debug, Parser)]
#[clap(name = format!("{CARGO_PKG_NAME} (v{CARGO_PKG_VERSION})"))]
#[clap(about = "An extremely fast Python linter.", long_about = None)]
#[clap(version)]
#[command(author, about = "ruff: An extremely fast Python linter.")]
#[command(version)]
struct Cli {
#[clap(parse(from_os_str), value_hint = ValueHint::AnyPath, required = true)]
#[arg(required = true)]
files: Vec<PathBuf>,
/// Enable verbose logging.
#[clap(short, long, action)]
#[arg(short, long)]
verbose: bool,
/// Disable all logging (but still exit with status code "1" upon detecting errors).
#[clap(short, long, action)]
#[arg(short, long)]
quiet: bool,
/// Exit with status code "0", even upon detecting errors.
#[clap(short, long, action)]
#[arg(short, long)]
exit_zero: bool,
/// Run in watch mode by re-running whenever files change.
#[clap(short, long, action)]
#[arg(short, long)]
watch: bool,
/// Attempt to automatically fix lint errors.
#[clap(short, long, action)]
#[arg(short, long)]
fix: bool,
/// Disable cache reads.
#[clap(short, long, action)]
#[arg(short, long)]
no_cache: bool,
/// List of error codes to enable.
#[clap(long, multiple = true)]
#[arg(long, value_delimiter = ',')]
select: Vec<CheckCode>,
/// Like --select, but adds additional error codes on top of the selected ones.
#[clap(long, multiple = true)]
#[arg(long, value_delimiter = ',')]
extend_select: Vec<CheckCode>,
/// List of error codes to ignore.
#[clap(long, multiple = true)]
#[arg(long, value_delimiter = ',')]
ignore: Vec<CheckCode>,
/// Like --ignore, but adds additional error codes on top of the ignored ones.
#[clap(long, multiple = true)]
#[arg(long, value_delimiter = ',')]
extend_ignore: Vec<CheckCode>,
/// List of paths, used to exclude files and/or directories from checks.
#[clap(long, multiple = true)]
#[arg(long, value_delimiter = ',')]
exclude: Vec<String>,
/// Like --exclude, but adds additional files and directories on top of the excluded ones.
#[clap(long, multiple = true)]
#[arg(long, value_delimiter = ',')]
extend_exclude: Vec<String>,
/// List of mappings from file pattern to code to exclude
#[clap(long, multiple = true)]
#[arg(long, value_delimiter = ',')]
per_file_ignores: Vec<StrCheckCodePair>,
/// Output serialization format for error messages.
#[clap(long, arg_enum, default_value_t=SerializationFormat::Text)]
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
format: SerializationFormat,
/// See the files ruff will be run against with the current settings.
#[clap(long, action)]
#[arg(long)]
show_files: bool,
/// See ruff's settings.
#[clap(long, action)]
#[arg(long)]
show_settings: bool,
/// Enable automatic additions of noqa directives to failing lines.
#[clap(long, action)]
#[arg(long)]
add_noqa: bool,
/// Regular expression matching the name of dummy variables.
#[clap(long)]
#[arg(long)]
dummy_variable_rgx: Option<Regex>,
}
@@ -118,8 +118,8 @@ fn check_for_updates() {
}
}
fn show_settings(settings: &Settings) {
println!("{:#?}", settings);
fn show_settings(settings: Settings) {
println!("{:#?}", CurrentSettings::from_settings(settings));
}
fn show_files(files: &[PathBuf], settings: &Settings) {
@@ -288,14 +288,14 @@ fn inner_main() -> Result<ExitCode> {
eprintln!("Error: specify --show-settings or show-files (not both).");
return Ok(ExitCode::FAILURE);
}
if cli.show_settings {
show_settings(&settings);
return Ok(ExitCode::SUCCESS);
}
if cli.show_files {
show_files(&cli.files, &settings);
return Ok(ExitCode::SUCCESS);
}
if cli.show_settings {
show_settings(settings);
return Ok(ExitCode::SUCCESS);
}
cache::init()?;

View File

@@ -1,13 +1,14 @@
use std::cmp::{max, min};
use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::path::Path;
use crate::checks::{Check, CheckCode};
use anyhow::Result;
use once_cell::sync::Lazy;
use regex::Regex;
use rustpython_parser::lexer::{LexResult, Tok};
use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::path::Path;
use crate::checks::{Check, CheckCode};
static NO_QA_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"(?i)(?P<noqa>\s*# noqa(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?)")
@@ -160,12 +161,12 @@ pub fn add_noqa(
#[cfg(test)]
mod tests {
use crate::checks::{Check, CheckKind};
use anyhow::Result;
use rustpython_parser::ast::Location;
use rustpython_parser::lexer;
use rustpython_parser::lexer::LexResult;
use crate::checks::{Check, CheckKind};
use crate::noqa::{add_noqa_inner, extract_noqa_line_for};
#[test]

View File

@@ -36,7 +36,7 @@ pub struct Config {
pub dummy_variable_rgx: Option<String>,
}
#[derive(Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StrCheckCodePair {
pub pattern: String,
pub code: CheckCode,

View File

@@ -200,3 +200,62 @@ impl Settings {
}
}
}
/// Struct to render user-facing exclusion patterns.
#[derive(Debug)]
#[allow(dead_code)]
pub struct Exclusion {
basename: Option<String>,
absolute: Option<String>,
}
impl Exclusion {
pub fn from_file_pattern(file_pattern: FilePattern) -> Self {
match file_pattern {
FilePattern::Simple(basename) => Exclusion {
basename: Some(basename.to_string()),
absolute: None,
},
FilePattern::Complex(absolute, basename) => Exclusion {
basename: basename.map(|pattern| pattern.to_string()),
absolute: Some(absolute.to_string()),
},
}
}
}
/// Struct to render user-facing Settings.
#[derive(Debug)]
pub struct CurrentSettings {
pub pyproject: Option<PathBuf>,
pub project_root: Option<PathBuf>,
pub line_length: usize,
pub exclude: Vec<Exclusion>,
pub extend_exclude: Vec<Exclusion>,
pub select: BTreeSet<CheckCode>,
pub per_file_ignores: Vec<PerFileIgnore>,
pub dummy_variable_rgx: Regex,
}
impl CurrentSettings {
pub fn from_settings(settings: Settings) -> Self {
Self {
pyproject: settings.pyproject,
project_root: settings.project_root,
line_length: settings.line_length,
exclude: settings
.exclude
.into_iter()
.map(Exclusion::from_file_pattern)
.collect(),
extend_exclude: settings
.extend_exclude
.into_iter()
.map(Exclusion::from_file_pattern)
.collect(),
select: settings.select,
per_file_ignores: settings.per_file_ignores,
dummy_variable_rgx: settings.dummy_variable_rgx,
}
}
}

View File

@@ -0,0 +1,11 @@
---
source: src/linter.rs
expression: checks
---
- kind:
SyntaxError: Got unexpected EOF
location:
row: 2
column: 1
fix: ~