Move `python` into its own `ruff_python` crate (#2593)

This commit is contained in:
Charlie Marsh 2023-02-05 17:53:58 -05:00 committed by GitHub
parent ecc9f5de99
commit 87d0aa5561
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 158 additions and 119 deletions

View File

@ -84,6 +84,7 @@ At time of writing, the repository includes the following crates:
- `crates/ruff_cli`: binary crate containing Ruff's command-line interface.
- `crates/ruff_dev`: binary crate containing utilities used in the development of Ruff itself (e.g., `cargo dev generate-all`).
- `crates/ruff_macros`: library crate containing macros used by Ruff.
- `crates/ruff_python`: library crate implementing Python-specific functionality (e.g., lists of standard library modules by versionb).
- `crates/flake8_to_ruff`: binary crate for generating Ruff configuration from Flake8 configuration.
### Example: Adding a new lint rule

10
Cargo.lock generated
View File

@ -1929,6 +1929,7 @@ dependencies = [
"path-absolutize",
"regex",
"ruff_macros",
"ruff_python",
"rustc-hash",
"rustpython-ast",
"rustpython-common",
@ -2018,6 +2019,15 @@ dependencies = [
"textwrap",
]
[[package]]
name = "ruff_python"
version = "0.0.241"
dependencies = [
"once_cell",
"regex",
"rustc-hash",
]
[[package]]
name = "rust-stemmers"
version = "1.2.0"

View File

@ -40,6 +40,7 @@ once_cell = { version = "1.16.0" }
path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix_paths_on_wasm"] }
regex = { version = "1.6.0" }
ruff_macros = { version = "0.0.241", path = "../ruff_macros" }
ruff_python = { version = "0.0.241", path = "../ruff_python" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "adc23253e4b58980b407ba2760dbe61681d752fc" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "adc23253e4b58980b407ba2760dbe61681d752fc" }

View File

@ -7,5 +7,6 @@ pub mod helpers;
pub mod operations;
pub mod relocate;
pub mod types;
pub mod typing;
pub mod visitor;
pub mod whitespace;

View File

@ -0,0 +1,74 @@
use rustpython_ast::{Expr, ExprKind};
use ruff_python::typing::{PEP_585_BUILTINS_ELIGIBLE, PEP_593_SUBSCRIPTS, SUBSCRIPTS};
use crate::ast::types::CallPath;
pub enum Callable {
ForwardRef,
Cast,
NewType,
TypeVar,
NamedTuple,
TypedDict,
MypyExtension,
}
pub enum SubscriptKind {
AnnotatedSubscript,
PEP593AnnotatedSubscript,
}
pub fn match_annotated_subscript<'a, F>(
expr: &'a Expr,
resolve_call_path: F,
typing_modules: impl Iterator<Item = &'a str>,
) -> Option<SubscriptKind>
where
F: FnOnce(&'a Expr) -> Option<CallPath<'a>>,
{
if !matches!(
expr.node,
ExprKind::Name { .. } | ExprKind::Attribute { .. }
) {
return None;
}
resolve_call_path(expr).and_then(|call_path| {
if SUBSCRIPTS.contains(&call_path.as_slice()) {
return Some(SubscriptKind::AnnotatedSubscript);
}
if PEP_593_SUBSCRIPTS.contains(&call_path.as_slice()) {
return Some(SubscriptKind::PEP593AnnotatedSubscript);
}
for module in typing_modules {
let module_call_path = module.split('.').collect::<Vec<_>>();
if call_path.starts_with(&module_call_path) {
for subscript in SUBSCRIPTS.iter() {
if call_path.last() == subscript.last() {
return Some(SubscriptKind::AnnotatedSubscript);
}
}
for subscript in PEP_593_SUBSCRIPTS.iter() {
if call_path.last() == subscript.last() {
return Some(SubscriptKind::PEP593AnnotatedSubscript);
}
}
}
}
None
})
}
/// Returns `true` if `Expr` represents a reference to a typing object with a
/// PEP 585 built-in.
pub fn is_pep585_builtin<'a, F>(expr: &'a Expr, resolve_call_path: F) -> bool
where
F: FnOnce(&'a Expr) -> Option<CallPath<'a>>,
{
resolve_call_path(expr).map_or(false, |call_path| {
PEP_585_BUILTINS_ELIGIBLE.contains(&call_path.as_slice())
})
}

View File

@ -16,6 +16,9 @@ use rustpython_parser::ast::{
use rustpython_parser::parser;
use smallvec::smallvec;
use ruff_python::builtins::{BUILTINS, MAGIC_GLOBALS};
use ruff_python::typing::TYPING_EXTENSIONS;
use crate::ast::helpers::{binding_range, collect_call_path, extract_handler_names};
use crate::ast::operations::{extract_all_names, AllNamesFlags};
use crate::ast::relocate::relocate_expr;
@ -23,13 +26,11 @@ use crate::ast::types::{
Binding, BindingKind, CallPath, ClassDef, ExecutionContext, FunctionDef, Lambda, Node, Range,
RefEquality, Scope, ScopeKind,
};
use crate::ast::typing::{match_annotated_subscript, Callable, SubscriptKind};
use crate::ast::visitor::{walk_excepthandler, Visitor};
use crate::ast::{branch_detection, cast, helpers, operations, visitor};
use crate::ast::{branch_detection, cast, helpers, operations, typing, visitor};
use crate::docstrings::definition::{Definition, DefinitionKind, Docstring, Documentable};
use crate::noqa::Directive;
use crate::python::builtins::{BUILTINS, MAGIC_GLOBALS};
use crate::python::typing;
use crate::python::typing::{Callable, SubscriptKind};
use crate::registry::{Diagnostic, Rule};
use crate::rules::{
flake8_2020, flake8_annotations, flake8_bandit, flake8_blind_except, flake8_boolean_trap,
@ -194,7 +195,7 @@ impl<'a> Checker<'a> {
return true;
}
if typing::TYPING_EXTENSIONS.contains(target) {
if TYPING_EXTENSIONS.contains(target) {
if call_path.as_slice() == ["typing_extensions", target] {
return true;
}
@ -2086,7 +2087,7 @@ where
|| (self.settings.target_version >= PythonVersion::Py37
&& self.annotations_future_enabled
&& self.in_annotation))
&& typing::is_pep585_builtin(self, expr)
&& typing::is_pep585_builtin(expr, |expr| self.resolve_call_path(expr))
{
pyupgrade::rules::use_pep585_annotation(self, expr);
}
@ -2131,7 +2132,7 @@ where
|| (self.settings.target_version >= PythonVersion::Py37
&& self.annotations_future_enabled
&& self.in_annotation))
&& typing::is_pep585_builtin(self, expr)
&& typing::is_pep585_builtin(expr, |expr| self.resolve_call_path(expr))
{
pyupgrade::rules::use_pep585_annotation(self, expr);
}
@ -3399,7 +3400,11 @@ where
self.in_subscript = true;
visitor::walk_expr(self, expr);
} else {
match typing::match_annotated_subscript(self, value) {
match match_annotated_subscript(
value,
|expr| self.resolve_call_path(expr),
self.settings.typing_modules.iter().map(String::as_str),
) {
Some(subscript) => {
match subscript {
// Ex) Optional[int]

View File

@ -22,7 +22,6 @@ pub mod linter;
pub mod logging;
pub mod message;
mod noqa;
mod python;
pub mod registry;
pub mod resolver;
mod rule_redirects;

View File

@ -3,11 +3,11 @@ use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::fix::Fix;
use crate::python::identifiers::{is_identifier, is_mangled_private};
use crate::python::keyword::KWLIST;
use crate::registry::Diagnostic;
use crate::violation::AlwaysAutofixableViolation;
use ruff_macros::derive_message_formats;
use ruff_python::identifiers::{is_identifier, is_mangled_private};
use ruff_python::keyword::KWLIST;
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location};
define_violation!(

View File

@ -2,10 +2,10 @@ use crate::ast::types::Range;
use crate::ast::visitor::Visitor;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::python::string::is_lower;
use crate::registry::Diagnostic;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use ruff_python::string::is_lower;
use rustpython_ast::{ExprKind, Stmt, StmtKind};
define_violation!(

View File

@ -3,12 +3,12 @@ use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::fix::Fix;
use crate::python::identifiers::{is_identifier, is_mangled_private};
use crate::python::keyword::KWLIST;
use crate::registry::Diagnostic;
use crate::source_code::Stylist;
use crate::violation::AlwaysAutofixableViolation;
use ruff_macros::derive_message_formats;
use ruff_python::identifiers::{is_identifier, is_mangled_private};
use ruff_python::keyword::KWLIST;
use rustpython_ast::{Constant, Expr, ExprContext, ExprKind, Location, Stmt, StmtKind};
define_violation!(

View File

@ -1,10 +1,10 @@
use super::types::ShadowingType;
use crate::ast::types::Range;
use crate::define_violation;
use crate::python::builtins::BUILTINS;
use crate::registry::{Diagnostic, DiagnosticKind};
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use ruff_python::builtins::BUILTINS;
use rustpython_ast::Located;
define_violation!(

View File

@ -11,9 +11,9 @@ use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::fix::Fix;
use crate::message::Location;
use crate::python::identifiers::is_identifier;
use crate::registry::Diagnostic;
use crate::violation::{AlwaysAutofixableViolation, Violation};
use ruff_python::identifiers::is_identifier;
define_violation!(
pub struct NoUnnecessaryPass;

View File

@ -2,11 +2,11 @@ use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::cst::matchers::{match_comparison, match_expression};
use crate::fix::Fix;
use crate::python::string::{self};
use crate::registry::Diagnostic;
use crate::source_code::{Locator, Stylist};
use crate::violation::{Availability, Violation};
use crate::{define_violation, AutofixKind};
use ruff_python::string::{self};
use anyhow::Result;
use libcst_native::{Codegen, CodegenState, CompOp};

View File

@ -6,10 +6,12 @@ use log::debug;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::types::{ImportBlock, Importable};
use crate::python::sys::KNOWN_STANDARD_LIBRARY;
use ruff_python::sys::KNOWN_STANDARD_LIBRARY;
use crate::settings::types::PythonVersion;
use super::types::{ImportBlock, Importable};
#[derive(
Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema, Hash,
)]
@ -58,7 +60,7 @@ pub fn categorize(
} else if module_base == "__future__" {
(ImportType::Future, Reason::Future)
} else if KNOWN_STANDARD_LIBRARY
.get(&target_version)
.get(&target_version.as_tuple())
.unwrap()
.contains(module_base)
{

View File

@ -5,7 +5,7 @@ use std::collections::BTreeSet;
use super::settings::RelativeImportsOrder;
use super::types::EitherImport::{Import, ImportFrom};
use super::types::{AliasData, EitherImport, ImportFromData};
use crate::python::string;
use ruff_python::string;
#[derive(PartialOrd, Ord, PartialEq, Eq)]
pub enum Prefix {

View File

@ -2,7 +2,7 @@ use itertools::Itertools;
use rustpython_ast::{Stmt, StmtKind};
use crate::checkers::ast::Checker;
use crate::python::string::{is_lower, is_upper};
use ruff_python::string::{is_lower, is_upper};
pub fn is_camelcase(name: &str) -> bool {
!is_lower(name) && !is_upper(name) && !name.contains('_')

View File

@ -6,11 +6,11 @@ use crate::ast::helpers::identifier_range;
use crate::ast::types::{Range, Scope, ScopeKind};
use crate::checkers::ast::Checker;
use crate::define_violation;
use crate::python::string::{self};
use crate::registry::Diagnostic;
use crate::source_code::Locator;
use crate::violation::Violation;
use ruff_macros::derive_message_formats;
use ruff_python::string::{self};
define_violation!(
pub struct InvalidClassName {

View File

@ -7,8 +7,8 @@ use rustpython_parser::lexer::Tok;
use crate::ast::types::Range;
use crate::cst::matchers::{match_expr, match_module};
use crate::fix::Fix;
use crate::python::string::strip_quotes_and_prefixes;
use crate::source_code::{Locator, Stylist};
use ruff_python::string::strip_quotes_and_prefixes;
/// Generate a [`Fix`] to remove unused keys from format dict.
pub fn remove_unused_format_arguments_from_dict(

View File

@ -1,11 +1,11 @@
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::python::future::ALL_FEATURE_NAMES;
use crate::registry::Diagnostic;
use crate::violation::{Availability, Violation};
use crate::{define_violation, AutofixKind};
use itertools::Itertools;
use ruff_macros::derive_message_formats;
use ruff_python::future::ALL_FEATURE_NAMES;
use rustpython_ast::Alias;
define_violation!(

View File

@ -9,10 +9,10 @@ use crate::ast::helpers::{create_expr, create_stmt, unparse_stmt};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::fix::Fix;
use crate::python::identifiers::is_identifier;
use crate::python::keyword::KWLIST;
use crate::registry::Diagnostic;
use crate::source_code::Stylist;
use ruff_python::identifiers::is_identifier;
use ruff_python::keyword::KWLIST;
define_violation!(
pub struct ConvertNamedTupleFunctionalToClass {

View File

@ -9,10 +9,10 @@ use crate::ast::helpers::{create_expr, create_stmt, unparse_stmt};
use crate::ast::types::Range;
use crate::checkers::ast::Checker;
use crate::fix::Fix;
use crate::python::identifiers::is_identifier;
use crate::python::keyword::KWLIST;
use crate::registry::Diagnostic;
use crate::source_code::Stylist;
use ruff_python::identifiers::is_identifier;
use ruff_python::keyword::KWLIST;
define_violation!(
pub struct ConvertTypedDictFunctionalToClass {

View File

@ -16,11 +16,11 @@ use crate::ast::types::Range;
use crate::ast::whitespace::indentation;
use crate::checkers::ast::Checker;
use crate::fix::Fix;
use crate::python::identifiers::is_identifier;
use crate::python::keyword::KWLIST;
use crate::registry::Diagnostic;
use crate::rules::pydocstyle::helpers::{leading_quote, trailing_quote};
use crate::rules::pyupgrade::helpers::curly_escape;
use ruff_python::identifiers::is_identifier;
use ruff_python::keyword::KWLIST;
define_violation!(
pub struct PrintfStringFormatting;

View File

@ -10,11 +10,12 @@ use rustc_hash::FxHashSet;
use schemars::JsonSchema;
use serde::{de, Deserialize, Deserializer, Serialize};
use super::hashable::HashableHashSet;
use crate::registry::Rule;
use crate::rule_selector::RuleSelector;
use crate::{fs, warn_user_once};
use super::hashable::HashableHashSet;
#[derive(
Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema,
)]

View File

@ -0,0 +1,11 @@
[package]
name = "ruff_python"
version = "0.0.241"
edition = "2021"
[lib]
[dependencies]
once_cell = { version = "1.16.0" }
regex = { version = "1.6.0" }
rustc-hash = { version = "1.1.0" }

View File

@ -43,7 +43,7 @@ pub fn strip_quotes_and_prefixes(s: &str) -> &str {
#[cfg(test)]
mod tests {
use crate::python::string::{is_lower, is_upper, strip_quotes_and_prefixes};
use crate::string::{is_lower, is_upper, strip_quotes_and_prefixes};
#[test]
fn test_is_lower() {

View File

@ -2,14 +2,12 @@
use once_cell::sync::Lazy;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::settings::types::PythonVersion;
// See: https://pycqa.github.io/isort/docs/configuration/options.html#known-standard-library
pub static KNOWN_STANDARD_LIBRARY: Lazy<FxHashMap<PythonVersion, FxHashSet<&'static str>>> =
pub static KNOWN_STANDARD_LIBRARY: Lazy<FxHashMap<(u32, u32), FxHashSet<&'static str>>> =
Lazy::new(|| {
FxHashMap::from_iter([
(
PythonVersion::Py37,
(3, 7),
FxHashSet::from_iter([
"_ast",
"_dummy_thread",
@ -230,7 +228,7 @@ pub static KNOWN_STANDARD_LIBRARY: Lazy<FxHashMap<PythonVersion, FxHashSet<&'sta
]),
),
(
PythonVersion::Py38,
(3, 8),
FxHashSet::from_iter([
"_ast",
"_dummy_thread",
@ -450,7 +448,7 @@ pub static KNOWN_STANDARD_LIBRARY: Lazy<FxHashMap<PythonVersion, FxHashSet<&'sta
]),
),
(
PythonVersion::Py39,
(3, 9),
FxHashSet::from_iter([
"_ast",
"_thread",
@ -670,7 +668,7 @@ pub static KNOWN_STANDARD_LIBRARY: Lazy<FxHashMap<PythonVersion, FxHashSet<&'sta
]),
),
(
PythonVersion::Py310,
(3, 10),
FxHashSet::from_iter([
"_ast",
"_thread",
@ -888,7 +886,7 @@ pub static KNOWN_STANDARD_LIBRARY: Lazy<FxHashMap<PythonVersion, FxHashSet<&'sta
]),
),
(
PythonVersion::Py311,
(3, 11),
FxHashSet::from_iter([
"_ast",
"_thread",

View File

@ -1,8 +1,5 @@
use once_cell::sync::Lazy;
use rustc_hash::FxHashSet;
use rustpython_ast::{Expr, ExprKind};
use crate::checkers::ast::Checker;
// See: https://pypi.org/project/typing-extensions/
pub static TYPING_EXTENSIONS: Lazy<FxHashSet<&'static str>> = Lazy::new(|| {
@ -62,7 +59,7 @@ pub static TYPING_EXTENSIONS: Lazy<FxHashSet<&'static str>> = Lazy::new(|| {
});
// See: https://docs.python.org/3/library/typing.html
const SUBSCRIPTS: &[&[&str]] = &[
pub const SUBSCRIPTS: &[&[&str]] = &[
// builtins
&["", "dict"],
&["", "frozenset"],
@ -183,56 +180,15 @@ const SUBSCRIPTS: &[&[&str]] = &[
];
// See: https://docs.python.org/3/library/typing.html
const PEP_593_SUBSCRIPTS: &[&[&str]] = &[
pub const PEP_593_SUBSCRIPTS: &[&[&str]] = &[
// `typing`
&["typing", "Annotated"],
// `typing_extensions`
&["typing_extensions", "Annotated"],
];
pub enum SubscriptKind {
AnnotatedSubscript,
PEP593AnnotatedSubscript,
}
pub fn match_annotated_subscript(checker: &Checker, expr: &Expr) -> Option<SubscriptKind> {
if !matches!(
expr.node,
ExprKind::Name { .. } | ExprKind::Attribute { .. }
) {
return None;
}
checker.resolve_call_path(expr).and_then(|call_path| {
if SUBSCRIPTS.contains(&call_path.as_slice()) {
return Some(SubscriptKind::AnnotatedSubscript);
}
if PEP_593_SUBSCRIPTS.contains(&call_path.as_slice()) {
return Some(SubscriptKind::PEP593AnnotatedSubscript);
}
for module in &checker.settings.typing_modules {
let module_call_path = module.split('.').collect::<Vec<_>>();
if call_path.starts_with(&module_call_path) {
for subscript in SUBSCRIPTS.iter() {
if call_path.last() == subscript.last() {
return Some(SubscriptKind::AnnotatedSubscript);
}
}
for subscript in PEP_593_SUBSCRIPTS.iter() {
if call_path.last() == subscript.last() {
return Some(SubscriptKind::PEP593AnnotatedSubscript);
}
}
}
}
None
})
}
// See: https://peps.python.org/pep-0585/
const PEP_585_BUILTINS_ELIGIBLE: &[&[&str]] = &[
pub const PEP_585_BUILTINS_ELIGIBLE: &[&[&str]] = &[
&["typing", "Dict"],
&["typing", "FrozenSet"],
&["typing", "List"],
@ -241,21 +197,3 @@ const PEP_585_BUILTINS_ELIGIBLE: &[&[&str]] = &[
&["typing", "Type"],
&["typing_extensions", "Type"],
];
/// Returns `true` if `Expr` represents a reference to a typing object with a
/// PEP 585 built-in.
pub fn is_pep585_builtin(checker: &Checker, expr: &Expr) -> bool {
checker.resolve_call_path(expr).map_or(false, |call_path| {
PEP_585_BUILTINS_ELIGIBLE.contains(&call_path.as_slice())
})
}
pub enum Callable {
ForwardRef,
Cast,
NewType,
TypeVar,
NamedTuple,
TypedDict,
MypyExtension,
}

View File

@ -8,13 +8,13 @@ from pathlib import Path
from sphinx.ext.intersphinx import fetch_inventory
URL = "https://docs.python.org/{}/objects.inv"
PATH = Path("src") / "python" / "sys.rs"
VERSIONS = [
("3", "7"),
("3", "8"),
("3", "9"),
("3", "10"),
("3", "11"),
PATH = Path("crates") / "ruff_python" / "src" / "sys.rs"
VERSIONS: list[tuple[int, int]] = [
(3, 7),
(3, 8),
(3, 9),
(3, 10),
(3, 11),
]
@ -36,16 +36,14 @@ with PATH.open("w") as f:
use once_cell::sync::Lazy;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::settings::types::PythonVersion;
// See: https://pycqa.github.io/isort/docs/configuration/options.html#known-standard-library
pub static KNOWN_STANDARD_LIBRARY: Lazy<FxHashMap<PythonVersion, FxHashSet<&'static str>>> =
pub static KNOWN_STANDARD_LIBRARY: Lazy<FxHashMap<(u32, u32), FxHashSet<&'static str>>> =
Lazy::new(|| {
FxHashMap::from_iter([
""",
)
for version_info in VERSIONS:
version = ".".join(version_info)
for (major, minor) in VERSIONS:
version = f"{major}.{minor}"
url = URL.format(version)
invdata = fetch_inventory(FakeApp(), "", url)
@ -67,7 +65,7 @@ pub static KNOWN_STANDARD_LIBRARY: Lazy<FxHashMap<PythonVersion, FxHashSet<&'sta
f.write(
f"""\
(
PythonVersion::Py{"".join(version_info)},
({major}, {minor}),
FxHashSet::from_iter([
""",
)