TypeScript
This page curates a list of example ast-grep rules to check and to rewrite TypeScript applications.
TypeScript and TSX are different.
TypeScript is a typed JavaScript extension and TSX is a further extension that allows JSX elements. They need different parsers because of conflicting syntax.
TS allows both the as operator and angle brackets (<>) for type assertions. While TSX only allows the as operator because it interprets angle brackets as JSX elements.
Unnecessary useState Type Has Fix
Description
React's useState is a Hook that lets you add a state variable to your component. The type annotation of useState's generic type argument, for example useState<number>(123), is unnecessary if TypeScript can infer the type of the state variable from the initial value.
We can usually skip annotating if the generic type argument is a single primitive type like number, string or boolean.
Pattern
sg -p 'useState<number>($A)' -r 'useState($A)' -l tssg -p 'useState<string>($A)' -r 'useState($A)'sg -p 'useState<boolean>($A)' -r 'useState($A)'Example
function Component() {
const [name, setName] = useState<string>('React')
}Diff
function Component() {
const [name, setName] = useState<string>('React')
const [name, setName] = useState('React')
}Contributed by
No await in Promise.all array Has Fix
Description
Using await inside an inline Promise.all array is usually a mistake, as it defeats the purpose of running the promises in parallel. Instead, the promises should be created without await and passed to Promise.all, which can then be awaited.
YAML
id: no-await-in-promise-all
language: typescript
rule:
pattern: await $A
inside:
pattern: Promise.all($)
stopBy:
not: { any: [{kind: array}, {kind: arguments}] }
fix: $AExample
const [foo, bar] = await Promise.all([
await getFoo(),
getBar(),
(async () => { await getBaz()})(),
])Diff
const [foo, bar] = await Promise.all([
await getFoo(),
getFoo(),
getBar(),
(async () => { await getBaz()})(),
])Contributed by
Inspired by Alvar Lagerlöf
No console except in catch block Has Fix
Description
Using console methods is usually for debugging purposes and therefore not suitable to ship to the client. console can expose sensitive information, clutter the output, or affect the performance.
The only exception is using console.error to log errors in the catch block, which can be useful for debugging production.
YAML
id: no-console-except-error
language: typescript
rule:
any:
- pattern: console.error($$$)
not:
inside:
kind: catch_clause
stopBy: end
- pattern: console.$METHOD($$$)
constraints:
METHOD:
regex: 'log|debug|warn'Example
console.debug('')
try {
console.log('hello')
} catch (e) {
console.error(e) // OK
}Diff
console.debug('')
try {
console.log('hello')
} catch (e) {
console.error(e) // OK
}Contributed by
Inspired by Jerry Mouse
Find Import File without Extension
Description
In ECMAScript modules (ESM), the module specifier must include the file extension, such as .js or .mjs, when importing local or absolute modules. This is because ESM does not perform any automatic file extension resolution, unlike CommonJS modules tools such as Webpack or Babel. This behavior matches how import behaves in browser environments, and is specified by the ESM module spec.
The rule finds all imports (static and dynamic) for files without a file extension.
YAML
id: find-import-file
language: js
rule:
regex: "/[^.]+[^/]$"
kind: string_fragment
any:
- inside:
stopBy: end
kind: import_statement
- inside:
stopBy: end
kind: call_expression
has:
field: function
regex: "^import$"Example
import a, {b, c, d} from "./file";
import e from "./other_file.js";
import "./folder/";
import {x} from "package";
import {y} from "package/with/path";
import("./dynamic1");
import("./dynamic2.js");
my_func("./unrelated_path_string")Contributed by
DasSurma in this tweet.