You may know that linting can improve your code quality, but linting isn’t the only way static analysis can ensure your team is writing high-quality code consistently. Static analysis tools can play an integral role in your development cycle, even in a dynamically typed language such as JavaScript.
In this guide, we’ll look at some of the most prominent static analysis tools available in the JavaScript ecosystem and discuss why and when you might use them.
First, let’s review the definition of static analysis and its place in the software development lifecycle.
Static analysis is the process of verifying that your code meets certain expectations without actually running it. Unlike unit and integration testing, static analysis can be performed on raw source code without the need for a web server or build process.
Static analyzers typically parse your code and turn it into what is known as an abstract syntax tree. This tree is then traversed and the pieces are checked based on the rules dictated by the static analyzer. Most static analyzers also include a way for developers to write their own custom rules, but that varies from tool to tool.
Static analysis is most commonly used to:
In a dynamically interpreted language such as JavaScript, developers must decide when and how they want to run static analysis on their code. I’ve most commonly seen static analysis run on each developer’s machine before they push changes (as a Git pre-commit hook) as part of a continuous integration server’s workflow or as part of every code review.
No matter when or how static analysis happens, the goal remains the same: to help make code more consistent, maintainable, and correct. It won’t replace automated or manual testing, but it may catch errors that other quality assurance tools miss.
Because JavaScript is dynamically typed and it has historically been hard to collect error logs from client-side applications, static analysis tools can be even more beneficial than in statically typed server-side languages.
If you’re new to static analysis, it can be overwhelming to sift through the many tools that are available. I’ll introduce you to some of the most common tools for static analysis in JavaScript. We’ll explore some of their use-cases and how to implement them in your development workflow.
ESLint is probably the most widely used static analysis tool for JavaScript today. Both Prettier and Standard JS use ESLint to power their formatting engines, so even if you don’t include it explicitly, you might be using it under the hood.
ESLint’s primary use case is as a linter — a specific kind of static analysis tool that catches inconsistent formatting, styling, and possible errors. ESLint does this by using predetermined rules that you can configure or customize based on your needs.
A good example of how this can be used is to prevent developers from accidentally using console
statements in production. If you’re writing a JavaScript function to sort numbers and you want to validate whether you did it correctly, you might use console.log()
to check yourself.
function sortNumbers(numbers) { console.log(numbers); const result = numbers.sort((a, b) => (a - b)); console.log(result); return result; } sortNumbers([30, 12, 22, 19]);
If the rule is enabled, ESLint will warn you about this likely mistake before you ship it to users.
Using ESLint can be overwhelming. There are dozens of rules and you can write custom ones on top of that. Some rules may require you to change the behavior of your application, so if you want to start by simply making your code’s formatting more consistent, Prettier might be right for you.
Prettier is not a full-featured linter; it only addresses style and formatting rules. It helps by limiting choices and automatically fixing code that does not conform to its style guide.
For example, say you wrote a function like this:
function createUser() { return { id: 1, name: "Karl", birthdate: "6/10/86", hometown: "Lansing, MI" }; }
If you were to run it through Prettier, it would rewrite the function to conform to its consistent style.
function createUser() { return { id: 1, name: "Karl", birthdate: "6/10/86", hometown: "Lansing, MI", }; }
While Prettier gives you fewer options for customizing styling rules, it is a great way to make sure everyone on your development team is using the same formatting and style in their code.
Somewhere between ESLint and Prettier is Standard. Like Prettier, Standard is opinionated — you don’t need to decide which ESLint rules to implement and configure — but it goes further than just fixing stylistic issues. It also includes rules that may reduce errors and change the behavior of your code.
For example, Standard includes the ESLint rule to always use ===
instead of ==
. Because JavaScript coerces types, blindly following Standard’s suggestion could change the behavior of your code in unexpected ways.
If you were to run the following code, all three log statements would be true
.
function isTrue (x) { return x == true } console.log(isTrue('1')) // true console.log(isTrue(1)) // true console.log(isTrue(true)) // true
But if you follow Standard’s suggestion and make x === true
, the results change:
function isTrue (x) { return x === true } console.log(isTrue('1')) // false console.log(isTrue(1)) // false console.log(isTrue(true)) // true
While Standard may not be as easy to implement on a large existing codebase, it’s still an excellent tool for linting your code. Removing petty disputes over coding style can boost developer productivity and speed up onboarding time.
Similar to ESLint, JSHint is a linting tool that enables you to set up and configure rules for catching common coding errors and formatting inconsistencies. In general, ESLint has more rules, and it’s a little easier to write custom rules for. The differences mostly come down to preference.
One special case that favors JSHint over ESLint is when you’re developing an application with features specific to Mozilla’s Firefox browser. JSHint has a rule to allow calls to Mozilla-specific JavaScript extensions while ESLint does not.
Like ESLint, going through the rules and deciding which ones are appropriate for your codebase is the part that will take the most time upfront. Because JSHint has fewer rules and configuration options, it might be a little faster to set up if you’re not trying to do something extremely specific.
If you’re building a Java or Saleforce Apex application, you might already be familiar with PMD. PMD — which doesn’t stand for anything, by the way — is a linter with support for several programming languages, including JavaScript.
Its ruleset for JavaScript applications is fairly limited, but unlike the above linting tools, PMD comes bundled with a copy-paste detector. This can help find duplicate or similar code across an extensive application and encourages DRY code.
Linting based on predetermined rules alone is a great way to increase your code’s quality, but it’s not the only way to check a codebase for common errors. The most significant disadvantage to linting is that it only knows about the rules you can tell it about.
LGTM — which stands for “looks good to me” — uses the fact that bugs often reoccur to check your codebase for common vulnerabilities and exploits that it learns about from analyzing other codebases. In other words, it’s looking not for rules programmers specifically tell it about but changes that may indicate the introduction of a new bug or security vulnerability.
While it’s free for open source projects, LGTM has paid offerings for private codebases.
SonarCloud provides a comprehensive suite of static analysis tools to assess your codebase’s quality across a wide range of measures. While private projects have to pay for access, it’s free for open-source projects and integrates into GitHub so you can ensure that every commit maintains your code quality.
If you want to dig into the checks that SonarCloud does, it provides a sampling of results from open-source projects on its website. Each is assessed based on reliability, security, maintainability, code coverage, and duplicate code.
You can also drill down into each file and see all the suggestions and errors that SonarCloud found, giving you granular access and the ability to tweak certain quality thresholds as it makes sense.
If you host your source code on GitHub, you’ve probably already seen Dependabot in action. GitHub acquired Dependabot in May 2019 and has since integrated it as a feature that is available to all repositories to help address security vulnerabilities from out-of-date dependencies.
Given the increasing dependency on third-party libraries in JavaScript, this can save teams time and close security gaps faster.
You do have to be a bit careful merging in Dependabot pull requests, though. If you don’t have a good set of automated tests, it’s possible that even minor version upgrades could cause breaking changes.
Since version 6, npm has had the audit
command, which offers similar checks to Dependabot. If you’re using npm as your package manager but prefer not to get automated pull requests on your code, running the npm audit
command is a good way to ensure that your third-party dependencies are up-to-date and secure.
Since it’s probably not wise to blindly update all your dependencies, npm audit
comes with a few options to limit its scope.
npm audit fix
automatically updates packages but only performs minor semver updatesnpm audit fix --dry-run
outputs a list of changes that will be made so you can double-check them before they take effectnpm audit fix --force
updates major and minor versions of all packages with security vulnerabilitiesAs with Dependabot, npm audit
should be combined with automated and manual tests to avoid breaking changes.
Facebook’s Flow can use either explicit annotations or implicit inferences to ensure type consistency in your JavaScript code.
Type-checking minimizes errors by ensuring that variables are used in a way that your program expects.
For example, say you have code like this:
function isTrue (x: bool) { return x === true; } isTrue(1);
Flow would throw an error because it expects x
to be a boolean
, not an integer. This warning is especially helpful when you want to ensure that objects contain specific properties or that numbers are not mistakenly coerced into strings.
Developed by Microsoft, TypeScript is used in Google’s Angular framework. TypeScript extends JavaScript and, like Flow, provides type annotations and type checking. But, unlike Flow, TypeScript is supported by most of the major JavaScript frameworks and IDEs, so it’s probably a better choice at this point.
While you don’t have to use all of TypeScript’s features to get some of the advantages, such as implicit type safety, you can dive deep to get a lot out it. With features such as interfaces, generics, template literals, and enums, there’s a lot for a JavaScript developer to learn.
This list is far from comprehensive, but I hope it helps you get started exploring and using static analysis to improve your codebase.
If you have favorite tools for static analysis in JavaScript, I’d love to hear about them. Leave me a comment below to continue the conversation.
Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.
LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.
LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
One Reply to "Static analysis in JavaScript: 11 tools to help you catch errors before users do"
There is also https://github.com/es-analysis/plato and https://github.com/smontanari/code-forensics which depends on https://github.com/adamtornhill/code-maat. Also Adam Tornhill is a good lead to follow on static analysis.