Because JavaScript is a dynamically typed language, programmers can more easily introduce subtle bugs and errors that fail at runtime compared to static languages with a compiler that picks up on common issues.
Take the following code for example:
console.log(someUndefinedVariable)
const constant = 0
constant = 3
const addOne = n => {
if (typeof n !== 'nubmer') {
throw new Error('n must be an number')
console.log('This will never be executed')
}
if (n < 0) console.log('negative number')
else return n + 1
}
console.log(addOne(-3) * 2)
const symbol = new Symbol()
This code will fail at runtime and also has some other issues that may lead to unexpected results.
Linters such as ESLint pick up on some of these issues with rules such as no-undef and no const-assign:
1:13 error 'someUndefinedVariable' is not defined no-undef
4:1 error 'constant' is constant no-const-assign
7:20 error Invalid typeof comparison value valid-typeof
9:3 error Unreachable code no-unreachable
16:20 error `Symbol` cannot be called as a constructor no-new-symbol
Similarly, TypeScript's compiler will also warn you about many of these issues:
Cannot find name 'someUndefinedVariable'.
Cannot assign to 'constant' because it is a constant.
This condition will always return 'true' since the types
'"string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"'
and '"nubmer"' have no overlap.
Unreachable code detected.
'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.
In this sense, ESLint and TypeScript have the same goal: to prevent likely programmer errors by warning you beforehand. For these issues, you can turn off the respective ESLint rules and use the TypeScript compiler instead.
However, the most important feature about TypeScript is its addition of static types to JavaScript. By adding type annotations to addOne
:
const addOne = (n: number): number => { /* ... */ }
TS tells us that Function lacks ending return statement and return type does not include 'undefined'.
because the function will return undefined
instead of a number if n
is a negative number. The result of addOne(-3) * 2
would be NaN
, instead of -4
like expected.
Another example, which ESLint would be completely fine with but fails at runtime:
const foo = 0
const bar = foo()
// ~~~
// This expression is not callable.
// Type 'Number' has no call signatures.
These are some of the many issues that TypeScript can help identify due to its type system.
On the other hand, linters including ESLint and the typescript-eslint plugin can enforce best practices such as using strict equality operators and correctly handling promises. They can also enforce stylistic conventions such as indentation, requiring or forbidding semicolons, or consistent type assertion styles.
TypeScript and ESLint have the similar goal of preventing programmer bugs and errors. However, due to its type system, TypeScript can pick up on more runtime and programming errors, whereas ESLint can enforce stylistic conventions and best practices.