{"id":742,"date":"2023-08-07T16:00:09","date_gmt":"2023-08-07T16:00:09","guid":{"rendered":"https:\/\/fde.cat\/index.php\/2023\/08\/07\/fixit-2-metas-next-generation-auto-fixing-linter\/"},"modified":"2023-08-07T16:00:09","modified_gmt":"2023-08-07T16:00:09","slug":"fixit-2-metas-next-generation-auto-fixing-linter","status":"publish","type":"post","link":"https:\/\/fde.cat\/index.php\/2023\/08\/07\/fixit-2-metas-next-generation-auto-fixing-linter\/","title":{"rendered":"Fixit 2: Meta\u2019s next-generation auto-fixing linter"},"content":{"rendered":"<p><span>Fixit is dead! Long live Fixit 2 \u2013 the latest version of our open-source auto-fixing linter.<\/span><br \/>\n<span>Fixit 2 allows developers to efficiently build custom lint rules and perform auto-fixes for their codebases.<\/span><br \/>\n<span>Fixit 2 is <\/span><span>available today on <\/span><a href=\"https:\/\/pypi.org\/project\/fixit\/2.0.0a1\/\" target=\"_blank\" rel=\"noopener\"><span>PyPI<\/span><\/a><span>.<\/span><\/p>\n<p><a href=\"https:\/\/engineering.fb.com\/2022\/07\/27\/developer-tools\/programming-languages-endorsed-for-server-side-use-at-meta\/\" target=\"_blank\" rel=\"noopener\"><span>Python is one of the most popular languages in use at Meta.<\/span><\/a><span> Meta\u2019s production engineers (PEs) are specialized software engineers (SWEs) who focus on reliability, efficiency, and scalability. They work on various projects, including debugging production services, rewriting inefficient libraries, orchestrating project deployments at scale, or capacity planning and scheduling. And Python is often one of the first tools that PEs reach for, as it offers rapid development, easy to read syntax, and a massive array of open source libraries.<\/span><\/p>\n<p><span>Meta\u2019s Python Language Foundation team \u2014 a hybrid team of both PEs and traditional SWEs \u2014 helps own and maintain the infrastructure and tooling for Python at Meta. The team supports engineers, data scientists, researchers, and anyone else at Meta using Python to get their work done.<\/span><\/p>\n<p><span>One of the ways we accomplish this is building tools that enable Python developers to write better, and more reliable code more efficiently. This includes tools like<\/span> <a href=\"https:\/\/ufmt.omnilib.dev\/\" target=\"_blank\" rel=\"noopener\"><span>automatic formatting<\/span><\/a><span> and<\/span> <a href=\"https:\/\/usort.rtfd.io\/\" target=\"_blank\" rel=\"noopener\"><span>import sorting<\/span><\/a><span> that eliminate tedium, or linters that guide engineers toward maintainable code with fewer bugs.<\/span><\/p>\n<p><span>This year, we have been building a new linter, <\/span><a href=\"https:\/\/pypi.org\/project\/fixit\/2.0.0a1\/\" target=\"_blank\" rel=\"noopener\"><span>Fixit 2<\/span><\/a><span>, designed from the ground up to make developers more efficient and capable, both in open source projects and the diverse landscape of our internal monorepo. At Meta, we are using Fixit 2 with a few early adopters, and plan to roll it out to the rest of our monorepo soon. But any developer can use it to <\/span><span>perform auto-fixing more efficiently and make faster improvements to their own codebases.<\/span><\/p>\n<h2><span>Why a new linter? (why not X?)<\/span><\/h2>\n<p><span>There are a variety of excellent linters in the Python ecosystem, many of which have a large community of third-party plugins providing a diverse array of lint rules. We have used <\/span><a href=\"https:\/\/flake8.pycqa.org\/\" target=\"_blank\" rel=\"noopener\"><span>Flake8<\/span><\/a><span> internally at Meta since 2016, and it has been very successful in helping developers reduce bugs and keep a clean codebase. The popular flake8-bugbear plugin was even created by \u0141ukasz Langa (author of <\/span><a href=\"https:\/\/black.readthedocs.io\/\" target=\"_blank\" rel=\"noopener\"><span>Black<\/span><\/a><span>, PSF developer-in-residence, and release manager for Python 3.8 and 3.9) while working at Meta (then Facebook), as a home for more opinionated lint rules that we could both use internally and share with the rest of the Python developer community.\u00a0<\/span><\/p>\n<p><span>We also have a large number of internal plugins built by various teams, and Flake8 allows them to write and enable custom lint rules directly in the codebase without getting sign-off from a central gatekeeper, and without waiting for a new deployment of Flake8 to roll out.<\/span><\/p>\n<p><span>But while Flake8 has long been a cornerstone of our linting solution, it also has some rough edges. Writing new lint rules requires building entire plugins (each claiming a portion of the \u201cnamespace\u201d for error codes) and encourages developers to build complicated plugins covering multiple classes of errors. When those lint errors are found, Flake8 can only point to a line and column number where it occurred, but has no way of suggesting changes to the developer looking at a list of lint results, leaving them in a state of trial and error to find changes that make the linter happy. Also, Flake8 uses the stdlib <\/span><span>ast<\/span><span> module, making it unable to parse future syntax features and forcing developers to wait for tools to upgrade before they can use the shiny new hotness.<\/span><\/p>\n<p><span>There are alternatives to Flake8 of course, but many of them suffer from one or more drawbacks:\u00a0<\/span><\/p>\n<p><span>A lack of support for \u201clocal\u201d in-repo plugins or custom lint rules.\u00a0<\/span><br \/>\n<span>Limited or no support for hierarchical configuration for different projects within a monorepo.<\/span><br \/>\n<span>No option for auto-fixes when errors are found.<\/span><br \/>\n<span>Slow performance on large codebases.<\/span><\/p>\n<p><span>While some of those features aren\u2019t critical, the most important for developer efficiency is offering auto-fixes \u2013 automatic suggested changes that would satisfy the lint rule. This takes the guesswork out of using a linter, and allows users to quickly review and accept those changes when possible, eliminating the need to re-run the linter until the code is finally clean. Combining these auto-fixes with in-repo, custom lint rules provides a level of tailored code quality improvements that is hard to beat.<\/span><\/p>\n<p><span>Unfortunately, even Fixit, the auto-fixing linter that we built for Instagram and open sourced, did not support local lint rules or hierarchical configuration \u2013 core requirements for our monorepo that is home to thousands of projects, many of which are themselves open source projects with their own distinct needs for linting and CI. We received many requests from developers to support Fixit in our monorepo, but there were enough hurdles that we were only able to support a small set of security lint rules, reducing the direct benefits to our Python codebase.\u00a0<\/span><\/p>\n<h2><span>Meet Fixit 2<\/span><\/h2>\n<p><span>After discussions with other teams, especially in the rapidly growing AI\/ML space, we considered our options and decided upon a partial rewrite of Fixit. We intentionally designed the new version with an open source-first mindset, while incorporating the needs and requirements of our own monorepos and open source projects from day one.<\/span><\/p>\n<p><span>The framework and linting engine would be rebuilt from the ground up while leaving the core design of lint rules largely untouched. The new system provides a hierarchical configuration based on the TOML format; support for local, in-repo lint rules similar to Flake8; and a much improved CLI and API for integration with other tools and automation.\u00a0<\/span><\/p>\n<p><span>Fixit itself builds on top of another Instagram open source project, <\/span><a href=\"https:\/\/libcst.rtfd.io\/\" target=\"_blank\" rel=\"noopener\"><span>LibCST<\/span><\/a><span>, a concrete syntax tree for Python with a tree and node API following the patterns of the <span>ast<\/span> <\/span><span>module in the standard library. The \u201cconcrete\u201d part of CST means that LibCST includes every part of the source file in the resulting tree after parsing, including whitespace, comments, and formatting elements that are ignored by the <span>ast<\/span> <\/span><span>module. This is what allows Fixit (and other tools we built, like <\/span><a href=\"https:\/\/usort.rtfd.io\/\" target=\"_blank\" rel=\"noopener\"><span>\u00b5sort<\/span><\/a><span>) to safely modify source files, without using regular expressions or the risk of producing broken syntax, and provides the foundation for Fixit to offer auto-fixes suggested by the lint rules themselves.<\/span><\/p>\n<p><span>Writing a new lint rule can be done with less than a dozen lines of code, and test cases are defined inline. You can even place it right next to the code that it will be linting:<\/span><\/p>\n<p># teambread\/rules\/hollywood.py<br \/>\nimport fixit<br \/>\nimport libcst<br \/>\nclass HollywoodName(fixit.LintRule):<br \/>\n    VALID = [&#8230;] # no lint errors here<br \/>\n    INVALID = [&#8230;] # bad code samples here<br \/>\n    def visit_SimpleString(self, node: libcst.SimpleString):<br \/>\n        if node.value in &#8220;&#8216;Paul'&#8221; or &#8216;&#8221;Paul&#8221;&#8216;:<br \/>\n            self.report(node, &#8220;It&#8217;s underbaked!&#8221;)<\/p>\n<p><span>Suggesting auto-fixes for the user is as easy as including a new CST node when reporting an error:<\/span><\/p>\n<p>def visit_SimpleString(self, node: libcst.SimpleString):<br \/>\n    if node.value in &#8220;&#8216;Paul'&#8221; or &#8216;&#8221;Paul&#8221;&#8216;:<br \/>\n        new_node = libcst.SimpleString(&#8216;&#8221;Mary&#8221;&#8216;)<br \/>\n        self.report(node, replacement=new_node)<\/p>\n<p><span>Enabling this new rule within the project\u2019s codebase can be done with a simple config change:<\/span><\/p>\n<p># teambread\/sourdough\/fixit.toml<br \/>\n[tool.fixit]<br \/>\nenable = [&#8220;.rules.hollywood&#8221;]<\/p>\n<p><span>Now we can run our linter against our project:<\/span><\/p>\n<p># teambread\/sourdough\/baker.py<br \/>\nname = &#8220;Paul&#8221;<br \/>\nprint(f&#8221;hello {name}!&#8221;)<br \/>\n$ fixit lint &#8211;diff sourdough\/baker.py<br \/>\nsourdough\/baker.py@7:11 HollywoodName: It&#8217;s underbaked! (has autofix)<br \/>\n&#8212; a\/baker.py<br \/>\n+++ b\/baker.py<br \/>\n@@ -6,3 +6,3 @@<br \/>\ndef main():<br \/>\n&#8211;    name = &#8220;Paul&#8221;<br \/>\n+    name = &#8220;Mary&#8221;<br \/>\n    print(f&#8221;hello {name}&#8221;)<br \/>\n  1 file checked, 1 file with errors, 1 auto-fix available<br \/>\n[1]<\/p>\n<p><span>The `lint` command only shows errors and suggested changes. If we use the `fix` command, we can apply those suggested changes back to the codebase:<\/span><\/p>\n<p>$ fixit fix &#8211;automatic sourdough\/baker.py<br \/>\nsourdough\/baker.py@7:11 HollywoodName: It&#8217;s underbaked! (has autofix)<br \/>\n  1 file checked, 1 file with errors, 1 auto-fix available, 1 fix applied <\/p>\n<p><span>Now that our auto-fixes have been applied, we can confirm that the project is now clean and lint-free:<\/span><\/p>\n<p>$ fixit lint sourdough\/baker.py<br \/>\n 1 file clean <\/p>\n<p><span>When running Fixit 2 with auto-fixing lint rules, any code that triggers the lint rule is an opportunity to get an automatic replacement, improving the codebase with less effort from the developer. Applied more broadly, Fixit 2 can even be used as a tool to enact sweeping codemods against a large codebase, while leaving a lint rule in place to handle any matching code in the future.<\/span><\/p>\n<h2><span>Try Fixit 2\u00a0<\/span><\/h2>\n<p><span>Fixit 2 is available today on <\/span><a href=\"https:\/\/pypi.org\/project\/fixit\/2.0.0a1\/\" target=\"_blank\" rel=\"noopener\"><span>PyPI<\/span><\/a><span>. You can install and test Fixit 2 with <\/span><span><span>pip<\/span> <span>install<\/span> <\/span><span><span>fixit<\/span>.\u00a0<\/span><\/p>\n<p><span>We have a\u00a0<\/span><a href=\"https:\/\/github.com\/Instagram\/Fixit\/milestones\" target=\"_blank\" rel=\"noopener\"><span>roadmap<\/span><\/a><span> with plans for future improvements and features, and a rich set of <\/span><a href=\"https:\/\/fixit.rtfd.io\/en\/latest\/\" target=\"_blank\" rel=\"noopener\"><span>documentation and user guides<\/span><\/a><span> to help you get started with Fixit 2 in your own projects or repositories. <\/span><span>We hope it proves useful in your projects, and we look forward to <\/span><a href=\"https:\/\/github.com\/Instagram\/Fixit\/issues\" target=\"_blank\" rel=\"noopener\"><span>hearing your feedback<\/span><\/a><span>!<\/span><\/p>\n<p>The post <a href=\"https:\/\/engineering.fb.com\/2023\/08\/07\/developer-tools\/fixit-2-linter-meta\/\">Fixit 2: Meta\u2019s next-generation auto-fixing linter<\/a> appeared first on <a href=\"https:\/\/engineering.fb.com\/\">Engineering at Meta<\/a>.<\/p>\n<p>Engineering at Meta<\/p>","protected":false},"excerpt":{"rendered":"<p>Fixit is dead! Long live Fixit 2 \u2013 the latest version of our open-source auto-fixing linter. Fixit 2 allows developers to efficiently build custom lint rules and perform auto-fixes for their codebases. Fixit 2 is available today on PyPI. Python is one of the most popular languages in use at Meta. Meta\u2019s production engineers (PEs)&hellip; <a class=\"more-link\" href=\"https:\/\/fde.cat\/index.php\/2023\/08\/07\/fixit-2-metas-next-generation-auto-fixing-linter\/\">Continue reading <span class=\"screen-reader-text\">Fixit 2: Meta\u2019s next-generation auto-fixing linter<\/span><\/a><\/p>\n","protected":false},"author":0,"featured_media":0,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"spay_email":"","footnotes":""},"categories":[7],"tags":[],"class_list":["post-742","post","type-post","status-publish","format-standard","hentry","category-technology","entry"],"jetpack_featured_media_url":"","jetpack-related-posts":[{"id":795,"url":"https:\/\/fde.cat\/index.php\/2023\/11\/21\/writing-and-linting-python-at-scale\/","url_meta":{"origin":742,"position":0},"title":"Writing and linting Python at scale","date":"November 21, 2023","format":false,"excerpt":"Python plays a big part at Meta. It powers Instagram\u2019s backend and plays an important role in our configuration systems, as well as much of our AI work. Meta even made contributions to Python 3.12, the latest version of Python. On this episode of the\u00a0Meta Tech Podcast, Meta engineer Pascal\u2026","rel":"","context":"In &quot;Technology&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":824,"url":"https:\/\/fde.cat\/index.php\/2024\/02\/12\/meta-loves-python\/","url_meta":{"origin":742,"position":1},"title":"Meta loves Python","date":"February 12, 2024","format":false,"excerpt":"By now you\u2019re already aware that Python 3.12 has been released. But did you know that several of its new features were developed by Meta? Meta engineer Pascal Hartig (@passy) is joined on the Meta Tech Podcast by Itamar Oren and Carl Meyer, two software engineers at Meta, to discuss\u2026","rel":"","context":"In &quot;Technology&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":897,"url":"https:\/\/fde.cat\/index.php\/2024\/07\/16\/ai-lab-the-secrets-to-keeping-machine-learning-engineers-moving-fast\/","url_meta":{"origin":742,"position":2},"title":"AI Lab: The secrets to keeping machine learning engineers moving fast","date":"July 16, 2024","format":false,"excerpt":"The key to developer velocity across AI lies in minimizing time to first batch (TTFB) for machine learning (ML) engineers. AI Lab is a pre-production framework used internally at Meta. It allows us to continuously A\/B test common ML workflows \u2013 enabling proactive improvements and automatically preventing regressions on TTFB.\u00a0\u2026","rel":"","context":"In &quot;Technology&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":888,"url":"https:\/\/fde.cat\/index.php\/2024\/06\/25\/the-key-to-a-happy-rust-c-relationship\/","url_meta":{"origin":742,"position":3},"title":"The key to a happy Rust\/C++ relationship","date":"June 25, 2024","format":false,"excerpt":"The history of Rust at Meta goes all the way back to 2016, when we first started using it for source control. Today, it has been widely embraced at Meta and is one of our primary supported server-side languages (along with C++, Python, and Hack). But that doesn\u2019t mean there\u2026","rel":"","context":"In &quot;Technology&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":787,"url":"https:\/\/fde.cat\/index.php\/2023\/11\/15\/watch-metas-engineers-on-building-network-infrastructure-for-ai\/","url_meta":{"origin":742,"position":4},"title":"Watch: Meta\u2019s engineers on building network infrastructure for AI","date":"November 15, 2023","format":false,"excerpt":"Meta is building for the future of AI at every level \u2013 from hardware like MTIA v1, Meta\u2019s first-generation AI inference accelerator to publicly released models like Llama 2, Meta\u2019s next-generation large language model, as well as new generative AI (GenAI) tools like Code Llama. Delivering next-generation AI products and\u2026","rel":"","context":"In &quot;Technology&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":614,"url":"https:\/\/fde.cat\/index.php\/2022\/07\/27\/programming-languages-endorsed-for-server-side-use-at-meta\/","url_meta":{"origin":742,"position":5},"title":"Programming languages endorsed for server-side use at Meta","date":"July 27, 2022","format":false,"excerpt":"\u2013 Supporting a programming language at Meta is a very careful and deliberate decision. \u2013 We\u2019re sharing our internal programming language guidance that helps our engineers and developers choose the best language for their projects. \u2013 Rust is the latest addition to Meta\u2019s list of supported server-side languages. At Meta,\u2026","rel":"","context":"In &quot;Technology&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/fde.cat\/index.php\/wp-json\/wp\/v2\/posts\/742","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fde.cat\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fde.cat\/index.php\/wp-json\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/fde.cat\/index.php\/wp-json\/wp\/v2\/comments?post=742"}],"version-history":[{"count":0,"href":"https:\/\/fde.cat\/index.php\/wp-json\/wp\/v2\/posts\/742\/revisions"}],"wp:attachment":[{"href":"https:\/\/fde.cat\/index.php\/wp-json\/wp\/v2\/media?parent=742"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fde.cat\/index.php\/wp-json\/wp\/v2\/categories?post=742"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fde.cat\/index.php\/wp-json\/wp\/v2\/tags?post=742"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}