Fixit: linting framework with auto-fixes

Documentation PyPI Changelog Project Roadmap MIT License

Fixit provides a highly configurable linting framework with support for auto-fixes, custom “local” lint rules, and hierarchical configuration, built on LibCST.

Fixit makes it quick and easy to write new lint rules and offer suggested changes for any errors found, which can then be accepted automatically, or presented to the user for consideration.

Fixit 2.0 has been rebuilt for better configuration and support for custom lint rules. If you are using Fixit 0.1.4 or older, take a look at the legacy documentation or the stable branch.

For more details, see the user guide.

Quick Start


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.


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.


Given the following code:


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 UseFstringRule: Do not use printf style formatting or .format(). Use f-string instead to be more readable and efficient. See 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/

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'"):
  , "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"'), "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

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


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/

def main():
    name = "Paul"
    print(f"hello {name}")
$ fixit lint --diff sourdough/
sourdough/ HollywoodNameRule: It's underproved! (has autofix)
--- a/
+++ b/
@@ -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 🛠️

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/
sourdough/ 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/
🧼 1 file clean 🧼


Fixit is MIT licensed, as found in the LICENSE file.