SkillAgentSearch skills...

Astx

Super powerful structural search and replace for JavaScript and TypeScript to automate your refactoring

Install / Use

/learn @codemodsquad/Astx

README

astx

CircleCI Coverage Status semantic-release Commitizen friendly npm version

Super powerful structural search and replace for JavaScript and TypeScript to automate your refactoring

Table of Contents

<!-- toc --> <!-- tocstop -->

Introduction

Simple refactors can be tedious and repetitive. For example say you want to make the following change across a codebase:

// before:
rmdir('old/stuff')
rmdir('new/stuff', true)

// after:
rmdir('old/stuff')
rmdir('new/stuff', { force: true })

Changing a bunch of calls to rmdir by hand would suck. You could try using regex replace, but it's fiddly and wouldn't tolerate whitespace and linebreaks well unless you work really hard at the regex. You could even use jscodeshift, but it takes too long for simple cases like this, and starts to feel harder than necessary...

Now there's a better option...you can refactor with confidence using astx!

astx \
  --find    'rmdir($path, $force)' \
  --replace 'rmdir($path, { force: $force })'

This is a basic example of astx patterns, which are just JS or TS code that can contain placeholders and other special matching constructs; astx looks for code matching the pattern, accepting any expression in place of $path and $force. Then astx replaces each match with the replace pattern, substituting the expressions it captured for $path ('new/stuff') and $force (true).

But this is just the beginning; astx patterns can be much more complex and powerful than this, and for really advanced use cases it has an intuitive API you can use:

for (const match of astx.find`rmdir($path, $force)`) {
  const { $path, $force } = match
  // do stuff with $path.node, $path.code, etc...
}

Usage examples

Fixing eslint errors

Got a lot of Do not access Object.prototype method 'hasOwnProperty' from target object errors?

// astx.js
exports.find = `$a.hasOwnProperty($b)`
exports.replace = `Object.hasOwn($a, $b)`

Restructure array concatenation

Recently for work I wanted to make this change:

// before
const pkg = OrgPackage({
  subPackage: [
    'services',
    async ? 'async' : 'blocking',
    ...namespace,
    Names.ServiceType({ resource, async }),
  ],
})

// after
const pkg = [
  ...OrgPackage(),
  'services',
  async ? 'async' : 'blocking',
  ...namespace,
  Names.ServiceType({ resource, async }),
]

This is simple to do with list matching:

// astx.js
exports.find = `OrgPackage({subPackage: [$$p]})`
exports.replace = `[...OrgPackage(), $$p]`

Convert require statements to imports

// astx.js
exports.find = `const $id = require('$source')`
exports.replace = `import $id from '$source'`

Combine unnecessary nested if statements

// astx.js
export const find = `if ($a) { if ($b) $body }`
export const replace = `if ($a && $b) $body`

Make code DRY

In jscodeshift-add-imports I had a bunch of test cases following this pattern:

it(`leaves existing default imports untouched`, function () {
  const code = `import Baz from 'baz'`
  const root = j(code)
  const result = addImports(root, statement`import Foo from 'baz'`)
  expect(result).to.deep.equal({ Foo: 'Baz' })
  expect(root.toSource()).to.equal(code)
})

I wanted to make them more DRY, like this:

it(`leaves existing default imports untouched`, function () {
  testCase({
    code: `import Baz from 'baz'`,
    add: `import Foo from 'baz'`,
    expectedCode: `import Baz from 'baz'`,
    expectedReturn: { Foo: 'Baz' },
  })
})

Here was a transform for the above. (Of course, I had to run a few variations of this for cases where the expected code was different, etc.)

exports.find = `
const code = $code
const root = j(code)
const result = addImports(root, statement\`$add\`)
expect(result).to.deep.equal($expectedReturn)
expect(root.toSource()).to.equal(code)
`

exports.replace = `
testCase({
  code: $code,
  add: \`$add\`,
  expectedCode: $code,
  expectedReturn: $expectedReturn,
})
`

Roadmap

I just finally got version 2 released as of December 2022 after tons of hard work 🎉 Right now I'm working on the VSCode Extension. After that I want to make a documentation website that better illustrates how to use astx.

VSCode Extension

The VSCode Extension is currently in beta, but try it out!

Screenshot

Prior art and philosophy

While I was thinking about making this I discovered grasp, a similar tool that inspired the $ capture syntax. There are several reasons I decided to make astx anyway:

  • Grasp uses the Acorn parser, which doesn't support TypeScript or Flow code AFAIK
  • Hasn't be
View on GitHub
GitHub Stars143
CategoryContent
Updated15d ago
Forks6

Languages

TypeScript

Security Score

100/100

Audited on Mar 26, 2026

No findings