Compare commits

..

8 Commits

Author SHA1 Message Date
Charlie Marsh
dd496c7b52 Bump version to 0.0.60 2022-10-07 17:36:33 -04:00
Charlie Marsh
78aafb4b34 Warn the user if an explicitly selected check code is ignored (#356) 2022-10-07 17:36:17 -04:00
Charlie Marsh
99c66d513a Rename refactor checks to upgrade checks (#354) 2022-10-07 16:54:55 -04:00
Charlie Marsh
04ade6a2f3 Update README.md 2022-10-07 16:50:17 -04:00
Charlie Marsh
4cf2682cda Implement type(primitive) (#353) 2022-10-07 16:47:06 -04:00
Charlie Marsh
e3a7357187 Wrap each import in its own backticks (#346) 2022-10-07 15:58:30 -04:00
Charlie Marsh
b60768debb Remove erroneous output.py file 2022-10-07 15:04:23 -04:00
Charlie Marsh
25e476639f Use or_default in lieu of or_insert 2022-10-07 14:56:56 -04:00
23 changed files with 483 additions and 216 deletions

2
Cargo.lock generated
View File

@@ -1907,7 +1907,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.59"
version = "0.0.60"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.59"
version = "0.0.60"
edition = "2021"
[lib]

View File

@@ -286,8 +286,9 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
| T203 | PPrintFound | `pprint` found | | 🛠 |
| U001 | UselessMetaclassType | `__metaclass__ = type` is implied | | 🛠 |
| U002 | UnnecessaryAbspath | `abspath(__file__)` is unnecessary in Python 3.9 and later | | 🛠 |
| R001 | UselessObjectInheritance | Class `...` inherits from object | | 🛠 |
| R002 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 |
| U003 | TypeOfPrimitive | Use `str` instead of `type(...)` | | 🛠 |
| U004 | UselessObjectInheritance | Class `...` inherits from object | | 🛠 |
| U005 | NoAssertEquals | `assertEquals` is deprecated, use `assertEqual` instead | | 🛠 |
| M001 | UnusedNOQA | Unused `noqa` directive | | 🛠 |
## Integrations

View File

@@ -1,37 +0,0 @@
from collections import Counter
def f() -> None:
"""Docstring goes here."""
for x in range(5):
print(x)
else:
print("Nope!")
a = {
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
"a",
"b",
"c",
}
cls(title=title, before_text=before_text, after_text=after_text, before_description=before_description, after_description=after_description, transform_type=transform_type)

5
resources/test/fixtures/U003.py vendored Normal file
View File

@@ -0,0 +1,5 @@
type('')
type(b'')
type(0)
type(0.)
type(0j)

View File

@@ -6,6 +6,7 @@ use rustpython_parser::ast::{
Arg, ArgData, Arguments, Cmpop, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind,
Stmt, StmtKind, Unaryop,
};
use serde::{Deserialize, Serialize};
use crate::ast::types::{
Binding, BindingKind, CheckLocator, FunctionScope, Range, Scope, ScopeKind,
@@ -155,6 +156,65 @@ pub fn check_unnecessary_abspath(func: &Expr, args: &Vec<Expr>, location: Range)
None
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Primitive {
Bool,
Str,
Bytes,
Int,
Float,
Complex,
}
impl Primitive {
fn from_constant(constant: &Constant) -> Option<Self> {
match constant {
Constant::Bool(_) => Some(Primitive::Bool),
Constant::Str(_) => Some(Primitive::Str),
Constant::Bytes(_) => Some(Primitive::Bytes),
Constant::Int(_) => Some(Primitive::Int),
Constant::Float(_) => Some(Primitive::Float),
Constant::Complex { .. } => Some(Primitive::Complex),
_ => None,
}
}
pub fn builtin(&self) -> String {
match self {
Primitive::Bool => "bool".to_string(),
Primitive::Str => "str".to_string(),
Primitive::Bytes => "bytes".to_string(),
Primitive::Int => "int".to_string(),
Primitive::Float => "float".to_string(),
Primitive::Complex => "complex".to_string(),
}
}
}
/// Check TypeOfPrimitive compliance.
pub fn check_type_of_primitive(func: &Expr, args: &Vec<Expr>, location: Range) -> Option<Check> {
// Validate the arguments.
if args.len() == 1 {
match &func.node {
ExprKind::Attribute { attr: id, .. } | ExprKind::Name { id, .. } => {
if id == "type" {
if let ExprKind::Constant { value, .. } = &args[0].node {
if let Some(primitive) = Primitive::from_constant(value) {
return Some(Check::new(
CheckKind::TypeOfPrimitive(primitive),
location,
));
}
}
}
}
_ => {}
}
}
None
}
fn is_ambiguous_name(name: &str) -> bool {
name == "l" || name == "I" || name == "O"
}

View File

@@ -388,7 +388,7 @@ where
decorator_list,
..
} => {
if self.settings.enabled.contains(&CheckCode::R001) {
if self.settings.enabled.contains(&CheckCode::U004) {
plugins::useless_object_inheritance(self, stmt, name, bases, keywords);
}
@@ -749,7 +749,7 @@ where
ExprContext::Del => self.handle_node_delete(expr),
},
ExprKind::Call { func, args, .. } => {
if self.settings.enabled.contains(&CheckCode::R002) {
if self.settings.enabled.contains(&CheckCode::U005) {
plugins::assert_equals(self, func);
}
@@ -801,6 +801,10 @@ where
plugins::unnecessary_abspath(self, expr, func, args);
}
if self.settings.enabled.contains(&CheckCode::U003) {
plugins::type_of_primitive(self, expr, func, args);
}
if let ExprKind::Name { id, ctx } = &func.node {
if id == "locals" && matches!(ctx, ExprContext::Load) {
let scope = &mut self.scopes[*(self
@@ -1698,7 +1702,7 @@ impl<'a> Checker<'a> {
context.defined_by,
context.defined_in,
))
.or_insert(vec![]);
.or_default();
full_names.push(full_name);
}
BindingKind::Importation(full_name, context)
@@ -1709,7 +1713,7 @@ impl<'a> Checker<'a> {
context.defined_by,
context.defined_in,
))
.or_insert(vec![]);
.or_default();
full_names.push(full_name);
}
_ => {}
@@ -1721,12 +1725,8 @@ impl<'a> Checker<'a> {
let child = self.parents[defined_by];
let parent = defined_in.map(|defined_in| self.parents[defined_in]);
let mut check = Check::new(
CheckKind::UnusedImport(full_names.join(", ")),
self.locate_check(Range::from_located(child)),
);
if matches!(self.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
let fix = if matches!(self.autofix, fixer::Mode::Generate | fixer::Mode::Apply)
{
let deleted: Vec<&Stmt> = self
.deletions
.iter()
@@ -1739,14 +1739,22 @@ impl<'a> Checker<'a> {
};
match removal_fn(&mut self.locator, &full_names, child, parent, &deleted) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
self.deletions.insert(defined_by);
}
check.amend(fix)
Ok(fix) => Some(fix),
Err(e) => {
error!("Failed to fix unused imports: {}", e);
None
}
Err(e) => error!("Failed to fix unused imports: {}", e),
}
} else {
None
};
let mut check = Check::new(
CheckKind::UnusedImport(full_names.into_iter().map(String::from).collect()),
self.locate_check(Range::from_located(child)),
);
if let Some(fix) = fix {
check.amend(fix);
}
self.checks.push(check);

View File

@@ -1,11 +1,13 @@
use std::str::FromStr;
use crate::ast::types::Range;
use crate::ast::checks::Primitive;
use anyhow::Result;
use itertools::Itertools;
use rustpython_parser::ast::Location;
use serde::{Deserialize, Serialize};
use crate::ast::types::Range;
pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
// pycodestyle
CheckCode::E402,
@@ -53,7 +55,7 @@ pub const DEFAULT_CHECK_CODES: [CheckCode; 42] = [
CheckCode::F901,
];
pub const ALL_CHECK_CODES: [CheckCode; 57] = [
pub const ALL_CHECK_CODES: [CheckCode; 58] = [
// pycodestyle
CheckCode::E402,
CheckCode::E501,
@@ -115,9 +117,9 @@ pub const ALL_CHECK_CODES: [CheckCode; 57] = [
// pyupgrade
CheckCode::U001,
CheckCode::U002,
// Refactor
CheckCode::R001,
CheckCode::R002,
CheckCode::U003,
CheckCode::U004,
CheckCode::U005,
// Meta
CheckCode::M001,
];
@@ -185,9 +187,9 @@ pub enum CheckCode {
// pyupgrade
U001,
U002,
// Refactor
R001,
R002,
U003,
U004,
U005,
// Meta
M001,
}
@@ -256,9 +258,9 @@ impl FromStr for CheckCode {
// pyupgrade
"U001" => Ok(CheckCode::U001),
"U002" => Ok(CheckCode::U002),
// Refactor
"R001" => Ok(CheckCode::R001),
"R002" => Ok(CheckCode::R002),
"U003" => Ok(CheckCode::U003),
"U004" => Ok(CheckCode::U004),
"U005" => Ok(CheckCode::U005),
// Meta
"M001" => Ok(CheckCode::M001),
_ => Err(anyhow::anyhow!("Unknown check code: {s}")),
@@ -330,9 +332,9 @@ impl CheckCode {
// pyupgrade
CheckCode::U001 => "U001",
CheckCode::U002 => "U002",
// Refactor
CheckCode::R001 => "R001",
CheckCode::R002 => "R002",
CheckCode::U003 => "U003",
CheckCode::U004 => "U004",
CheckCode::U005 => "U005",
// Meta
CheckCode::M001 => "M001",
}
@@ -366,7 +368,7 @@ impl CheckCode {
CheckCode::E902 => CheckKind::IOError("IOError: `...`".to_string()),
CheckCode::E999 => CheckKind::SyntaxError("`...`".to_string()),
// pyflakes
CheckCode::F401 => CheckKind::UnusedImport("...".to_string()),
CheckCode::F401 => CheckKind::UnusedImport(vec!["...".to_string()]),
CheckCode::F402 => CheckKind::ImportShadowedByLoopVar("...".to_string(), 1),
CheckCode::F403 => CheckKind::ImportStarUsed("...".to_string()),
CheckCode::F404 => CheckKind::LateFutureImport,
@@ -413,9 +415,9 @@ impl CheckCode {
// pyupgrade
CheckCode::U001 => CheckKind::UselessMetaclassType,
CheckCode::U002 => CheckKind::UnnecessaryAbspath,
// Refactor
CheckCode::R001 => CheckKind::UselessObjectInheritance("...".to_string()),
CheckCode::R002 => CheckKind::NoAssertEquals,
CheckCode::U003 => CheckKind::TypeOfPrimitive(Primitive::Str),
CheckCode::U004 => CheckKind::UselessObjectInheritance("...".to_string()),
CheckCode::U005 => CheckKind::NoAssertEquals,
// Meta
CheckCode::M001 => CheckKind::UnusedNOQA(None),
}
@@ -476,7 +478,7 @@ pub enum CheckKind {
UndefinedExport(String),
UndefinedLocal(String),
UndefinedName(String),
UnusedImport(String),
UnusedImport(Vec<String>),
UnusedVariable(String),
YieldOutsideFunction,
// flake8-builtin
@@ -494,9 +496,9 @@ pub enum CheckKind {
PrintFound,
PPrintFound,
// pyupgrade
TypeOfPrimitive(Primitive),
UnnecessaryAbspath,
UselessMetaclassType,
// Refactor
NoAssertEquals,
UselessObjectInheritance(String),
// Meta
@@ -564,9 +566,9 @@ impl CheckKind {
CheckKind::PrintFound => "PrintFound",
CheckKind::PPrintFound => "PPrintFound",
// pyupgrade
CheckKind::TypeOfPrimitive(_) => "TypeOfPrimitive",
CheckKind::UnnecessaryAbspath => "UnnecessaryAbspath",
CheckKind::UselessMetaclassType => "UselessMetaclassType",
// Refactor
CheckKind::NoAssertEquals => "NoAssertEquals",
CheckKind::UselessObjectInheritance(_) => "UselessObjectInheritance",
// Meta
@@ -634,11 +636,11 @@ impl CheckKind {
CheckKind::PrintFound => &CheckCode::T201,
CheckKind::PPrintFound => &CheckCode::T203,
// pyupgrade
CheckKind::TypeOfPrimitive(_) => &CheckCode::U003,
CheckKind::UnnecessaryAbspath => &CheckCode::U002,
CheckKind::UselessMetaclassType => &CheckCode::U001,
// Refactor
CheckKind::NoAssertEquals => &CheckCode::R002,
CheckKind::UselessObjectInheritance(_) => &CheckCode::R001,
CheckKind::NoAssertEquals => &CheckCode::U005,
CheckKind::UselessObjectInheritance(_) => &CheckCode::U004,
// Meta
CheckKind::UnusedNOQA(_) => &CheckCode::M001,
}
@@ -764,7 +766,10 @@ impl CheckKind {
CheckKind::UndefinedName(name) => {
format!("Undefined name `{name}`")
}
CheckKind::UnusedImport(name) => format!("`{name}` imported but unused"),
CheckKind::UnusedImport(names) => {
let names = names.iter().map(|name| format!("`{name}`")).join(", ");
format!("{names} imported but unused")
}
CheckKind::UnusedVariable(name) => {
format!("Local variable `{name}` is assigned to but never used")
}
@@ -803,11 +808,13 @@ impl CheckKind {
CheckKind::PrintFound => "`print` found".to_string(),
CheckKind::PPrintFound => "`pprint` found".to_string(),
// pyupgrade
CheckKind::TypeOfPrimitive(primitive) => {
format!("Use `{}` instead of `type(...)`", primitive.builtin())
}
CheckKind::UnnecessaryAbspath => {
"`abspath(__file__)` is unnecessary in Python 3.9 and later".to_string()
}
CheckKind::UselessMetaclassType => "`__metaclass__ = type` is implied".to_string(),
// Refactor
CheckKind::NoAssertEquals => {
"`assertEquals` is deprecated, use `assertEqual` instead".to_string()
}
@@ -830,6 +837,7 @@ impl CheckKind {
| CheckKind::PPrintFound
| CheckKind::PrintFound
| CheckKind::SuperCallWithParameters
| CheckKind::TypeOfPrimitive(_)
| CheckKind::UnnecessaryAbspath
| CheckKind::UnusedImport(_)
| CheckKind::UnusedNOQA(_)

136
src/cli.rs Normal file
View File

@@ -0,0 +1,136 @@
use std::fmt;
use std::path::PathBuf;
use clap::{command, Parser};
use log::warn;
use regex::Regex;
use crate::checks::CheckCode;
use crate::printer::SerializationFormat;
use crate::pyproject::StrCheckCodePair;
use crate::settings::PythonVersion;
use crate::RawSettings;
#[derive(Debug, Parser)]
#[command(author, about = "ruff: An extremely fast Python linter.")]
#[command(version)]
pub struct Cli {
#[arg(required = true)]
pub files: Vec<PathBuf>,
/// Enable verbose logging.
#[arg(short, long)]
pub verbose: bool,
/// Disable all logging (but still exit with status code "1" upon detecting errors).
#[arg(short, long)]
pub quiet: bool,
/// Exit with status code "0", even upon detecting errors.
#[arg(short, long)]
pub exit_zero: bool,
/// Run in watch mode by re-running whenever files change.
#[arg(short, long)]
pub watch: bool,
/// Attempt to automatically fix lint errors.
#[arg(short, long)]
pub fix: bool,
/// Disable cache reads.
#[arg(short, long)]
pub no_cache: bool,
/// List of error codes to enable.
#[arg(long, value_delimiter = ',')]
pub select: Vec<CheckCode>,
/// Like --select, but adds additional error codes on top of the selected ones.
#[arg(long, value_delimiter = ',')]
pub extend_select: Vec<CheckCode>,
/// List of error codes to ignore.
#[arg(long, value_delimiter = ',')]
pub ignore: Vec<CheckCode>,
/// Like --ignore, but adds additional error codes on top of the ignored ones.
#[arg(long, value_delimiter = ',')]
pub extend_ignore: Vec<CheckCode>,
/// List of paths, used to exclude files and/or directories from checks.
#[arg(long, value_delimiter = ',')]
pub exclude: Vec<String>,
/// Like --exclude, but adds additional files and directories on top of the excluded ones.
#[arg(long, value_delimiter = ',')]
pub extend_exclude: Vec<String>,
/// List of mappings from file pattern to code to exclude
#[arg(long, value_delimiter = ',')]
pub per_file_ignores: Vec<StrCheckCodePair>,
/// Output serialization format for error messages.
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
pub format: SerializationFormat,
/// See the files ruff will be run against with the current settings.
#[arg(long)]
pub show_files: bool,
/// See ruff's settings.
#[arg(long)]
pub show_settings: bool,
/// Enable automatic additions of noqa directives to failing lines.
#[arg(long)]
pub add_noqa: bool,
/// Regular expression matching the name of dummy variables.
#[arg(long)]
pub dummy_variable_rgx: Option<Regex>,
/// The minimum Python version that should be supported.
#[arg(long)]
pub target_version: Option<PythonVersion>,
/// Round-trip auto-formatting.
// TODO(charlie): This should be a sub-command.
#[arg(long, hide = true)]
pub autoformat: bool,
}
pub enum Warnable {
Select,
ExtendSelect,
}
impl fmt::Display for Warnable {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
Warnable::Select => fmt.write_str("--select"),
Warnable::ExtendSelect => fmt.write_str("--extend-select"),
}
}
}
/// Warn the user if they attempt to enable a code that won't be respected.
pub fn warn_on(
flag: Warnable,
codes: &Vec<CheckCode>,
cli_ignore: &Vec<CheckCode>,
cli_extend_ignore: &Vec<CheckCode>,
pyproject_settings: &RawSettings,
pyproject_path: &Option<PathBuf>,
) {
for code in codes {
if !cli_ignore.is_empty() {
if cli_ignore.contains(code) {
warn!("{code:?} was passed to {flag}, but ignored via --ignore")
}
} else if pyproject_settings.ignore.contains(code) {
if let Some(path) = pyproject_path {
warn!(
"{code:?} was passed to {flag}, but ignored by the `ignore` field in {}",
path.to_string_lossy()
)
} else {
warn!("{code:?} was passed to {flag}, but ignored by the default `ignore` field",)
}
}
if !cli_extend_ignore.is_empty() {
if cli_extend_ignore.contains(code) {
warn!("{code:?} was passed to {flag}, but ignored via --extend-ignore")
}
} else if pyproject_settings.extend_ignore.contains(code) {
if let Some(path) = pyproject_path {
warn!(
"{code:?} was passed to {flag}, but ignored by the `extend_ignore` field in {}",
path.to_string_lossy()
)
} else {
warn!("{code:?} was passed to {flag}, but ignored by the default `extend_ignore` field")
}
}
}
}

View File

@@ -15,6 +15,7 @@ pub mod cache;
pub mod check_ast;
mod check_lines;
pub mod checks;
pub mod cli;
pub mod code_gen;
pub mod fs;
pub mod linter;
@@ -41,7 +42,7 @@ pub fn check(path: &Path, contents: &str) -> Result<Vec<Message>> {
None => debug!("Unable to find pyproject.toml; using default settings..."),
};
let settings = Settings::from_raw(RawSettings::from_pyproject(pyproject, project_root)?);
let settings = Settings::from_raw(RawSettings::from_pyproject(&pyproject, &project_root)?);
// Tokenize once.
let tokens: Vec<LexResult> = tokenize(contents);

View File

@@ -690,30 +690,6 @@ mod tests {
Ok(())
}
#[test]
fn r001() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/R001.py"),
&settings::Settings::for_rule(CheckCode::R001),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn r002() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/R002.py"),
&settings::Settings::for_rule(CheckCode::R002),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn init() -> Result<()> {
let mut checks = check_path(
@@ -893,4 +869,40 @@ mod tests {
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u003() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U003.py"),
&settings::Settings::for_rule(CheckCode::U003),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u004() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U004.py"),
&settings::Settings::for_rule(CheckCode::U004),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn u005() -> Result<()> {
let mut checks = check_path(
Path::new("./resources/test/fixtures/U005.py"),
&settings::Settings::for_rule(CheckCode::U005),
&fixer::Mode::Generate,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View File

@@ -5,17 +5,17 @@ use std::sync::mpsc::channel;
use std::time::Instant;
use anyhow::Result;
use clap::{command, Parser};
use clap::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;
use ruff::checks::CheckCode;
use ruff::checks::CheckKind;
use ruff::cli::{warn_on, Cli, Warnable};
use ruff::fs::iter_python_files;
use ruff::linter::add_noqa_to_path;
use ruff::linter::autoformat_path;
@@ -23,84 +23,15 @@ use ruff::linter::lint_path;
use ruff::logging::set_up_logging;
use ruff::message::Message;
use ruff::printer::{Printer, SerializationFormat};
use ruff::pyproject::{self, StrCheckCodePair};
use ruff::pyproject::{self};
use ruff::settings::CurrentSettings;
use ruff::settings::RawSettings;
use ruff::settings::{CurrentSettings, PythonVersion};
use ruff::settings::{FilePattern, PerFileIgnore, Settings};
use ruff::tell_user;
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Debug, Parser)]
#[command(author, about = "ruff: An extremely fast Python linter.")]
#[command(version)]
struct Cli {
#[arg(required = true)]
files: Vec<PathBuf>,
/// Enable verbose logging.
#[arg(short, long)]
verbose: bool,
/// Disable all logging (but still exit with status code "1" upon detecting errors).
#[arg(short, long)]
quiet: bool,
/// Exit with status code "0", even upon detecting errors.
#[arg(short, long)]
exit_zero: bool,
/// Run in watch mode by re-running whenever files change.
#[arg(short, long)]
watch: bool,
/// Attempt to automatically fix lint errors.
#[arg(short, long)]
fix: bool,
/// Disable cache reads.
#[arg(short, long)]
no_cache: bool,
/// List of error codes to enable.
#[arg(long, value_delimiter = ',')]
select: Vec<CheckCode>,
/// Like --select, but adds additional error codes on top of the selected ones.
#[arg(long, value_delimiter = ',')]
extend_select: Vec<CheckCode>,
/// List of error codes to ignore.
#[arg(long, value_delimiter = ',')]
ignore: Vec<CheckCode>,
/// Like --ignore, but adds additional error codes on top of the ignored ones.
#[arg(long, value_delimiter = ',')]
extend_ignore: Vec<CheckCode>,
/// List of paths, used to exclude files and/or directories from checks.
#[arg(long, value_delimiter = ',')]
exclude: Vec<String>,
/// Like --exclude, but adds additional files and directories on top of the excluded ones.
#[arg(long, value_delimiter = ',')]
extend_exclude: Vec<String>,
/// List of mappings from file pattern to code to exclude
#[arg(long, value_delimiter = ',')]
per_file_ignores: Vec<StrCheckCodePair>,
/// Output serialization format for error messages.
#[arg(long, value_enum, default_value_t=SerializationFormat::Text)]
format: SerializationFormat,
/// See the files ruff will be run against with the current settings.
#[arg(long)]
show_files: bool,
/// See ruff's settings.
#[arg(long)]
show_settings: bool,
/// Enable automatic additions of noqa directives to failing lines.
#[arg(long)]
add_noqa: bool,
/// Regular expression matching the name of dummy variables.
#[arg(long)]
dummy_variable_rgx: Option<Regex>,
/// The minimum Python version that should be supported.
#[arg(long)]
target_version: Option<PythonVersion>,
/// Round-trip auto-formatting.
// TODO(charlie): This should be a sub-command.
#[arg(long, hide = true)]
autoformat: bool,
}
#[cfg(feature = "update-informer")]
fn check_for_updates() {
use update_informer::{registry, Check};
@@ -125,8 +56,11 @@ fn check_for_updates() {
}
}
fn show_settings(settings: RawSettings) {
println!("{:#?}", CurrentSettings::from_settings(settings));
fn show_settings(settings: RawSettings, project_root: Option<PathBuf>, pyproject: Option<PathBuf>) {
println!(
"{:#?}",
CurrentSettings::from_settings(settings, project_root, pyproject)
);
}
fn show_files(files: &[PathBuf], settings: &Settings) {
@@ -292,7 +226,7 @@ fn inner_main() -> Result<ExitCode> {
.map(|pair| PerFileIgnore::new(pair, &project_root))
.collect();
let mut settings = RawSettings::from_pyproject(pyproject, project_root)?;
let mut settings = RawSettings::from_pyproject(&pyproject, &project_root)?;
if !exclude.is_empty() {
settings.exclude = exclude;
}
@@ -303,9 +237,25 @@ fn inner_main() -> Result<ExitCode> {
settings.per_file_ignores = per_file_ignores;
}
if !cli.select.is_empty() {
warn_on(
Warnable::Select,
&cli.select,
&cli.ignore,
&cli.extend_ignore,
&settings,
&pyproject,
);
settings.select = cli.select;
}
if !cli.extend_select.is_empty() {
warn_on(
Warnable::ExtendSelect,
&cli.extend_select,
&cli.ignore,
&cli.extend_ignore,
&settings,
&pyproject,
);
settings.extend_select = cli.extend_select;
}
if !cli.ignore.is_empty() {
@@ -326,7 +276,7 @@ fn inner_main() -> Result<ExitCode> {
return Ok(ExitCode::FAILURE);
}
if cli.show_settings {
show_settings(settings);
show_settings(settings, project_root, pyproject);
return Ok(ExitCode::SUCCESS);
}

View File

@@ -104,9 +104,7 @@ fn add_noqa_inner(
.unwrap_or(lineno);
if !codes.is_empty() {
let matches = matches_by_line
.entry(noqa_lineno)
.or_insert_with(BTreeSet::new);
let matches = matches_by_line.entry(noqa_lineno).or_default();
matches.append(&mut codes);
}
}

View File

@@ -4,6 +4,7 @@ mod if_tuple;
mod invalid_print_syntax;
mod print_call;
mod super_call_with_parameters;
mod type_of_primitive;
mod unnecessary_abspath;
mod useless_metaclass_type;
mod useless_object_inheritance;
@@ -14,6 +15,7 @@ pub use if_tuple::if_tuple;
pub use invalid_print_syntax::invalid_print_syntax;
pub use print_call::print_call;
pub use super_call_with_parameters::super_call_with_parameters;
pub use type_of_primitive::type_of_primitive;
pub use unnecessary_abspath::unnecessary_abspath;
pub use useless_metaclass_type::useless_metaclass_type;
pub use useless_object_inheritance::useless_object_inheritance;

View File

@@ -0,0 +1,25 @@
use rustpython_ast::Expr;
use crate::ast::checks;
use crate::ast::types::{CheckLocator, Range};
use crate::autofix::fixer;
use crate::check_ast::Checker;
use crate::checks::{CheckKind, Fix};
pub fn type_of_primitive(checker: &mut Checker, expr: &Expr, func: &Expr, args: &Vec<Expr>) {
if let Some(mut check) =
checks::check_type_of_primitive(func, args, checker.locate_check(Range::from_located(expr)))
{
if matches!(checker.autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if let CheckKind::TypeOfPrimitive(primitive) = &check.kind {
check.amend(Fix {
content: primitive.builtin(),
location: expr.location,
end_location: expr.end_location,
applied: false,
});
}
}
checker.add_check(check);
}
}

View File

@@ -1,4 +1,3 @@
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
@@ -8,6 +7,7 @@ use anyhow::{anyhow, Result};
use glob::Pattern;
use once_cell::sync::Lazy;
use regex::Regex;
use serde::{Deserialize, Serialize};
use crate::checks::{CheckCode, DEFAULT_CHECK_CODES};
use crate::fs;
@@ -94,8 +94,6 @@ pub struct RawSettings {
pub ignore: Vec<CheckCode>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
pub project_root: Option<PathBuf>,
pub pyproject: Option<PathBuf>,
pub select: Vec<CheckCode>,
pub target_version: PythonVersion,
}
@@ -129,10 +127,10 @@ static DEFAULT_DUMMY_VARIABLE_RGX: Lazy<Regex> =
impl RawSettings {
pub fn from_pyproject(
pyproject: Option<PathBuf>,
project_root: Option<PathBuf>,
pyproject: &Option<PathBuf>,
project_root: &Option<PathBuf>,
) -> Result<Self> {
let config = load_config(&pyproject)?;
let config = load_config(pyproject)?;
Ok(RawSettings {
dummy_variable_rgx: match config.dummy_variable_rgx {
Some(pattern) => Regex::new(&pattern)
@@ -145,14 +143,14 @@ impl RawSettings {
.map(|paths| {
paths
.iter()
.map(|path| FilePattern::from_user(path, &project_root))
.map(|path| FilePattern::from_user(path, project_root))
.collect()
})
.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()),
extend_exclude: config
.extend_exclude
.iter()
.map(|path| FilePattern::from_user(path, &project_root))
.map(|path| FilePattern::from_user(path, project_root))
.collect(),
extend_ignore: config.extend_ignore,
select: config
@@ -164,10 +162,8 @@ impl RawSettings {
per_file_ignores: config
.per_file_ignores
.into_iter()
.map(|pair| PerFileIgnore::new(pair, &project_root))
.map(|pair| PerFileIgnore::new(pair, project_root))
.collect(),
project_root,
pyproject,
})
}
}
@@ -278,14 +274,18 @@ pub struct CurrentSettings {
pub ignore: Vec<CheckCode>,
pub line_length: usize,
pub per_file_ignores: Vec<PerFileIgnore>,
pub project_root: Option<PathBuf>,
pub pyproject: Option<PathBuf>,
pub select: Vec<CheckCode>,
pub target_version: PythonVersion,
pub project_root: Option<PathBuf>,
pub pyproject: Option<PathBuf>,
}
impl CurrentSettings {
pub fn from_settings(settings: RawSettings) -> Self {
pub fn from_settings(
settings: RawSettings,
project_root: Option<PathBuf>,
pyproject: Option<PathBuf>,
) -> Self {
Self {
dummy_variable_rgx: settings.dummy_variable_rgx,
exclude: settings
@@ -303,10 +303,10 @@ impl CurrentSettings {
ignore: settings.ignore,
line_length: settings.line_length,
per_file_ignores: settings.per_file_ignores,
project_root: settings.project_root,
pyproject: settings.pyproject,
select: settings.select,
target_version: settings.target_version,
project_root,
pyproject,
}
}
}

View File

@@ -3,7 +3,8 @@ source: src/linter.rs
expression: checks
---
- kind:
UnusedImport: functools
UnusedImport:
- functools
location:
row: 2
column: 1
@@ -20,7 +21,8 @@ expression: checks
column: 21
applied: false
- kind:
UnusedImport: collections.OrderedDict
UnusedImport:
- collections.OrderedDict
location:
row: 4
column: 1
@@ -37,7 +39,8 @@ expression: checks
column: 2
applied: false
- kind:
UnusedImport: logging.handlers
UnusedImport:
- logging.handlers
location:
row: 12
column: 1
@@ -54,7 +57,8 @@ expression: checks
column: 24
applied: false
- kind:
UnusedImport: shelve
UnusedImport:
- shelve
location:
row: 33
column: 5
@@ -71,7 +75,8 @@ expression: checks
column: 1
applied: false
- kind:
UnusedImport: importlib
UnusedImport:
- importlib
location:
row: 34
column: 5
@@ -79,16 +84,17 @@ expression: checks
row: 34
column: 21
fix:
content: pass
content: ""
location:
row: 34
column: 5
column: 1
end_location:
row: 34
column: 21
row: 35
column: 1
applied: false
- kind:
UnusedImport: pathlib
UnusedImport:
- pathlib
location:
row: 38
column: 5
@@ -105,7 +111,8 @@ expression: checks
column: 1
applied: false
- kind:
UnusedImport: pickle
UnusedImport:
- pickle
location:
row: 53
column: 9

View File

@@ -3,7 +3,8 @@ source: src/linter.rs
expression: checks
---
- kind:
UnusedImport: models.Nut
UnusedImport:
- models.Nut
location:
row: 5
column: 1

View File

@@ -0,0 +1,90 @@
---
source: src/linter.rs
expression: checks
---
- kind:
TypeOfPrimitive: Str
location:
row: 1
column: 1
end_location:
row: 1
column: 9
fix:
content: str
location:
row: 1
column: 1
end_location:
row: 1
column: 9
applied: false
- kind:
TypeOfPrimitive: Bytes
location:
row: 2
column: 1
end_location:
row: 2
column: 10
fix:
content: bytes
location:
row: 2
column: 1
end_location:
row: 2
column: 10
applied: false
- kind:
TypeOfPrimitive: Int
location:
row: 3
column: 1
end_location:
row: 3
column: 8
fix:
content: int
location:
row: 3
column: 1
end_location:
row: 3
column: 8
applied: false
- kind:
TypeOfPrimitive: Float
location:
row: 4
column: 1
end_location:
row: 4
column: 9
fix:
content: float
location:
row: 4
column: 1
end_location:
row: 4
column: 9
applied: false
- kind:
TypeOfPrimitive: Complex
location:
row: 5
column: 1
end_location:
row: 5
column: 9
fix:
content: complex
location:
row: 5
column: 1
end_location:
row: 5
column: 9
applied: false