Skip to content

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

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

ts
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.

yaml
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

js
import { Machine, interpret } from 'xstate';

const machine = Machine({ /*...*/});

const specificMachine = machine.withConfig({
  actions: { /* ... */ },
  guards: { /* ... */ },
  services: { /* ... */ },
});

const actor = interpret(specificMachine, {
  /* actor options */
});

Diff

js
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

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

ts
const [foo, bar] = await Promise.all([
  await getFoo(),
  getBar(),
  (async () => { await getBaz()})(),
])

Diff

ts
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

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

ts
console.debug('')
try {
    console.log('hello')
} catch (e) {
    console.error(e) // OK
}

Diff

ts
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

bash
sg -p 'useState<number>($A)' -r 'useState($A)' -l ts
bash
sg -p 'useState<string>($A)' -r 'useState($A)'
bash
sg -p 'useState<boolean>($A)' -r 'useState($A)'

Example

ts
function Component() {
  const [name, setName] = useState<string>('React')
}

Diff

ts
function Component() {
  const [name, setName] = useState<string>('React') 
  const [name, setName] = useState('React') 
}

Contributed by

Herrington Darkholme

Made with ❤️ with Rust