Repository: MartinThoma/flake8-simplify
Pull request #154 (2022-09-26 18:20:16)

PEP 621: Migrate config to pyproject.toml
Migrate to `pyproject.toml` using [`ini2toml`](https://pypi.org/project/ini2toml) to do the file conversion and running [`validate-pyproject`](https://github.com/abravalheri/validate-pyproject) to validate results.
* Currently `flake8` is not yet compatible with `pyproject.toml` so move its config to `.flake8`



Patch:
diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..85cd360
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,5 @@
+[flake8]
+ignore = E203, W503, PT007
+exclude = build/*
+per-file-ignores =
+    tests/*: ASS001
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 81ec8fb..eb59794 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -18,9 +18,9 @@ jobs:
         python-version: [3.8]
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v4
       with:
         python-version: ${{ matrix.python-version }}
     - name: Install dependencies
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 48f6d4c..b888c9c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,7 +13,7 @@ Released on 29.03.2022
 Removed rules due to false-positives:
 
 * SIM903: Positional-only parameters cannot be identified in the AST
-* SIM909: Class attribute assignments are not reflexive assignements
+* SIM909: Class attribute assignments are not reflexive assignments
 
 ### 0.19.1
 Released on 29.03.2022
diff --git a/README.md b/README.md
index 4aeac9c..c1ef70d 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ Install with `pip`:
 pip install flake8-simplify
 ```
 
-Python 3.6 to 3.8 are supported.
+Python 3.6 to 3.10 are supported.
 
 
 ## Usage
@@ -105,6 +105,7 @@ Current experimental rules:
 * [`SIM907`](https://github.com/MartinThoma/flake8-simplify/issues/64): Use Optional[Type] instead of Union[Type, None] ([example](#SIM907))
 * [`SIM908`](https://github.com/MartinThoma/flake8-simplify/issues/50): Use dict.get(key) ([example](#SIM908))
 * [`SIM909`](https://github.com/MartinThoma/flake8-simplify/issues/114): Avoid reflexive assignments ([example](#SIM909))
+* [`SIM910`](https://github.com/MartinThoma/flake8-simplify/issues/171): Avoid to use `dict.get(key, None)` ([example](#SIM910))
 
 ## Disabling Rules
 
@@ -614,3 +615,13 @@ foo = foo = 42
 # Good
 foo = 42
 ```
+
+### SIM910
+
+```python
+# Bad
+dict.get(key, None)
+
+# Good
+dict.get(key)
+```
diff --git a/flake8_simplify/__init__.py b/flake8_simplify/__init__.py
index 2207de6..3fd1f1e 100644
--- a/flake8_simplify/__init__.py
+++ b/flake8_simplify/__init__.py
@@ -19,6 +19,7 @@ from flake8_simplify.rules.ast_call import (
     get_sim901,
     get_sim905,
     get_sim906,
+    get_sim910,
 )
 from flake8_simplify.rules.ast_classdef import get_sim120
 from flake8_simplify.rules.ast_compare import get_sim118, get_sim300
@@ -74,6 +75,7 @@ class Visitor(ast.NodeVisitor):
         self.errors += get_sim901(node)
         self.errors += get_sim905(node)
         self.errors += get_sim906(node)
+        self.errors += get_sim910(Call(node))
         self.generic_visit(node)
 
     def visit_With(self, node: ast.With) -> Any:
diff --git a/flake8_simplify/rules/ast_call.py b/flake8_simplify/rules/ast_call.py
index b78c460..60947f6 100644
--- a/flake8_simplify/rules/ast_call.py
+++ b/flake8_simplify/rules/ast_call.py
@@ -5,6 +5,7 @@ import logging
 from typing import List, Tuple
 
 # First party
+from flake8_simplify.constants import BOOL_CONST_TYPES
 from flake8_simplify.utils import Call, to_source
 
 logger = logging.getLogger(__name__)
@@ -164,7 +165,7 @@ def get_sim906(node: ast.Call) -> List[Tuple[int, int, str]]:
                 names.append(f"'{arg.s}'")
             else:
                 logger.debug(
-                    f"Unexpexted os.path.join arg: {arg} -- {to_source(arg)}"
+                    f"Unexpected os.path.join arg: {arg} -- {to_source(arg)}"
                 )
         return names
 
@@ -180,3 +181,55 @@ def get_sim906(node: ast.Call) -> List[Tuple[int, int, str]]:
         )
     )
     return errors
+
+
+def get_sim910(node: Call) -> List[Tuple[int, int, str]]:
+    """
+    Get a list of all usages of "dict.get(key, None)"
+
+    Example AST
+    -----------
+        Expr(
+            value=Call(
+                func=Attribute(
+                    value=Name(id='a_dict', ctx=Load()),
+                    attr='get',
+                    ctx=Load()
+                ),
+                args=[
+                    Name(id='key', ctx=Load()),
+                    Constant(value=None)
+                ],
+                keywords=[]
+            ),
+        ),
+    """
+    RULE = "SIM910 Use '{expected}' instead of '{actual}'"
+    errors: List[Tuple[int, int, str]] = []
+    if not (
+        isinstance(node.func, ast.Attribute)
+        and node.func.attr == "get"
+        and isinstance(node.func.ctx, ast.Load)
+    ):
+        return errors
+
+    # check the argument value
+    if not (
+        len(node.args) == 2
+        and isinstance(node.args[1], BOOL_CONST_TYPES)
+        and node.args[1].value is None
+    ):
+        return errors
+
+    actual = to_source(node)
+    func = to_source(node.func)
+    key = to_source(node.args[0])
+    expected = f"{func}({key})"
+    errors.append(
+        (
+            node.lineno,
+            node.col_offset,
+            RULE.format(actual=actual, expected=expected),
+        )
+    )
+    return errors
diff --git a/flake8_simplify/rules/ast_for.py b/flake8_simplify/rules/ast_for.py
index e19f64c..a0a4cdf 100644
--- a/flake8_simplify/rules/ast_for.py
+++ b/flake8_simplify/rules/ast_for.py
@@ -149,7 +149,7 @@ def get_sim113(node: For) -> List[Tuple[int, int, str]]:
                 Expr(
                     value=Constant(value=Ellipsis, kind=None),
                 ),
-                AugAssign( -- agument and assign, aka "+= 1"
+                AugAssign( -- argument and assign, aka "+= 1"
                     target=Name(id='idx', ctx=Store()),
                     op=Add(),
                     value=Constant(value=1, kind=None),
diff --git a/flake8_simplify/rules/ast_if.py b/flake8_simplify/rules/ast_if.py
index b45c98e..236d492 100644
--- a/flake8_simplify/rules/ast_if.py
+++ b/flake8_simplify/rules/ast_if.py
@@ -28,7 +28,7 @@ def get_sim102(node: ast.If) -> List[Tuple[int, int, str]]:
         and node.body[0].orelse == []
     )
     # ## Pattern 2
-    # if a: < irrelvant for here
+    # if a: < irrelevant for here
     #     pass
     # elif b:  <--- this is treated like a nested block
     #     if c: <---
diff --git a/pyproject.toml b/pyproject.toml
index 7795d0b..adde887 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,3 +1,89 @@
+[build-system]
+build-backend = "setuptools.build_meta"
+requires = [
+  "setuptools>=61.2",
+]
+
+[project]
+name = "flake8_simplify"
+version = "0.19.3"
+description = "flake8 plugin which checks for code that can be simplified"
+keywords = [
+  "flake8",
+]
+license = {text = "MIT"}
+maintainers = [{name = "Martin Thoma", email = "info@martin-thoma.de"}]
+authors = [{name = "Martin Thoma", email = "info@martin-thoma.de"}]
+requires-python = ">=3.6.1"
+dependencies = [
+  "astor>=0.1",
+  "flake8>=3.7",
+  'importlib-metadata>=0.9; python_version < "3.8"',
+]
+classifiers = [
+    "Development Status :: 4 - Beta",
+    "Environment :: Console",
+    "Intended Audience :: Developers",
+    "License :: OSI Approved :: MIT License",
+    "Natural Language :: English",
+    "Operating System :: OS Independent",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3 :: Only",
+    "Programming Language :: Python :: 3.7",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Topic :: Software Development",
+    "Framework :: Flake8",
+]
+
+[project.readme]
+file = "README.md"
+content-type = "text/markdown"
+
+[project.entry-points]
+"flake8.extension" = {SIM = "flake8_simplify:Plugin"}
+
+[project.urls]
+Homepage = "https://github.com/MartinThoma/flake8-simplify"
+
+[tool.setuptools]
+license-files = ["LICENSE"]
+include-package-data = false
+
+[tool.setuptools.packages.find]
+exclude = ["tests"]
+namespaces = false
+
 [tool.black]
 line-length = 79
 target-version = ['py37']
+
+[tool.mutmut]
+backup = "False"
+runner = "./mutmut-test.sh"
+tests_dir = "tests/"
+paths_to_mutate = "flake8_simplify.py"
+
+[tool.mypy]
+exclude = "build/lib/flake8_simplify.py"
+ignore_missing_imports = true
+strict = true
+check_untyped_defs = true
+disallow_any_generics = true
+disallow_incomplete_defs = true
+disallow_untyped_defs = true
+no_implicit_optional = true
+warn_unused_ignores = false
+show_error_codes = true
+
+[[tool.mypy.overrides]]
+disallow_untyped_defs = false
+module = [
+    "testing.*",
+    "tests.*",
+]
+
+[tool.pytest.ini_options]
+addopts = "--doctest-modules --cov=. --cov-report html:tests/reports/coverage-html --cov-report term-missing --ignore=docs/ --durations=3 --timeout=30"
+doctest_encoding = "utf-8"
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index d88b77a..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,82 +0,0 @@
-[metadata]
-# https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files
-name = flake8_simplify
-
-author = Martin Thoma
-author_email = info@martin-thoma.de
-maintainer = Martin Thoma
-maintainer_email = info@martin-thoma.de
-
-version = 0.19.3
-
-description = flake8 plugin which checks for code that can be simplified
-long_description = file: README.md
-long_description_content_type = text/markdown
-keywords = flake8,
-
-url = https://github.com/MartinThoma/flake8-simplify
-license = MIT
-license_file = LICENSE
-
-# https://pypi.org/pypi?%3Aaction=list_classifiers
-classifiers =
-    Development Status :: 4 - Beta
-    Environment :: Console
-    Intended Audience :: Developers
-    License :: OSI Approved :: MIT License
-    Natural Language :: English
-    Operating System :: OS Independent
-    Programming Language :: Python :: 3
-    Programming Language :: Python :: 3 :: Only
-    Programming Language :: Python :: 3.7
-    Programming Language :: Python :: 3.8
-    Programming Language :: Python :: 3.9
-    Programming Language :: Python :: 3.10
-    Topic :: Software Development
-    Framework :: Flake8
-
-[options]
-python_requires = >=3.6.1
-install_requires =
-    flake8>=3.7
-    importlib-metadata>=0.9;python_version<"3.8"
-    astor>=0.1
-
-[options.entry_points]
-flake8.extension =
-    SIM=flake8_simplify:Plugin
-
-[tool:pytest]
-addopts = --doctest-modules --cov=. --cov-report html:tests/reports/coverage-html --cov-report term-missing --ignore=docs/ --durations=3 --timeout=30
-doctest_encoding = utf-8
-
-
-[mutmut]
-backup = False
-runner = ./mutmut-test.sh
-tests_dir = tests/
-paths_to_mutate=flake8_simplify.py
-
-[mypy]
-exclude = build/lib/flake8_simplify.py
-ignore_missing_imports = true
-strict = true
-check_untyped_defs = true
-disallow_any_generics = true
-disallow_incomplete_defs = true
-disallow_untyped_defs = true
-no_implicit_optional = true
-warn_unused_ignores = false
-show_error_codes = true
-
-[mypy-testing.*]
-disallow_untyped_defs = false
-
-[mypy-tests.*]
-disallow_untyped_defs = false
-
-[flake8]
-ignore = E203, W503, PT007
-exclude = build/*
-per-file-ignores =
-    tests/*: ASS001
diff --git a/setup.py b/setup.py
deleted file mode 100644
index fdf402b..0000000
--- a/setup.py
+++ /dev/null
@@ -1,6 +0,0 @@
-# Third party
-from setuptools import find_packages, setup
-
-setup(
-    packages=find_packages(exclude=("tests",)),
-)


Test patch:
diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml
index e8bf60c..badc228 100644
--- a/.github/workflows/unit-test.yml
+++ b/.github/workflows/unit-test.yml
@@ -15,12 +15,12 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: [3.7, 3.8, 3.9, "3.10"]
+        python-version: [3.7, 3.8, 3.9, "3.10", 3.11]
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v4
       with:
         python-version: ${{ matrix.python-version }}
     - name: Install dependencies
diff --git a/tests/test_900_rules.py b/tests/test_900_rules.py
index 029080b..3f1d17b 100644
--- a/tests/test_900_rules.py
+++ b/tests/test_900_rules.py
@@ -181,3 +181,26 @@ def test_sim909_false_positives(s):
     results = _results(s)
     for result in results:
         assert "SIM909" not in result
+
+
+@pytest.mark.parametrize(
+    ("s", "msg"),
+    (
+        (
+            "d.get(key, None)",
+            "1:0 SIM910 Use 'd.get(key)' instead of 'd.get(key, None)'",
+        ),
+        (
+            "d.get('key', None)",
+            "1:0 SIM910 Use 'd.get(\"key\")' instead of 'd.get('key', None)'",
+        ),
+        (
+            "d.get(key)",
+            None,
+        ),
+    ),
+)
+def test_sim910(s, msg):
+    expected = {msg} if msg is not None else set()
+    results = _results(s)
+    assert results == expected