mirror of
				https://github.com/astral-sh/ruff.git
				synced 2025-11-03 21:23:45 +00:00 
			
		
		
		
	[ruff] Added cls.__dict__.get('__annotations__') check (RUF063) (#18233)
				
					
				
			Added `cls.__dict__.get('__annotations__')` check for Python 3.10+ and
Python < 3.10 with `typing-extensions` enabled.
Closes #17853 
<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:
- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title? (Please prefix
with `[ty]` for ty pull
  requests.)
- Does this pull request include references to any relevant issues?
-->
## Summary
Added `cls.__dict__.get('__annotations__')` check for Python 3.10+ and
Python < 3.10 with `typing-extensions` enabled.
## Test Plan
`cargo test`
---------
Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
			
			
This commit is contained in:
		
							parent
							
								
									f544026b81
								
							
						
					
					
						commit
						e66f182045
					
				
					 11 changed files with 391 additions and 0 deletions
				
			
		
							
								
								
									
										18
									
								
								crates/ruff_linter/resources/test/fixtures/ruff/RUF063.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								crates/ruff_linter/resources/test/fixtures/ruff/RUF063.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					# RUF063
 | 
				
			||||||
 | 
					# Cases that should trigger the violation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					foo.__dict__.get("__annotations__")  # RUF063
 | 
				
			||||||
 | 
					foo.__dict__.get("__annotations__", None)  # RUF063
 | 
				
			||||||
 | 
					foo.__dict__.get("__annotations__", {})  # RUF063
 | 
				
			||||||
 | 
					foo.__dict__["__annotations__"]  # RUF063
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Cases that should NOT trigger the violation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					foo.__dict__.get("not__annotations__")
 | 
				
			||||||
 | 
					foo.__dict__.get("not__annotations__", None)
 | 
				
			||||||
 | 
					foo.__dict__.get("not__annotations__", {})
 | 
				
			||||||
 | 
					foo.__dict__["not__annotations__"]
 | 
				
			||||||
 | 
					foo.__annotations__
 | 
				
			||||||
 | 
					foo.get("__annotations__")
 | 
				
			||||||
 | 
					foo.get("__annotations__", None)
 | 
				
			||||||
 | 
					foo.get("__annotations__", {})
 | 
				
			||||||
| 
						 | 
					@ -179,6 +179,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
 | 
				
			||||||
            if checker.enabled(Rule::MissingMaxsplitArg) {
 | 
					            if checker.enabled(Rule::MissingMaxsplitArg) {
 | 
				
			||||||
                pylint::rules::missing_maxsplit_arg(checker, value, slice, expr);
 | 
					                pylint::rules::missing_maxsplit_arg(checker, value, slice, expr);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            if checker.enabled(Rule::AccessAnnotationsFromClassDict) {
 | 
				
			||||||
 | 
					                ruff::rules::access_annotations_from_class_dict_by_key(checker, subscript);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            pandas_vet::rules::subscript(checker, value, expr);
 | 
					            pandas_vet::rules::subscript(checker, value, expr);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        Expr::Tuple(ast::ExprTuple {
 | 
					        Expr::Tuple(ast::ExprTuple {
 | 
				
			||||||
| 
						 | 
					@ -1196,6 +1199,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
 | 
				
			||||||
            if checker.enabled(Rule::StarmapZip) {
 | 
					            if checker.enabled(Rule::StarmapZip) {
 | 
				
			||||||
                ruff::rules::starmap_zip(checker, call);
 | 
					                ruff::rules::starmap_zip(checker, call);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            if checker.enabled(Rule::AccessAnnotationsFromClassDict) {
 | 
				
			||||||
 | 
					                ruff::rules::access_annotations_from_class_dict_with_get(checker, call);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            if checker.enabled(Rule::LogExceptionOutsideExceptHandler) {
 | 
					            if checker.enabled(Rule::LogExceptionOutsideExceptHandler) {
 | 
				
			||||||
                flake8_logging::rules::log_exception_outside_except_handler(checker, call);
 | 
					                flake8_logging::rules::log_exception_outside_except_handler(checker, call);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1028,6 +1028,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
 | 
				
			||||||
        (Ruff, "059") => (RuleGroup::Preview, rules::ruff::rules::UnusedUnpackedVariable),
 | 
					        (Ruff, "059") => (RuleGroup::Preview, rules::ruff::rules::UnusedUnpackedVariable),
 | 
				
			||||||
        (Ruff, "060") => (RuleGroup::Preview, rules::ruff::rules::InEmptyCollection),
 | 
					        (Ruff, "060") => (RuleGroup::Preview, rules::ruff::rules::InEmptyCollection),
 | 
				
			||||||
        (Ruff, "061") => (RuleGroup::Preview, rules::ruff::rules::LegacyFormPytestRaises),
 | 
					        (Ruff, "061") => (RuleGroup::Preview, rules::ruff::rules::LegacyFormPytestRaises),
 | 
				
			||||||
 | 
					        (Ruff, "063") => (RuleGroup::Preview, rules::ruff::rules::AccessAnnotationsFromClassDict),
 | 
				
			||||||
        (Ruff, "064") => (RuleGroup::Preview, rules::ruff::rules::NonOctalPermissions),
 | 
					        (Ruff, "064") => (RuleGroup::Preview, rules::ruff::rules::NonOctalPermissions),
 | 
				
			||||||
        (Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
 | 
					        (Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
 | 
				
			||||||
        (Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
 | 
					        (Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -171,6 +171,60 @@ mod tests {
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn access_annotations_from_class_dict_py39_no_typing_extensions() -> Result<()> {
 | 
				
			||||||
 | 
					        let diagnostics = test_path(
 | 
				
			||||||
 | 
					            Path::new("ruff/RUF063.py"),
 | 
				
			||||||
 | 
					            &LinterSettings {
 | 
				
			||||||
 | 
					                typing_extensions: false,
 | 
				
			||||||
 | 
					                unresolved_target_version: PythonVersion::PY39.into(),
 | 
				
			||||||
 | 
					                ..LinterSettings::for_rule(Rule::AccessAnnotationsFromClassDict)
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )?;
 | 
				
			||||||
 | 
					        assert_diagnostics!(diagnostics);
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn access_annotations_from_class_dict_py39_with_typing_extensions() -> Result<()> {
 | 
				
			||||||
 | 
					        let diagnostics = test_path(
 | 
				
			||||||
 | 
					            Path::new("ruff/RUF063.py"),
 | 
				
			||||||
 | 
					            &LinterSettings {
 | 
				
			||||||
 | 
					                typing_extensions: true,
 | 
				
			||||||
 | 
					                unresolved_target_version: PythonVersion::PY39.into(),
 | 
				
			||||||
 | 
					                ..LinterSettings::for_rule(Rule::AccessAnnotationsFromClassDict)
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )?;
 | 
				
			||||||
 | 
					        assert_diagnostics!(diagnostics);
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn access_annotations_from_class_dict_py310() -> Result<()> {
 | 
				
			||||||
 | 
					        let diagnostics = test_path(
 | 
				
			||||||
 | 
					            Path::new("ruff/RUF063.py"),
 | 
				
			||||||
 | 
					            &LinterSettings {
 | 
				
			||||||
 | 
					                unresolved_target_version: PythonVersion::PY310.into(),
 | 
				
			||||||
 | 
					                ..LinterSettings::for_rule(Rule::AccessAnnotationsFromClassDict)
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )?;
 | 
				
			||||||
 | 
					        assert_diagnostics!(diagnostics);
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test]
 | 
				
			||||||
 | 
					    fn access_annotations_from_class_dict_py314() -> Result<()> {
 | 
				
			||||||
 | 
					        let diagnostics = test_path(
 | 
				
			||||||
 | 
					            Path::new("ruff/RUF063.py"),
 | 
				
			||||||
 | 
					            &LinterSettings {
 | 
				
			||||||
 | 
					                unresolved_target_version: PythonVersion::PY314.into(),
 | 
				
			||||||
 | 
					                ..LinterSettings::for_rule(Rule::AccessAnnotationsFromClassDict)
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )?;
 | 
				
			||||||
 | 
					        assert_diagnostics!(diagnostics);
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[test]
 | 
					    #[test]
 | 
				
			||||||
    fn confusables() -> Result<()> {
 | 
					    fn confusables() -> Result<()> {
 | 
				
			||||||
        let diagnostics = test_path(
 | 
					        let diagnostics = test_path(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,185 @@
 | 
				
			||||||
 | 
					use crate::checkers::ast::Checker;
 | 
				
			||||||
 | 
					use crate::{FixAvailability, Violation};
 | 
				
			||||||
 | 
					use ruff_macros::{ViolationMetadata, derive_message_formats};
 | 
				
			||||||
 | 
					use ruff_python_ast::{Expr, ExprCall, ExprSubscript, PythonVersion};
 | 
				
			||||||
 | 
					use ruff_text_size::Ranged;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// ## What it does
 | 
				
			||||||
 | 
					/// Checks for uses of `foo.__dict__.get("__annotations__")` or
 | 
				
			||||||
 | 
					/// `foo.__dict__["__annotations__"]` on Python 3.10+ and Python < 3.10 when
 | 
				
			||||||
 | 
					/// [typing-extensions](https://docs.astral.sh/ruff/settings/#lint_typing-extensions)
 | 
				
			||||||
 | 
					/// is enabled.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// ## Why is this bad?
 | 
				
			||||||
 | 
					/// Starting with Python 3.14, directly accessing `__annotations__` via
 | 
				
			||||||
 | 
					/// `foo.__dict__.get("__annotations__")` or `foo.__dict__["__annotations__"]`
 | 
				
			||||||
 | 
					/// will only return annotations if the class is defined under
 | 
				
			||||||
 | 
					/// `from __future__ import annotations`.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// Therefore, it is better to use dedicated library functions like
 | 
				
			||||||
 | 
					/// `annotationlib.get_annotations` (Python 3.14+), `inspect.get_annotations`
 | 
				
			||||||
 | 
					/// (Python 3.10+), or `typing_extensions.get_annotations` (for Python < 3.10 if
 | 
				
			||||||
 | 
					/// [typing-extensions](https://pypi.org/project/typing-extensions/) is
 | 
				
			||||||
 | 
					/// available).
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// The benefits of using these functions include:
 | 
				
			||||||
 | 
					/// 1.  **Avoiding Undocumented Internals:** They provide a stable, public API,
 | 
				
			||||||
 | 
					///     unlike direct `__dict__` access which relies on implementation details.
 | 
				
			||||||
 | 
					/// 2.  **Forward-Compatibility:** They are designed to handle changes in
 | 
				
			||||||
 | 
					///     Python's annotation system across versions, ensuring your code remains
 | 
				
			||||||
 | 
					///     robust (e.g., correctly handling the Python 3.14 behavior mentioned
 | 
				
			||||||
 | 
					///     above).
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// See [Python Annotations Best Practices](https://docs.python.org/3.14/howto/annotations.html)
 | 
				
			||||||
 | 
					/// for alternatives.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// ## Example
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// ```python
 | 
				
			||||||
 | 
					/// foo.__dict__.get("__annotations__", {})
 | 
				
			||||||
 | 
					/// # or
 | 
				
			||||||
 | 
					/// foo.__dict__["__annotations__"]
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// On Python 3.14+, use instead:
 | 
				
			||||||
 | 
					/// ```python
 | 
				
			||||||
 | 
					/// import annotationlib
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// annotationlib.get_annotations(foo)
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// On Python 3.10+, use instead:
 | 
				
			||||||
 | 
					/// ```python
 | 
				
			||||||
 | 
					/// import inspect
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// inspect.get_annotations(foo)
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// On Python < 3.10 with [typing-extensions](https://pypi.org/project/typing-extensions/)
 | 
				
			||||||
 | 
					/// installed, use instead:
 | 
				
			||||||
 | 
					/// ```python
 | 
				
			||||||
 | 
					/// import typing_extensions
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// typing_extensions.get_annotations(foo)
 | 
				
			||||||
 | 
					/// ```
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// ## Fix safety
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// No autofix is currently provided for this rule.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// ## Fix availability
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// No autofix is currently provided for this rule.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// ## References
 | 
				
			||||||
 | 
					/// - [Python Annotations Best Practices](https://docs.python.org/3.14/howto/annotations.html)
 | 
				
			||||||
 | 
					#[derive(ViolationMetadata)]
 | 
				
			||||||
 | 
					pub(crate) struct AccessAnnotationsFromClassDict {
 | 
				
			||||||
 | 
					    python_version: PythonVersion,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Violation for AccessAnnotationsFromClassDict {
 | 
				
			||||||
 | 
					    const FIX_AVAILABILITY: FixAvailability = FixAvailability::None;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive_message_formats]
 | 
				
			||||||
 | 
					    fn message(&self) -> String {
 | 
				
			||||||
 | 
					        let suggestion = if self.python_version >= PythonVersion::PY314 {
 | 
				
			||||||
 | 
					            "annotationlib.get_annotations"
 | 
				
			||||||
 | 
					        } else if self.python_version >= PythonVersion::PY310 {
 | 
				
			||||||
 | 
					            "inspect.get_annotations"
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            "typing_extensions.get_annotations"
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        format!("Use `{suggestion}` instead of `__dict__` access")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// RUF063
 | 
				
			||||||
 | 
					pub(crate) fn access_annotations_from_class_dict_with_get(checker: &Checker, call: &ExprCall) {
 | 
				
			||||||
 | 
					    let python_version = checker.target_version();
 | 
				
			||||||
 | 
					    let typing_extensions = checker.settings.typing_extensions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Only apply this rule for Python 3.10 and newer unless `typing-extensions` is enabled.
 | 
				
			||||||
 | 
					    if python_version < PythonVersion::PY310 && !typing_extensions {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Expected pattern: foo.__dict__.get("__annotations__" [, <default>])
 | 
				
			||||||
 | 
					    // Here, `call` is the `.get(...)` part.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 1. Check that the `call.func` is `get`
 | 
				
			||||||
 | 
					    let get_attribute = match call.func.as_ref() {
 | 
				
			||||||
 | 
					        Expr::Attribute(attr) if attr.attr.as_str() == "get" => attr,
 | 
				
			||||||
 | 
					        _ => return,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 2. Check that the `get_attribute.value` is `__dict__`
 | 
				
			||||||
 | 
					    match get_attribute.value.as_ref() {
 | 
				
			||||||
 | 
					        Expr::Attribute(attr) if attr.attr.as_str() == "__dict__" => {}
 | 
				
			||||||
 | 
					        _ => return,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // At this point, we have `foo.__dict__.get`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 3. Check arguments to `.get()`:
 | 
				
			||||||
 | 
					    //    - No keyword arguments.
 | 
				
			||||||
 | 
					    //    - One or two positional arguments.
 | 
				
			||||||
 | 
					    //    - First positional argument must be the string literal "__annotations__".
 | 
				
			||||||
 | 
					    //    - The value of the second positional argument (if present) does not affect the match.
 | 
				
			||||||
 | 
					    if !call.arguments.keywords.is_empty() || call.arguments.len() > 2 {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let Some(first_arg) = &call.arguments.find_positional(0) else {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let is_first_arg_correct = first_arg
 | 
				
			||||||
 | 
					        .as_string_literal_expr()
 | 
				
			||||||
 | 
					        .is_some_and(|s| s.value.to_str() == "__annotations__");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if is_first_arg_correct {
 | 
				
			||||||
 | 
					        checker.report_diagnostic(
 | 
				
			||||||
 | 
					            AccessAnnotationsFromClassDict { python_version },
 | 
				
			||||||
 | 
					            call.range(),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// RUF063
 | 
				
			||||||
 | 
					pub(crate) fn access_annotations_from_class_dict_by_key(
 | 
				
			||||||
 | 
					    checker: &Checker,
 | 
				
			||||||
 | 
					    subscript: &ExprSubscript,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    let python_version = checker.target_version();
 | 
				
			||||||
 | 
					    let typing_extensions = checker.settings.typing_extensions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Only apply this rule for Python 3.10 and newer unless `typing-extensions` is enabled.
 | 
				
			||||||
 | 
					    if python_version < PythonVersion::PY310 && !typing_extensions {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Expected pattern: foo.__dict__["__annotations__"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 1. Check that the slice is a string literal "__annotations__"
 | 
				
			||||||
 | 
					    if subscript
 | 
				
			||||||
 | 
					        .slice
 | 
				
			||||||
 | 
					        .as_string_literal_expr()
 | 
				
			||||||
 | 
					        .is_none_or(|s| s.value.to_str() != "__annotations__")
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 2. Check that the `subscript.value` is `__dict__`
 | 
				
			||||||
 | 
					    let is_value_correct = subscript
 | 
				
			||||||
 | 
					        .value
 | 
				
			||||||
 | 
					        .as_attribute_expr()
 | 
				
			||||||
 | 
					        .is_some_and(|attr| attr.attr.as_str() == "__dict__");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if is_value_correct {
 | 
				
			||||||
 | 
					        checker.report_diagnostic(
 | 
				
			||||||
 | 
					            AccessAnnotationsFromClassDict { python_version },
 | 
				
			||||||
 | 
					            subscript.range(),
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
 | 
					pub(crate) use access_annotations_from_class_dict::*;
 | 
				
			||||||
pub(crate) use ambiguous_unicode_character::*;
 | 
					pub(crate) use ambiguous_unicode_character::*;
 | 
				
			||||||
pub(crate) use assert_with_print_message::*;
 | 
					pub(crate) use assert_with_print_message::*;
 | 
				
			||||||
pub(crate) use assignment_in_assert::*;
 | 
					pub(crate) use assignment_in_assert::*;
 | 
				
			||||||
| 
						 | 
					@ -59,6 +60,7 @@ pub(crate) use used_dummy_variable::*;
 | 
				
			||||||
pub(crate) use useless_if_else::*;
 | 
					pub(crate) use useless_if_else::*;
 | 
				
			||||||
pub(crate) use zip_instead_of_pairwise::*;
 | 
					pub(crate) use zip_instead_of_pairwise::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod access_annotations_from_class_dict;
 | 
				
			||||||
mod ambiguous_unicode_character;
 | 
					mod ambiguous_unicode_character;
 | 
				
			||||||
mod assert_with_print_message;
 | 
					mod assert_with_print_message;
 | 
				
			||||||
mod assignment_in_assert;
 | 
					mod assignment_in_assert;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					source: crates/ruff_linter/src/rules/ruff/mod.rs
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					RUF063.py:4:1: RUF063 Use `inspect.get_annotations` instead of `__dict__` access
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					2 | # Cases that should trigger the violation
 | 
				
			||||||
 | 
					3 |
 | 
				
			||||||
 | 
					4 | foo.__dict__.get("__annotations__")  # RUF063
 | 
				
			||||||
 | 
					  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF063
 | 
				
			||||||
 | 
					5 | foo.__dict__.get("__annotations__", None)  # RUF063
 | 
				
			||||||
 | 
					6 | foo.__dict__.get("__annotations__", {})  # RUF063
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUF063.py:5:1: RUF063 Use `inspect.get_annotations` instead of `__dict__` access
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					4 | foo.__dict__.get("__annotations__")  # RUF063
 | 
				
			||||||
 | 
					5 | foo.__dict__.get("__annotations__", None)  # RUF063
 | 
				
			||||||
 | 
					  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF063
 | 
				
			||||||
 | 
					6 | foo.__dict__.get("__annotations__", {})  # RUF063
 | 
				
			||||||
 | 
					7 | foo.__dict__["__annotations__"]  # RUF063
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUF063.py:6:1: RUF063 Use `inspect.get_annotations` instead of `__dict__` access
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					4 | foo.__dict__.get("__annotations__")  # RUF063
 | 
				
			||||||
 | 
					5 | foo.__dict__.get("__annotations__", None)  # RUF063
 | 
				
			||||||
 | 
					6 | foo.__dict__.get("__annotations__", {})  # RUF063
 | 
				
			||||||
 | 
					  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF063
 | 
				
			||||||
 | 
					7 | foo.__dict__["__annotations__"]  # RUF063
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUF063.py:7:1: RUF063 Use `inspect.get_annotations` instead of `__dict__` access
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					5 | foo.__dict__.get("__annotations__", None)  # RUF063
 | 
				
			||||||
 | 
					6 | foo.__dict__.get("__annotations__", {})  # RUF063
 | 
				
			||||||
 | 
					7 | foo.__dict__["__annotations__"]  # RUF063
 | 
				
			||||||
 | 
					  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF063
 | 
				
			||||||
 | 
					8 |
 | 
				
			||||||
 | 
					9 | # Cases that should NOT trigger the violation
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					source: crates/ruff_linter/src/rules/ruff/mod.rs
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					RUF063.py:4:1: RUF063 Use `annotationlib.get_annotations` instead of `__dict__` access
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					2 | # Cases that should trigger the violation
 | 
				
			||||||
 | 
					3 |
 | 
				
			||||||
 | 
					4 | foo.__dict__.get("__annotations__")  # RUF063
 | 
				
			||||||
 | 
					  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF063
 | 
				
			||||||
 | 
					5 | foo.__dict__.get("__annotations__", None)  # RUF063
 | 
				
			||||||
 | 
					6 | foo.__dict__.get("__annotations__", {})  # RUF063
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUF063.py:5:1: RUF063 Use `annotationlib.get_annotations` instead of `__dict__` access
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					4 | foo.__dict__.get("__annotations__")  # RUF063
 | 
				
			||||||
 | 
					5 | foo.__dict__.get("__annotations__", None)  # RUF063
 | 
				
			||||||
 | 
					  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF063
 | 
				
			||||||
 | 
					6 | foo.__dict__.get("__annotations__", {})  # RUF063
 | 
				
			||||||
 | 
					7 | foo.__dict__["__annotations__"]  # RUF063
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUF063.py:6:1: RUF063 Use `annotationlib.get_annotations` instead of `__dict__` access
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					4 | foo.__dict__.get("__annotations__")  # RUF063
 | 
				
			||||||
 | 
					5 | foo.__dict__.get("__annotations__", None)  # RUF063
 | 
				
			||||||
 | 
					6 | foo.__dict__.get("__annotations__", {})  # RUF063
 | 
				
			||||||
 | 
					  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF063
 | 
				
			||||||
 | 
					7 | foo.__dict__["__annotations__"]  # RUF063
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUF063.py:7:1: RUF063 Use `annotationlib.get_annotations` instead of `__dict__` access
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					5 | foo.__dict__.get("__annotations__", None)  # RUF063
 | 
				
			||||||
 | 
					6 | foo.__dict__.get("__annotations__", {})  # RUF063
 | 
				
			||||||
 | 
					7 | foo.__dict__["__annotations__"]  # RUF063
 | 
				
			||||||
 | 
					  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF063
 | 
				
			||||||
 | 
					8 |
 | 
				
			||||||
 | 
					9 | # Cases that should NOT trigger the violation
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,4 @@
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					source: crates/ruff_linter/src/rules/ruff/mod.rs
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					source: crates/ruff_linter/src/rules/ruff/mod.rs
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					RUF063.py:4:1: RUF063 Use `typing_extensions.get_annotations` instead of `__dict__` access
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					2 | # Cases that should trigger the violation
 | 
				
			||||||
 | 
					3 |
 | 
				
			||||||
 | 
					4 | foo.__dict__.get("__annotations__")  # RUF063
 | 
				
			||||||
 | 
					  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF063
 | 
				
			||||||
 | 
					5 | foo.__dict__.get("__annotations__", None)  # RUF063
 | 
				
			||||||
 | 
					6 | foo.__dict__.get("__annotations__", {})  # RUF063
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUF063.py:5:1: RUF063 Use `typing_extensions.get_annotations` instead of `__dict__` access
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					4 | foo.__dict__.get("__annotations__")  # RUF063
 | 
				
			||||||
 | 
					5 | foo.__dict__.get("__annotations__", None)  # RUF063
 | 
				
			||||||
 | 
					  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF063
 | 
				
			||||||
 | 
					6 | foo.__dict__.get("__annotations__", {})  # RUF063
 | 
				
			||||||
 | 
					7 | foo.__dict__["__annotations__"]  # RUF063
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUF063.py:6:1: RUF063 Use `typing_extensions.get_annotations` instead of `__dict__` access
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					4 | foo.__dict__.get("__annotations__")  # RUF063
 | 
				
			||||||
 | 
					5 | foo.__dict__.get("__annotations__", None)  # RUF063
 | 
				
			||||||
 | 
					6 | foo.__dict__.get("__annotations__", {})  # RUF063
 | 
				
			||||||
 | 
					  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF063
 | 
				
			||||||
 | 
					7 | foo.__dict__["__annotations__"]  # RUF063
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUF063.py:7:1: RUF063 Use `typing_extensions.get_annotations` instead of `__dict__` access
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
 | 
					5 | foo.__dict__.get("__annotations__", None)  # RUF063
 | 
				
			||||||
 | 
					6 | foo.__dict__.get("__annotations__", {})  # RUF063
 | 
				
			||||||
 | 
					7 | foo.__dict__["__annotations__"]  # RUF063
 | 
				
			||||||
 | 
					  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF063
 | 
				
			||||||
 | 
					8 |
 | 
				
			||||||
 | 
					9 | # Cases that should NOT trigger the violation
 | 
				
			||||||
 | 
					  |
 | 
				
			||||||
							
								
								
									
										1
									
								
								ruff.schema.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								ruff.schema.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -4040,6 +4040,7 @@
 | 
				
			||||||
        "RUF06",
 | 
					        "RUF06",
 | 
				
			||||||
        "RUF060",
 | 
					        "RUF060",
 | 
				
			||||||
        "RUF061",
 | 
					        "RUF061",
 | 
				
			||||||
 | 
					        "RUF063",
 | 
				
			||||||
        "RUF064",
 | 
					        "RUF064",
 | 
				
			||||||
        "RUF1",
 | 
					        "RUF1",
 | 
				
			||||||
        "RUF10",
 | 
					        "RUF10",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue