mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-26 20:10:09 +00:00
Move required import parsing out of lint rule (#12536)
## Summary Instead, make it part of the serialization and deserialization itself. This makes it _much_ easier to reuse when solving https://github.com/astral-sh/ruff/issues/12458.
This commit is contained in:
parent
7ad4df9e9f
commit
d930052de8
18 changed files with 330 additions and 276 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -2400,13 +2400,17 @@ version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"is-macro",
|
"is-macro",
|
||||||
|
"ruff_cache",
|
||||||
"ruff_index",
|
"ruff_index",
|
||||||
|
"ruff_macros",
|
||||||
"ruff_python_ast",
|
"ruff_python_ast",
|
||||||
"ruff_python_parser",
|
"ruff_python_parser",
|
||||||
"ruff_python_stdlib",
|
"ruff_python_stdlib",
|
||||||
"ruff_source_file",
|
"ruff_source_file",
|
||||||
"ruff_text_size",
|
"ruff_text_size",
|
||||||
"rustc-hash 2.0.0",
|
"rustc-hash 2.0.0",
|
||||||
|
"schemars",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2539,6 +2543,7 @@ dependencies = [
|
||||||
"ruff_macros",
|
"ruff_macros",
|
||||||
"ruff_python_ast",
|
"ruff_python_ast",
|
||||||
"ruff_python_formatter",
|
"ruff_python_formatter",
|
||||||
|
"ruff_python_semantic",
|
||||||
"ruff_source_file",
|
"ruff_source_file",
|
||||||
"rustc-hash 2.0.0",
|
"rustc-hash 2.0.0",
|
||||||
"schemars",
|
"schemars",
|
||||||
|
|
|
@ -9,11 +9,12 @@ use anyhow::Result;
|
||||||
use libcst_native::{ImportAlias, Name as cstName, NameOrAttribute};
|
use libcst_native::{ImportAlias, Name as cstName, NameOrAttribute};
|
||||||
|
|
||||||
use ruff_diagnostics::Edit;
|
use ruff_diagnostics::Edit;
|
||||||
use ruff_python_ast::imports::{AnyImport, Import, ImportFrom};
|
|
||||||
use ruff_python_ast::{self as ast, ModModule, Stmt};
|
use ruff_python_ast::{self as ast, ModModule, Stmt};
|
||||||
use ruff_python_codegen::Stylist;
|
use ruff_python_codegen::Stylist;
|
||||||
use ruff_python_parser::{Parsed, Tokens};
|
use ruff_python_parser::{Parsed, Tokens};
|
||||||
use ruff_python_semantic::{ImportedName, SemanticModel};
|
use ruff_python_semantic::{
|
||||||
|
ImportedName, MemberNameImport, ModuleNameImport, NameImport, SemanticModel,
|
||||||
|
};
|
||||||
use ruff_python_trivia::textwrap::indent;
|
use ruff_python_trivia::textwrap::indent;
|
||||||
use ruff_source_file::Locator;
|
use ruff_source_file::Locator;
|
||||||
use ruff_text_size::{Ranged, TextSize};
|
use ruff_text_size::{Ranged, TextSize};
|
||||||
|
@ -71,7 +72,7 @@ impl<'a> Importer<'a> {
|
||||||
/// If there are no existing imports, the new import will be added at the top
|
/// If there are no existing imports, the new import will be added at the top
|
||||||
/// of the file. Otherwise, it will be added after the most recent top-level
|
/// of the file. Otherwise, it will be added after the most recent top-level
|
||||||
/// import statement.
|
/// import statement.
|
||||||
pub(crate) fn add_import(&self, import: &AnyImport, at: TextSize) -> Edit {
|
pub(crate) fn add_import(&self, import: &NameImport, at: TextSize) -> Edit {
|
||||||
let required_import = import.to_string();
|
let required_import = import.to_string();
|
||||||
if let Some(stmt) = self.preceding_import(at) {
|
if let Some(stmt) = self.preceding_import(at) {
|
||||||
// Insert after the last top-level import.
|
// Insert after the last top-level import.
|
||||||
|
@ -359,8 +360,12 @@ impl<'a> Importer<'a> {
|
||||||
// Case 2a: No `functools` import is in scope; thus, we add `import functools`,
|
// Case 2a: No `functools` import is in scope; thus, we add `import functools`,
|
||||||
// and return `"functools.cache"` as the bound name.
|
// and return `"functools.cache"` as the bound name.
|
||||||
if semantic.is_available(symbol.module) {
|
if semantic.is_available(symbol.module) {
|
||||||
let import_edit =
|
let import_edit = self.add_import(
|
||||||
self.add_import(&AnyImport::Import(Import::module(symbol.module)), at);
|
&NameImport::Import(ModuleNameImport::module(
|
||||||
|
symbol.module.to_string(),
|
||||||
|
)),
|
||||||
|
at,
|
||||||
|
);
|
||||||
Ok((
|
Ok((
|
||||||
import_edit,
|
import_edit,
|
||||||
format!(
|
format!(
|
||||||
|
@ -378,9 +383,9 @@ impl<'a> Importer<'a> {
|
||||||
// `from functools import cache`, and return `"cache"` as the bound name.
|
// `from functools import cache`, and return `"cache"` as the bound name.
|
||||||
if semantic.is_available(symbol.member) {
|
if semantic.is_available(symbol.member) {
|
||||||
let import_edit = self.add_import(
|
let import_edit = self.add_import(
|
||||||
&AnyImport::ImportFrom(ImportFrom::member(
|
&NameImport::ImportFrom(MemberNameImport::member(
|
||||||
symbol.module,
|
symbol.module.to_string(),
|
||||||
symbol.member,
|
symbol.member.to_string(),
|
||||||
)),
|
)),
|
||||||
at,
|
at,
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,8 +2,8 @@ use std::fmt;
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
|
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::imports::{AnyImport, ImportFrom};
|
|
||||||
use ruff_python_ast::Expr;
|
use ruff_python_ast::Expr;
|
||||||
|
use ruff_python_semantic::{MemberNameImport, NameImport};
|
||||||
use ruff_text_size::{Ranged, TextSize};
|
use ruff_text_size::{Ranged, TextSize};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
@ -86,7 +86,10 @@ impl AlwaysFixableViolation for FutureRequiredTypeAnnotation {
|
||||||
/// FA102
|
/// FA102
|
||||||
pub(crate) fn future_required_type_annotation(checker: &mut Checker, expr: &Expr, reason: Reason) {
|
pub(crate) fn future_required_type_annotation(checker: &mut Checker, expr: &Expr, reason: Reason) {
|
||||||
let mut diagnostic = Diagnostic::new(FutureRequiredTypeAnnotation { reason }, expr.range());
|
let mut diagnostic = Diagnostic::new(FutureRequiredTypeAnnotation { reason }, expr.range());
|
||||||
let required_import = AnyImport::ImportFrom(ImportFrom::member("__future__", "annotations"));
|
let required_import = NameImport::ImportFrom(MemberNameImport::member(
|
||||||
|
"__future__".to_string(),
|
||||||
|
"annotations".to_string(),
|
||||||
|
));
|
||||||
diagnostic.set_fix(Fix::unsafe_edit(
|
diagnostic.set_fix(Fix::unsafe_edit(
|
||||||
checker
|
checker
|
||||||
.importer()
|
.importer()
|
||||||
|
|
|
@ -282,11 +282,11 @@ mod tests {
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use ruff_python_semantic::{MemberNameImport, ModuleNameImport, NameImport};
|
||||||
|
use ruff_text_size::Ranged;
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use test_case::test_case;
|
use test_case::test_case;
|
||||||
|
|
||||||
use ruff_text_size::Ranged;
|
|
||||||
|
|
||||||
use crate::assert_messages;
|
use crate::assert_messages;
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
use crate::rules::isort::categorize::{ImportSection, KnownModules};
|
use crate::rules::isort::categorize::{ImportSection, KnownModules};
|
||||||
|
@ -804,9 +804,12 @@ mod tests {
|
||||||
&LinterSettings {
|
&LinterSettings {
|
||||||
src: vec![test_resource_path("fixtures/isort")],
|
src: vec![test_resource_path("fixtures/isort")],
|
||||||
isort: super::settings::Settings {
|
isort: super::settings::Settings {
|
||||||
required_imports: BTreeSet::from_iter([
|
required_imports: BTreeSet::from_iter([NameImport::ImportFrom(
|
||||||
"from __future__ import annotations".to_string()
|
MemberNameImport::member(
|
||||||
]),
|
"__future__".to_string(),
|
||||||
|
"annotations".to_string(),
|
||||||
|
),
|
||||||
|
)]),
|
||||||
..super::settings::Settings::default()
|
..super::settings::Settings::default()
|
||||||
},
|
},
|
||||||
..LinterSettings::for_rule(Rule::MissingRequiredImport)
|
..LinterSettings::for_rule(Rule::MissingRequiredImport)
|
||||||
|
@ -834,9 +837,13 @@ mod tests {
|
||||||
&LinterSettings {
|
&LinterSettings {
|
||||||
src: vec![test_resource_path("fixtures/isort")],
|
src: vec![test_resource_path("fixtures/isort")],
|
||||||
isort: super::settings::Settings {
|
isort: super::settings::Settings {
|
||||||
required_imports: BTreeSet::from_iter([
|
required_imports: BTreeSet::from_iter([NameImport::ImportFrom(
|
||||||
"from __future__ import annotations as _annotations".to_string(),
|
MemberNameImport::alias(
|
||||||
]),
|
"__future__".to_string(),
|
||||||
|
"annotations".to_string(),
|
||||||
|
"_annotations".to_string(),
|
||||||
|
),
|
||||||
|
)]),
|
||||||
..super::settings::Settings::default()
|
..super::settings::Settings::default()
|
||||||
},
|
},
|
||||||
..LinterSettings::for_rule(Rule::MissingRequiredImport)
|
..LinterSettings::for_rule(Rule::MissingRequiredImport)
|
||||||
|
@ -858,8 +865,14 @@ mod tests {
|
||||||
src: vec![test_resource_path("fixtures/isort")],
|
src: vec![test_resource_path("fixtures/isort")],
|
||||||
isort: super::settings::Settings {
|
isort: super::settings::Settings {
|
||||||
required_imports: BTreeSet::from_iter([
|
required_imports: BTreeSet::from_iter([
|
||||||
"from __future__ import annotations".to_string(),
|
NameImport::ImportFrom(MemberNameImport::member(
|
||||||
"from __future__ import generator_stop".to_string(),
|
"__future__".to_string(),
|
||||||
|
"annotations".to_string(),
|
||||||
|
)),
|
||||||
|
NameImport::ImportFrom(MemberNameImport::member(
|
||||||
|
"__future__".to_string(),
|
||||||
|
"generator_stop".to_string(),
|
||||||
|
)),
|
||||||
]),
|
]),
|
||||||
..super::settings::Settings::default()
|
..super::settings::Settings::default()
|
||||||
},
|
},
|
||||||
|
@ -870,29 +883,6 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_case(Path::new("docstring.py"))]
|
|
||||||
#[test_case(Path::new("docstring.pyi"))]
|
|
||||||
#[test_case(Path::new("docstring_only.py"))]
|
|
||||||
#[test_case(Path::new("empty.py"))]
|
|
||||||
fn combined_required_imports(path: &Path) -> Result<()> {
|
|
||||||
let snapshot = format!("combined_required_imports_{}", path.to_string_lossy());
|
|
||||||
let diagnostics = test_path(
|
|
||||||
Path::new("isort/required_imports").join(path).as_path(),
|
|
||||||
&LinterSettings {
|
|
||||||
src: vec![test_resource_path("fixtures/isort")],
|
|
||||||
isort: super::settings::Settings {
|
|
||||||
required_imports: BTreeSet::from_iter(["from __future__ import annotations, \
|
|
||||||
generator_stop"
|
|
||||||
.to_string()]),
|
|
||||||
..super::settings::Settings::default()
|
|
||||||
},
|
|
||||||
..LinterSettings::for_rule(Rule::MissingRequiredImport)
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
assert_messages!(snapshot, diagnostics);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test_case(Path::new("docstring.py"))]
|
#[test_case(Path::new("docstring.py"))]
|
||||||
#[test_case(Path::new("docstring.pyi"))]
|
#[test_case(Path::new("docstring.pyi"))]
|
||||||
#[test_case(Path::new("docstring_only.py"))]
|
#[test_case(Path::new("docstring_only.py"))]
|
||||||
|
@ -904,7 +894,9 @@ mod tests {
|
||||||
&LinterSettings {
|
&LinterSettings {
|
||||||
src: vec![test_resource_path("fixtures/isort")],
|
src: vec![test_resource_path("fixtures/isort")],
|
||||||
isort: super::settings::Settings {
|
isort: super::settings::Settings {
|
||||||
required_imports: BTreeSet::from_iter(["import os".to_string()]),
|
required_imports: BTreeSet::from_iter([NameImport::Import(
|
||||||
|
ModuleNameImport::module("os".to_string()),
|
||||||
|
)]),
|
||||||
..super::settings::Settings::default()
|
..super::settings::Settings::default()
|
||||||
},
|
},
|
||||||
..LinterSettings::for_rule(Rule::MissingRequiredImport)
|
..LinterSettings::for_rule(Rule::MissingRequiredImport)
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
use log::error;
|
|
||||||
|
|
||||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
|
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Fix};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_ast::helpers::is_docstring_stmt;
|
use ruff_python_ast::helpers::is_docstring_stmt;
|
||||||
use ruff_python_ast::imports::{Alias, AnyImport, FutureImport, Import, ImportFrom};
|
|
||||||
use ruff_python_ast::{self as ast, ModModule, PySourceType, Stmt};
|
use ruff_python_ast::{self as ast, ModModule, PySourceType, Stmt};
|
||||||
use ruff_python_codegen::Stylist;
|
use ruff_python_codegen::Stylist;
|
||||||
use ruff_python_parser::{parse_module, Parsed};
|
use ruff_python_parser::Parsed;
|
||||||
|
use ruff_python_semantic::{FutureImport, NameImport};
|
||||||
use ruff_source_file::Locator;
|
use ruff_source_file::Locator;
|
||||||
use ruff_text_size::{TextRange, TextSize};
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
|
|
||||||
|
@ -53,18 +51,19 @@ impl AlwaysFixableViolation for MissingRequiredImport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `true` if the [`Stmt`] includes the given [`AnyImport`].
|
/// Return `true` if the [`Stmt`] includes the given [`AnyImportRef`].
|
||||||
fn includes_import(stmt: &Stmt, target: &AnyImport) -> bool {
|
fn includes_import(stmt: &Stmt, target: &NameImport) -> bool {
|
||||||
match target {
|
match target {
|
||||||
AnyImport::Import(target) => {
|
NameImport::Import(target) => {
|
||||||
let Stmt::Import(ast::StmtImport { names, range: _ }) = &stmt else {
|
let Stmt::Import(ast::StmtImport { names, range: _ }) = &stmt else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
names.iter().any(|alias| {
|
names.iter().any(|alias| {
|
||||||
&alias.name == target.name.name && alias.asname.as_deref() == target.name.as_name
|
alias.name == target.name.name
|
||||||
|
&& alias.asname.as_deref() == target.name.as_name.as_deref()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
AnyImport::ImportFrom(target) => {
|
NameImport::ImportFrom(target) => {
|
||||||
let Stmt::ImportFrom(ast::StmtImportFrom {
|
let Stmt::ImportFrom(ast::StmtImportFrom {
|
||||||
module,
|
module,
|
||||||
names,
|
names,
|
||||||
|
@ -74,11 +73,11 @@ fn includes_import(stmt: &Stmt, target: &AnyImport) -> bool {
|
||||||
else {
|
else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
module.as_deref() == target.module
|
module.as_deref() == target.module.as_deref()
|
||||||
&& *level == target.level
|
&& *level == target.level
|
||||||
&& names.iter().any(|alias| {
|
&& names.iter().any(|alias| {
|
||||||
&alias.name == target.name.name
|
alias.name == target.name.name
|
||||||
&& alias.asname.as_deref() == target.name.as_name
|
&& alias.asname.as_deref() == target.name.as_name.as_deref()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +85,7 @@ fn includes_import(stmt: &Stmt, target: &AnyImport) -> bool {
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn add_required_import(
|
fn add_required_import(
|
||||||
required_import: &AnyImport,
|
required_import: &NameImport,
|
||||||
parsed: &Parsed<ModModule>,
|
parsed: &Parsed<ModModule>,
|
||||||
locator: &Locator,
|
locator: &Locator,
|
||||||
stylist: &Stylist,
|
stylist: &Stylist,
|
||||||
|
@ -134,69 +133,8 @@ pub(crate) fn add_required_imports(
|
||||||
.isort
|
.isort
|
||||||
.required_imports
|
.required_imports
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|required_import| {
|
.filter_map(|required_import| {
|
||||||
let Ok(body) = parse_module(required_import).map(Parsed::into_suite) else {
|
add_required_import(required_import, parsed, locator, stylist, source_type)
|
||||||
error!("Failed to parse required import: `{}`", required_import);
|
|
||||||
return vec![];
|
|
||||||
};
|
|
||||||
if body.is_empty() || body.len() > 1 {
|
|
||||||
error!(
|
|
||||||
"Expected require import to contain a single statement: `{}`",
|
|
||||||
required_import
|
|
||||||
);
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
let stmt = &body[0];
|
|
||||||
match stmt {
|
|
||||||
Stmt::ImportFrom(ast::StmtImportFrom {
|
|
||||||
module,
|
|
||||||
names,
|
|
||||||
level,
|
|
||||||
range: _,
|
|
||||||
}) => names
|
|
||||||
.iter()
|
|
||||||
.filter_map(|name| {
|
|
||||||
add_required_import(
|
|
||||||
&AnyImport::ImportFrom(ImportFrom {
|
|
||||||
module: module.as_deref(),
|
|
||||||
name: Alias {
|
|
||||||
name: name.name.as_str(),
|
|
||||||
as_name: name.asname.as_deref(),
|
|
||||||
},
|
|
||||||
level: *level,
|
|
||||||
}),
|
|
||||||
parsed,
|
|
||||||
locator,
|
|
||||||
stylist,
|
|
||||||
source_type,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
Stmt::Import(ast::StmtImport { names, range: _ }) => names
|
|
||||||
.iter()
|
|
||||||
.filter_map(|name| {
|
|
||||||
add_required_import(
|
|
||||||
&AnyImport::Import(Import {
|
|
||||||
name: Alias {
|
|
||||||
name: name.name.as_str(),
|
|
||||||
as_name: name.asname.as_deref(),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
parsed,
|
|
||||||
locator,
|
|
||||||
stylist,
|
|
||||||
source_type,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
_ => {
|
|
||||||
error!(
|
|
||||||
"Expected required import to be in import-from style: `{}`",
|
|
||||||
required_import
|
|
||||||
);
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,11 @@ use rustc_hash::FxHashSet;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
use ruff_macros::CacheKey;
|
|
||||||
|
|
||||||
use crate::display_settings;
|
use crate::display_settings;
|
||||||
use crate::rules::isort::categorize::KnownModules;
|
use crate::rules::isort::categorize::KnownModules;
|
||||||
use crate::rules::isort::ImportType;
|
use crate::rules::isort::ImportType;
|
||||||
|
use ruff_macros::CacheKey;
|
||||||
|
use ruff_python_semantic::NameImport;
|
||||||
|
|
||||||
use super::categorize::ImportSection;
|
use super::categorize::ImportSection;
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ impl Display for RelativeImportsOrder {
|
||||||
#[derive(Debug, Clone, CacheKey)]
|
#[derive(Debug, Clone, CacheKey)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub required_imports: BTreeSet<String>,
|
pub required_imports: BTreeSet<NameImport>,
|
||||||
pub combine_as_imports: bool,
|
pub combine_as_imports: bool,
|
||||||
pub force_single_line: bool,
|
pub force_single_line: bool,
|
||||||
pub force_sort_within_sections: bool,
|
pub force_sort_within_sections: bool,
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
---
|
|
||||||
source: crates/ruff_linter/src/rules/isort/mod.rs
|
|
||||||
---
|
|
||||||
docstring.py:1:1: I002 [*] Missing required import: `from __future__ import annotations`
|
|
||||||
ℹ Safe fix
|
|
||||||
1 1 | """Hello, world!"""
|
|
||||||
2 |+from __future__ import annotations
|
|
||||||
2 3 |
|
|
||||||
3 4 | x = 1
|
|
||||||
|
|
||||||
docstring.py:1:1: I002 [*] Missing required import: `from __future__ import generator_stop`
|
|
||||||
ℹ Safe fix
|
|
||||||
1 1 | """Hello, world!"""
|
|
||||||
2 |+from __future__ import generator_stop
|
|
||||||
2 3 |
|
|
||||||
3 4 | x = 1
|
|
|
@ -1,4 +0,0 @@
|
||||||
---
|
|
||||||
source: crates/ruff_linter/src/rules/isort/mod.rs
|
|
||||||
---
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
---
|
|
||||||
source: crates/ruff_linter/src/rules/isort/mod.rs
|
|
||||||
---
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
---
|
|
||||||
source: crates/ruff_linter/src/rules/isort/mod.rs
|
|
||||||
---
|
|
||||||
|
|
|
@ -1,114 +0,0 @@
|
||||||
/// A representation of an individual name imported via any import statement.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub enum AnyImport<'a> {
|
|
||||||
Import(Import<'a>),
|
|
||||||
ImportFrom(ImportFrom<'a>),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A representation of an individual name imported via an `import` statement.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Import<'a> {
|
|
||||||
pub name: Alias<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A representation of an individual name imported via a `from ... import` statement.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct ImportFrom<'a> {
|
|
||||||
pub module: Option<&'a str>,
|
|
||||||
pub name: Alias<'a>,
|
|
||||||
pub level: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Alias<'a> {
|
|
||||||
pub name: &'a str,
|
|
||||||
pub as_name: Option<&'a str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Import<'a> {
|
|
||||||
/// Creates a new `Import` to import the specified module.
|
|
||||||
pub fn module(name: &'a str) -> Self {
|
|
||||||
Self {
|
|
||||||
name: Alias {
|
|
||||||
name,
|
|
||||||
as_name: None,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ImportFrom<'a> {
|
|
||||||
/// Creates a new `ImportFrom` to import a member from the specified module.
|
|
||||||
pub fn member(module: &'a str, name: &'a str) -> Self {
|
|
||||||
Self {
|
|
||||||
module: Some(module),
|
|
||||||
name: Alias {
|
|
||||||
name,
|
|
||||||
as_name: None,
|
|
||||||
},
|
|
||||||
level: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for AnyImport<'_> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
AnyImport::Import(import) => write!(f, "{import}"),
|
|
||||||
AnyImport::ImportFrom(import_from) => write!(f, "{import_from}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for Import<'_> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(f, "import {}", self.name.name)?;
|
|
||||||
if let Some(as_name) = self.name.as_name {
|
|
||||||
write!(f, " as {as_name}")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for ImportFrom<'_> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(f, "from ")?;
|
|
||||||
if self.level > 0 {
|
|
||||||
write!(f, "{}", ".".repeat(self.level as usize))?;
|
|
||||||
}
|
|
||||||
if let Some(module) = self.module {
|
|
||||||
write!(f, "{module}")?;
|
|
||||||
}
|
|
||||||
write!(f, " import {}", self.name.name)?;
|
|
||||||
if let Some(as_name) = self.name.as_name {
|
|
||||||
write!(f, " as {as_name}")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait FutureImport {
|
|
||||||
/// Returns `true` if this import is from the `__future__` module.
|
|
||||||
fn is_future_import(&self) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FutureImport for Import<'_> {
|
|
||||||
fn is_future_import(&self) -> bool {
|
|
||||||
self.name.name == "__future__"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FutureImport for ImportFrom<'_> {
|
|
||||||
fn is_future_import(&self) -> bool {
|
|
||||||
self.module == Some("__future__")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FutureImport for AnyImport<'_> {
|
|
||||||
fn is_future_import(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
AnyImport::Import(import) => import.is_future_import(),
|
|
||||||
AnyImport::ImportFrom(import_from) => import_from.is_future_import(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,7 +12,6 @@ mod expression;
|
||||||
pub mod hashable;
|
pub mod hashable;
|
||||||
pub mod helpers;
|
pub mod helpers;
|
||||||
pub mod identifier;
|
pub mod identifier;
|
||||||
pub mod imports;
|
|
||||||
mod int;
|
mod int;
|
||||||
pub mod name;
|
pub mod name;
|
||||||
mod node;
|
mod node;
|
||||||
|
|
|
@ -11,8 +11,11 @@ repository = { workspace = true }
|
||||||
license = { workspace = true }
|
license = { workspace = true }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
ruff_cache = { workspace = true }
|
||||||
ruff_index = { workspace = true }
|
ruff_index = { workspace = true }
|
||||||
|
ruff_macros = { workspace = true }
|
||||||
ruff_python_ast = { workspace = true }
|
ruff_python_ast = { workspace = true }
|
||||||
|
ruff_python_parser = { workspace = true }
|
||||||
ruff_python_stdlib = { workspace = true }
|
ruff_python_stdlib = { workspace = true }
|
||||||
ruff_source_file = { workspace = true }
|
ruff_source_file = { workspace = true }
|
||||||
ruff_text_size = { workspace = true }
|
ruff_text_size = { workspace = true }
|
||||||
|
@ -20,6 +23,8 @@ ruff_text_size = { workspace = true }
|
||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
is-macro = { workspace = true }
|
is-macro = { workspace = true }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
|
schemars = { workspace = true, optional = true }
|
||||||
|
serde = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ruff_python_parser = { workspace = true }
|
ruff_python_parser = { workspace = true }
|
||||||
|
@ -27,3 +32,6 @@ ruff_python_parser = { workspace = true }
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
|
[package.metadata.cargo-shear]
|
||||||
|
# Used via `CacheKey` macro expansion.
|
||||||
|
ignored = ["ruff_cache"]
|
||||||
|
|
235
crates/ruff_python_semantic/src/imports.rs
Normal file
235
crates/ruff_python_semantic/src/imports.rs
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
use ruff_macros::CacheKey;
|
||||||
|
|
||||||
|
/// A list of names imported via any import statement.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, CacheKey)]
|
||||||
|
pub struct NameImports(Vec<NameImport>);
|
||||||
|
|
||||||
|
/// A representation of an individual name imported via any import statement.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, CacheKey)]
|
||||||
|
pub enum NameImport {
|
||||||
|
Import(ModuleNameImport),
|
||||||
|
ImportFrom(MemberNameImport),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A representation of an individual name imported via an `import` statement.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, CacheKey)]
|
||||||
|
pub struct ModuleNameImport {
|
||||||
|
pub name: Alias,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A representation of an individual name imported via a `from ... import` statement.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, CacheKey)]
|
||||||
|
pub struct MemberNameImport {
|
||||||
|
pub module: Option<String>,
|
||||||
|
pub name: Alias,
|
||||||
|
pub level: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, CacheKey)]
|
||||||
|
pub struct Alias {
|
||||||
|
pub name: String,
|
||||||
|
pub as_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NameImports {
|
||||||
|
pub fn into_imports(self) -> Vec<NameImport> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModuleNameImport {
|
||||||
|
/// Creates a new `Import` to import the specified module.
|
||||||
|
pub fn module(name: String) -> Self {
|
||||||
|
Self {
|
||||||
|
name: Alias {
|
||||||
|
name,
|
||||||
|
as_name: None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemberNameImport {
|
||||||
|
/// Creates a new `ImportFrom` to import a member from the specified module.
|
||||||
|
pub fn member(module: String, name: String) -> Self {
|
||||||
|
Self {
|
||||||
|
module: Some(module),
|
||||||
|
name: Alias {
|
||||||
|
name,
|
||||||
|
as_name: None,
|
||||||
|
},
|
||||||
|
level: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn alias(module: String, name: String, as_name: String) -> Self {
|
||||||
|
Self {
|
||||||
|
module: Some(module),
|
||||||
|
name: Alias {
|
||||||
|
name,
|
||||||
|
as_name: Some(as_name),
|
||||||
|
},
|
||||||
|
level: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for NameImport {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
NameImport::Import(import) => write!(f, "{import}"),
|
||||||
|
NameImport::ImportFrom(import_from) => write!(f, "{import_from}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ModuleNameImport {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "import {}", self.name.name)?;
|
||||||
|
if let Some(as_name) = self.name.as_name.as_ref() {
|
||||||
|
write!(f, " as {as_name}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for MemberNameImport {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "from ")?;
|
||||||
|
if self.level > 0 {
|
||||||
|
write!(f, "{}", ".".repeat(self.level as usize))?;
|
||||||
|
}
|
||||||
|
if let Some(module) = self.module.as_ref() {
|
||||||
|
write!(f, "{module}")?;
|
||||||
|
}
|
||||||
|
write!(f, " import {}", self.name.name)?;
|
||||||
|
if let Some(as_name) = self.name.as_name.as_ref() {
|
||||||
|
write!(f, " as {as_name}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FutureImport {
|
||||||
|
/// Returns `true` if this import is from the `__future__` module.
|
||||||
|
fn is_future_import(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FutureImport for ModuleNameImport {
|
||||||
|
fn is_future_import(&self) -> bool {
|
||||||
|
self.name.name == "__future__"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FutureImport for MemberNameImport {
|
||||||
|
fn is_future_import(&self) -> bool {
|
||||||
|
self.module.as_deref() == Some("__future__")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FutureImport for NameImport {
|
||||||
|
fn is_future_import(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
NameImport::Import(import) => import.is_future_import(),
|
||||||
|
NameImport::ImportFrom(import_from) => import_from.is_future_import(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
impl serde::Serialize for NameImports {
|
||||||
|
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
self.0.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
impl serde::Serialize for NameImport {
|
||||||
|
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
match self {
|
||||||
|
NameImport::Import(import) => serializer.collect_str(import),
|
||||||
|
NameImport::ImportFrom(import_from) => serializer.collect_str(import_from),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
impl<'de> serde::de::Deserialize<'de> for NameImports {
|
||||||
|
fn deserialize<D: serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
use ruff_python_ast::{self as ast, Stmt};
|
||||||
|
use ruff_python_parser::Parsed;
|
||||||
|
|
||||||
|
struct AnyNameImportsVisitor;
|
||||||
|
|
||||||
|
impl<'de> serde::de::Visitor<'de> for AnyNameImportsVisitor {
|
||||||
|
type Value = NameImports;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
formatter.write_str("an import statement")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
|
||||||
|
let body = ruff_python_parser::parse_module(value)
|
||||||
|
.map(Parsed::into_suite)
|
||||||
|
.map_err(E::custom)?;
|
||||||
|
let [stmt] = body.as_slice() else {
|
||||||
|
return Err(E::custom("Expected a single statement"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let imports = match stmt {
|
||||||
|
Stmt::ImportFrom(ast::StmtImportFrom {
|
||||||
|
module,
|
||||||
|
names,
|
||||||
|
level,
|
||||||
|
range: _,
|
||||||
|
}) => names
|
||||||
|
.iter()
|
||||||
|
.map(|name| {
|
||||||
|
NameImport::ImportFrom(MemberNameImport {
|
||||||
|
module: module.as_deref().map(ToString::to_string),
|
||||||
|
name: Alias {
|
||||||
|
name: name.name.to_string(),
|
||||||
|
as_name: name.asname.as_deref().map(ToString::to_string),
|
||||||
|
},
|
||||||
|
level: *level,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
Stmt::Import(ast::StmtImport { names, range: _ }) => names
|
||||||
|
.iter()
|
||||||
|
.map(|name| {
|
||||||
|
NameImport::Import(ModuleNameImport {
|
||||||
|
name: Alias {
|
||||||
|
name: name.name.to_string(),
|
||||||
|
as_name: name.asname.as_deref().map(ToString::to_string),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
_ => {
|
||||||
|
return Err(E::custom("Expected an import statement"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(NameImports(imports))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_str(AnyNameImportsVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "schemars")]
|
||||||
|
impl schemars::JsonSchema for NameImports {
|
||||||
|
fn schema_name() -> String {
|
||||||
|
"NameImports".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||||
|
schemars::schema::SchemaObject {
|
||||||
|
instance_type: Some(schemars::schema::InstanceType::String.into()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ mod branches;
|
||||||
mod context;
|
mod context;
|
||||||
mod definition;
|
mod definition;
|
||||||
mod globals;
|
mod globals;
|
||||||
|
mod imports;
|
||||||
mod model;
|
mod model;
|
||||||
mod nodes;
|
mod nodes;
|
||||||
mod reference;
|
mod reference;
|
||||||
|
@ -15,6 +16,7 @@ pub use branches::*;
|
||||||
pub use context::*;
|
pub use context::*;
|
||||||
pub use definition::*;
|
pub use definition::*;
|
||||||
pub use globals::*;
|
pub use globals::*;
|
||||||
|
pub use imports::*;
|
||||||
pub use model::*;
|
pub use model::*;
|
||||||
pub use nodes::*;
|
pub use nodes::*;
|
||||||
pub use reference::*;
|
pub use reference::*;
|
||||||
|
|
|
@ -17,6 +17,7 @@ ruff_linter = { workspace = true }
|
||||||
ruff_formatter = { workspace = true }
|
ruff_formatter = { workspace = true }
|
||||||
ruff_python_formatter = { workspace = true, features = ["serde"] }
|
ruff_python_formatter = { workspace = true, features = ["serde"] }
|
||||||
ruff_python_ast = { workspace = true }
|
ruff_python_ast = { workspace = true }
|
||||||
|
ruff_python_semantic = { workspace = true, features = ["serde"] }
|
||||||
ruff_source_file = { workspace = true }
|
ruff_source_file = { workspace = true }
|
||||||
ruff_cache = { workspace = true }
|
ruff_cache = { workspace = true }
|
||||||
ruff_macros = { workspace = true }
|
ruff_macros = { workspace = true }
|
||||||
|
@ -55,7 +56,7 @@ ignored = ["colored"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
schemars = ["dep:schemars", "ruff_formatter/schemars", "ruff_python_formatter/schemars"]
|
schemars = ["dep:schemars", "ruff_formatter/schemars", "ruff_python_formatter/schemars", "ruff_python_semantic/schemars"]
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
@ -5,6 +5,8 @@ use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
use crate::options_base::{OptionsMetadata, Visit};
|
||||||
|
use crate::settings::LineEnding;
|
||||||
use ruff_formatter::IndentStyle;
|
use ruff_formatter::IndentStyle;
|
||||||
use ruff_linter::line_width::{IndentWidth, LineLength};
|
use ruff_linter::line_width::{IndentWidth, LineLength};
|
||||||
use ruff_linter::rules::flake8_import_conventions::settings::BannedAliases;
|
use ruff_linter::rules::flake8_import_conventions::settings::BannedAliases;
|
||||||
|
@ -30,9 +32,7 @@ use ruff_linter::{warn_user_once, RuleSelector};
|
||||||
use ruff_macros::{CombineOptions, OptionsMetadata};
|
use ruff_macros::{CombineOptions, OptionsMetadata};
|
||||||
use ruff_python_ast::name::Name;
|
use ruff_python_ast::name::Name;
|
||||||
use ruff_python_formatter::{DocstringCodeLineWidth, QuoteStyle};
|
use ruff_python_formatter::{DocstringCodeLineWidth, QuoteStyle};
|
||||||
|
use ruff_python_semantic::NameImports;
|
||||||
use crate::options_base::{OptionsMetadata, Visit};
|
|
||||||
use crate::settings::LineEnding;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Default, OptionsMetadata, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Default, OptionsMetadata, Serialize, Deserialize)]
|
||||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||||
|
@ -481,12 +481,12 @@ impl OptionsMetadata for DeprecatedTopLevelLintOptions {
|
||||||
|
|
||||||
#[cfg(feature = "schemars")]
|
#[cfg(feature = "schemars")]
|
||||||
impl schemars::JsonSchema for DeprecatedTopLevelLintOptions {
|
impl schemars::JsonSchema for DeprecatedTopLevelLintOptions {
|
||||||
fn schema_name() -> std::string::String {
|
fn schema_name() -> String {
|
||||||
"DeprecatedTopLevelLintOptions".to_owned()
|
"DeprecatedTopLevelLintOptions".to_owned()
|
||||||
}
|
}
|
||||||
fn schema_id() -> std::borrow::Cow<'static, str> {
|
fn schema_id() -> std::borrow::Cow<'static, str> {
|
||||||
std::borrow::Cow::Borrowed(std::concat!(
|
std::borrow::Cow::Borrowed(concat!(
|
||||||
std::module_path!(),
|
module_path!(),
|
||||||
"::",
|
"::",
|
||||||
"DeprecatedTopLevelLintOptions"
|
"DeprecatedTopLevelLintOptions"
|
||||||
))
|
))
|
||||||
|
@ -2035,7 +2035,7 @@ pub struct IsortOptions {
|
||||||
required-imports = ["from __future__ import annotations"]
|
required-imports = ["from __future__ import annotations"]
|
||||||
"#
|
"#
|
||||||
)]
|
)]
|
||||||
pub required_imports: Option<Vec<String>>,
|
pub required_imports: Option<Vec<NameImports>>,
|
||||||
|
|
||||||
/// An override list of tokens to always recognize as a Class for
|
/// An override list of tokens to always recognize as a Class for
|
||||||
/// [`order-by-type`](#lint_isort_order-by-type) regardless of casing.
|
/// [`order-by-type`](#lint_isort_order-by-type) regardless of casing.
|
||||||
|
@ -2435,7 +2435,12 @@ impl IsortOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(isort::settings::Settings {
|
Ok(isort::settings::Settings {
|
||||||
required_imports: BTreeSet::from_iter(self.required_imports.unwrap_or_default()),
|
required_imports: self
|
||||||
|
.required_imports
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(NameImports::into_imports)
|
||||||
|
.collect(),
|
||||||
combine_as_imports: self.combine_as_imports.unwrap_or(false),
|
combine_as_imports: self.combine_as_imports.unwrap_or(false),
|
||||||
force_single_line: self.force_single_line.unwrap_or(false),
|
force_single_line: self.force_single_line.unwrap_or(false),
|
||||||
force_sort_within_sections,
|
force_sort_within_sections,
|
||||||
|
|
5
ruff.schema.json
generated
5
ruff.schema.json
generated
|
@ -1676,7 +1676,7 @@
|
||||||
"null"
|
"null"
|
||||||
],
|
],
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"$ref": "#/definitions/NameImports"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"section-order": {
|
"section-order": {
|
||||||
|
@ -2293,6 +2293,9 @@
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
"NameImports": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"OutputFormat": {
|
"OutputFormat": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue