TypeScript
This page curates a list of example ast-grep rules to check and to rewrite TypeScript applications. Check out the Repository of ESLint rules recreated with ast-grep.
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.
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.
Migrate XState to v5 from v4 Has Fix
Description
XState is a state management/orchestration library based on state machines, statecharts, and the actor model. It allows you to model complex logic in event-driven ways, and orchestrate the behavior of many actors communicating with each other.
XState's v5 version introduced some breaking changes and new features compared to v4. While the migration should be a straightforward process, it is a tedious process and requires knowledge of the differences between v4 and v5.
ast-grep provides a way to automate the process and a way to encode valuable knowledge to executable rules.
The following example picks up some migration items and demonstrates the power of ast-grep's rule system.
YAML
The rules below correspond to XState v5's createMachine
, createActor
, and machine.provide
.
The example shows how ast-grep can use various features like utility rule, transformation and multiple rule in single file to automate the migration. Each rule has a clear and descriptive id
field that explains its purpose.
For more information, you can use @ast-grep-bot to provide more detailed explanation for each rule.
id: migrate-import-name
utils:
FROM_XS: {kind: import_statement, has: {kind: string, regex: xstate}}
XS_EXPORT:
kind: identifier
inside: { has: { matches: FROM_XS }, stopBy: end }
rule: { regex: ^Machine|interpret$, pattern: $IMPT, matches: XS_EXPORT }
transform:
STEP1:
replace: {by: create$1, replace: (Machine), source: $IMPT }
FINAL:
replace: { by: createActor, replace: interpret, source: $STEP1 }
fix: $FINAL
---
id: migrate-to-provide
rule: { pattern: $MACHINE.withConfig }
fix: $MACHINE.provide
---
id: migrate-to-actors
rule:
kind: property_identifier
regex: ^services$
inside: { pattern: $M.withConfig($$$ARGS), stopBy: end }
fix: actors
Example
import { Machine, interpret } from 'xstate';
const machine = Machine({ /*...*/});
const specificMachine = machine.withConfig({
actions: { /* ... */ },
guards: { /* ... */ },
services: { /* ... */ },
});
const actor = interpret(specificMachine, {
/* actor options */
});
Diff
import { Machine, interpret } from 'xstate';
import { createMachine, createActor } from 'xstate';
const machine = Machine({ /*...*/});
const machine = createMachine({ /*...*/});
const specificMachine = machine.withConfig({
const specificMachine = machine.provide({
actions: { /* ... */ },
guards: { /* ... */ },
services: { /* ... */ },
actors: { /* ... */ },
});
const actor = interpret(specificMachine, {
const actor = createActor(specificMachine, {
/* actor options */
});
Contributed by
Inspired by XState's blog.
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: $A
Example
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
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 ts
sg -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')
}