You're likely familiar with the concept of a code review: scrutinizing code by visual inspection, without the assistance of automated tools. You know manual processes are prone to human error and you'd jump at the opportunity to automate some of that process if you could, right? Enter static analysis:
In short, static analysis can:
- provide an understanding of the code structure
- help to ensure that the code adheres to a set of standards
- and reveal errors that don't manifest themselves until disaster strikes
- which could be weeks, months or years post-release
Types of Static Analysis & Tooling
Automated tools can assist developers in carrying out different types of static analysis, loosely defined as linting, formatting and type-checking.
Linting - ESLint
ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code, with the goal of making code more consistent and avoiding bugs
High-level Overview
- ESLint statically analyzes your code to quickly find problems
- it's built into most editors like VSCode
- it can be run as part of a continuous integration pipeline
- Some problems are fixed automatically
- fixes are syntax-aware
- you won't experience errors introduced by traditional find-and-replace algorithms
- fixes are syntax-aware
- And it's customizable to work exactly the way you need it for your project
- you can write your own rules that work alongside ESLint's built-in rules
- there are plenty of valuable plugins (rulesets written by others) to save you time
Example
Given this single line of code:
var foo = bar;
ESLint would output the following messages:
1:5 - 'foo' is assigned a value but never used. (no-unused-vars)
1:11 - 'bar' is not defined. (no-undef)
That output is a product of the recommended configuration which enforces rules no-unused-vars and no-undef (along with many others) that can be reconfigured to show a warning instead of an error or even be turned off completely.
Beyond initial setup and configuration, linting your entire codebase is easy:
$ npx eslint .
To lint React jsx
files:
$ npx eslint --ext .jsx .
ESLint can be used to enforce standards around formatting with very fine control - down to the character. Even the whitespace around your code can be scrutinized! Other more opinionated formatting tools are designed to avoid this sort of bike-shedding.
Code Formatting - Prettier
Prettier reformats your code in a way that's more readable and consistent. While being a stickler for code style can seem unfruitful, there are some hidden code-quality gems to be found. Here's an example:
Example
const a = false
const b = false
const c = true
const d = a && b || c
If you don’t remember the order of operations of &&
and ||
- or you don’t trust that all developers on the team do - in order to get the value of d, then a bug could get introduced when this get refactored.
Formatting the code with Prettier results in this:
const a = false
const b = false
const c = true
const d = (a && b) || c
The extra parentheses are helpful even if you do know the order of operations. If by chance you realize that wasn’t your intention, you can add the parentheses yourself and Prettier will leave it that way:
const a = false
const b = false
const c = true
const d = a && (b || c)
Code formatting can make the intent of your code more obvious, reducing cognitive load on the developer reviewing or refactoring the code.
By design, Prettier configuration is limited. This delegation is at the cost of finer control over formatting which can be achieved by using ESLint instead to format your code.
Type-checking - TypeScript
Trying to invoke an object that’s not a function results (unsurprisingly) in an error like this:
x is not a function
However this happens at execution time.
A static type checker allows you to specify what data type a variable is to ensure - at compile time - that it’s being used properly by adding syntax to JavaScript to follow that variable through the code.
You might be saying to yourself, but JavaScript is not a compiled language! - and you'd be right! But TypeScript's compiler can be used to type-check your code - whether it's TypeScript or JavaScript.
Example
This code contains a bug:
function getFullName(customer) {
const {
name: {first, middle, last},
} = customer
return [first, middle, last].filter(Boolean).join(' ')
}
getFullName({first: 'John', middle: 'A', last: 'Smith'})
Adding type-checking helps us find the bug automatically:
type Customer = {
name: {
first: string,
middle: string,
last: string,
},
}
function getFullName(user: Customer): string {
const {
name: {first, middle, last},
} = customer
return [first, middle, last].filter(Boolean).join(' ')
}
Here’s the output:
Argument of type '{ first: string; middle: string; last: string; }' is not assignable to parameter of type 'Customer'.2 Object literal may only specify known properties, and 'first' does not exist in type 'Customer'.(0123)
Type-checking can be adopted incrementally:
- with Babel
- with a supported subset of JSDoc annotations
Conclusion
Static analysis should be an arrow in every JavaScript developer's quiver. In addition to manual and automated testing, it will give you more confidence that you're shipping quality code.
Top comments (0)