From 5a8b7c1d20cdc672b259cde4faec1a89d4210ece Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Fri, 11 Nov 2022 17:39:37 -0800 Subject: [PATCH] Implement flake8-2020 (sys.version, sys.version_info misuse) (#688) --- README.md | 24 ++- resources/test/fixtures/YTT101.py | 13 ++ resources/test/fixtures/YTT102.py | 5 + resources/test/fixtures/YTT103.py | 8 + resources/test/fixtures/YTT201.py | 10 + resources/test/fixtures/YTT202.py | 7 + resources/test/fixtures/YTT203.py | 5 + resources/test/fixtures/YTT204.py | 5 + resources/test/fixtures/YTT301.py | 5 + resources/test/fixtures/YTT302.py | 8 + resources/test/fixtures/YTT303.py | 5 + src/check_ast.rs | 81 +++++--- src/checks.rs | 89 ++++++++ src/checks_gen.rs | 72 +++++++ src/flake8_2020/mod.rs | 1 + src/flake8_2020/plugins.rs | 192 ++++++++++++++++++ src/lib.rs | 1 + src/linter.rs | 10 + ...ruff__linter__tests__YTT101_YTT101.py.snap | 21 ++ ...ruff__linter__tests__YTT102_YTT102.py.snap | 21 ++ ...ruff__linter__tests__YTT103_YTT103.py.snap | 45 ++++ ...ruff__linter__tests__YTT201_YTT201.py.snap | 37 ++++ ...ruff__linter__tests__YTT202_YTT202.py.snap | 21 ++ ...ruff__linter__tests__YTT203_YTT203.py.snap | 21 ++ ...ruff__linter__tests__YTT204_YTT204.py.snap | 21 ++ ...ruff__linter__tests__YTT301_YTT301.py.snap | 21 ++ ...ruff__linter__tests__YTT302_YTT302.py.snap | 45 ++++ ...ruff__linter__tests__YTT303_YTT303.py.snap | 21 ++ 28 files changed, 787 insertions(+), 28 deletions(-) create mode 100644 resources/test/fixtures/YTT101.py create mode 100644 resources/test/fixtures/YTT102.py create mode 100644 resources/test/fixtures/YTT103.py create mode 100644 resources/test/fixtures/YTT201.py create mode 100644 resources/test/fixtures/YTT202.py create mode 100644 resources/test/fixtures/YTT203.py create mode 100644 resources/test/fixtures/YTT204.py create mode 100644 resources/test/fixtures/YTT301.py create mode 100644 resources/test/fixtures/YTT302.py create mode 100644 resources/test/fixtures/YTT303.py create mode 100644 src/flake8_2020/mod.rs create mode 100644 src/flake8_2020/plugins.rs create mode 100644 src/snapshots/ruff__linter__tests__YTT101_YTT101.py.snap create mode 100644 src/snapshots/ruff__linter__tests__YTT102_YTT102.py.snap create mode 100644 src/snapshots/ruff__linter__tests__YTT103_YTT103.py.snap create mode 100644 src/snapshots/ruff__linter__tests__YTT201_YTT201.py.snap create mode 100644 src/snapshots/ruff__linter__tests__YTT202_YTT202.py.snap create mode 100644 src/snapshots/ruff__linter__tests__YTT203_YTT203.py.snap create mode 100644 src/snapshots/ruff__linter__tests__YTT204_YTT204.py.snap create mode 100644 src/snapshots/ruff__linter__tests__YTT301_YTT301.py.snap create mode 100644 src/snapshots/ruff__linter__tests__YTT302_YTT302.py.snap create mode 100644 src/snapshots/ruff__linter__tests__YTT303_YTT303.py.snap diff --git a/README.md b/README.md index 2f0465bbb2..679a2596d1 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,9 @@ Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-mu 9. [flake8-print](#flake8-print) 10. [flake8-quotes](#flake8-quotes) 11. [flake8-annotations](#flake8-annotations) - 12. [Ruff-specific rules](#ruff-specific-rules) - 13. [Meta rules](#meta-rules) + 12. [flake8-2020](#flake8-2020) + 13. [Ruff-specific rules](#ruff-specific-rules) + 14. [Meta rules](#meta-rules) 5. [Editor Integrations](#editor-integrations) 6. [FAQ](#faq) 7. [Development](#development) @@ -561,6 +562,23 @@ For more, see [flake8-annotations](https://pypi.org/project/flake8-annotations/2 | ANN206 | MissingReturnTypeClassMethod | Missing return type annotation for classmethod `...` | | | ANN401 | DynamicallyTypedExpression | Dynamically typed expressions (typing.Any) are disallowed in `...` | | +### flake8-2020 + +For more, see [flake8-2020](https://pypi.org/project/flake8-2020/1.7.0/) on PyPI. + +| Code | Name | Message | Fix | +| ---- | ---- | ------- | --- | +| YTT101 | SysVersionSlice3Referenced | `sys.version[:3]` referenced (python3.10), use `sys.version_info` | | +| YTT102 | SysVersion2Referenced | `sys.version[2]` referenced (python3.10), use `sys.version_info` | | +| YTT103 | SysVersionCmpStr3 | `sys.version` compared to string (python3.10), use `sys.version_info` | | +| YTT201 | SysVersionInfo0Eq3Referenced | `sys.version_info[0] == 3` referenced (python4), use `>=` | | +| YTT202 | SixPY3Referenced | `six.PY3` referenced (python4), use `not six.PY2` | | +| YTT203 | SysVersionInfo1CmpInt | `sys.version_info[1]` compared to integer (python4), compare `sys.version_info` to tuple | | +| YTT204 | SysVersionInfoMinorCmpInt | `sys.version_info.minor` compared to integer (python4), compare `sys.version_info` to tuple | | +| YTT301 | SysVersion0Referenced | `sys.version[0]` referenced (python10), use `sys.version_info` | | +| YTT302 | SysVersionCmpStr10 | `sys.version` compared to string (python10), use `sys.version_info` | | +| YTT303 | SysVersionSlice1Referenced | `sys.version[:1]` referenced (python10), use `sys.version_info` | | + ### Ruff-specific rules | Code | Name | Message | Fix | @@ -667,6 +685,7 @@ including: - [`flake8-annotations`](https://pypi.org/project/flake8-annotations/) - [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) - [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (20/32) +- [`flake8-2020`](https://pypi.org/project/flake8-2020/) - [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/34) - [`autoflake`](https://pypi.org/project/autoflake/) (1/7) @@ -690,6 +709,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl - [`flake8-annotations`](https://pypi.org/project/flake8-annotations/) - [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/) - [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (20/32) +- [`flake8-2020`](https://pypi.org/project/flake8-2020/) Ruff can also replace [`isort`](https://pypi.org/project/isort/), [`yesqa`](https://github.com/asottile/yesqa), and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (14/34). diff --git a/resources/test/fixtures/YTT101.py b/resources/test/fixtures/YTT101.py new file mode 100644 index 0000000000..d21870b9bb --- /dev/null +++ b/resources/test/fixtures/YTT101.py @@ -0,0 +1,13 @@ +import sys +from sys import version, version as v + +print(sys.version) + +print(sys.version[:3]) +print(version[:3]) + +# ignore from imports with aliases, patches welcome +print(v[:3]) +# the tool is timid and only flags certain numeric slices +i = 3 +print(sys.version[:i]) diff --git a/resources/test/fixtures/YTT102.py b/resources/test/fixtures/YTT102.py new file mode 100644 index 0000000000..950e465486 --- /dev/null +++ b/resources/test/fixtures/YTT102.py @@ -0,0 +1,5 @@ +import sys +from sys import version + +py_minor = sys.version[2] +py_minor = version[2] diff --git a/resources/test/fixtures/YTT103.py b/resources/test/fixtures/YTT103.py new file mode 100644 index 0000000000..8f55ab5cc4 --- /dev/null +++ b/resources/test/fixtures/YTT103.py @@ -0,0 +1,8 @@ +import sys +from sys import version + +version < "3.5" +sys.version < "3.5" +sys.version <= "3.5" +sys.version > "3.5" +sys.version >= "3.5" diff --git a/resources/test/fixtures/YTT201.py b/resources/test/fixtures/YTT201.py new file mode 100644 index 0000000000..eab189ea4c --- /dev/null +++ b/resources/test/fixtures/YTT201.py @@ -0,0 +1,10 @@ +import sys +from sys import version_info + +print("{}.{}".format(*sys.version_info)) +PY3 = sys.version_info[0] >= 3 + +PY3 = sys.version_info[0] == 3 +PY3 = version_info[0] == 3 +PY2 = sys.version_info[0] != 3 +PY2 = version_info[0] != 3 diff --git a/resources/test/fixtures/YTT202.py b/resources/test/fixtures/YTT202.py new file mode 100644 index 0000000000..ea56f889a1 --- /dev/null +++ b/resources/test/fixtures/YTT202.py @@ -0,0 +1,7 @@ +import six +from six import PY3 + +if six.PY3: + print("3") +if PY3: + print("3") diff --git a/resources/test/fixtures/YTT203.py b/resources/test/fixtures/YTT203.py new file mode 100644 index 0000000000..07da4d0f4a --- /dev/null +++ b/resources/test/fixtures/YTT203.py @@ -0,0 +1,5 @@ +import sys +from sys import version_info + +sys.version_info[1] >= 5 +version_info[1] < 6 diff --git a/resources/test/fixtures/YTT204.py b/resources/test/fixtures/YTT204.py new file mode 100644 index 0000000000..56474ade2b --- /dev/null +++ b/resources/test/fixtures/YTT204.py @@ -0,0 +1,5 @@ +import sys +from sys import version_info + +sys.version_info.minor <= 7 +version_info.minor > 8 diff --git a/resources/test/fixtures/YTT301.py b/resources/test/fixtures/YTT301.py new file mode 100644 index 0000000000..03611f84d4 --- /dev/null +++ b/resources/test/fixtures/YTT301.py @@ -0,0 +1,5 @@ +import sys +from sys import version + +py_major = sys.version[0] +py_major = version[0] diff --git a/resources/test/fixtures/YTT302.py b/resources/test/fixtures/YTT302.py new file mode 100644 index 0000000000..5bcdde7477 --- /dev/null +++ b/resources/test/fixtures/YTT302.py @@ -0,0 +1,8 @@ +import sys +from sys import version + +version < "3" +sys.version < "3" +sys.version <= "3" +sys.version > "3" +sys.version >= "3" diff --git a/resources/test/fixtures/YTT303.py b/resources/test/fixtures/YTT303.py new file mode 100644 index 0000000000..2d6b484246 --- /dev/null +++ b/resources/test/fixtures/YTT303.py @@ -0,0 +1,5 @@ +import sys +from sys import version + +print(sys.version[:1]) +print(version[:1]) diff --git a/src/check_ast.rs b/src/check_ast.rs index 81f3b2da6a..ede664ac3f 100644 --- a/src/check_ast.rs +++ b/src/check_ast.rs @@ -32,17 +32,19 @@ use crate::settings::Settings; use crate::source_code_locator::SourceCodeLocator; use crate::visibility::{module_visibility, transition_scope, Modifier, Visibility, VisibleScope}; use crate::{ - docstrings, flake8_annotations, flake8_bugbear, flake8_builtins, flake8_comprehensions, - flake8_print, pep8_naming, pycodestyle, pydocstyle, pyflakes, pyupgrade, + docstrings, flake8_2020, flake8_annotations, flake8_bugbear, flake8_builtins, + flake8_comprehensions, flake8_print, pep8_naming, pycodestyle, pydocstyle, pyflakes, pyupgrade, }; const GLOBAL_SCOPE_INDEX: usize = 0; -const TRACK_FROM_IMPORTS: [&str; 10] = [ +const TRACK_FROM_IMPORTS: [&str; 12] = [ "collections", "collections.abc", "contextlib", "functools", "re", + "six", + "sys", "typing", "typing.io", "typing.re", @@ -984,6 +986,14 @@ where if self.match_typing_module(value, "Literal") { self.in_literal = true; } + + if self.settings.enabled.contains(&CheckCode::YTT101) + || self.settings.enabled.contains(&CheckCode::YTT102) + || self.settings.enabled.contains(&CheckCode::YTT301) + || self.settings.enabled.contains(&CheckCode::YTT303) + { + flake8_2020::plugins::subscript(self, value, slice); + } } ExprKind::Tuple { elts, ctx } | ExprKind::List { elts, ctx } => { if matches!(ctx, ExprContext::Store) { @@ -1001,34 +1011,40 @@ where } } } - ExprKind::Name { id, ctx } => match ctx { - ExprContext::Load => { - // Ex) List[...] - if self.settings.enabled.contains(&CheckCode::U006) - && self.settings.target_version >= PythonVersion::Py39 - && typing::is_pep585_builtin(expr, self.from_imports.get("typing")) - { - pyupgrade::plugins::use_pep585_annotation(self, expr, id); - } - - self.handle_node_load(expr); - } - ExprContext::Store => { - if self.settings.enabled.contains(&CheckCode::E741) { - if let Some(check) = pycodestyle::checks::ambiguous_variable_name( - id, - Range::from_located(expr), - ) { - self.add_check(check); + ExprKind::Name { id, ctx } => { + match ctx { + ExprContext::Load => { + // Ex) List[...] + if self.settings.enabled.contains(&CheckCode::U006) + && self.settings.target_version >= PythonVersion::Py39 + && typing::is_pep585_builtin(expr, self.from_imports.get("typing")) + { + pyupgrade::plugins::use_pep585_annotation(self, expr, id); } + + self.handle_node_load(expr); } + ExprContext::Store => { + if self.settings.enabled.contains(&CheckCode::E741) { + if let Some(check) = pycodestyle::checks::ambiguous_variable_name( + id, + Range::from_located(expr), + ) { + self.add_check(check); + } + } - self.check_builtin_shadowing(id, Range::from_located(expr), true); + self.check_builtin_shadowing(id, Range::from_located(expr), true); - self.handle_node_store(expr, self.current_parent()); + self.handle_node_store(expr, self.current_parent()); + } + ExprContext::Del => self.handle_node_delete(expr), } - ExprContext::Del => self.handle_node_delete(expr), - }, + + if self.settings.enabled.contains(&CheckCode::YTT202) { + flake8_2020::plugins::name_or_attribute(self, expr); + } + } ExprKind::Attribute { attr, .. } => { // Ex) typing.List[...] if self.settings.enabled.contains(&CheckCode::U006) @@ -1037,6 +1053,10 @@ where { pyupgrade::plugins::use_pep585_annotation(self, expr, attr); } + + if self.settings.enabled.contains(&CheckCode::YTT202) { + flake8_2020::plugins::name_or_attribute(self, expr); + } } ExprKind::Call { func, @@ -1422,6 +1442,15 @@ where .into_iter(), ); } + + if self.settings.enabled.contains(&CheckCode::YTT103) + || self.settings.enabled.contains(&CheckCode::YTT201) + || self.settings.enabled.contains(&CheckCode::YTT203) + || self.settings.enabled.contains(&CheckCode::YTT204) + || self.settings.enabled.contains(&CheckCode::YTT302) + { + flake8_2020::plugins::compare(self, left, ops, comparators); + } } ExprKind::Constant { value: Constant::Str(value), diff --git a/src/checks.rs b/src/checks.rs index 711c47cd89..f8e9a8e601 100644 --- a/src/checks.rs +++ b/src/checks.rs @@ -132,6 +132,17 @@ pub enum CheckCode { ANN205, ANN206, ANN401, + // flake8-2020 + YTT101, + YTT102, + YTT103, + YTT201, + YTT202, + YTT203, + YTT204, + YTT301, + YTT302, + YTT303, // pyupgrade U001, U002, @@ -229,6 +240,7 @@ pub enum CheckCategory { Flake8Print, Flake8Quotes, Flake8Annotations, + Flake82020, Ruff, Meta, } @@ -245,6 +257,7 @@ impl CheckCategory { CheckCategory::Flake8Print => "flake8-print", CheckCategory::Flake8Quotes => "flake8-quotes", CheckCategory::Flake8Annotations => "flake8-annotations", + CheckCategory::Flake82020 => "flake8-2020", CheckCategory::Pyupgrade => "pyupgrade", CheckCategory::Pydocstyle => "pydocstyle", CheckCategory::PEP8Naming => "pep8-naming", @@ -272,6 +285,7 @@ impl CheckCategory { CheckCategory::Flake8Annotations => { Some("https://pypi.org/project/flake8-annotations/2.9.1/") } + CheckCategory::Flake82020 => Some("https://pypi.org/project/flake8-2020/1.7.0/"), CheckCategory::Pyupgrade => Some("https://pypi.org/project/pyupgrade/3.2.0/"), CheckCategory::Pydocstyle => Some("https://pypi.org/project/pydocstyle/6.1.1/"), CheckCategory::PEP8Naming => Some("https://pypi.org/project/pep8-naming/0.13.2/"), @@ -405,6 +419,17 @@ pub enum CheckKind { MissingReturnTypeStaticMethod(String), MissingReturnTypeClassMethod(String), DynamicallyTypedExpression(String), + // flake8-2020 + SysVersionSlice3Referenced, + SysVersion2Referenced, + SysVersionCmpStr3, + SysVersionInfo0Eq3Referenced, + SixPY3Referenced, + SysVersionInfo1CmpInt, + SysVersionInfoMinorCmpInt, + SysVersion0Referenced, + SysVersionCmpStr10, + SysVersionSlice1Referenced, // pyupgrade TypeOfPrimitive(Primitive), UnnecessaryAbspath, @@ -637,6 +662,17 @@ impl CheckCode { CheckCode::ANN205 => CheckKind::MissingReturnTypeStaticMethod("...".to_string()), CheckCode::ANN206 => CheckKind::MissingReturnTypeClassMethod("...".to_string()), CheckCode::ANN401 => CheckKind::DynamicallyTypedExpression("...".to_string()), + // flake8-2020 + CheckCode::YTT101 => CheckKind::SysVersionSlice3Referenced, + CheckCode::YTT102 => CheckKind::SysVersion2Referenced, + CheckCode::YTT103 => CheckKind::SysVersionCmpStr3, + CheckCode::YTT201 => CheckKind::SysVersionInfo0Eq3Referenced, + CheckCode::YTT202 => CheckKind::SixPY3Referenced, + CheckCode::YTT203 => CheckKind::SysVersionInfo1CmpInt, + CheckCode::YTT204 => CheckKind::SysVersionInfoMinorCmpInt, + CheckCode::YTT301 => CheckKind::SysVersion0Referenced, + CheckCode::YTT302 => CheckKind::SysVersionCmpStr10, + CheckCode::YTT303 => CheckKind::SysVersionSlice1Referenced, // pyupgrade CheckCode::U001 => CheckKind::UselessMetaclassType, CheckCode::U002 => CheckKind::UnnecessaryAbspath, @@ -840,6 +876,16 @@ impl CheckCode { CheckCode::ANN205 => CheckCategory::Flake8Annotations, CheckCode::ANN206 => CheckCategory::Flake8Annotations, CheckCode::ANN401 => CheckCategory::Flake8Annotations, + CheckCode::YTT101 => CheckCategory::Flake82020, + CheckCode::YTT102 => CheckCategory::Flake82020, + CheckCode::YTT103 => CheckCategory::Flake82020, + CheckCode::YTT201 => CheckCategory::Flake82020, + CheckCode::YTT202 => CheckCategory::Flake82020, + CheckCode::YTT203 => CheckCategory::Flake82020, + CheckCode::YTT204 => CheckCategory::Flake82020, + CheckCode::YTT301 => CheckCategory::Flake82020, + CheckCode::YTT302 => CheckCategory::Flake82020, + CheckCode::YTT303 => CheckCategory::Flake82020, CheckCode::U001 => CheckCategory::Pyupgrade, CheckCode::U002 => CheckCategory::Pyupgrade, CheckCode::U003 => CheckCategory::Pyupgrade, @@ -1029,6 +1075,17 @@ impl CheckKind { CheckKind::MissingReturnTypeStaticMethod(_) => &CheckCode::ANN205, CheckKind::MissingReturnTypeClassMethod(_) => &CheckCode::ANN206, CheckKind::DynamicallyTypedExpression(_) => &CheckCode::ANN401, + // flake8-2020 + CheckKind::SysVersionSlice3Referenced => &CheckCode::YTT101, + CheckKind::SysVersion2Referenced => &CheckCode::YTT102, + CheckKind::SysVersionCmpStr3 => &CheckCode::YTT103, + CheckKind::SysVersionInfo0Eq3Referenced => &CheckCode::YTT201, + CheckKind::SixPY3Referenced => &CheckCode::YTT202, + CheckKind::SysVersionInfo1CmpInt => &CheckCode::YTT203, + CheckKind::SysVersionInfoMinorCmpInt => &CheckCode::YTT204, + CheckKind::SysVersion0Referenced => &CheckCode::YTT301, + CheckKind::SysVersionCmpStr10 => &CheckCode::YTT302, + CheckKind::SysVersionSlice1Referenced => &CheckCode::YTT303, // pyupgrade CheckKind::TypeOfPrimitive(_) => &CheckCode::U003, CheckKind::UnnecessaryAbspath => &CheckCode::U002, @@ -1473,6 +1530,38 @@ impl CheckKind { CheckKind::DynamicallyTypedExpression(name) => { format!("Dynamically typed expressions (typing.Any) are disallowed in `{name}`") } + // flake8-2020 + CheckKind::SysVersionSlice3Referenced => { + "`sys.version[:3]` referenced (python3.10), use `sys.version_info`".to_string() + } + CheckKind::SysVersion2Referenced => { + "`sys.version[2]` referenced (python3.10), use `sys.version_info`".to_string() + } + CheckKind::SysVersionCmpStr3 => { + "`sys.version` compared to string (python3.10), use `sys.version_info`".to_string() + } + CheckKind::SysVersionInfo0Eq3Referenced => { + "`sys.version_info[0] == 3` referenced (python4), use `>=`".to_string() + } + CheckKind::SixPY3Referenced => { + "`six.PY3` referenced (python4), use `not six.PY2`".to_string() + } + CheckKind::SysVersionInfo1CmpInt => "`sys.version_info[1]` compared to integer \ + (python4), compare `sys.version_info` to tuple" + .to_string(), + CheckKind::SysVersionInfoMinorCmpInt => "`sys.version_info.minor` compared to integer \ + (python4), compare `sys.version_info` to \ + tuple" + .to_string(), + CheckKind::SysVersion0Referenced => { + "`sys.version[0]` referenced (python10), use `sys.version_info`".to_string() + } + CheckKind::SysVersionCmpStr10 => { + "`sys.version` compared to string (python10), use `sys.version_info`".to_string() + } + CheckKind::SysVersionSlice1Referenced => { + "`sys.version[:1]` referenced (python10), use `sys.version_info`".to_string() + } // pyupgrade CheckKind::TypeOfPrimitive(primitive) => { format!("Use `{}` instead of `type(...)`", primitive.builtin()) diff --git a/src/checks_gen.rs b/src/checks_gen.rs index e720cd9f37..a45d6ed14c 100644 --- a/src/checks_gen.rs +++ b/src/checks_gen.rs @@ -271,6 +271,23 @@ pub enum CheckCodePrefix { W6, W60, W605, + YTT, + YTT1, + YTT10, + YTT101, + YTT102, + YTT103, + YTT2, + YTT20, + YTT201, + YTT202, + YTT203, + YTT204, + YTT3, + YTT30, + YTT301, + YTT302, + YTT303, } #[derive(PartialEq, Eq, PartialOrd, Ord)] @@ -1026,6 +1043,44 @@ impl CheckCodePrefix { CheckCodePrefix::W6 => vec![CheckCode::W605], CheckCodePrefix::W60 => vec![CheckCode::W605], CheckCodePrefix::W605 => vec![CheckCode::W605], + CheckCodePrefix::YTT => vec![ + CheckCode::YTT101, + CheckCode::YTT102, + CheckCode::YTT103, + CheckCode::YTT201, + CheckCode::YTT202, + CheckCode::YTT203, + CheckCode::YTT204, + CheckCode::YTT301, + CheckCode::YTT302, + CheckCode::YTT303, + ], + CheckCodePrefix::YTT1 => vec![CheckCode::YTT101, CheckCode::YTT102, CheckCode::YTT103], + CheckCodePrefix::YTT10 => vec![CheckCode::YTT101, CheckCode::YTT102, CheckCode::YTT103], + CheckCodePrefix::YTT101 => vec![CheckCode::YTT101], + CheckCodePrefix::YTT102 => vec![CheckCode::YTT102], + CheckCodePrefix::YTT103 => vec![CheckCode::YTT103], + CheckCodePrefix::YTT2 => vec![ + CheckCode::YTT201, + CheckCode::YTT202, + CheckCode::YTT203, + CheckCode::YTT204, + ], + CheckCodePrefix::YTT20 => vec![ + CheckCode::YTT201, + CheckCode::YTT202, + CheckCode::YTT203, + CheckCode::YTT204, + ], + CheckCodePrefix::YTT201 => vec![CheckCode::YTT201], + CheckCodePrefix::YTT202 => vec![CheckCode::YTT202], + CheckCodePrefix::YTT203 => vec![CheckCode::YTT203], + CheckCodePrefix::YTT204 => vec![CheckCode::YTT204], + CheckCodePrefix::YTT3 => vec![CheckCode::YTT301, CheckCode::YTT302, CheckCode::YTT303], + CheckCodePrefix::YTT30 => vec![CheckCode::YTT301, CheckCode::YTT302, CheckCode::YTT303], + CheckCodePrefix::YTT301 => vec![CheckCode::YTT301], + CheckCodePrefix::YTT302 => vec![CheckCode::YTT302], + CheckCodePrefix::YTT303 => vec![CheckCode::YTT303], } } } @@ -1297,6 +1352,23 @@ impl CheckCodePrefix { CheckCodePrefix::W6 => PrefixSpecificity::Hundreds, CheckCodePrefix::W60 => PrefixSpecificity::Tens, CheckCodePrefix::W605 => PrefixSpecificity::Explicit, + CheckCodePrefix::YTT => PrefixSpecificity::Category, + CheckCodePrefix::YTT1 => PrefixSpecificity::Hundreds, + CheckCodePrefix::YTT10 => PrefixSpecificity::Tens, + CheckCodePrefix::YTT101 => PrefixSpecificity::Explicit, + CheckCodePrefix::YTT102 => PrefixSpecificity::Explicit, + CheckCodePrefix::YTT103 => PrefixSpecificity::Explicit, + CheckCodePrefix::YTT2 => PrefixSpecificity::Hundreds, + CheckCodePrefix::YTT20 => PrefixSpecificity::Tens, + CheckCodePrefix::YTT201 => PrefixSpecificity::Explicit, + CheckCodePrefix::YTT202 => PrefixSpecificity::Explicit, + CheckCodePrefix::YTT203 => PrefixSpecificity::Explicit, + CheckCodePrefix::YTT204 => PrefixSpecificity::Explicit, + CheckCodePrefix::YTT3 => PrefixSpecificity::Hundreds, + CheckCodePrefix::YTT30 => PrefixSpecificity::Tens, + CheckCodePrefix::YTT301 => PrefixSpecificity::Explicit, + CheckCodePrefix::YTT302 => PrefixSpecificity::Explicit, + CheckCodePrefix::YTT303 => PrefixSpecificity::Explicit, } } } diff --git a/src/flake8_2020/mod.rs b/src/flake8_2020/mod.rs new file mode 100644 index 0000000000..25a06164e5 --- /dev/null +++ b/src/flake8_2020/mod.rs @@ -0,0 +1 @@ +pub mod plugins; diff --git a/src/flake8_2020/plugins.rs b/src/flake8_2020/plugins.rs new file mode 100644 index 0000000000..02d4be2c68 --- /dev/null +++ b/src/flake8_2020/plugins.rs @@ -0,0 +1,192 @@ +use num_bigint::BigInt; +use rustpython_ast::{Cmpop, Constant, Expr, ExprKind, Located}; + +use crate::ast::helpers::match_name_or_attr_from_module; +use crate::ast::types::Range; +use crate::check_ast::Checker; +use crate::checks::{Check, CheckCode, CheckKind}; + +fn is_sys(checker: &Checker, expr: &Expr, target: &str) -> bool { + match_name_or_attr_from_module(expr, target, "sys", checker.from_imports.get("sys")) +} + +/// YTT101, YTT102, YTT301, YTT303 +pub fn subscript(checker: &mut Checker, value: &Expr, slice: &Expr) { + if is_sys(checker, value, "version") { + match &slice.node { + ExprKind::Slice { + lower: None, + upper: Some(upper), + step: None, + .. + } => { + if let ExprKind::Constant { + value: Constant::Int(i), + .. + } = &upper.node + { + if *i == BigInt::from(1) + && checker.settings.enabled.contains(&CheckCode::YTT303) + { + checker.add_check(Check::new( + CheckKind::SysVersionSlice1Referenced, + Range::from_located(value), + )); + } else if *i == BigInt::from(3) + && checker.settings.enabled.contains(&CheckCode::YTT101) + { + checker.add_check(Check::new( + CheckKind::SysVersionSlice3Referenced, + Range::from_located(value), + )); + } + } + } + + ExprKind::Constant { + value: Constant::Int(i), + .. + } => { + if *i == BigInt::from(2) && checker.settings.enabled.contains(&CheckCode::YTT102) { + checker.add_check(Check::new( + CheckKind::SysVersion2Referenced, + Range::from_located(value), + )); + } else if *i == BigInt::from(0) + && checker.settings.enabled.contains(&CheckCode::YTT301) + { + checker.add_check(Check::new( + CheckKind::SysVersion0Referenced, + Range::from_located(value), + )); + } + } + + _ => {} + } + } +} + +/// YTT103, YTT201, YTT203, YTT204, YTT302 +pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &[Expr]) { + match &left.node { + ExprKind::Subscript { value, slice, .. } if is_sys(checker, value, "version_info") => { + if let ExprKind::Constant { + value: Constant::Int(i), + .. + } = &slice.node + { + if *i == BigInt::from(0) { + if let ( + [Cmpop::Eq | Cmpop::NotEq], + [Located { + node: + ExprKind::Constant { + value: Constant::Int(n), + .. + }, + .. + }], + ) = (ops, comparators) + { + if *n == BigInt::from(3) + && checker.settings.enabled.contains(&CheckCode::YTT201) + { + checker.add_check(Check::new( + CheckKind::SysVersionInfo0Eq3Referenced, + Range::from_located(left), + )); + } + } + } else if *i == BigInt::from(1) { + if let ( + [Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE], + [Located { + node: + ExprKind::Constant { + value: Constant::Int(_), + .. + }, + .. + }], + ) = (ops, comparators) + { + if checker.settings.enabled.contains(&CheckCode::YTT203) { + checker.add_check(Check::new( + CheckKind::SysVersionInfo1CmpInt, + Range::from_located(left), + )); + } + } + } + } + } + + ExprKind::Attribute { value, attr, .. } + if is_sys(checker, value, "version_info") && attr == "minor" => + { + if let ( + [Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE], + [Located { + node: + ExprKind::Constant { + value: Constant::Int(_), + .. + }, + .. + }], + ) = (ops, comparators) + { + if checker.settings.enabled.contains(&CheckCode::YTT204) { + checker.add_check(Check::new( + CheckKind::SysVersionInfoMinorCmpInt, + Range::from_located(left), + )); + } + } + } + + _ => {} + } + + if is_sys(checker, left, "version") { + if let ( + [Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE], + [Located { + node: + ExprKind::Constant { + value: Constant::Str(s), + .. + }, + .. + }], + ) = (ops, comparators) + { + if s.len() == 1 { + if checker.settings.enabled.contains(&CheckCode::YTT302) { + checker.add_check(Check::new( + CheckKind::SysVersionCmpStr10, + Range::from_located(left), + )); + } + } else if checker.settings.enabled.contains(&CheckCode::YTT103) { + checker.add_check(Check::new( + CheckKind::SysVersionCmpStr3, + Range::from_located(left), + )); + } + } + } +} + +/// YTT202 +pub fn name_or_attribute(checker: &mut Checker, expr: &Expr) { + if match_name_or_attr_from_module(expr, "PY3", "six", checker.from_imports.get("six")) + && checker.settings.enabled.contains(&CheckCode::YTT202) + { + checker.add_check(Check::new( + CheckKind::SixPY3Referenced, + Range::from_located(expr), + )); + } +} diff --git a/src/lib.rs b/src/lib.rs index 0d16e49258..2f228edf77 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ pub mod code_gen; mod cst; mod directives; mod docstrings; +mod flake8_2020; pub mod flake8_annotations; mod flake8_bugbear; mod flake8_builtins; diff --git a/src/linter.rs b/src/linter.rs index 00590c8c56..4350b1c47c 100644 --- a/src/linter.rs +++ b/src/linter.rs @@ -493,6 +493,16 @@ mod tests { #[test_case(CheckCode::RUF001, Path::new("RUF001.py"); "RUF001")] #[test_case(CheckCode::RUF002, Path::new("RUF002.py"); "RUF002")] #[test_case(CheckCode::RUF003, Path::new("RUF003.py"); "RUF003")] + #[test_case(CheckCode::YTT101, Path::new("YTT101.py"); "YTT101")] + #[test_case(CheckCode::YTT102, Path::new("YTT102.py"); "YTT102")] + #[test_case(CheckCode::YTT103, Path::new("YTT103.py"); "YTT103")] + #[test_case(CheckCode::YTT201, Path::new("YTT201.py"); "YTT201")] + #[test_case(CheckCode::YTT202, Path::new("YTT202.py"); "YTT202")] + #[test_case(CheckCode::YTT203, Path::new("YTT203.py"); "YTT203")] + #[test_case(CheckCode::YTT204, Path::new("YTT204.py"); "YTT204")] + #[test_case(CheckCode::YTT301, Path::new("YTT301.py"); "YTT301")] + #[test_case(CheckCode::YTT302, Path::new("YTT302.py"); "YTT302")] + #[test_case(CheckCode::YTT303, Path::new("YTT303.py"); "YTT303")] fn checks(check_code: CheckCode, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy()); let mut checks = test_path( diff --git a/src/snapshots/ruff__linter__tests__YTT101_YTT101.py.snap b/src/snapshots/ruff__linter__tests__YTT101_YTT101.py.snap new file mode 100644 index 0000000000..023a3bf7c3 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__YTT101_YTT101.py.snap @@ -0,0 +1,21 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: SysVersionSlice3Referenced + location: + row: 6 + column: 6 + end_location: + row: 6 + column: 17 + fix: ~ +- kind: SysVersionSlice3Referenced + location: + row: 7 + column: 6 + end_location: + row: 7 + column: 13 + fix: ~ + diff --git a/src/snapshots/ruff__linter__tests__YTT102_YTT102.py.snap b/src/snapshots/ruff__linter__tests__YTT102_YTT102.py.snap new file mode 100644 index 0000000000..c0827b9f68 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__YTT102_YTT102.py.snap @@ -0,0 +1,21 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: SysVersion2Referenced + location: + row: 4 + column: 11 + end_location: + row: 4 + column: 22 + fix: ~ +- kind: SysVersion2Referenced + location: + row: 5 + column: 11 + end_location: + row: 5 + column: 18 + fix: ~ + diff --git a/src/snapshots/ruff__linter__tests__YTT103_YTT103.py.snap b/src/snapshots/ruff__linter__tests__YTT103_YTT103.py.snap new file mode 100644 index 0000000000..c271407d8e --- /dev/null +++ b/src/snapshots/ruff__linter__tests__YTT103_YTT103.py.snap @@ -0,0 +1,45 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: SysVersionCmpStr3 + location: + row: 4 + column: 0 + end_location: + row: 4 + column: 7 + fix: ~ +- kind: SysVersionCmpStr3 + location: + row: 5 + column: 0 + end_location: + row: 5 + column: 11 + fix: ~ +- kind: SysVersionCmpStr3 + location: + row: 6 + column: 0 + end_location: + row: 6 + column: 11 + fix: ~ +- kind: SysVersionCmpStr3 + location: + row: 7 + column: 0 + end_location: + row: 7 + column: 11 + fix: ~ +- kind: SysVersionCmpStr3 + location: + row: 8 + column: 0 + end_location: + row: 8 + column: 11 + fix: ~ + diff --git a/src/snapshots/ruff__linter__tests__YTT201_YTT201.py.snap b/src/snapshots/ruff__linter__tests__YTT201_YTT201.py.snap new file mode 100644 index 0000000000..0b3c7ca17b --- /dev/null +++ b/src/snapshots/ruff__linter__tests__YTT201_YTT201.py.snap @@ -0,0 +1,37 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: SysVersionInfo0Eq3Referenced + location: + row: 7 + column: 6 + end_location: + row: 7 + column: 25 + fix: ~ +- kind: SysVersionInfo0Eq3Referenced + location: + row: 8 + column: 6 + end_location: + row: 8 + column: 21 + fix: ~ +- kind: SysVersionInfo0Eq3Referenced + location: + row: 9 + column: 6 + end_location: + row: 9 + column: 25 + fix: ~ +- kind: SysVersionInfo0Eq3Referenced + location: + row: 10 + column: 6 + end_location: + row: 10 + column: 21 + fix: ~ + diff --git a/src/snapshots/ruff__linter__tests__YTT202_YTT202.py.snap b/src/snapshots/ruff__linter__tests__YTT202_YTT202.py.snap new file mode 100644 index 0000000000..62a89d1196 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__YTT202_YTT202.py.snap @@ -0,0 +1,21 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: SixPY3Referenced + location: + row: 4 + column: 3 + end_location: + row: 4 + column: 10 + fix: ~ +- kind: SixPY3Referenced + location: + row: 6 + column: 3 + end_location: + row: 6 + column: 6 + fix: ~ + diff --git a/src/snapshots/ruff__linter__tests__YTT203_YTT203.py.snap b/src/snapshots/ruff__linter__tests__YTT203_YTT203.py.snap new file mode 100644 index 0000000000..7be13d5842 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__YTT203_YTT203.py.snap @@ -0,0 +1,21 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: SysVersionInfo1CmpInt + location: + row: 4 + column: 0 + end_location: + row: 4 + column: 19 + fix: ~ +- kind: SysVersionInfo1CmpInt + location: + row: 5 + column: 0 + end_location: + row: 5 + column: 15 + fix: ~ + diff --git a/src/snapshots/ruff__linter__tests__YTT204_YTT204.py.snap b/src/snapshots/ruff__linter__tests__YTT204_YTT204.py.snap new file mode 100644 index 0000000000..22bede3d69 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__YTT204_YTT204.py.snap @@ -0,0 +1,21 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: SysVersionInfoMinorCmpInt + location: + row: 4 + column: 0 + end_location: + row: 4 + column: 22 + fix: ~ +- kind: SysVersionInfoMinorCmpInt + location: + row: 5 + column: 0 + end_location: + row: 5 + column: 18 + fix: ~ + diff --git a/src/snapshots/ruff__linter__tests__YTT301_YTT301.py.snap b/src/snapshots/ruff__linter__tests__YTT301_YTT301.py.snap new file mode 100644 index 0000000000..1784fd4089 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__YTT301_YTT301.py.snap @@ -0,0 +1,21 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: SysVersion0Referenced + location: + row: 4 + column: 11 + end_location: + row: 4 + column: 22 + fix: ~ +- kind: SysVersion0Referenced + location: + row: 5 + column: 11 + end_location: + row: 5 + column: 18 + fix: ~ + diff --git a/src/snapshots/ruff__linter__tests__YTT302_YTT302.py.snap b/src/snapshots/ruff__linter__tests__YTT302_YTT302.py.snap new file mode 100644 index 0000000000..c45c63d270 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__YTT302_YTT302.py.snap @@ -0,0 +1,45 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: SysVersionCmpStr10 + location: + row: 4 + column: 0 + end_location: + row: 4 + column: 7 + fix: ~ +- kind: SysVersionCmpStr10 + location: + row: 5 + column: 0 + end_location: + row: 5 + column: 11 + fix: ~ +- kind: SysVersionCmpStr10 + location: + row: 6 + column: 0 + end_location: + row: 6 + column: 11 + fix: ~ +- kind: SysVersionCmpStr10 + location: + row: 7 + column: 0 + end_location: + row: 7 + column: 11 + fix: ~ +- kind: SysVersionCmpStr10 + location: + row: 8 + column: 0 + end_location: + row: 8 + column: 11 + fix: ~ + diff --git a/src/snapshots/ruff__linter__tests__YTT303_YTT303.py.snap b/src/snapshots/ruff__linter__tests__YTT303_YTT303.py.snap new file mode 100644 index 0000000000..a2aaa9edb7 --- /dev/null +++ b/src/snapshots/ruff__linter__tests__YTT303_YTT303.py.snap @@ -0,0 +1,21 @@ +--- +source: src/linter.rs +expression: checks +--- +- kind: SysVersionSlice1Referenced + location: + row: 4 + column: 6 + end_location: + row: 4 + column: 17 + fix: ~ +- kind: SysVersionSlice1Referenced + location: + row: 5 + column: 6 + end_location: + row: 5 + column: 13 + fix: ~ +