mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:25:17 +00:00
Add config option to disable typing_extensions
imports (#17611)
Summary -- This PR resolves https://github.com/astral-sh/ruff/issues/9761 by adding a linter configuration option to disable `typing_extensions` imports. As mentioned [here], it would be ideal if we could detect whether or not `typing_extensions` is available as a dependency automatically, but this seems like a much easier fix in the meantime. The default for the new option, `typing-extensions`, is `true`, preserving the current behavior. Setting it to `false` will bail out of the new `Checker::typing_importer` method, which has been refactored from the `Checker::import_from_typing` method in https://github.com/astral-sh/ruff/pull/17340), with `None`, which is then handled specially by each rule that calls it. I considered some alternatives to a config option, such as checking if `typing_extensions` has been imported or checking for a `TYPE_CHECKING` block we could use, but I think defaulting to allowing `typing_extensions` imports and allowing the user to disable this with an option is both simple to implement and pretty intuitive. [here]: https://github.com/astral-sh/ruff/issues/9761#issuecomment-2790492853 Test Plan -- New linter tests exercising several combinations of Python versions and the new config option for PYI019. I also added tests for the other affected rules, but only in the case where the new config option is enabled. The rules' existing tests also cover the default case.
This commit is contained in:
parent
405878a128
commit
01a31c08f5
26 changed files with 470 additions and 160 deletions
|
@ -994,6 +994,7 @@ fn value_given_to_table_key_is_not_inline_table_2() {
|
|||
- `lint.extend-per-file-ignores`
|
||||
- `lint.exclude`
|
||||
- `lint.preview`
|
||||
- `lint.typing-extensions`
|
||||
|
||||
For more information, try '--help'.
|
||||
");
|
||||
|
@ -2117,7 +2118,7 @@ requires-python = ">= 3.11"
|
|||
.arg("test.py")
|
||||
.arg("-")
|
||||
.current_dir(project_dir)
|
||||
, @r###"
|
||||
, @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -2207,6 +2208,7 @@ requires-python = ">= 3.11"
|
|||
XXX,
|
||||
]
|
||||
linter.typing_modules = []
|
||||
linter.typing_extensions = true
|
||||
|
||||
# Linter Plugins
|
||||
linter.flake8_annotations.mypy_init_return = false
|
||||
|
@ -2390,7 +2392,7 @@ requires-python = ">= 3.11"
|
|||
analyze.include_dependencies = {}
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
"#);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2428,7 +2430,7 @@ requires-python = ">= 3.11"
|
|||
.arg("test.py")
|
||||
.arg("-")
|
||||
.current_dir(project_dir)
|
||||
, @r###"
|
||||
, @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -2518,6 +2520,7 @@ requires-python = ">= 3.11"
|
|||
XXX,
|
||||
]
|
||||
linter.typing_modules = []
|
||||
linter.typing_extensions = true
|
||||
|
||||
# Linter Plugins
|
||||
linter.flake8_annotations.mypy_init_return = false
|
||||
|
@ -2701,7 +2704,7 @@ requires-python = ">= 3.11"
|
|||
analyze.include_dependencies = {}
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
"#);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
@ -2790,7 +2793,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("test.py")
|
||||
.arg("--show-settings")
|
||||
.current_dir(project_dir), @r###"
|
||||
.current_dir(project_dir), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -2881,6 +2884,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
XXX,
|
||||
]
|
||||
linter.typing_modules = []
|
||||
linter.typing_extensions = true
|
||||
|
||||
# Linter Plugins
|
||||
linter.flake8_annotations.mypy_init_return = false
|
||||
|
@ -3064,7 +3068,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
analyze.include_dependencies = {}
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
"#);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
@ -3170,7 +3174,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
.arg("--show-settings")
|
||||
.args(["--select","UP007"])
|
||||
.arg("foo/test.py")
|
||||
.current_dir(&project_dir), @r###"
|
||||
.current_dir(&project_dir), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -3260,6 +3264,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
XXX,
|
||||
]
|
||||
linter.typing_modules = []
|
||||
linter.typing_extensions = true
|
||||
|
||||
# Linter Plugins
|
||||
linter.flake8_annotations.mypy_init_return = false
|
||||
|
@ -3443,7 +3448,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
analyze.include_dependencies = {}
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
"#);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
@ -3497,7 +3502,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
.arg("--show-settings")
|
||||
.args(["--select","UP007"])
|
||||
.arg("foo/test.py")
|
||||
.current_dir(&project_dir), @r###"
|
||||
.current_dir(&project_dir), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -3587,6 +3592,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
XXX,
|
||||
]
|
||||
linter.typing_modules = []
|
||||
linter.typing_extensions = true
|
||||
|
||||
# Linter Plugins
|
||||
linter.flake8_annotations.mypy_init_return = false
|
||||
|
@ -3770,7 +3776,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
analyze.include_dependencies = {}
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
"#);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
@ -3823,7 +3829,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--show-settings")
|
||||
.arg("foo/test.py")
|
||||
.current_dir(&project_dir), @r###"
|
||||
.current_dir(&project_dir), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -3914,6 +3920,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
XXX,
|
||||
]
|
||||
linter.typing_modules = []
|
||||
linter.typing_extensions = true
|
||||
|
||||
# Linter Plugins
|
||||
linter.flake8_annotations.mypy_init_return = false
|
||||
|
@ -4097,7 +4104,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
analyze.include_dependencies = {}
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
"#);
|
||||
});
|
||||
|
||||
insta::with_settings!({
|
||||
|
@ -4107,7 +4114,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--show-settings")
|
||||
.arg("test.py")
|
||||
.current_dir(project_dir.join("foo")), @r###"
|
||||
.current_dir(project_dir.join("foo")), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -4198,6 +4205,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
XXX,
|
||||
]
|
||||
linter.typing_modules = []
|
||||
linter.typing_extensions = true
|
||||
|
||||
# Linter Plugins
|
||||
linter.flake8_annotations.mypy_init_return = false
|
||||
|
@ -4381,7 +4389,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
analyze.include_dependencies = {}
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
"#);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
@ -4444,7 +4452,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
.args(STDIN_BASE_OPTIONS)
|
||||
.arg("--show-settings")
|
||||
.arg("test.py")
|
||||
.current_dir(&project_dir), @r###"
|
||||
.current_dir(&project_dir), @r#"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
@ -4535,6 +4543,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
XXX,
|
||||
]
|
||||
linter.typing_modules = []
|
||||
linter.typing_extensions = true
|
||||
|
||||
# Linter Plugins
|
||||
linter.flake8_annotations.mypy_init_return = false
|
||||
|
@ -4718,7 +4727,7 @@ from typing import Union;foo: Union[int, str] = 1
|
|||
analyze.include_dependencies = {}
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
"#);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -213,6 +213,7 @@ linter.task_tags = [
|
|||
XXX,
|
||||
]
|
||||
linter.typing_modules = []
|
||||
linter.typing_extensions = true
|
||||
|
||||
# Linter Plugins
|
||||
linter.flake8_annotations.mypy_init_return = false
|
||||
|
|
|
@ -534,26 +534,50 @@ impl<'a> Checker<'a> {
|
|||
self.semantic_checker = checker;
|
||||
}
|
||||
|
||||
/// Attempt to create an [`Edit`] that imports `member`.
|
||||
/// Create a [`TypingImporter`] that will import `member` from either `typing` or
|
||||
/// `typing_extensions`.
|
||||
///
|
||||
/// On Python <`version_added_to_typing`, `member` is imported from `typing_extensions`, while
|
||||
/// on Python >=`version_added_to_typing`, it is imported from `typing`.
|
||||
///
|
||||
/// See [`Importer::get_or_import_symbol`] for more details on the returned values.
|
||||
pub(crate) fn import_from_typing(
|
||||
&self,
|
||||
member: &str,
|
||||
position: TextSize,
|
||||
/// If the Python version is less than `version_added_to_typing` but
|
||||
/// `LinterSettings::typing_extensions` is `false`, this method returns `None`.
|
||||
pub(crate) fn typing_importer<'b>(
|
||||
&'b self,
|
||||
member: &'b str,
|
||||
version_added_to_typing: PythonVersion,
|
||||
) -> Result<(Edit, String), ResolutionError> {
|
||||
) -> Option<TypingImporter<'b, 'a>> {
|
||||
let source_module = if self.target_version() >= version_added_to_typing {
|
||||
"typing"
|
||||
} else if !self.settings.typing_extensions {
|
||||
return None;
|
||||
} else {
|
||||
"typing_extensions"
|
||||
};
|
||||
let request = ImportRequest::import_from(source_module, member);
|
||||
self.importer()
|
||||
.get_or_import_symbol(&request, position, self.semantic())
|
||||
Some(TypingImporter {
|
||||
checker: self,
|
||||
source_module,
|
||||
member,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TypingImporter<'a, 'b> {
|
||||
checker: &'a Checker<'b>,
|
||||
source_module: &'static str,
|
||||
member: &'a str,
|
||||
}
|
||||
|
||||
impl TypingImporter<'_, '_> {
|
||||
/// Create an [`Edit`] that makes the requested symbol available at `position`.
|
||||
///
|
||||
/// See [`Importer::get_or_import_symbol`] for more details on the returned values and
|
||||
/// [`Checker::typing_importer`] for a way to construct a [`TypingImporter`].
|
||||
pub(crate) fn import(&self, position: TextSize) -> Result<(Edit, String), ResolutionError> {
|
||||
let request = ImportRequest::import_from(self.source_module, self.member);
|
||||
self.checker
|
||||
.importer
|
||||
.get_or_import_symbol(&request, position, self.checker.semantic())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -791,8 +791,9 @@ mod tests {
|
|||
use crate::linter::check_path;
|
||||
use crate::message::Message;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::LinterSettings;
|
||||
use crate::source_kind::SourceKind;
|
||||
use crate::test::{assert_notebook_path, test_contents, TestedNotebook};
|
||||
use crate::test::{assert_notebook_path, test_contents, test_snippet, TestedNotebook};
|
||||
use crate::{assert_messages, directives, settings, Locator};
|
||||
|
||||
/// Construct a path to a Jupyter notebook in the `resources/test/fixtures/jupyter` directory.
|
||||
|
@ -811,7 +812,7 @@ mod tests {
|
|||
} = assert_notebook_path(
|
||||
&actual,
|
||||
expected,
|
||||
&settings::LinterSettings::for_rule(Rule::UnsortedImports),
|
||||
&LinterSettings::for_rule(Rule::UnsortedImports),
|
||||
)?;
|
||||
assert_messages!(messages, actual, source_notebook);
|
||||
Ok(())
|
||||
|
@ -828,7 +829,7 @@ mod tests {
|
|||
} = assert_notebook_path(
|
||||
&actual,
|
||||
expected,
|
||||
&settings::LinterSettings::for_rule(Rule::UnusedImport),
|
||||
&LinterSettings::for_rule(Rule::UnusedImport),
|
||||
)?;
|
||||
assert_messages!(messages, actual, source_notebook);
|
||||
Ok(())
|
||||
|
@ -845,7 +846,7 @@ mod tests {
|
|||
} = assert_notebook_path(
|
||||
&actual,
|
||||
expected,
|
||||
&settings::LinterSettings::for_rule(Rule::UnusedVariable),
|
||||
&LinterSettings::for_rule(Rule::UnusedVariable),
|
||||
)?;
|
||||
assert_messages!(messages, actual, source_notebook);
|
||||
Ok(())
|
||||
|
@ -862,7 +863,7 @@ mod tests {
|
|||
} = assert_notebook_path(
|
||||
&actual,
|
||||
expected,
|
||||
&settings::LinterSettings::for_rule(Rule::UndefinedName),
|
||||
&LinterSettings::for_rule(Rule::UndefinedName),
|
||||
)?;
|
||||
assert_messages!(messages, actual, source_notebook);
|
||||
Ok(())
|
||||
|
@ -879,7 +880,7 @@ mod tests {
|
|||
} = assert_notebook_path(
|
||||
actual_path,
|
||||
&expected_path,
|
||||
&settings::LinterSettings::for_rule(Rule::UnusedImport),
|
||||
&LinterSettings::for_rule(Rule::UnusedImport),
|
||||
)?;
|
||||
let mut writer = Vec::new();
|
||||
fixed_notebook.write(&mut writer)?;
|
||||
|
@ -900,7 +901,7 @@ mod tests {
|
|||
} = assert_notebook_path(
|
||||
&actual,
|
||||
expected,
|
||||
&settings::LinterSettings::for_rule(Rule::UnusedImport),
|
||||
&LinterSettings::for_rule(Rule::UnusedImport),
|
||||
)?;
|
||||
assert_messages!(messages, actual, source_notebook);
|
||||
Ok(())
|
||||
|
@ -930,7 +931,7 @@ mod tests {
|
|||
let (_, transformed) = test_contents(
|
||||
&source_kind,
|
||||
path,
|
||||
&settings::LinterSettings::for_rule(Rule::UnusedImport),
|
||||
&LinterSettings::for_rule(Rule::UnusedImport),
|
||||
);
|
||||
let linted_notebook = transformed.into_owned().expect_ipy_notebook();
|
||||
let mut writer = Vec::new();
|
||||
|
@ -946,10 +947,7 @@ mod tests {
|
|||
|
||||
/// Wrapper around `test_contents_syntax_errors` for testing a snippet of code instead of a
|
||||
/// file.
|
||||
fn test_snippet_syntax_errors(
|
||||
contents: &str,
|
||||
settings: &settings::LinterSettings,
|
||||
) -> Vec<Message> {
|
||||
fn test_snippet_syntax_errors(contents: &str, settings: &LinterSettings) -> Vec<Message> {
|
||||
let contents = dedent(contents);
|
||||
test_contents_syntax_errors(
|
||||
&SourceKind::Python(contents.to_string()),
|
||||
|
@ -963,7 +961,7 @@ mod tests {
|
|||
fn test_contents_syntax_errors(
|
||||
source_kind: &SourceKind,
|
||||
path: &Path,
|
||||
settings: &settings::LinterSettings,
|
||||
settings: &LinterSettings,
|
||||
) -> Vec<Message> {
|
||||
let source_type = PySourceType::from(path);
|
||||
let options =
|
||||
|
@ -1032,7 +1030,7 @@ mod tests {
|
|||
let snapshot = format!("async_comprehension_in_sync_comprehension_{name}_{python_version}");
|
||||
let messages = test_snippet_syntax_errors(
|
||||
contents,
|
||||
&settings::LinterSettings {
|
||||
&LinterSettings {
|
||||
rules: settings::rule_table::RuleTable::empty(),
|
||||
unresolved_target_version: python_version,
|
||||
preview: settings::types::PreviewMode::Enabled,
|
||||
|
@ -1051,7 +1049,7 @@ mod tests {
|
|||
let messages = test_contents_syntax_errors(
|
||||
&SourceKind::IpyNotebook(Notebook::from_path(path)?),
|
||||
path,
|
||||
&settings::LinterSettings {
|
||||
&LinterSettings {
|
||||
unresolved_target_version: python_version,
|
||||
rules: settings::rule_table::RuleTable::empty(),
|
||||
preview: settings::types::PreviewMode::Enabled,
|
||||
|
@ -1076,7 +1074,7 @@ mod tests {
|
|||
let messages = test_contents_syntax_errors(
|
||||
&SourceKind::Python(std::fs::read_to_string(&path)?),
|
||||
&path,
|
||||
&settings::LinterSettings::for_rule(rule),
|
||||
&LinterSettings::for_rule(rule),
|
||||
);
|
||||
insta::with_settings!({filters => vec![(r"\\", "/")]}, {
|
||||
assert_messages!(snapshot, messages);
|
||||
|
@ -1095,10 +1093,108 @@ mod tests {
|
|||
} = assert_notebook_path(
|
||||
path,
|
||||
path,
|
||||
&settings::LinterSettings::for_rule(Rule::YieldOutsideFunction),
|
||||
&LinterSettings::for_rule(Rule::YieldOutsideFunction),
|
||||
)?;
|
||||
assert_messages!(messages, path, source_notebook);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const PYI019_EXAMPLE: &str = r#"
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T", bound="_NiceReprEnum")
|
||||
|
||||
class C:
|
||||
def __new__(cls: type[T]) -> T:
|
||||
return cls
|
||||
"#;
|
||||
|
||||
#[test_case(
|
||||
"pyi019_adds_typing_extensions",
|
||||
PYI019_EXAMPLE,
|
||||
&LinterSettings {
|
||||
unresolved_target_version: PythonVersion::PY310,
|
||||
typing_extensions: true,
|
||||
..LinterSettings::for_rule(Rule::CustomTypeVarForSelf)
|
||||
}
|
||||
)]
|
||||
#[test_case(
|
||||
"pyi019_does_not_add_typing_extensions",
|
||||
PYI019_EXAMPLE,
|
||||
&LinterSettings {
|
||||
unresolved_target_version: PythonVersion::PY310,
|
||||
typing_extensions: false,
|
||||
..LinterSettings::for_rule(Rule::CustomTypeVarForSelf)
|
||||
}
|
||||
)]
|
||||
#[test_case(
|
||||
"pyi019_adds_typing_without_extensions_disabled",
|
||||
PYI019_EXAMPLE,
|
||||
&LinterSettings {
|
||||
unresolved_target_version: PythonVersion::PY311,
|
||||
typing_extensions: true,
|
||||
..LinterSettings::for_rule(Rule::CustomTypeVarForSelf)
|
||||
}
|
||||
)]
|
||||
#[test_case(
|
||||
"pyi019_adds_typing_with_extensions_disabled",
|
||||
PYI019_EXAMPLE,
|
||||
&LinterSettings {
|
||||
unresolved_target_version: PythonVersion::PY311,
|
||||
typing_extensions: false,
|
||||
..LinterSettings::for_rule(Rule::CustomTypeVarForSelf)
|
||||
}
|
||||
)]
|
||||
#[test_case(
|
||||
"pyi034_disabled",
|
||||
"
|
||||
class C:
|
||||
def __new__(cls) -> C: ...
|
||||
",
|
||||
&LinterSettings {
|
||||
unresolved_target_version: PythonVersion { major: 3, minor: 10 },
|
||||
typing_extensions: false,
|
||||
..LinterSettings::for_rule(Rule::NonSelfReturnType)
|
||||
}
|
||||
)]
|
||||
#[test_case(
|
||||
"fast002_disabled",
|
||||
r#"
|
||||
from fastapi import Depends, FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.get("/items/")
|
||||
async def read_items(commons: dict = Depends(common_parameters)):
|
||||
return commons
|
||||
"#,
|
||||
&LinterSettings {
|
||||
unresolved_target_version: PythonVersion { major: 3, minor: 8 },
|
||||
typing_extensions: false,
|
||||
..LinterSettings::for_rule(Rule::FastApiNonAnnotatedDependency)
|
||||
}
|
||||
)]
|
||||
fn test_disabled_typing_extensions(name: &str, contents: &str, settings: &LinterSettings) {
|
||||
let snapshot = format!("disabled_typing_extensions_{name}");
|
||||
let messages = test_snippet(contents, settings);
|
||||
assert_messages!(snapshot, messages);
|
||||
}
|
||||
|
||||
#[test_case(
|
||||
"pyi026_disabled",
|
||||
"Vector = list[float]",
|
||||
&LinterSettings {
|
||||
unresolved_target_version: PythonVersion { major: 3, minor: 9 },
|
||||
typing_extensions: false,
|
||||
..LinterSettings::for_rule(Rule::TypeAliasWithoutAnnotation)
|
||||
}
|
||||
)]
|
||||
fn test_disabled_typing_extensions_pyi(name: &str, contents: &str, settings: &LinterSettings) {
|
||||
let snapshot = format!("disabled_typing_extensions_pyi_{name}");
|
||||
let path = Path::new("<filename>.pyi");
|
||||
let contents = dedent(contents);
|
||||
let messages = test_contents(&SourceKind::Python(contents.into_owned()), path, settings).0;
|
||||
assert_messages!(snapshot, messages);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,6 +59,16 @@ use ruff_python_ast::PythonVersion;
|
|||
/// return commons
|
||||
/// ```
|
||||
///
|
||||
/// ## Availability
|
||||
///
|
||||
/// Because this rule relies on the third-party `typing_extensions` module for Python versions
|
||||
/// before 3.9, its diagnostic will not be emitted, and no fix will be offered, if
|
||||
/// `typing_extensions` imports have been disabled by the [`lint.typing-extensions`] linter option.
|
||||
///
|
||||
/// ## Options
|
||||
///
|
||||
/// - `lint.typing-extensions`
|
||||
///
|
||||
/// [FastAPI documentation]: https://fastapi.tiangolo.com/tutorial/query-params-str-validations/?h=annotated#advantages-of-annotated
|
||||
/// [typing-annotated]: https://docs.python.org/3/library/typing.html#typing.Annotated
|
||||
/// [typing-extensions]: https://typing-extensions.readthedocs.io/en/stable/
|
||||
|
@ -223,6 +233,10 @@ fn create_diagnostic(
|
|||
dependency_call: Option<DependencyCall>,
|
||||
mut seen_default: bool,
|
||||
) -> bool {
|
||||
let Some(importer) = checker.typing_importer("Annotated", PythonVersion::PY39) else {
|
||||
return seen_default;
|
||||
};
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
FastApiNonAnnotatedDependency {
|
||||
py_version: checker.target_version(),
|
||||
|
@ -231,11 +245,7 @@ fn create_diagnostic(
|
|||
);
|
||||
|
||||
let try_generate_fix = || {
|
||||
let (import_edit, binding) = checker.import_from_typing(
|
||||
"Annotated",
|
||||
parameter.range.start(),
|
||||
PythonVersion::PY39,
|
||||
)?;
|
||||
let (import_edit, binding) = importer.import(parameter.range.start())?;
|
||||
|
||||
// Each of these classes takes a single, optional default
|
||||
// argument, followed by kw-only arguments
|
||||
|
|
|
@ -131,7 +131,8 @@ impl AutoPythonType {
|
|||
"NoReturn"
|
||||
};
|
||||
let (no_return_edit, binding) = checker
|
||||
.import_from_typing(member, at, PythonVersion::lowest())
|
||||
.typing_importer(member, PythonVersion::lowest())?
|
||||
.import(at)
|
||||
.ok()?;
|
||||
let expr = Expr::Name(ast::ExprName {
|
||||
id: Name::from(binding),
|
||||
|
@ -169,7 +170,8 @@ impl AutoPythonType {
|
|||
|
||||
// Ex) `Optional[int]`
|
||||
let (optional_edit, binding) = checker
|
||||
.import_from_typing("Optional", at, PythonVersion::lowest())
|
||||
.typing_importer("Optional", PythonVersion::lowest())?
|
||||
.import(at)
|
||||
.ok()?;
|
||||
let expr = typing_optional(element, Name::from(binding));
|
||||
Some((expr, vec![optional_edit]))
|
||||
|
@ -182,7 +184,8 @@ impl AutoPythonType {
|
|||
|
||||
// Ex) `Union[int, str]`
|
||||
let (union_edit, binding) = checker
|
||||
.import_from_typing("Union", at, PythonVersion::lowest())
|
||||
.typing_importer("Union", PythonVersion::lowest())?
|
||||
.import(at)
|
||||
.ok()?;
|
||||
let expr = typing_union(&elements, Name::from(binding));
|
||||
Some((expr, vec![union_edit]))
|
||||
|
|
|
@ -224,6 +224,16 @@ impl Violation for MissingTypeCls {
|
|||
/// def add(a: int, b: int) -> int:
|
||||
/// return a + b
|
||||
/// ```
|
||||
///
|
||||
/// ## Availability
|
||||
///
|
||||
/// Because this rule relies on the third-party `typing_extensions` module for some Python versions,
|
||||
/// its diagnostic will not be emitted, and no fix will be offered, if `typing_extensions` imports
|
||||
/// have been disabled by the [`lint.typing-extensions`] linter option.
|
||||
///
|
||||
/// ## Options
|
||||
///
|
||||
/// - `lint.typing-extensions`
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MissingReturnTypeUndocumentedPublicFunction {
|
||||
name: String,
|
||||
|
@ -267,6 +277,16 @@ impl Violation for MissingReturnTypeUndocumentedPublicFunction {
|
|||
/// def _add(a: int, b: int) -> int:
|
||||
/// return a + b
|
||||
/// ```
|
||||
///
|
||||
/// ## Availability
|
||||
///
|
||||
/// Because this rule relies on the third-party `typing_extensions` module for some Python versions,
|
||||
/// its diagnostic will not be emitted, and no fix will be offered, if `typing_extensions` imports
|
||||
/// have been disabled by the [`lint.typing-extensions`] linter option.
|
||||
///
|
||||
/// ## Options
|
||||
///
|
||||
/// - `lint.typing-extensions`
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct MissingReturnTypePrivateFunction {
|
||||
name: String,
|
||||
|
|
|
@ -10,7 +10,7 @@ use ruff_python_semantic::analyze::visibility::{is_abstract, is_overload};
|
|||
use ruff_python_semantic::{Binding, ResolvedReference, ScopeId, SemanticModel};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::checkers::ast::{Checker, TypingImporter};
|
||||
use ruff_python_ast::PythonVersion;
|
||||
|
||||
/// ## What it does
|
||||
|
@ -70,6 +70,16 @@ use ruff_python_ast::PythonVersion;
|
|||
/// The fix is only marked as unsafe if there is the possibility that it might delete a comment
|
||||
/// from your code.
|
||||
///
|
||||
/// ## Availability
|
||||
///
|
||||
/// Because this rule relies on the third-party `typing_extensions` module for Python versions
|
||||
/// before 3.11, its diagnostic will not be emitted, and no fix will be offered, if
|
||||
/// `typing_extensions` imports have been disabled by the [`lint.typing-extensions`] linter option.
|
||||
///
|
||||
/// ## Options
|
||||
///
|
||||
/// - `lint.typing-extensions`
|
||||
///
|
||||
/// [PEP 673]: https://peps.python.org/pep-0673/#motivation
|
||||
/// [PEP-695]: https://peps.python.org/pep-0695/
|
||||
/// [PYI018]: https://docs.astral.sh/ruff/rules/unused-private-type-var/
|
||||
|
@ -109,6 +119,7 @@ pub(crate) fn custom_type_var_instead_of_self(
|
|||
let semantic = checker.semantic();
|
||||
let current_scope = &semantic.scopes[binding.scope];
|
||||
let function_def = binding.statement(semantic)?.as_function_def_stmt()?;
|
||||
let importer = checker.typing_importer("Self", PythonVersion::PY311)?;
|
||||
|
||||
let ast::StmtFunctionDef {
|
||||
name: function_name,
|
||||
|
@ -178,6 +189,7 @@ pub(crate) fn custom_type_var_instead_of_self(
|
|||
diagnostic.try_set_fix(|| {
|
||||
replace_custom_typevar_with_self(
|
||||
checker,
|
||||
&importer,
|
||||
function_def,
|
||||
custom_typevar,
|
||||
self_or_cls_parameter,
|
||||
|
@ -310,14 +322,14 @@ fn custom_typevar<'a>(
|
|||
/// * If it was a PEP-695 type variable, removes that `TypeVar` from the PEP-695 type-parameter list
|
||||
fn replace_custom_typevar_with_self(
|
||||
checker: &Checker,
|
||||
importer: &TypingImporter,
|
||||
function_def: &ast::StmtFunctionDef,
|
||||
custom_typevar: TypeVar,
|
||||
self_or_cls_parameter: &ast::ParameterWithDefault,
|
||||
self_or_cls_annotation: &ast::Expr,
|
||||
) -> anyhow::Result<Fix> {
|
||||
// (1) Import `Self` (if necessary)
|
||||
let (import_edit, self_symbol_binding) =
|
||||
checker.import_from_typing("Self", function_def.start(), PythonVersion::PY311)?;
|
||||
let (import_edit, self_symbol_binding) = importer.import(function_def.start())?;
|
||||
|
||||
// (2) Remove the first parameter's annotation
|
||||
let mut other_edits = vec![Edit::deletion(
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::name::Name;
|
||||
use ruff_python_ast::{
|
||||
Expr, ExprBinOp, ExprContext, ExprName, ExprSubscript, ExprTuple, Operator, PythonVersion,
|
||||
};
|
||||
use ruff_python_ast::{Expr, ExprBinOp, Operator, PythonVersion};
|
||||
use ruff_python_semantic::analyze::typing::traverse_union;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
use super::generate_union_fix;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for duplicate union members.
|
||||
///
|
||||
|
@ -118,7 +115,19 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) {
|
|||
applicability,
|
||||
)),
|
||||
UnionKind::TypingUnion => {
|
||||
generate_union_fix(checker, unique_nodes, expr, applicability).ok()
|
||||
// Request `typing.Union`
|
||||
let Some(importer) = checker.typing_importer("Union", PythonVersion::lowest())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
generate_union_fix(
|
||||
checker.generator(),
|
||||
&importer,
|
||||
unique_nodes,
|
||||
expr,
|
||||
applicability,
|
||||
)
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -171,40 +180,3 @@ fn generate_pep604_fix(
|
|||
applicability,
|
||||
)
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] for two or more type expressions, e.g. `typing.Union[int, float, complex]`.
|
||||
fn generate_union_fix(
|
||||
checker: &Checker,
|
||||
nodes: Vec<&Expr>,
|
||||
annotation: &Expr,
|
||||
applicability: Applicability,
|
||||
) -> Result<Fix> {
|
||||
debug_assert!(nodes.len() >= 2, "At least two nodes required");
|
||||
|
||||
// Request `typing.Union`
|
||||
let (import_edit, binding) =
|
||||
checker.import_from_typing("Union", annotation.start(), PythonVersion::lowest())?;
|
||||
|
||||
// Construct the expression as `Subscript[typing.Union, Tuple[expr, [expr, ...]]]`
|
||||
let new_expr = Expr::Subscript(ExprSubscript {
|
||||
range: TextRange::default(),
|
||||
value: Box::new(Expr::Name(ExprName {
|
||||
id: Name::new(binding),
|
||||
ctx: ExprContext::Store,
|
||||
range: TextRange::default(),
|
||||
})),
|
||||
slice: Box::new(Expr::Tuple(ExprTuple {
|
||||
elts: nodes.into_iter().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: false,
|
||||
})),
|
||||
ctx: ExprContext::Load,
|
||||
});
|
||||
|
||||
Ok(Fix::applicable_edits(
|
||||
Edit::range_replacement(checker.generator().expr(&new_expr), annotation.range()),
|
||||
[import_edit],
|
||||
applicability,
|
||||
))
|
||||
}
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
use std::fmt;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use ruff_diagnostics::{Applicability, Edit, Fix};
|
||||
use ruff_python_ast::{name::Name, Expr, ExprContext, ExprName, ExprSubscript, ExprTuple};
|
||||
use ruff_python_codegen::Generator;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::TypingImporter;
|
||||
|
||||
pub(crate) use any_eq_ne_annotation::*;
|
||||
pub(crate) use bad_generator_return_type::*;
|
||||
pub(crate) use bad_version_info_comparison::*;
|
||||
|
@ -108,3 +117,39 @@ impl fmt::Display for TypingModule {
|
|||
fmt.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] for two or more type expressions, e.g. `typing.Union[int, float, complex]`.
|
||||
fn generate_union_fix(
|
||||
generator: Generator,
|
||||
importer: &TypingImporter,
|
||||
nodes: Vec<&Expr>,
|
||||
annotation: &Expr,
|
||||
applicability: Applicability,
|
||||
) -> Result<Fix> {
|
||||
debug_assert!(nodes.len() >= 2, "At least two nodes required");
|
||||
|
||||
let (import_edit, binding) = importer.import(annotation.start())?;
|
||||
|
||||
// Construct the expression as `Subscript[typing.Union, Tuple[expr, [expr, ...]]]`
|
||||
let new_expr = Expr::Subscript(ExprSubscript {
|
||||
range: TextRange::default(),
|
||||
value: Box::new(Expr::Name(ExprName {
|
||||
id: Name::new(binding),
|
||||
ctx: ExprContext::Store,
|
||||
range: TextRange::default(),
|
||||
})),
|
||||
slice: Box::new(Expr::Tuple(ExprTuple {
|
||||
elts: nodes.into_iter().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: false,
|
||||
})),
|
||||
ctx: ExprContext::Load,
|
||||
});
|
||||
|
||||
Ok(Fix::applicable_edits(
|
||||
Edit::range_replacement(generator.expr(&new_expr), annotation.range()),
|
||||
[import_edit],
|
||||
applicability,
|
||||
))
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::checkers::ast::Checker;
|
||||
use crate::checkers::ast::{Checker, TypingImporter};
|
||||
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast as ast;
|
||||
|
@ -75,6 +75,16 @@ use ruff_text_size::Ranged;
|
|||
/// ## Fix safety
|
||||
/// This rule's fix is marked as unsafe as it changes the meaning of your type annotations.
|
||||
///
|
||||
/// ## Availability
|
||||
///
|
||||
/// Because this rule relies on the third-party `typing_extensions` module for Python versions
|
||||
/// before 3.11, its diagnostic will not be emitted, and no fix will be offered, if
|
||||
/// `typing_extensions` imports have been disabled by the [`lint.typing-extensions`] linter option.
|
||||
///
|
||||
/// ## Options
|
||||
///
|
||||
/// - `lint.typing-extensions`
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `typing.Self`](https://docs.python.org/3/library/typing.html#typing.Self)
|
||||
#[derive(ViolationMetadata)]
|
||||
|
@ -192,6 +202,10 @@ fn add_diagnostic(
|
|||
class_def: &ast::StmtClassDef,
|
||||
method_name: &str,
|
||||
) {
|
||||
let Some(importer) = checker.typing_importer("Self", PythonVersion::PY311) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
NonSelfReturnType {
|
||||
class_name: class_def.name.to_string(),
|
||||
|
@ -200,21 +214,21 @@ fn add_diagnostic(
|
|||
stmt.identifier(),
|
||||
);
|
||||
|
||||
diagnostic.try_set_fix(|| replace_with_self_fix(checker, stmt, returns, class_def));
|
||||
diagnostic.try_set_fix(|| {
|
||||
replace_with_self_fix(checker.semantic(), &importer, stmt, returns, class_def)
|
||||
});
|
||||
|
||||
checker.report_diagnostic(diagnostic);
|
||||
}
|
||||
|
||||
fn replace_with_self_fix(
|
||||
checker: &Checker,
|
||||
semantic: &SemanticModel,
|
||||
importer: &TypingImporter,
|
||||
stmt: &ast::Stmt,
|
||||
returns: &ast::Expr,
|
||||
class_def: &ast::StmtClassDef,
|
||||
) -> anyhow::Result<Fix> {
|
||||
let semantic = checker.semantic();
|
||||
|
||||
let (self_import, self_binding) =
|
||||
checker.import_from_typing("Self", returns.start(), PythonVersion::PY311)?;
|
||||
let (self_import, self_binding) = importer.import(returns.start())?;
|
||||
|
||||
let mut others = Vec::with_capacity(2);
|
||||
|
||||
|
@ -230,7 +244,7 @@ fn replace_with_self_fix(
|
|||
others.extend(remove_first_argument_type_hint());
|
||||
others.push(Edit::range_replacement(self_binding, returns.range()));
|
||||
|
||||
let applicability = if might_be_generic(class_def, checker.semantic()) {
|
||||
let applicability = if might_be_generic(class_def, semantic) {
|
||||
Applicability::DisplayOnly
|
||||
} else {
|
||||
Applicability::Unsafe
|
||||
|
|
|
@ -222,11 +222,11 @@ fn create_fix(
|
|||
|
||||
let fix = match union_kind {
|
||||
UnionKind::TypingOptional => {
|
||||
let (import_edit, bound_name) = checker.import_from_typing(
|
||||
"Optional",
|
||||
literal_expr.start(),
|
||||
PythonVersion::lowest(),
|
||||
)?;
|
||||
let Some(importer) = checker.typing_importer("Optional", PythonVersion::lowest())
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let (import_edit, bound_name) = importer.import(literal_expr.start())?;
|
||||
let optional_expr = typing_optional(new_literal_expr, Name::from(bound_name));
|
||||
let content = checker.generator().expr(&optional_expr);
|
||||
let optional_edit = Edit::range_replacement(content, literal_expr.range());
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
use bitflags::bitflags;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{
|
||||
name::Name, AnyParameterRef, Expr, ExprBinOp, ExprContext, ExprName, ExprSubscript, ExprTuple,
|
||||
Operator, Parameters, PythonVersion,
|
||||
};
|
||||
use ruff_python_ast::{AnyParameterRef, Expr, ExprBinOp, Operator, Parameters, PythonVersion};
|
||||
use ruff_python_semantic::analyze::typing::traverse_union;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
use super::generate_union_fix;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for parameter annotations that contain redundant unions between
|
||||
/// builtin numeric types (e.g., `int | float`).
|
||||
|
@ -157,7 +154,18 @@ fn check_annotation<'a>(checker: &Checker, annotation: &'a Expr) {
|
|||
applicability,
|
||||
)),
|
||||
UnionKind::TypingUnion => {
|
||||
generate_union_fix(checker, necessary_nodes, annotation, applicability).ok()
|
||||
let Some(importer) = checker.typing_importer("Union", PythonVersion::lowest())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
generate_union_fix(
|
||||
checker.generator(),
|
||||
&importer,
|
||||
necessary_nodes,
|
||||
annotation,
|
||||
applicability,
|
||||
)
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -257,40 +265,3 @@ fn generate_pep604_fix(
|
|||
applicability,
|
||||
)
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] for two or more type expressions, e.g. `typing.Union[int, float, complex]`.
|
||||
fn generate_union_fix(
|
||||
checker: &Checker,
|
||||
nodes: Vec<&Expr>,
|
||||
annotation: &Expr,
|
||||
applicability: Applicability,
|
||||
) -> Result<Fix> {
|
||||
debug_assert!(nodes.len() >= 2, "At least two nodes required");
|
||||
|
||||
// Request `typing.Union`
|
||||
let (import_edit, binding) =
|
||||
checker.import_from_typing("Optional", annotation.start(), PythonVersion::lowest())?;
|
||||
|
||||
// Construct the expression as `Subscript[typing.Union, Tuple[expr, [expr, ...]]]`
|
||||
let new_expr = Expr::Subscript(ExprSubscript {
|
||||
range: TextRange::default(),
|
||||
value: Box::new(Expr::Name(ExprName {
|
||||
id: Name::new(binding),
|
||||
ctx: ExprContext::Store,
|
||||
range: TextRange::default(),
|
||||
})),
|
||||
slice: Box::new(Expr::Tuple(ExprTuple {
|
||||
elts: nodes.into_iter().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: false,
|
||||
})),
|
||||
ctx: ExprContext::Load,
|
||||
});
|
||||
|
||||
Ok(Fix::applicable_edits(
|
||||
Edit::range_replacement(checker.generator().expr(&new_expr), annotation.range()),
|
||||
[import_edit],
|
||||
applicability,
|
||||
))
|
||||
}
|
||||
|
|
|
@ -217,6 +217,16 @@ impl Violation for UnassignedSpecialVariableInStub {
|
|||
///
|
||||
/// Vector: TypeAlias = list[float]
|
||||
/// ```
|
||||
///
|
||||
/// ## Availability
|
||||
///
|
||||
/// Because this rule relies on the third-party `typing_extensions` module for Python versions
|
||||
/// before 3.10, its diagnostic will not be emitted, and no fix will be offered, if
|
||||
/// `typing_extensions` imports have been disabled by the [`lint.typing-extensions`] linter option.
|
||||
///
|
||||
/// ## Options
|
||||
///
|
||||
/// - `lint.typing-extensions`
|
||||
#[derive(ViolationMetadata)]
|
||||
pub(crate) struct TypeAliasWithoutAnnotation {
|
||||
module: TypingModule,
|
||||
|
@ -672,6 +682,10 @@ pub(crate) fn type_alias_without_annotation(checker: &Checker, value: &Expr, tar
|
|||
TypingModule::TypingExtensions
|
||||
};
|
||||
|
||||
let Some(importer) = checker.typing_importer("TypeAlias", PythonVersion::PY310) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
TypeAliasWithoutAnnotation {
|
||||
module,
|
||||
|
@ -681,8 +695,7 @@ pub(crate) fn type_alias_without_annotation(checker: &Checker, value: &Expr, tar
|
|||
target.range(),
|
||||
);
|
||||
diagnostic.try_set_fix(|| {
|
||||
let (import_edit, binding) =
|
||||
checker.import_from_typing("TypeAlias", target.start(), PythonVersion::PY310)?;
|
||||
let (import_edit, binding) = importer.import(target.start())?;
|
||||
Ok(Fix::safe_edits(
|
||||
Edit::range_replacement(format!("{id}: {binding}"), target.range()),
|
||||
[import_edit],
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::fmt;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
|
@ -136,8 +136,10 @@ fn generate_fix(checker: &Checker, conversion_type: ConversionType, expr: &Expr)
|
|||
)))
|
||||
}
|
||||
ConversionType::Optional => {
|
||||
let (import_edit, binding) =
|
||||
checker.import_from_typing("Optional", expr.start(), PythonVersion::lowest())?;
|
||||
let importer = checker
|
||||
.typing_importer("Optional", PythonVersion::lowest())
|
||||
.context("Optional should be available on all supported Python versions")?;
|
||||
let (import_edit, binding) = importer.import(expr.start())?;
|
||||
let new_expr = Expr::Subscript(ast::ExprSubscript {
|
||||
range: TextRange::default(),
|
||||
value: Box::new(Expr::Name(ast::ExprName {
|
||||
|
|
|
@ -250,6 +250,7 @@ pub struct LinterSettings {
|
|||
pub line_length: LineLength,
|
||||
pub task_tags: Vec<String>,
|
||||
pub typing_modules: Vec<String>,
|
||||
pub typing_extensions: bool,
|
||||
|
||||
// Plugins
|
||||
pub flake8_annotations: flake8_annotations::settings::Settings,
|
||||
|
@ -313,6 +314,7 @@ impl Display for LinterSettings {
|
|||
self.line_length,
|
||||
self.task_tags | array,
|
||||
self.typing_modules | array,
|
||||
self.typing_extensions,
|
||||
]
|
||||
}
|
||||
writeln!(f, "\n# Linter Plugins")?;
|
||||
|
@ -450,6 +452,7 @@ impl LinterSettings {
|
|||
preview: PreviewMode::default(),
|
||||
explicit_preview_rules: false,
|
||||
extension: ExtensionMapping::default(),
|
||||
typing_extensions: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:7:13: PYI019 [*] Use `Self` instead of custom TypeVar `T`
|
||||
|
|
||||
6 | class C:
|
||||
7 | def __new__(cls: type[T]) -> T:
|
||||
| ^^^^^^^^^^^^^^^^^^^ PYI019
|
||||
8 | return cls
|
||||
|
|
||||
= help: Replace TypeVar `T` with `Self`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 |
|
||||
2 2 | from typing import TypeVar
|
||||
3 |+from typing_extensions import Self
|
||||
3 4 |
|
||||
4 5 | T = TypeVar("T", bound="_NiceReprEnum")
|
||||
5 6 |
|
||||
6 7 | class C:
|
||||
7 |- def __new__(cls: type[T]) -> T:
|
||||
8 |+ def __new__(cls) -> Self:
|
||||
8 9 | return cls
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:7:13: PYI019 [*] Use `Self` instead of custom TypeVar `T`
|
||||
|
|
||||
6 | class C:
|
||||
7 | def __new__(cls: type[T]) -> T:
|
||||
| ^^^^^^^^^^^^^^^^^^^ PYI019
|
||||
8 | return cls
|
||||
|
|
||||
= help: Replace TypeVar `T` with `Self`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 |
|
||||
2 |-from typing import TypeVar
|
||||
2 |+from typing import TypeVar, Self
|
||||
3 3 |
|
||||
4 4 | T = TypeVar("T", bound="_NiceReprEnum")
|
||||
5 5 |
|
||||
6 6 | class C:
|
||||
7 |- def __new__(cls: type[T]) -> T:
|
||||
7 |+ def __new__(cls) -> Self:
|
||||
8 8 | return cls
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
<filename>:7:13: PYI019 [*] Use `Self` instead of custom TypeVar `T`
|
||||
|
|
||||
6 | class C:
|
||||
7 | def __new__(cls: type[T]) -> T:
|
||||
| ^^^^^^^^^^^^^^^^^^^ PYI019
|
||||
8 | return cls
|
||||
|
|
||||
= help: Replace TypeVar `T` with `Self`
|
||||
|
||||
ℹ Safe fix
|
||||
1 1 |
|
||||
2 |-from typing import TypeVar
|
||||
2 |+from typing import TypeVar, Self
|
||||
3 3 |
|
||||
4 4 | T = TypeVar("T", bound="_NiceReprEnum")
|
||||
5 5 |
|
||||
6 6 | class C:
|
||||
7 |- def __new__(cls: type[T]) -> T:
|
||||
7 |+ def __new__(cls) -> Self:
|
||||
8 8 | return cls
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
source: crates/ruff_linter/src/linter.rs
|
||||
---
|
||||
|
|
@ -430,6 +430,7 @@ impl Configuration {
|
|||
.ruff
|
||||
.map(RuffOptions::into_settings)
|
||||
.unwrap_or_default(),
|
||||
typing_extensions: lint.typing_extensions.unwrap_or(true),
|
||||
},
|
||||
|
||||
formatter,
|
||||
|
@ -633,6 +634,7 @@ pub struct LintConfiguration {
|
|||
pub logger_objects: Option<Vec<String>>,
|
||||
pub task_tags: Option<Vec<String>>,
|
||||
pub typing_modules: Option<Vec<String>>,
|
||||
pub typing_extensions: Option<bool>,
|
||||
|
||||
// Plugins
|
||||
pub flake8_annotations: Option<Flake8AnnotationsOptions>,
|
||||
|
@ -746,6 +748,7 @@ impl LintConfiguration {
|
|||
task_tags: options.common.task_tags,
|
||||
logger_objects: options.common.logger_objects,
|
||||
typing_modules: options.common.typing_modules,
|
||||
typing_extensions: options.typing_extensions,
|
||||
|
||||
// Plugins
|
||||
flake8_annotations: options.common.flake8_annotations,
|
||||
|
@ -1170,6 +1173,7 @@ impl LintConfiguration {
|
|||
pylint: self.pylint.combine(config.pylint),
|
||||
pyupgrade: self.pyupgrade.combine(config.pyupgrade),
|
||||
ruff: self.ruff.combine(config.ruff),
|
||||
typing_extensions: self.typing_extensions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -513,6 +513,22 @@ pub struct LintOptions {
|
|||
"#
|
||||
)]
|
||||
pub preview: Option<bool>,
|
||||
|
||||
/// Whether to allow imports from the third-party `typing_extensions` module for Python versions
|
||||
/// before a symbol was added to the first-party `typing` module.
|
||||
///
|
||||
/// Many rules try to import symbols from the `typing` module but fall back to
|
||||
/// `typing_extensions` for earlier versions of Python. This option can be used to disable this
|
||||
/// fallback behavior in cases where `typing_extensions` is not installed.
|
||||
#[option(
|
||||
default = "true",
|
||||
value_type = "bool",
|
||||
example = r#"
|
||||
# Disable `typing_extensions` imports
|
||||
typing-extensions = false
|
||||
"#
|
||||
)]
|
||||
pub typing_extensions: Option<bool>,
|
||||
}
|
||||
|
||||
/// Newtype wrapper for [`LintCommonOptions`] that allows customizing the JSON schema and omitting the fields from the [`OptionsMetadata`].
|
||||
|
@ -3876,6 +3892,7 @@ pub struct LintOptionsWire {
|
|||
pydoclint: Option<PydoclintOptions>,
|
||||
ruff: Option<RuffOptions>,
|
||||
preview: Option<bool>,
|
||||
typing_extensions: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<LintOptionsWire> for LintOptions {
|
||||
|
@ -3930,6 +3947,7 @@ impl From<LintOptionsWire> for LintOptions {
|
|||
pydoclint,
|
||||
ruff,
|
||||
preview,
|
||||
typing_extensions,
|
||||
} = value;
|
||||
|
||||
LintOptions {
|
||||
|
@ -3985,6 +4003,7 @@ impl From<LintOptionsWire> for LintOptions {
|
|||
pydoclint,
|
||||
ruff,
|
||||
preview,
|
||||
typing_extensions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
7
ruff.schema.json
generated
7
ruff.schema.json
generated
|
@ -2459,6 +2459,13 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
"typing-extensions": {
|
||||
"description": "Whether to allow imports from the third-party `typing_extensions` module for Python versions before a symbol was added to the first-party `typing` module.\n\nMany rules try to import symbols from the `typing` module but fall back to `typing_extensions` for earlier versions of Python. This option can be used to disable this fallback behavior in cases where `typing_extensions` is not installed.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"typing-modules": {
|
||||
"description": "A list of modules whose exports should be treated equivalently to members of the `typing` module.\n\nThis is useful for ensuring proper type annotation inference for projects that re-export `typing` and `typing_extensions` members from a compatibility module. If omitted, any members imported from modules apart from `typing` and `typing_extensions` will be treated as ordinary Python objects.",
|
||||
"type": [
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue