Fixit: linting framework with auto-fixes¶
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¶
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 🧼
License¶
Fixit is MIT licensed, as found in the LICENSE file.