Trunk Code Quality stands on top of the shoulders of incredible open-source linters, formatters, and static analysis tools. ESLint is a core part of this Trunk Code Quality plugin ecosystem. It's the most popular linter plugin and is enabled by default on all JavaScript and TypeScript projects running Code Quality. ESLint also runs on all of Trunk's TypeScript source code.
Trunk is proud to announce that we're sponsoring open-source projects like ESLint to give back to the open-source ecosystem. But just sponsoring isn't enough, we believe Code Quality is also a great addition to open-source repos to improve their open-source contributor's developer experience.
Code Quality is free for all open-source projects for this exact reason. We go one step further for special partners like ESLint by helping them adopt Code Quality with a PR.
We reached out to ESLint, the most popular linter running on Trunk Code Quality, to help them improve their static analysis setup. Here’s what we did in our PR.
Where does it hurt?
ESLint’s repo has immaculate code quality, which you’d expect. They dogfood their own linter from the source on their source code. The pain for ESLint isn’t poor code quality, but rather what they have to do to enforce code quality.
ESLint doesn’t import ESLint from npm as a dependency. They build ESLint from the source and run it from the source. It results in a setup where ESLint’s source code is used to lint itself.
ESLint also lints in three stages using a custom script called Makefile.js
. First, they validate all their JavaScript, excluding the docs. Then, they lint all the Markdown. Lastly, they lint all of the JavaScript in their docs separately. The custom script also performs custom logic, including file directory and extension matching, ignores, and the 3-stage linting process. This also manifests in their CI as they run the scripts directly.
The problem with one-off scripts like this is that they are difficult to maintain, and they do too much. ESLint currently only lints their JavaScript and Markdown. They don’t run other forms of static analysis for the myriad of other file types and problems they could encounter. If they did, they’d have to continue to add more and more to this giant script.
Gaps in your linting?
ESLint does a great job of linting its JavaScript, which is expected. Like most GitHub projects out there, linting and formatting outside their main programming language is neglected. It’s always the corners and gaps that are hardest to clean.
The places that are usually overlooked are:
Dependencies
Infrastructure config
Leaked secrets
Image optimization
Config files, scripts, and markdown docs
As these files become more and more messy, they become difficult to understand and challenging to update. You tend to also waste a lot of time during code reviews addressing changes in these files, like 2 spaces vs. 4 spaces for tabs and other trivial standards. Automated tools take out personal preferences and subjectivity.
Why it’s so hard to lint your entire repo
Introducing new linters to an open-source project is a grind. New linters to old repositories usually surface a lot of existing issues. Adding ignores for all of them defeats the purpose, but fixing all of them up front is not practical. You end up with a giant PR that’s especially hard to review with the asynchronous communication common in open source organizations. This also shotguns the entire commit history. Git blaming will now reveal a giant fault line of when that linter was introduced.
Running more linters is also not trivial. The problem with linters is that they install differently, they run differently, and they output differently.
Some linters are installed through npm, which is the best-case scenario. Some other linters, like oxipng and trivy, don’t install through npm. You end up looking for node wrappers, which may not exist or are prone to rot because they’re supported by third parties. The alternative is to ask maintainers to install binaries, which is a nightmare to orchestrate across developers and the cloud. This is especially true if certain versions of linters must be pinned because of breaking changes.
Then there’s the problem with running linters and getting them to output consistently. You can’t expect maintainers to run multiple lint commands. Just chaining commands together yields an incomprehensible mess because every linter has a different output format. ESLint already faces this issue and solves it with a big Makefile.js. Running more linters will require a larger script and more one-off scripts to maintain. If one of the linters updates with a breaking change, either with the way it runs or the output format changes, you’ll have to update your linter script.
Communication challenges
Another challenge with open-source repos is getting people to fix linter errors. I’ve maintained a popular open-source project through events like Hacktoberfest. I had to set up a macro on my machine to reply to PRs with the words “please run linters and formatters first,” because every PR came in with linter problems that CI catches.
If a project as popular as ESLint is going to set up more static analysis, there needs to be a more prominent way to prompt contributors to run them without a long communication round trip.
What Trunk Code Quality offers
Trunk Code Quality is a metalinter. It’s a way to configure and run all the linters with one tool and get consistent output. Think of it as a much more advanced take on GitHub’s Superlinter project. It runs in more than just GitHub Actions. It runs hermetically, as a command line tool, and has many more advanced features. It can run locally and integrate with VSCode.
New linters
For ESLint, we enabled these linters that run with one command using Trunk:
The existing linters eslint, markdownlint, and prettier are now run via Trunk. This is from both the npm lint commands and lint-staged.
Enabled new linters: actionlint, checkov, renovate, trivy, and trufflehog.
Enabled the eslint-plugin-yml eslint plugin.
Trunk supports 100+ static analysis tools for all languages, which are contributed as plugins by the community.
Hermetic installs
Trunk installs with the repo through npm, and on the first run, it hermetically installs all the linters and dependencies. This way, ESLint maintainers will not have to mess with installing a bunch of different tools onto their system. For the tools that require a runtime, like a python, node, or ruby environment, Trunk will also manage those environments for them.
Unified reporting for every linter
Trunk Code Quality also reformats the output of these tools to be consistent, unified, and organized by file, so it’s easier to read through the linting issues, even if there are many issues from different linters.
Highly configurable
Trunk Code Quality also lets you manage ignores and the files a linter should run on in a unified config. You can even define custom linter definitions and the commands/scripts they run.
Remember ESLint’s setup of linting ESLint source code with ESLint source code? Here’s the configuration to lint JavaScript and YAML files with ESLint from the source:
1lint:2 definitions:3 - name: eslint4 files: [typescript, javascript, yaml] # Add YAML to default files.5 hold_the_line: false6 commands:7 - name: lint8 disable_upstream: true9 run: node ${workspace}/bin/eslint.js --output-file ${tmpfile} --format json ${target}
Hold-the-line
Trunk has a feature called hold-the-line. If adopting all these new linters revealed too many old issues to fix upfront, we would enable hold-the-line. This would mean Trunk only shows new issues introduced with changes moving forward. This helps prevent new issues and reminds you to clean up files you change as you go.
Annotating PRs
ESLint already runs linters in CI, but Trunk adds a few benefits. Trunk provides a GitHub action trunk-io/trunk-action@v1 that you can use in CI. This GitHub Action allows you to run Trunk Code Quality in CI mode and generate PR annotations that show up right in your PR views.
Followup improvements
Introducing a ton of changes at once to a mature open-source repo like ESLint with many stakeholders is not a great idea. Our initial PR is relatively scope-limited to solve the most painful parts of ESLint’s linting experience. We can bring a few additional improvements to the ESLint repo in the future.
Another feature is Trunk Announce. Using Trunk Announce, you can post announcements to a repo by appending specially formatted text blocks to your commit messages. When a developer pulls that commit in the future, a git-hook displays the announcement in a readable format. This is helpful when you introduce breaking changes or refactoring code. Trunk Announce can help open-source developers stay in the loop.
ESLint also has a handful of other repositories. One really cool feature of Trunk Code Quality is that you can share plugin configs. With this feature, we can help ESLint standardize the linter configuration of all repositories. This prevents the code standards of these repos drifting apart over time.
Try Trunk in your repo.
Trunk is free for open source! If you have an open-source repo, try Trunk Code Quality. All you need to get started is to install Trunk and run trunk init
. Trunk will recommend some linters with default configs to help you get started.
Here are some resources to help you get started.
📚 Read the docs: https://docs.trunk.io/code-quality/
💬 Join us in Slack: https://slack.trunk.io/
🐦 Follow us on X: https://x.com/trunkio
👥 Follow us on LinkedIn: https://www.linkedin.com/company/trunkio