User Guide

Quick Start

Setup

Install Fixit from PyPI:

$ pip install --pre "fixit >1"

By default, Fixit enables all of the lint rules that ship with Fixit, all of which are part of the fixit.rules package.

If you want to customize the list of enabled rules, either to add new rules or disable others, see the Configuration Guide for details and options available.

Usage

See lints and suggested changes for a set of source files:

$ fixit lint <path>

Apply suggested changes on those same files automatically:

$ fixit fix <path>

If given directories, Fixit will automatically recurse them, finding any files with the .py extension, while obeying your repo’s global .gitignore.

See the Command Reference for more details.

Example

Given the following code:

# custom_object.py

class Foo(object):
    def bar(self, value: str) -> str:
        return "value is {}".format(value)

When running Fixit, we see two separate lint errors:

$ fixit lint custom_object.py
custom_object.py@4:15 UseFstringRule: Do not use printf style formatting or .format(). Use f-string instead to be more readable and efficient. See https://www.python.org/dev/peps/pep-0498/
custom_object.py@2:0 NoInheritFromObjectRule: Inheriting from object is a no-op.  'class Foo:' is just fine =)

Custom Rules

Fixit makes it easy to write and enable new lint rules, directly in your existing codebase alongside the code they will be linting.

Lint rules in Fixit are built on top of LibCST using a LintRule to combine visitors and tests together in a single unit. A (very) simple rule looks like this:

# teambread/rules/hollywood.py

from fixit import LintRule, InvalidTestCase, ValidTestCase
import libcst

class HollywoodNameRule(LintRule):
    # clean code samples
    VALID = [
        ValidTestCase('name = "Susan"'),
    ]
    # code that triggers this rule
    INVALID = [
        InvalidTestCase('name = "Paul"'),
    ]

    def visit_SimpleString(self, node: libcst.SimpleString) -> None:
        if name.value in ('"Paul"', "'Paul'"):
            self.report(node, "It's underproved!")

Rules can suggest auto-fixes for the user by including a replacement CST node when reporting an error:

def visit_SimpleString(self, node: libcst.SimpleString) -> None:
    if name.value in ('"Paul"', "'Paul'"):
        new_node = libcst.SimpleString('"Mary"')
        self.report(node, "It's underproved!", replacement=new_node)

The best lint rules will provide a clear error message, a suggested replacement, and multiple valid and invalid tests cases that exercise as many edge cases for the lint rule as possible.

Once written, the new lint rule can be enabled by adding it to the list of enabled lint rules in the project’s Configuration file:

# teambread/pyproject.toml

[tool.fixit]
enable = [
    ".rules.hollywood",  # enable just the rules in hollywood.py
    ".rules",  # enable rules from all files in the rules/ directory
]

Note

The leading . (period) is required when using in-repo, or “local”, lint rules, with a module path relative to the directory containing the config file. This allows Fixit to locate and import the lint rule without needing to install a plugin in the user’s environment.

However, be aware that if your custom lint rule needs to import other libraries from the repo, those libraries must be imported using relative imports, and must be contained within the same directory tree as the configuration file.

Once enabled, Fixit can run that new lint rule against the codebase:

# teambread/sourdough/baker.py

def main():
    name = "Paul"
    print(f"hello {name}")
$ fixit lint --diff sourdough/baker.py
sourdough/baker.py@7:11 HollywoodNameRule: It's underproved! (has autofix)
--- a/baker.py
+++ b/baker.py
@@ -6,3 +6,3 @@
def main():
-    name = "Paul"
+    name = "Mary"
    print(f"hello {name}")
🛠️  1 file checked, 1 file with errors, 1 auto-fix available 🛠️
[1]

Note that the lint command only shows lint errors (and suggested changes). The fix command will apply these suggested changes to the codebase:

$ fixit fix --automatic sourdough/baker.py
sourdough/baker.py@7:11 HollywoodNameRule: It's underproved! (has autofix)
🛠️  1 file checked, 1 file with errors, 1 auto-fix available, 1 fix applied 🛠️

By default, the fix command will interactively prompt the user for each suggested change available, which the user can then accept or decline.

Now that the suggested changes have been applied, the codebase is clean:

$ fixit lint sourdough/baker.py
🧼 1 file clean 🧼

Commands

lint

Lint one or more paths, and print a list of lint errors.

$ fixit lint [--diff] [PATH ...]
--diff / -d

Show suggested fixes, in unified diff format, when available.

fix

Lint one or more paths, and apply suggested fixes.

$ fixit fix [--interactive | --automatic [--diff]] [PATH ...]
--interactive / -i

Interactively prompt the user to apply or decline suggested fixes for each auto-fix available. default

--automatic / -a

Automatically apply suggested fixes for all lint errors when available.

--diff / -d

Show applied fixes in unified diff format when applied automatically.

test

Test one or more lint rules using their VALID and INVALID test cases.

Expects qualified lint rule packages or names, with the same form as when configuring enable and disable.

$ fixit test [RULES ...]

Example:

$ fixit test .examples.teambread.rules
test_INVALID_0 (fixit.testing.HollywoodNameRule) ... ok
test_INVALID_1 (fixit.testing.HollywoodNameRule) ... ok
test_VALID_0 (fixit.testing.HollywoodNameRule) ... ok
test_VALID_1 (fixit.testing.HollywoodNameRule) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.024s

OK

debug

Debug options for validating Fixit configuration.

$ fixit debug [PATH ...]

Configuration

Fixit uses TOML format for configuration, and supports hierarchical, cascading configuration. Fixit will read values from both the standardized pyproject.toml file as well as a separate .fixit.toml or fixit.toml file, with values from the latter taking precendence over the former, and values from files “nearer” to those being linted taking precedence over values from files “further” away.

When determining the configuration to use for a given path, Fixit will continue looking upward in the filesystem until it reaches either the root of the filesystem, or a configuration file is found with root set to True. Fixit will then read configuration values from each file, from further to nearest, and merge or override values as appropriate.

This behavior enables a monorepo to provide a baseline configuration, while individual projects can choose to either add to that baseline, or define their own root of configuration to ignore any other baseline configs. This also allows the inclusion of external or third-party projects to maintain consistent linting results, regardless of whether they are being linted externally or within the containing monorepo.

[tool.fixit]

The main configuration table.

root: bool = False

Marks this file as a root of the configuration hierarchy.

If set to True, Fixit will not visit any files further up the hierarchy.

enable: list[str] = []

List of modules or individual rules to enable when linting files covered by this configuration.

Rules bundled with Fixit, or available in the environment’s site-packages, can be referenced as a group by their fully-qualified package name, or individually by adding a colon and the rule name:

enable = [
    "fixit.rules",  # all lint rules in this package (non-recursive)
    "fixit.rules:UseFstringRule",  # single lint rule by name
]

Local rules, available only in the repo being linted, can be referenced by their locally-qualified package names, as if they were being imported from a Python module relative to the configuration file specifying the rule:

# teambread/fixit.toml
enable = [
    ".rules",  # all rules in teambread/rules/ (non-recursive)
    ".rules.hollywood",  # all rules in teambread/rules/hollywood.py
    ".rules:HollywoodNameRule",  # single lint rule by name
]

Overrides disabled rules from any configuration further up the hierarchy.

Fixit will enable the built-in fixit.rules lint rules by default.

disable: list[str] = []

List of modules or individual rules to disable when linting files covered by this configuration.

Overrides enabled rules from this file, as well any configuration files further up the hierarchy.

See enable for details on referencing lint rules.

[tool.fixit.options]

The options table allows setting options for individual lint rules, by mapping the fully-qualified rule name to a dictionary of key/value pairs:

[tool.fixit.options]
"fixit.rules:ExampleRule" = {greeting = "hello world"}

Alternatively, for rules with a large number of options, the rule name can be included in the table name for easier usage. Note that the quotes in the table name are required for correctly specifying options:

[tool.fixit.options."fixit.rules:ExampleRule"]
greeting = "hello world"
answer = 42

[[tool.fixit.overrides]]

Overrides provide a mechanism for hierarchical configuration within a single configuration file. They are defined as an array of tables, with each table defining the subpath it applies to, along with any values from the tables above:

[[tool.fixit.overrides]]
path = "foo/bar"
disable = ["fixit.rules:ExampleRule"]

[[tool.fixit.overrides.options]]
# applies to the above override path only
"fixit.rules:Story" = {closing = "goodnight moon"}

[[tool.fixit.overrides]]
path = "fizz/buzz"
enable = ["plugin:SomethingNeat"]

Integrations

pre-commit

Fixit can be included as a hook for pre-commit.

Once you install it, you can add Fixit’s pre-commit hook to the .pre-commit-config.yaml file in your repository.

  • To run lint rules on commit, add:

repos:
  - repo: https://github.com/Instagram/Fixit
    rev: 0.0.0  # replace with the Fixit version to use
    hooks:
      - id: fixit-lint
  • To run lint rules and apply autofixes, add:

repos:
  - repo: https://github.com/Instagram/Fixit
    rev: 0.0.0  # replace with the Fixit version to use
    hooks:
      - id: fixit-fix

To read more about how you can customize your pre-commit configuration, see the pre-commit docs.

Built-in Rules

These rules are all part of the fixit.rules package, and are enabled by default unless explicitly listed in the disable configuration option.

class fixit.rules.AvoidOrInExceptRule

Discourages use of or in except clauses. If an except clause needs to catch multiple exceptions, they must be expressed as a parenthesized tuple, for example: except (ValueError, TypeError) (https://docs.python.org/3/tutorial/errors.html#handling-exceptions)

When or is used, only the first operand exception type of the conditional statement will be caught. For example:

In [1]: class Exc1(Exception):
    ...:     pass
    ...:

In [2]: class Exc2(Exception):
    ...:     pass
    ...:

In [3]: try:
    ...:     raise Exception()
    ...: except Exc1 or Exc2:
    ...:     print("caught!")
    ...:
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
<ipython-input-3-3340d66a006c> in <module>
    1 try:
----> 2     raise Exception()
    3 except Exc1 or Exc2:
    4     print("caught!")
    5

Exception:

In [4]: try:
    ...:     raise Exc1()
    ...: except Exc1 or Exc2:
    ...:     print("caught!")
    ...:
    caught!

In [5]: try:
    ...:     raise Exc2()
    ...: except Exc1 or Exc2:
    ...:     print("caught!")
    ...:
---------------------------------------------------------------------------
Exc2                                      Traceback (most recent call last)
<ipython-input-5-5d29c1589cc0> in <module>
    1 try:
----> 2     raise Exc2()
    3 except Exc1 or Exc2:
    4     print("caught!")
    5

Exc2:
MESSAGE

Avoid using ‘or’ in an except block. For example:’except ValueError or TypeError’ only catches ‘ValueError’. Instead, use parentheses, ‘except (ValueError, TypeError)’

VALID
try:
    print()
except (ValueError, TypeError) as err:
    pass
INVALID
try:
    print()
except ValueError or TypeError:
    pass
class fixit.rules.CollapseIsinstanceChecksRule

The built-in isinstance function, instead of a single type, can take a tuple of types and check whether given target suits any of them. Rather than chaining multiple isinstance calls with a boolean-or operation, a single isinstance call where the second argument is a tuple of all types can be used.

MESSAGE

Multiple isinstance calls with the same target but different types can be collapsed into a single call with a tuple of types.

AUTOFIX: Yes
VALID
foo() or foo()
foo(x, y) or foo(x, z)
INVALID
isinstance(x, y) or isinstance(x, z)

# suggested fix
isinstance(x, (y, z))
isinstance(x, y) or isinstance(x, z) or isinstance(x, q)

# suggested fix
isinstance(x, (y, z, q))
class fixit.rules.ComparePrimitivesByEqualRule

Enforces the use of == and != in comparisons to primitives rather than is and is not. The == operator checks equality (https://docs.python.org/3/reference/datamodel.html#object.__eq__), while is checks identity (https://docs.python.org/3/reference/expressions.html#is).

MESSAGE

Don’t use is or is not to compare primitives, as they compare references. Use == or != instead.

AUTOFIX: Yes
VALID
a == 1
a == '1'
INVALID
a is 1

# suggested fix
a == 1
a is '1'

# suggested fix
a == '1'
class fixit.rules.CompareSingletonPrimitivesByIsRule

Enforces the use of is and is not in comparisons to singleton primitives (None, True, False) rather than == and !=. The == operator checks equality, when in this scenario, we want to check identity. See Flake8 rules E711 (https://www.flake8rules.com/rules/E711.html) and E712 (https://www.flake8rules.com/rules/E712.html).

MESSAGE

Comparisons to singleton primitives should not be done with == or !=, as they check equality rather than identiy. Use is or is not instead.

AUTOFIX: Yes
VALID
if x: pass
if not x: pass
INVALID
x != True

# suggested fix
x is not True
x != False

# suggested fix
x is not False
class fixit.rules.DeprecatedUnittestAssertsRule

Discourages the use of various deprecated unittest.TestCase functions

See https://docs.python.org/3/library/unittest.html#deprecated-aliases

MESSAGE

{deprecated} is deprecated, use {replacement} instead

AUTOFIX: Yes
VALID
self.assertEqual(a, b)
self.assertNotEqual(a, b)
INVALID
self.assertEquals(a, b)

# suggested fix
self.assertEqual(a, b)
self.assertNotEquals(a, b)

# suggested fix
self.assertNotEqual(a, b)
class fixit.rules.ExplicitFrozenDataclassRule

Encourages the use of frozen dataclass objects by telling users to specify the kwarg.

Without this lint rule, most users of dataclass won’t know to use the kwarg, and may unintentionally end up with mutable objects.

MESSAGE

When using dataclasses, explicitly specify a frozen keyword argument. Example: @dataclass(frozen=True) or @dataclass(frozen=False). Docs: https://docs.python.org/3/library/dataclasses.html

AUTOFIX: Yes
VALID
@some_other_decorator
class Cls: pass
from dataclasses import dataclass
@dataclass(frozen=False)
class Cls: pass
INVALID
from dataclasses import dataclass
@some_unrelated_decorator
@dataclass  # not called as a function
@another_unrelated_decorator
class Cls: pass

# suggested fix
from dataclasses import dataclass
@some_unrelated_decorator
@dataclass(frozen=True)  # not called as a function
@another_unrelated_decorator
class Cls: pass
from dataclasses import dataclass
@dataclass()  # called as a function, no kwargs
class Cls: pass

# suggested fix
from dataclasses import dataclass
@dataclass(frozen=True)  # called as a function, no kwargs
class Cls: pass
class fixit.rules.NoAssertTrueForComparisonsRule

Finds incorrect use of assertTrue when the intention is to compare two values. These calls are replaced with assertEqual. Comparisons with True, False and None are replaced with one-argument calls to assertTrue, assertFalse and assertIsNone.

MESSAGE

“assertTrue” does not compare its arguments, use “assertEqual” or other appropriate functions.

AUTOFIX: Yes
VALID
self.assertTrue(a == b)
self.assertTrue(data.is_valid(), "is_valid() method")
INVALID
self.assertTrue(a, 3)

# suggested fix
self.assertEqual(a, 3)
self.assertTrue(hash(s[:4]), 0x1234)

# suggested fix
self.assertEqual(hash(s[:4]), 0x1234)
class fixit.rules.NoInheritFromObjectRule

In Python 3, a class is inherited from object by default. Explicitly inheriting from object is redundant, so removing it keeps the code simpler.

MESSAGE

Inheriting from object is a no-op. ‘class Foo:’ is just fine =)

AUTOFIX: Yes
VALID
class A(something):    pass
class A:
    pass
INVALID
class B(object):
    pass

# suggested fix
class B:
    pass
class B(object, A):
    pass

# suggested fix
class B(A):
    pass
class fixit.rules.NoNamedTupleRule

Enforce the use of dataclasses.dataclass decorator instead of NamedTuple for cleaner customization and inheritance. It supports default value, combining fields for inheritance, and omitting optional fields at instantiation. See PEP 557. @dataclass is faster at reading an object’s nested properties and executing its methods. (benchmark)

MESSAGE

Instead of NamedTuple, consider using the @dataclass decorator from dataclasses instead for simplicity, efficiency and consistency.

AUTOFIX: Yes
VALID
@dataclass(frozen=True)
class Foo:
    pass
@dataclass(frozen=False)
class Foo:
    pass
INVALID
from typing import NamedTuple

class Foo(NamedTuple):
    pass

# suggested fix
from typing import NamedTuple

@dataclass(frozen=True)
class Foo:
    pass
from typing import NamedTuple as NT

class Foo(NT):
    pass

# suggested fix
from typing import NamedTuple as NT

@dataclass(frozen=True)
class Foo:
    pass
class fixit.rules.NoRedundantArgumentsSuperRule

Remove redundant arguments when using super for readability.

MESSAGE

Do not use arguments when calling super for the parent class. See https://www.python.org/dev/peps/pep-3135/

AUTOFIX: Yes
VALID
class Foo(Bar):
    def foo(self, bar):
        super().foo(bar)
class Foo(Bar):
    def foo(self, bar):
        super(Bar, self).foo(bar)
INVALID
class Foo(Bar):
    def foo(self, bar):
        super(Foo, self).foo(bar)

# suggested fix
class Foo(Bar):
    def foo(self, bar):
        super().foo(bar)
class Foo(Bar):
    @classmethod
    def foo(cls, bar):
        super(Foo, cls).foo(bar)

# suggested fix
class Foo(Bar):
    @classmethod
    def foo(cls, bar):
        super().foo(bar)
class fixit.rules.NoRedundantFStringRule

Remove redundant f-string without placeholders.

MESSAGE

f-string doesn’t have placeholders, remove redundant f-string.

AUTOFIX: Yes
VALID
good: str = "good"
good: str = f"with_arg{arg}"
INVALID
bad: str = f"bad" + "bad"

# suggested fix
bad: str = "bad" + "bad"
bad: str = f'bad'

# suggested fix
bad: str = 'bad'
class fixit.rules.NoRedundantLambdaRule

A lamba function which has a single objective of passing all it is arguments to another callable can be safely replaced by that callable.

AUTOFIX: Yes
VALID
lambda x: foo(y)
lambda x: foo(x, y)
INVALID
lambda: self.func()

# suggested fix
self.func
lambda x: foo(x)

# suggested fix
foo
class fixit.rules.NoRedundantListComprehensionRule

A derivative of flake8-comprehensions’s C407 rule.

AUTOFIX: Yes
VALID
any(val for val in iterable)
all(val for val in iterable)
INVALID
any([val for val in iterable])

# suggested fix
any(val for val in iterable)
all([val for val in iterable])

# suggested fix
all(val for val in iterable)
class fixit.rules.NoStaticIfConditionRule

Discourages if conditions which evaluate to a static value (e.g. or True, and False, etc).

MESSAGE

Your if condition appears to evaluate to a static value (e.g. or True, and False). Please double check this logic and if it is actually temporary debug code.

VALID
if my_func() or not else_func():
    pass
if function_call(True):
    pass
INVALID
if True:
    do_something()
if crazy_expression or True:
    do_something()
class fixit.rules.NoStringTypeAnnotationRule

Enforce the use of type identifier instead of using string type hints for simplicity and better syntax highlighting. Starting in Python 3.7, from __future__ import annotations can postpone evaluation of type annotations PEP 563 and thus forward references no longer need to use string annotation style.

MESSAGE

String type hints are no longer necessary in Python, use the type identifier directly.

AUTOFIX: Yes
VALID
from a.b import Class

def foo() -> Class:
    return Class()
import typing
from a.b import Class

def foo() -> typing.Type[Class]:
    return Class
INVALID
from __future__ import annotations

from a.b import Class

def foo() -> "Class":
    return Class()

# suggested fix
from __future__ import annotations

from a.b import Class

def foo() -> Class:
    return Class()
from __future__ import annotations

from a.b import Class

async def foo() -> "Class":
    return await Class()

# suggested fix
from __future__ import annotations

from a.b import Class

async def foo() -> Class:
    return await Class()
class fixit.rules.ReplaceUnionWithOptionalRule

Enforces the use of Optional[T] over Union[T, None] and Union[None, T]. See https://docs.python.org/3/library/typing.html#typing.Optional to learn more about Optionals.

MESSAGE

Optional[T] is preferred over Union[T, None] or Union[None, T]. Learn more: https://docs.python.org/3/library/typing.html#typing.Optional

AUTOFIX: Yes
VALID
def func() -> Optional[str]:
    pass
def func() -> Optional[Dict]:
    pass
INVALID
def func() -> Union[str, None]:
    pass
from typing import Optional
def func() -> Union[Dict[str, int], None]:
    pass

# suggested fix
from typing import Optional
def func() -> Optional[Dict[str, int]]:
    pass
class fixit.rules.RewriteToComprehensionRule

A derivative of flake8-comprehensions’s C400-C402 and C403-C404. Comprehensions are more efficient than functions calls. This C400-C402 suggest to use dict/set/list comprehensions rather than respective function calls whenever possible. C403-C404 suggest to remove unnecessary list comprehension in a set/dict call, and replace it with set/dict comprehension.

AUTOFIX: Yes
VALID
[val for val in iterable]
{val for val in iterable}
INVALID
list(val for val in iterable)

# suggested fix
[val for val in iterable]
list(val for row in matrix for val in row)

# suggested fix
[val for row in matrix for val in row]
class fixit.rules.RewriteToLiteralRule

A derivative of flake8-comprehensions’ C405-C406 and C409-C410. It’s unnecessary to use a list or tuple literal within a call to tuple, list, set, or dict since there is literal syntax for these types.

AUTOFIX: Yes
VALID
(1, 2)
()
INVALID
tuple([1, 2])

# suggested fix
(1, 2)
tuple((1, 2))

# suggested fix
(1, 2)
class fixit.rules.SortedAttributesRule

Ever wanted to sort a bunch of class attributes alphabetically? Well now it’s easy! Just add “@sorted-attributes” in the doc string of a class definition and lint will automatically sort all attributes alphabetically.

Feel free to add other methods and such – it should only affect class attributes.

MESSAGE

It appears you are using the @sorted-attributes directive and the class variables are unsorted. See the lint autofix suggestion.

AUTOFIX: Yes
VALID
class MyConstants:
    """
    @sorted-attributes
    """
    A = 'zzz123'
    B = 'aaa234'

class MyUnsortedConstants:
    B = 'aaa234'
    A = 'zzz123'
INVALID
class MyUnsortedConstants:
    """
    @sorted-attributes
    """
    z = "hehehe"
    B = 'aaa234'
    A = 'zzz123'
    cab = "foo bar"
    Daaa = "banana"

    @classmethod
    def get_foo(cls) -> str:
        return "some random thing"

# suggested fix
class MyUnsortedConstants:
    """
    @sorted-attributes
    """
    A = 'zzz123'
    B = 'aaa234'
    Daaa = "banana"
    cab = "foo bar"
    z = "hehehe"

    @classmethod
    def get_foo(cls) -> str:
        return "some random thing"
class fixit.rules.UseAssertInRule

Discourages use of assertTrue(x in y) and assertFalse(x in y) as it is deprecated (https://docs.python.org/3.8/library/unittest.html#deprecated-aliases). Use assertIn(x, y) and assertNotIn(x, y)) instead.

MESSAGE

Use assertIn/assertNotIn instead of assertTrue/assertFalse for inclusion check. See https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertIn)

AUTOFIX: Yes
VALID
self.assertIn(a, b)
self.assertIn(f(), b)
INVALID
self.assertTrue(a in b)

# suggested fix
self.assertIn(a, b)
self.assertTrue(f() in b)

# suggested fix
self.assertIn(f(), b)
class fixit.rules.UseAssertIsNotNoneRule

Discourages use of assertTrue(x is not None) and assertFalse(x is not None) as it is deprecated (https://docs.python.org/3.8/library/unittest.html#deprecated-aliases). Use assertIsNotNone(x) and assertIsNone(x)) instead.

MESSAGE

“assertTrue” and “assertFalse” are deprecated. Use “assertIsNotNone” and “assertIsNone” instead. See https://docs.python.org/3.8/library/unittest.html#deprecated-aliases

AUTOFIX: Yes
VALID
self.assertIsNotNone(x)
self.assertIsNone(x)
INVALID
self.assertTrue(a is not None)

# suggested fix
self.assertIsNotNone(a)
self.assertTrue(not x is None)

# suggested fix
self.assertIsNotNone(x)
class fixit.rules.UseAsyncSleepInAsyncDefRule

Detect if asyncio.sleep is used in an async function

MESSAGE

Use asyncio.sleep in async function

VALID
import time
def func():
    time.sleep(1)
from time import sleep
def func():
    sleep(1)
INVALID
import time
async def func():
    time.sleep(1)
from time import sleep
async def func():
    sleep(1)
class fixit.rules.UseClassNameAsCodeRule

Meta lint rule which checks that codes of lint rules are migrated to new format in lint rule class definitions.

MESSAGE

IG-series codes are deprecated. Use class name as code instead.

AUTOFIX: Yes
VALID
MESSAGE = "This is a message"
from fixit.common.base import CstLintRule
class FakeRule(CstLintRule):
    MESSAGE = "This is a message"
INVALID
MESSAGE = "IG90000 Message"

# suggested fix
MESSAGE = "Message"
from fixit.common.base import CstLintRule
class FakeRule(CstLintRule):
    INVALID = [
        Invalid(
            code="",
            kind="IG000"
        )
    ]

# suggested fix
from fixit.common.base import CstLintRule
class FakeRule(CstLintRule):
    INVALID = [
        Invalid(
            code="",
            )
    ]
class fixit.rules.UseClsInClassmethodRule

Enforces using cls as the first argument in a @classmethod.

MESSAGE

When using @classmethod, the first argument must be cls.

AUTOFIX: Yes
VALID
class foo:
    # classmethod with cls first arg.
    @classmethod
    def cm(cls, a, b, c):
        pass
class foo:
    # non-classmethod with non-cls first arg.
    def nm(self, a, b, c):
        pass
INVALID
class foo:
    # No args at all.
    @classmethod
    def cm():
        pass

# suggested fix
class foo:
    # No args at all.
    @classmethod
    def cm(cls):
        pass
class foo:
    # Single arg + reference.
    @classmethod
    def cm(a):
        return a

# suggested fix
class foo:
    # Single arg + reference.
    @classmethod
    def cm(cls):
        return cls
class fixit.rules.UseFstringRule

Encourages the use of f-string instead of %-formatting or .format() for high code quality and efficiency.

Following two cases not covered:

  1. arguments length greater than 30 characters: for better readibility reason

    For example:

    1: this is the answer: %d” % (a_long_function_call() + b_another_long_function_call()) 2: f”this is the answer: {a_long_function_call() + b_another_long_function_call()}” 3: result = a_long_function_call() + b_another_long_function_call() f”this is the answer: {result}”

    Line 1 is more readable than line 2. Ideally, we’d like developers to manually fix this case to line 3

  2. only %s placeholders are linted against for now. We leave it as future work to support other placeholders.

    For example, %d raises TypeError for non-numeric objects, whereas f”{x:d}” raises ValueError. This discrepancy in the type of exception raised could potentially break the logic in the code where the exception is handled

MESSAGE

Do not use printf style formatting or .format(). Use f-string instead to be more readable and efficient. See https://www.python.org/dev/peps/pep-0498/

AUTOFIX: Yes
VALID
somebody='you'; f"Hey, {somebody}."
"hey"
INVALID
"Hey, {somebody}.".format(somebody="you")
"%s" % "hi"

# suggested fix
f"{'hi'}"
class fixit.rules.UseLintFixmeCommentRule

To silence a lint warning, use lint-fixme (when plans to fix the issue later) or lint-ignore (when the lint warning is not valid) comments. The comment requires to be in a standalone comment line and follows the format lint-fixme: RULE_NAMES EXTRA_COMMENTS. It suppresses the lint warning with the RULE_NAMES in the next line. RULE_NAMES can be one or more lint rule names separated by comma. noqa is deprecated and not supported because explicitly providing lint rule names to be suppressed in lint-fixme comment is preferred over implicit noqa comments. Implicit noqa suppression comments sometimes accidentally silence warnings unexpectedly.

MESSAGE

noqa is deprecated. Use lint-fixme or lint-ignore instead.

VALID
# lint-fixme: UseFstringRule
"%s" % "hi"
# lint-ignore: UsePlusForStringConcatRule
'ab' 'cd'
INVALID
fn() # noqa
(
 1,
 2,  # noqa
)
class fixit.rules.UseTypesFromTypingRule

Enforces the use of types from the typing module in type annotations in place of builtins.{builtin_type} since the type system doesn’t recognize the latter as a valid type.

AUTOFIX: Yes
VALID
def fuction(list: List[str]) -> None:
    pass
def function() -> None:
    thing: Dict[str, str] = {}
INVALID
from typing import List
def whatever(list: list[str]) -> None:
    pass

# suggested fix
from typing import List
def whatever(list: List[str]) -> None:
    pass
def function(list: list[str]) -> None:
    pass