mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 21:05:08 +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.extend-per-file-ignores`
|
||||||
- `lint.exclude`
|
- `lint.exclude`
|
||||||
- `lint.preview`
|
- `lint.preview`
|
||||||
|
- `lint.typing-extensions`
|
||||||
|
|
||||||
For more information, try '--help'.
|
For more information, try '--help'.
|
||||||
");
|
");
|
||||||
|
@ -2117,7 +2118,7 @@ requires-python = ">= 3.11"
|
||||||
.arg("test.py")
|
.arg("test.py")
|
||||||
.arg("-")
|
.arg("-")
|
||||||
.current_dir(project_dir)
|
.current_dir(project_dir)
|
||||||
, @r###"
|
, @r#"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -2207,6 +2208,7 @@ requires-python = ">= 3.11"
|
||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
|
@ -2390,7 +2392,7 @@ requires-python = ">= 3.11"
|
||||||
analyze.include_dependencies = {}
|
analyze.include_dependencies = {}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"#);
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -2428,7 +2430,7 @@ requires-python = ">= 3.11"
|
||||||
.arg("test.py")
|
.arg("test.py")
|
||||||
.arg("-")
|
.arg("-")
|
||||||
.current_dir(project_dir)
|
.current_dir(project_dir)
|
||||||
, @r###"
|
, @r#"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -2518,6 +2520,7 @@ requires-python = ">= 3.11"
|
||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
|
@ -2701,7 +2704,7 @@ requires-python = ">= 3.11"
|
||||||
analyze.include_dependencies = {}
|
analyze.include_dependencies = {}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"#);
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -2790,7 +2793,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg("test.py")
|
.arg("test.py")
|
||||||
.arg("--show-settings")
|
.arg("--show-settings")
|
||||||
.current_dir(project_dir), @r###"
|
.current_dir(project_dir), @r#"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -2881,6 +2884,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
|
@ -3064,7 +3068,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
analyze.include_dependencies = {}
|
analyze.include_dependencies = {}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"#);
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -3170,7 +3174,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
.arg("--show-settings")
|
.arg("--show-settings")
|
||||||
.args(["--select","UP007"])
|
.args(["--select","UP007"])
|
||||||
.arg("foo/test.py")
|
.arg("foo/test.py")
|
||||||
.current_dir(&project_dir), @r###"
|
.current_dir(&project_dir), @r#"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -3260,6 +3264,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
|
@ -3443,7 +3448,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
analyze.include_dependencies = {}
|
analyze.include_dependencies = {}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"#);
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -3497,7 +3502,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
.arg("--show-settings")
|
.arg("--show-settings")
|
||||||
.args(["--select","UP007"])
|
.args(["--select","UP007"])
|
||||||
.arg("foo/test.py")
|
.arg("foo/test.py")
|
||||||
.current_dir(&project_dir), @r###"
|
.current_dir(&project_dir), @r#"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -3587,6 +3592,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
|
@ -3770,7 +3776,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
analyze.include_dependencies = {}
|
analyze.include_dependencies = {}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"#);
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -3823,7 +3829,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg("--show-settings")
|
.arg("--show-settings")
|
||||||
.arg("foo/test.py")
|
.arg("foo/test.py")
|
||||||
.current_dir(&project_dir), @r###"
|
.current_dir(&project_dir), @r#"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -3914,6 +3920,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
|
@ -4097,7 +4104,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
analyze.include_dependencies = {}
|
analyze.include_dependencies = {}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"#);
|
||||||
});
|
});
|
||||||
|
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
|
@ -4107,7 +4114,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg("--show-settings")
|
.arg("--show-settings")
|
||||||
.arg("test.py")
|
.arg("test.py")
|
||||||
.current_dir(project_dir.join("foo")), @r###"
|
.current_dir(project_dir.join("foo")), @r#"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -4198,6 +4205,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
|
@ -4381,7 +4389,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
analyze.include_dependencies = {}
|
analyze.include_dependencies = {}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"#);
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -4444,7 +4452,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
.args(STDIN_BASE_OPTIONS)
|
.args(STDIN_BASE_OPTIONS)
|
||||||
.arg("--show-settings")
|
.arg("--show-settings")
|
||||||
.arg("test.py")
|
.arg("test.py")
|
||||||
.current_dir(&project_dir), @r###"
|
.current_dir(&project_dir), @r#"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -4535,6 +4543,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
|
@ -4718,7 +4727,7 @@ from typing import Union;foo: Union[int, str] = 1
|
||||||
analyze.include_dependencies = {}
|
analyze.include_dependencies = {}
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
"###);
|
"#);
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -213,6 +213,7 @@ linter.task_tags = [
|
||||||
XXX,
|
XXX,
|
||||||
]
|
]
|
||||||
linter.typing_modules = []
|
linter.typing_modules = []
|
||||||
|
linter.typing_extensions = true
|
||||||
|
|
||||||
# Linter Plugins
|
# Linter Plugins
|
||||||
linter.flake8_annotations.mypy_init_return = false
|
linter.flake8_annotations.mypy_init_return = false
|
||||||
|
|
|
@ -534,26 +534,50 @@ impl<'a> Checker<'a> {
|
||||||
self.semantic_checker = checker;
|
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`, `member` is imported from `typing_extensions`, while
|
||||||
/// on Python >=`version_added_to_typing`, it is imported from `typing`.
|
/// on Python >=`version_added_to_typing`, it is imported from `typing`.
|
||||||
///
|
///
|
||||||
/// See [`Importer::get_or_import_symbol`] for more details on the returned values.
|
/// If the Python version is less than `version_added_to_typing` but
|
||||||
pub(crate) fn import_from_typing(
|
/// `LinterSettings::typing_extensions` is `false`, this method returns `None`.
|
||||||
&self,
|
pub(crate) fn typing_importer<'b>(
|
||||||
member: &str,
|
&'b self,
|
||||||
position: TextSize,
|
member: &'b str,
|
||||||
version_added_to_typing: PythonVersion,
|
version_added_to_typing: PythonVersion,
|
||||||
) -> Result<(Edit, String), ResolutionError> {
|
) -> Option<TypingImporter<'b, 'a>> {
|
||||||
let source_module = if self.target_version() >= version_added_to_typing {
|
let source_module = if self.target_version() >= version_added_to_typing {
|
||||||
"typing"
|
"typing"
|
||||||
|
} else if !self.settings.typing_extensions {
|
||||||
|
return None;
|
||||||
} else {
|
} else {
|
||||||
"typing_extensions"
|
"typing_extensions"
|
||||||
};
|
};
|
||||||
let request = ImportRequest::import_from(source_module, member);
|
Some(TypingImporter {
|
||||||
self.importer()
|
checker: self,
|
||||||
.get_or_import_symbol(&request, position, self.semantic())
|
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::linter::check_path;
|
||||||
use crate::message::Message;
|
use crate::message::Message;
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
|
use crate::settings::LinterSettings;
|
||||||
use crate::source_kind::SourceKind;
|
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};
|
use crate::{assert_messages, directives, settings, Locator};
|
||||||
|
|
||||||
/// Construct a path to a Jupyter notebook in the `resources/test/fixtures/jupyter` directory.
|
/// Construct a path to a Jupyter notebook in the `resources/test/fixtures/jupyter` directory.
|
||||||
|
@ -811,7 +812,7 @@ mod tests {
|
||||||
} = assert_notebook_path(
|
} = assert_notebook_path(
|
||||||
&actual,
|
&actual,
|
||||||
expected,
|
expected,
|
||||||
&settings::LinterSettings::for_rule(Rule::UnsortedImports),
|
&LinterSettings::for_rule(Rule::UnsortedImports),
|
||||||
)?;
|
)?;
|
||||||
assert_messages!(messages, actual, source_notebook);
|
assert_messages!(messages, actual, source_notebook);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -828,7 +829,7 @@ mod tests {
|
||||||
} = assert_notebook_path(
|
} = assert_notebook_path(
|
||||||
&actual,
|
&actual,
|
||||||
expected,
|
expected,
|
||||||
&settings::LinterSettings::for_rule(Rule::UnusedImport),
|
&LinterSettings::for_rule(Rule::UnusedImport),
|
||||||
)?;
|
)?;
|
||||||
assert_messages!(messages, actual, source_notebook);
|
assert_messages!(messages, actual, source_notebook);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -845,7 +846,7 @@ mod tests {
|
||||||
} = assert_notebook_path(
|
} = assert_notebook_path(
|
||||||
&actual,
|
&actual,
|
||||||
expected,
|
expected,
|
||||||
&settings::LinterSettings::for_rule(Rule::UnusedVariable),
|
&LinterSettings::for_rule(Rule::UnusedVariable),
|
||||||
)?;
|
)?;
|
||||||
assert_messages!(messages, actual, source_notebook);
|
assert_messages!(messages, actual, source_notebook);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -862,7 +863,7 @@ mod tests {
|
||||||
} = assert_notebook_path(
|
} = assert_notebook_path(
|
||||||
&actual,
|
&actual,
|
||||||
expected,
|
expected,
|
||||||
&settings::LinterSettings::for_rule(Rule::UndefinedName),
|
&LinterSettings::for_rule(Rule::UndefinedName),
|
||||||
)?;
|
)?;
|
||||||
assert_messages!(messages, actual, source_notebook);
|
assert_messages!(messages, actual, source_notebook);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -879,7 +880,7 @@ mod tests {
|
||||||
} = assert_notebook_path(
|
} = assert_notebook_path(
|
||||||
actual_path,
|
actual_path,
|
||||||
&expected_path,
|
&expected_path,
|
||||||
&settings::LinterSettings::for_rule(Rule::UnusedImport),
|
&LinterSettings::for_rule(Rule::UnusedImport),
|
||||||
)?;
|
)?;
|
||||||
let mut writer = Vec::new();
|
let mut writer = Vec::new();
|
||||||
fixed_notebook.write(&mut writer)?;
|
fixed_notebook.write(&mut writer)?;
|
||||||
|
@ -900,7 +901,7 @@ mod tests {
|
||||||
} = assert_notebook_path(
|
} = assert_notebook_path(
|
||||||
&actual,
|
&actual,
|
||||||
expected,
|
expected,
|
||||||
&settings::LinterSettings::for_rule(Rule::UnusedImport),
|
&LinterSettings::for_rule(Rule::UnusedImport),
|
||||||
)?;
|
)?;
|
||||||
assert_messages!(messages, actual, source_notebook);
|
assert_messages!(messages, actual, source_notebook);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -930,7 +931,7 @@ mod tests {
|
||||||
let (_, transformed) = test_contents(
|
let (_, transformed) = test_contents(
|
||||||
&source_kind,
|
&source_kind,
|
||||||
path,
|
path,
|
||||||
&settings::LinterSettings::for_rule(Rule::UnusedImport),
|
&LinterSettings::for_rule(Rule::UnusedImport),
|
||||||
);
|
);
|
||||||
let linted_notebook = transformed.into_owned().expect_ipy_notebook();
|
let linted_notebook = transformed.into_owned().expect_ipy_notebook();
|
||||||
let mut writer = Vec::new();
|
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
|
/// Wrapper around `test_contents_syntax_errors` for testing a snippet of code instead of a
|
||||||
/// file.
|
/// file.
|
||||||
fn test_snippet_syntax_errors(
|
fn test_snippet_syntax_errors(contents: &str, settings: &LinterSettings) -> Vec<Message> {
|
||||||
contents: &str,
|
|
||||||
settings: &settings::LinterSettings,
|
|
||||||
) -> Vec<Message> {
|
|
||||||
let contents = dedent(contents);
|
let contents = dedent(contents);
|
||||||
test_contents_syntax_errors(
|
test_contents_syntax_errors(
|
||||||
&SourceKind::Python(contents.to_string()),
|
&SourceKind::Python(contents.to_string()),
|
||||||
|
@ -963,7 +961,7 @@ mod tests {
|
||||||
fn test_contents_syntax_errors(
|
fn test_contents_syntax_errors(
|
||||||
source_kind: &SourceKind,
|
source_kind: &SourceKind,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
settings: &settings::LinterSettings,
|
settings: &LinterSettings,
|
||||||
) -> Vec<Message> {
|
) -> Vec<Message> {
|
||||||
let source_type = PySourceType::from(path);
|
let source_type = PySourceType::from(path);
|
||||||
let options =
|
let options =
|
||||||
|
@ -1032,7 +1030,7 @@ mod tests {
|
||||||
let snapshot = format!("async_comprehension_in_sync_comprehension_{name}_{python_version}");
|
let snapshot = format!("async_comprehension_in_sync_comprehension_{name}_{python_version}");
|
||||||
let messages = test_snippet_syntax_errors(
|
let messages = test_snippet_syntax_errors(
|
||||||
contents,
|
contents,
|
||||||
&settings::LinterSettings {
|
&LinterSettings {
|
||||||
rules: settings::rule_table::RuleTable::empty(),
|
rules: settings::rule_table::RuleTable::empty(),
|
||||||
unresolved_target_version: python_version,
|
unresolved_target_version: python_version,
|
||||||
preview: settings::types::PreviewMode::Enabled,
|
preview: settings::types::PreviewMode::Enabled,
|
||||||
|
@ -1051,7 +1049,7 @@ mod tests {
|
||||||
let messages = test_contents_syntax_errors(
|
let messages = test_contents_syntax_errors(
|
||||||
&SourceKind::IpyNotebook(Notebook::from_path(path)?),
|
&SourceKind::IpyNotebook(Notebook::from_path(path)?),
|
||||||
path,
|
path,
|
||||||
&settings::LinterSettings {
|
&LinterSettings {
|
||||||
unresolved_target_version: python_version,
|
unresolved_target_version: python_version,
|
||||||
rules: settings::rule_table::RuleTable::empty(),
|
rules: settings::rule_table::RuleTable::empty(),
|
||||||
preview: settings::types::PreviewMode::Enabled,
|
preview: settings::types::PreviewMode::Enabled,
|
||||||
|
@ -1076,7 +1074,7 @@ mod tests {
|
||||||
let messages = test_contents_syntax_errors(
|
let messages = test_contents_syntax_errors(
|
||||||
&SourceKind::Python(std::fs::read_to_string(&path)?),
|
&SourceKind::Python(std::fs::read_to_string(&path)?),
|
||||||
&path,
|
&path,
|
||||||
&settings::LinterSettings::for_rule(rule),
|
&LinterSettings::for_rule(rule),
|
||||||
);
|
);
|
||||||
insta::with_settings!({filters => vec![(r"\\", "/")]}, {
|
insta::with_settings!({filters => vec![(r"\\", "/")]}, {
|
||||||
assert_messages!(snapshot, messages);
|
assert_messages!(snapshot, messages);
|
||||||
|
@ -1095,10 +1093,108 @@ mod tests {
|
||||||
} = assert_notebook_path(
|
} = assert_notebook_path(
|
||||||
path,
|
path,
|
||||||
path,
|
path,
|
||||||
&settings::LinterSettings::for_rule(Rule::YieldOutsideFunction),
|
&LinterSettings::for_rule(Rule::YieldOutsideFunction),
|
||||||
)?;
|
)?;
|
||||||
assert_messages!(messages, path, source_notebook);
|
assert_messages!(messages, path, source_notebook);
|
||||||
|
|
||||||
Ok(())
|
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
|
/// 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
|
/// [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-annotated]: https://docs.python.org/3/library/typing.html#typing.Annotated
|
||||||
/// [typing-extensions]: https://typing-extensions.readthedocs.io/en/stable/
|
/// [typing-extensions]: https://typing-extensions.readthedocs.io/en/stable/
|
||||||
|
@ -223,6 +233,10 @@ fn create_diagnostic(
|
||||||
dependency_call: Option<DependencyCall>,
|
dependency_call: Option<DependencyCall>,
|
||||||
mut seen_default: bool,
|
mut seen_default: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
let Some(importer) = checker.typing_importer("Annotated", PythonVersion::PY39) else {
|
||||||
|
return seen_default;
|
||||||
|
};
|
||||||
|
|
||||||
let mut diagnostic = Diagnostic::new(
|
let mut diagnostic = Diagnostic::new(
|
||||||
FastApiNonAnnotatedDependency {
|
FastApiNonAnnotatedDependency {
|
||||||
py_version: checker.target_version(),
|
py_version: checker.target_version(),
|
||||||
|
@ -231,11 +245,7 @@ fn create_diagnostic(
|
||||||
);
|
);
|
||||||
|
|
||||||
let try_generate_fix = || {
|
let try_generate_fix = || {
|
||||||
let (import_edit, binding) = checker.import_from_typing(
|
let (import_edit, binding) = importer.import(parameter.range.start())?;
|
||||||
"Annotated",
|
|
||||||
parameter.range.start(),
|
|
||||||
PythonVersion::PY39,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Each of these classes takes a single, optional default
|
// Each of these classes takes a single, optional default
|
||||||
// argument, followed by kw-only arguments
|
// argument, followed by kw-only arguments
|
||||||
|
|
|
@ -131,7 +131,8 @@ impl AutoPythonType {
|
||||||
"NoReturn"
|
"NoReturn"
|
||||||
};
|
};
|
||||||
let (no_return_edit, binding) = checker
|
let (no_return_edit, binding) = checker
|
||||||
.import_from_typing(member, at, PythonVersion::lowest())
|
.typing_importer(member, PythonVersion::lowest())?
|
||||||
|
.import(at)
|
||||||
.ok()?;
|
.ok()?;
|
||||||
let expr = Expr::Name(ast::ExprName {
|
let expr = Expr::Name(ast::ExprName {
|
||||||
id: Name::from(binding),
|
id: Name::from(binding),
|
||||||
|
@ -169,7 +170,8 @@ impl AutoPythonType {
|
||||||
|
|
||||||
// Ex) `Optional[int]`
|
// Ex) `Optional[int]`
|
||||||
let (optional_edit, binding) = checker
|
let (optional_edit, binding) = checker
|
||||||
.import_from_typing("Optional", at, PythonVersion::lowest())
|
.typing_importer("Optional", PythonVersion::lowest())?
|
||||||
|
.import(at)
|
||||||
.ok()?;
|
.ok()?;
|
||||||
let expr = typing_optional(element, Name::from(binding));
|
let expr = typing_optional(element, Name::from(binding));
|
||||||
Some((expr, vec![optional_edit]))
|
Some((expr, vec![optional_edit]))
|
||||||
|
@ -182,7 +184,8 @@ impl AutoPythonType {
|
||||||
|
|
||||||
// Ex) `Union[int, str]`
|
// Ex) `Union[int, str]`
|
||||||
let (union_edit, binding) = checker
|
let (union_edit, binding) = checker
|
||||||
.import_from_typing("Union", at, PythonVersion::lowest())
|
.typing_importer("Union", PythonVersion::lowest())?
|
||||||
|
.import(at)
|
||||||
.ok()?;
|
.ok()?;
|
||||||
let expr = typing_union(&elements, Name::from(binding));
|
let expr = typing_union(&elements, Name::from(binding));
|
||||||
Some((expr, vec![union_edit]))
|
Some((expr, vec![union_edit]))
|
||||||
|
|
|
@ -224,6 +224,16 @@ impl Violation for MissingTypeCls {
|
||||||
/// def add(a: int, b: int) -> int:
|
/// def add(a: int, b: int) -> int:
|
||||||
/// return a + b
|
/// 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)]
|
#[derive(ViolationMetadata)]
|
||||||
pub(crate) struct MissingReturnTypeUndocumentedPublicFunction {
|
pub(crate) struct MissingReturnTypeUndocumentedPublicFunction {
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -267,6 +277,16 @@ impl Violation for MissingReturnTypeUndocumentedPublicFunction {
|
||||||
/// def _add(a: int, b: int) -> int:
|
/// def _add(a: int, b: int) -> int:
|
||||||
/// return a + b
|
/// 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)]
|
#[derive(ViolationMetadata)]
|
||||||
pub(crate) struct MissingReturnTypePrivateFunction {
|
pub(crate) struct MissingReturnTypePrivateFunction {
|
||||||
name: String,
|
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_python_semantic::{Binding, ResolvedReference, ScopeId, SemanticModel};
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::{Checker, TypingImporter};
|
||||||
use ruff_python_ast::PythonVersion;
|
use ruff_python_ast::PythonVersion;
|
||||||
|
|
||||||
/// ## What it does
|
/// ## 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
|
/// The fix is only marked as unsafe if there is the possibility that it might delete a comment
|
||||||
/// from your code.
|
/// 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 673]: https://peps.python.org/pep-0673/#motivation
|
||||||
/// [PEP-695]: https://peps.python.org/pep-0695/
|
/// [PEP-695]: https://peps.python.org/pep-0695/
|
||||||
/// [PYI018]: https://docs.astral.sh/ruff/rules/unused-private-type-var/
|
/// [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 semantic = checker.semantic();
|
||||||
let current_scope = &semantic.scopes[binding.scope];
|
let current_scope = &semantic.scopes[binding.scope];
|
||||||
let function_def = binding.statement(semantic)?.as_function_def_stmt()?;
|
let function_def = binding.statement(semantic)?.as_function_def_stmt()?;
|
||||||
|
let importer = checker.typing_importer("Self", PythonVersion::PY311)?;
|
||||||
|
|
||||||
let ast::StmtFunctionDef {
|
let ast::StmtFunctionDef {
|
||||||
name: function_name,
|
name: function_name,
|
||||||
|
@ -178,6 +189,7 @@ pub(crate) fn custom_type_var_instead_of_self(
|
||||||
diagnostic.try_set_fix(|| {
|
diagnostic.try_set_fix(|| {
|
||||||
replace_custom_typevar_with_self(
|
replace_custom_typevar_with_self(
|
||||||
checker,
|
checker,
|
||||||
|
&importer,
|
||||||
function_def,
|
function_def,
|
||||||
custom_typevar,
|
custom_typevar,
|
||||||
self_or_cls_parameter,
|
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
|
/// * If it was a PEP-695 type variable, removes that `TypeVar` from the PEP-695 type-parameter list
|
||||||
fn replace_custom_typevar_with_self(
|
fn replace_custom_typevar_with_self(
|
||||||
checker: &Checker,
|
checker: &Checker,
|
||||||
|
importer: &TypingImporter,
|
||||||
function_def: &ast::StmtFunctionDef,
|
function_def: &ast::StmtFunctionDef,
|
||||||
custom_typevar: TypeVar,
|
custom_typevar: TypeVar,
|
||||||
self_or_cls_parameter: &ast::ParameterWithDefault,
|
self_or_cls_parameter: &ast::ParameterWithDefault,
|
||||||
self_or_cls_annotation: &ast::Expr,
|
self_or_cls_annotation: &ast::Expr,
|
||||||
) -> anyhow::Result<Fix> {
|
) -> anyhow::Result<Fix> {
|
||||||
// (1) Import `Self` (if necessary)
|
// (1) Import `Self` (if necessary)
|
||||||
let (import_edit, self_symbol_binding) =
|
let (import_edit, self_symbol_binding) = importer.import(function_def.start())?;
|
||||||
checker.import_from_typing("Self", function_def.start(), PythonVersion::PY311)?;
|
|
||||||
|
|
||||||
// (2) Remove the first parameter's annotation
|
// (2) Remove the first parameter's annotation
|
||||||
let mut other_edits = vec![Edit::deletion(
|
let mut other_edits = vec![Edit::deletion(
|
||||||
|
|
|
@ -1,21 +1,18 @@
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
|
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||||
use ruff_python_ast::comparable::ComparableExpr;
|
use ruff_python_ast::comparable::ComparableExpr;
|
||||||
use ruff_python_ast::name::Name;
|
use ruff_python_ast::{Expr, ExprBinOp, Operator, PythonVersion};
|
||||||
use ruff_python_ast::{
|
|
||||||
Expr, ExprBinOp, ExprContext, ExprName, ExprSubscript, ExprTuple, Operator, PythonVersion,
|
|
||||||
};
|
|
||||||
use ruff_python_semantic::analyze::typing::traverse_union;
|
use ruff_python_semantic::analyze::typing::traverse_union;
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
|
use super::generate_union_fix;
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for duplicate union members.
|
/// Checks for duplicate union members.
|
||||||
///
|
///
|
||||||
|
@ -118,7 +115,19 @@ pub(crate) fn duplicate_union_member<'a>(checker: &Checker, expr: &'a Expr) {
|
||||||
applicability,
|
applicability,
|
||||||
)),
|
)),
|
||||||
UnionKind::TypingUnion => {
|
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,
|
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 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 any_eq_ne_annotation::*;
|
||||||
pub(crate) use bad_generator_return_type::*;
|
pub(crate) use bad_generator_return_type::*;
|
||||||
pub(crate) use bad_version_info_comparison::*;
|
pub(crate) use bad_version_info_comparison::*;
|
||||||
|
@ -108,3 +117,39 @@ impl fmt::Display for TypingModule {
|
||||||
fmt.write_str(self.as_str())
|
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_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||||
use ruff_python_ast as ast;
|
use ruff_python_ast as ast;
|
||||||
|
@ -75,6 +75,16 @@ use ruff_text_size::Ranged;
|
||||||
/// ## Fix safety
|
/// ## Fix safety
|
||||||
/// This rule's fix is marked as unsafe as it changes the meaning of your type annotations.
|
/// 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
|
/// ## References
|
||||||
/// - [Python documentation: `typing.Self`](https://docs.python.org/3/library/typing.html#typing.Self)
|
/// - [Python documentation: `typing.Self`](https://docs.python.org/3/library/typing.html#typing.Self)
|
||||||
#[derive(ViolationMetadata)]
|
#[derive(ViolationMetadata)]
|
||||||
|
@ -192,6 +202,10 @@ fn add_diagnostic(
|
||||||
class_def: &ast::StmtClassDef,
|
class_def: &ast::StmtClassDef,
|
||||||
method_name: &str,
|
method_name: &str,
|
||||||
) {
|
) {
|
||||||
|
let Some(importer) = checker.typing_importer("Self", PythonVersion::PY311) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let mut diagnostic = Diagnostic::new(
|
let mut diagnostic = Diagnostic::new(
|
||||||
NonSelfReturnType {
|
NonSelfReturnType {
|
||||||
class_name: class_def.name.to_string(),
|
class_name: class_def.name.to_string(),
|
||||||
|
@ -200,21 +214,21 @@ fn add_diagnostic(
|
||||||
stmt.identifier(),
|
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);
|
checker.report_diagnostic(diagnostic);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_with_self_fix(
|
fn replace_with_self_fix(
|
||||||
checker: &Checker,
|
semantic: &SemanticModel,
|
||||||
|
importer: &TypingImporter,
|
||||||
stmt: &ast::Stmt,
|
stmt: &ast::Stmt,
|
||||||
returns: &ast::Expr,
|
returns: &ast::Expr,
|
||||||
class_def: &ast::StmtClassDef,
|
class_def: &ast::StmtClassDef,
|
||||||
) -> anyhow::Result<Fix> {
|
) -> anyhow::Result<Fix> {
|
||||||
let semantic = checker.semantic();
|
let (self_import, self_binding) = importer.import(returns.start())?;
|
||||||
|
|
||||||
let (self_import, self_binding) =
|
|
||||||
checker.import_from_typing("Self", returns.start(), PythonVersion::PY311)?;
|
|
||||||
|
|
||||||
let mut others = Vec::with_capacity(2);
|
let mut others = Vec::with_capacity(2);
|
||||||
|
|
||||||
|
@ -230,7 +244,7 @@ fn replace_with_self_fix(
|
||||||
others.extend(remove_first_argument_type_hint());
|
others.extend(remove_first_argument_type_hint());
|
||||||
others.push(Edit::range_replacement(self_binding, returns.range()));
|
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
|
Applicability::DisplayOnly
|
||||||
} else {
|
} else {
|
||||||
Applicability::Unsafe
|
Applicability::Unsafe
|
||||||
|
|
|
@ -222,11 +222,11 @@ fn create_fix(
|
||||||
|
|
||||||
let fix = match union_kind {
|
let fix = match union_kind {
|
||||||
UnionKind::TypingOptional => {
|
UnionKind::TypingOptional => {
|
||||||
let (import_edit, bound_name) = checker.import_from_typing(
|
let Some(importer) = checker.typing_importer("Optional", PythonVersion::lowest())
|
||||||
"Optional",
|
else {
|
||||||
literal_expr.start(),
|
return Ok(None);
|
||||||
PythonVersion::lowest(),
|
};
|
||||||
)?;
|
let (import_edit, bound_name) = importer.import(literal_expr.start())?;
|
||||||
let optional_expr = typing_optional(new_literal_expr, Name::from(bound_name));
|
let optional_expr = typing_optional(new_literal_expr, Name::from(bound_name));
|
||||||
let content = checker.generator().expr(&optional_expr);
|
let content = checker.generator().expr(&optional_expr);
|
||||||
let optional_edit = Edit::range_replacement(content, literal_expr.range());
|
let optional_edit = Edit::range_replacement(content, literal_expr.range());
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
|
use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||||
use ruff_python_ast::{
|
use ruff_python_ast::{AnyParameterRef, Expr, ExprBinOp, Operator, Parameters, PythonVersion};
|
||||||
name::Name, AnyParameterRef, Expr, ExprBinOp, ExprContext, ExprName, ExprSubscript, ExprTuple,
|
|
||||||
Operator, Parameters, PythonVersion,
|
|
||||||
};
|
|
||||||
use ruff_python_semantic::analyze::typing::traverse_union;
|
use ruff_python_semantic::analyze::typing::traverse_union;
|
||||||
use ruff_text_size::{Ranged, TextRange};
|
use ruff_text_size::{Ranged, TextRange};
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
|
use super::generate_union_fix;
|
||||||
|
|
||||||
/// ## What it does
|
/// ## What it does
|
||||||
/// Checks for parameter annotations that contain redundant unions between
|
/// Checks for parameter annotations that contain redundant unions between
|
||||||
/// builtin numeric types (e.g., `int | float`).
|
/// builtin numeric types (e.g., `int | float`).
|
||||||
|
@ -157,7 +154,18 @@ fn check_annotation<'a>(checker: &Checker, annotation: &'a Expr) {
|
||||||
applicability,
|
applicability,
|
||||||
)),
|
)),
|
||||||
UnionKind::TypingUnion => {
|
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,
|
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]
|
/// 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)]
|
#[derive(ViolationMetadata)]
|
||||||
pub(crate) struct TypeAliasWithoutAnnotation {
|
pub(crate) struct TypeAliasWithoutAnnotation {
|
||||||
module: TypingModule,
|
module: TypingModule,
|
||||||
|
@ -672,6 +682,10 @@ pub(crate) fn type_alias_without_annotation(checker: &Checker, value: &Expr, tar
|
||||||
TypingModule::TypingExtensions
|
TypingModule::TypingExtensions
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let Some(importer) = checker.typing_importer("TypeAlias", PythonVersion::PY310) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let mut diagnostic = Diagnostic::new(
|
let mut diagnostic = Diagnostic::new(
|
||||||
TypeAliasWithoutAnnotation {
|
TypeAliasWithoutAnnotation {
|
||||||
module,
|
module,
|
||||||
|
@ -681,8 +695,7 @@ pub(crate) fn type_alias_without_annotation(checker: &Checker, value: &Expr, tar
|
||||||
target.range(),
|
target.range(),
|
||||||
);
|
);
|
||||||
diagnostic.try_set_fix(|| {
|
diagnostic.try_set_fix(|| {
|
||||||
let (import_edit, binding) =
|
let (import_edit, binding) = importer.import(target.start())?;
|
||||||
checker.import_from_typing("TypeAlias", target.start(), PythonVersion::PY310)?;
|
|
||||||
Ok(Fix::safe_edits(
|
Ok(Fix::safe_edits(
|
||||||
Edit::range_replacement(format!("{id}: {binding}"), target.range()),
|
Edit::range_replacement(format!("{id}: {binding}"), target.range()),
|
||||||
[import_edit],
|
[import_edit],
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||||
|
@ -136,8 +136,10 @@ fn generate_fix(checker: &Checker, conversion_type: ConversionType, expr: &Expr)
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
ConversionType::Optional => {
|
ConversionType::Optional => {
|
||||||
let (import_edit, binding) =
|
let importer = checker
|
||||||
checker.import_from_typing("Optional", expr.start(), PythonVersion::lowest())?;
|
.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 {
|
let new_expr = Expr::Subscript(ast::ExprSubscript {
|
||||||
range: TextRange::default(),
|
range: TextRange::default(),
|
||||||
value: Box::new(Expr::Name(ast::ExprName {
|
value: Box::new(Expr::Name(ast::ExprName {
|
||||||
|
|
|
@ -250,6 +250,7 @@ pub struct LinterSettings {
|
||||||
pub line_length: LineLength,
|
pub line_length: LineLength,
|
||||||
pub task_tags: Vec<String>,
|
pub task_tags: Vec<String>,
|
||||||
pub typing_modules: Vec<String>,
|
pub typing_modules: Vec<String>,
|
||||||
|
pub typing_extensions: bool,
|
||||||
|
|
||||||
// Plugins
|
// Plugins
|
||||||
pub flake8_annotations: flake8_annotations::settings::Settings,
|
pub flake8_annotations: flake8_annotations::settings::Settings,
|
||||||
|
@ -313,6 +314,7 @@ impl Display for LinterSettings {
|
||||||
self.line_length,
|
self.line_length,
|
||||||
self.task_tags | array,
|
self.task_tags | array,
|
||||||
self.typing_modules | array,
|
self.typing_modules | array,
|
||||||
|
self.typing_extensions,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
writeln!(f, "\n# Linter Plugins")?;
|
writeln!(f, "\n# Linter Plugins")?;
|
||||||
|
@ -450,6 +452,7 @@ impl LinterSettings {
|
||||||
preview: PreviewMode::default(),
|
preview: PreviewMode::default(),
|
||||||
explicit_preview_rules: false,
|
explicit_preview_rules: false,
|
||||||
extension: ExtensionMapping::default(),
|
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
|
.ruff
|
||||||
.map(RuffOptions::into_settings)
|
.map(RuffOptions::into_settings)
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
|
typing_extensions: lint.typing_extensions.unwrap_or(true),
|
||||||
},
|
},
|
||||||
|
|
||||||
formatter,
|
formatter,
|
||||||
|
@ -633,6 +634,7 @@ pub struct LintConfiguration {
|
||||||
pub logger_objects: Option<Vec<String>>,
|
pub logger_objects: Option<Vec<String>>,
|
||||||
pub task_tags: Option<Vec<String>>,
|
pub task_tags: Option<Vec<String>>,
|
||||||
pub typing_modules: Option<Vec<String>>,
|
pub typing_modules: Option<Vec<String>>,
|
||||||
|
pub typing_extensions: Option<bool>,
|
||||||
|
|
||||||
// Plugins
|
// Plugins
|
||||||
pub flake8_annotations: Option<Flake8AnnotationsOptions>,
|
pub flake8_annotations: Option<Flake8AnnotationsOptions>,
|
||||||
|
@ -746,6 +748,7 @@ impl LintConfiguration {
|
||||||
task_tags: options.common.task_tags,
|
task_tags: options.common.task_tags,
|
||||||
logger_objects: options.common.logger_objects,
|
logger_objects: options.common.logger_objects,
|
||||||
typing_modules: options.common.typing_modules,
|
typing_modules: options.common.typing_modules,
|
||||||
|
typing_extensions: options.typing_extensions,
|
||||||
|
|
||||||
// Plugins
|
// Plugins
|
||||||
flake8_annotations: options.common.flake8_annotations,
|
flake8_annotations: options.common.flake8_annotations,
|
||||||
|
@ -1170,6 +1173,7 @@ impl LintConfiguration {
|
||||||
pylint: self.pylint.combine(config.pylint),
|
pylint: self.pylint.combine(config.pylint),
|
||||||
pyupgrade: self.pyupgrade.combine(config.pyupgrade),
|
pyupgrade: self.pyupgrade.combine(config.pyupgrade),
|
||||||
ruff: self.ruff.combine(config.ruff),
|
ruff: self.ruff.combine(config.ruff),
|
||||||
|
typing_extensions: self.typing_extensions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -513,6 +513,22 @@ pub struct LintOptions {
|
||||||
"#
|
"#
|
||||||
)]
|
)]
|
||||||
pub preview: Option<bool>,
|
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`].
|
/// 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>,
|
pydoclint: Option<PydoclintOptions>,
|
||||||
ruff: Option<RuffOptions>,
|
ruff: Option<RuffOptions>,
|
||||||
preview: Option<bool>,
|
preview: Option<bool>,
|
||||||
|
typing_extensions: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<LintOptionsWire> for LintOptions {
|
impl From<LintOptionsWire> for LintOptions {
|
||||||
|
@ -3930,6 +3947,7 @@ impl From<LintOptionsWire> for LintOptions {
|
||||||
pydoclint,
|
pydoclint,
|
||||||
ruff,
|
ruff,
|
||||||
preview,
|
preview,
|
||||||
|
typing_extensions,
|
||||||
} = value;
|
} = value;
|
||||||
|
|
||||||
LintOptions {
|
LintOptions {
|
||||||
|
@ -3985,6 +4003,7 @@ impl From<LintOptionsWire> for LintOptions {
|
||||||
pydoclint,
|
pydoclint,
|
||||||
ruff,
|
ruff,
|
||||||
preview,
|
preview,
|
||||||
|
typing_extensions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
7
ruff.schema.json
generated
7
ruff.schema.json
generated
|
@ -2459,6 +2459,13 @@
|
||||||
"type": "string"
|
"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": {
|
"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.",
|
"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": [
|
"type": [
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue