{"id":13493504,"url":"https://github.com/codemodsquad/astx","last_synced_at":"2025-07-10T01:30:59.444Z","repository":{"id":38418435,"uuid":"315412856","full_name":"codemodsquad/astx","owner":"codemodsquad","description":"Super powerful structural search and replace for JavaScript and TypeScript to automate your refactoring","archived":false,"fork":false,"pushed_at":"2024-04-14T19:58:11.000Z","size":2916,"stargazers_count":85,"open_issues_count":9,"forks_count":6,"subscribers_count":4,"default_branch":"beta","last_synced_at":"2024-04-14T21:35:17.693Z","etag":null,"topics":["astx","automated","codemod","find","javascript","match","matching","pattern","refactor","refactoring","replace","rewrite","rewriting","search","semantic","structural","syntax","transform","transformation","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/codemodsquad.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2020-11-23T19:00:45.000Z","updated_at":"2024-04-18T04:31:45.028Z","dependencies_parsed_at":"2023-01-25T01:30:29.089Z","dependency_job_id":"a0bb0df4-5547-4104-b59e-e3bc9a90408b","html_url":"https://github.com/codemodsquad/astx","commit_stats":{"total_commits":277,"total_committers":1,"mean_commits":277.0,"dds":0.0,"last_synced_commit":"5eaec0f5b726f2fa7b53d8fa563cc4f562b9eec4"},"previous_names":[],"tags_count":88,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codemodsquad%2Fastx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codemodsquad%2Fastx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codemodsquad%2Fastx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/codemodsquad%2Fastx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/codemodsquad","download_url":"https://codeload.github.com/codemodsquad/astx/tar.gz/refs/heads/beta","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225607490,"owners_count":17495741,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["astx","automated","codemod","find","javascript","match","matching","pattern","refactor","refactoring","replace","rewrite","rewriting","search","semantic","structural","syntax","transform","transformation","typescript"],"created_at":"2024-07-31T19:01:15.978Z","updated_at":"2024-11-20T18:21:08.702Z","avatar_url":"https://github.com/codemodsquad.png","language":"TypeScript","readme":"# astx\n\n[![CircleCI](https://circleci.com/gh/codemodsquad/astx.svg?style=svg)](https://circleci.com/gh/codemodsquad/astx)\n[![Coverage Status](https://codecov.io/gh/codemodsquad/astx/branch/master/graph/badge.svg)](https://codecov.io/gh/codemodsquad/astx)\n[![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)\n[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)\n[![npm version](https://badge.fury.io/js/astx.svg)](https://badge.fury.io/js/astx)\n\nSuper powerful structural search and replace for JavaScript and TypeScript to automate your refactoring\n\n# Table of Contents\n\n\u003c!-- toc --\u003e\n\n- [astx](#astx)\n- [Table of Contents](#table-of-contents)\n- [Introduction](#introduction)\n- [Usage examples](#usage-examples)\n  - [Fixing eslint errors](#fixing-eslint-errors)\n  - [Restructure array concatenation](#restructure-array-concatenation)\n  - [Convert require statements to imports](#convert-require-statements-to-imports)\n  - [Combine unnecessary nested if statements](#combine-unnecessary-nested-if-statements)\n  - [Make code DRY](#make-code-dry)\n- [Roadmap](#roadmap)\n- [VSCode Extension](#vscode-extension)\n- [Prior art and philosophy](#prior-art-and-philosophy)\n- [Pattern Language](#pattern-language)\n  - [Placeholders](#placeholders)\n    - [Node Placeholders (`$\u003cname\u003e`)](#node-placeholders-name)\n    - [Array Placeholders (`$$\u003cname\u003e`)](#array-placeholders-name)\n    - [Rest Placeholders (`$$$\u003cname\u003e`)](#rest-placeholders-name)\n    - [Anonymous Placeholders](#anonymous-placeholders)\n    - [Backreferences](#backreferences)\n  - [Object Matching](#object-matching)\n  - [List Matching](#list-matching)\n    - [Ordered and unordered List Matching](#ordered-and-unordered-list-matching)\n    - [Support Table](#support-table)\n  - [String Matching](#string-matching)\n  - [Extracting nodes](#extracting-nodes)\n  - [Special Constructs](#special-constructs)\n    - [`$Maybe(pattern)`](#maybepattern)\n    - [`$Or(...)`](#or)\n    - [`$And(...)`](#and)\n    - [`$Maybe\u003cpattern\u003e`](#maybepattern-1)\n    - [`$Or\u003c...\u003e`](#or-1)\n    - [`$And\u003c...\u003e`](#and-1)\n    - [`$Ordered`](#ordered)\n    - [`$Unordered`](#unordered)\n- [API](#api)\n  - [interface NodePath](#interface-nodepath)\n  - [class Astx](#class-astx)\n    - [`constructor(backend: Backend, paths: NodePath\u003cany\u003e[] | Match[], options?: { withCaptures?: Match[] })`](#constructorbackend-backend-paths-nodepathany--match-options--withcaptures-match-)\n    - [`.find(...)` (`Astx`)](#find-astx)\n    - [`.closest(...)` (`Astx`)](#closest-astx)\n    - [`.destruct(...)` (`Astx`)](#destruct-astx)\n    - [`FindOptions`](#findoptions)\n      - [`FindOptions.where` (`{ [captureName: string]: (path: Astx) =\u003e boolean }`)](#findoptionswhere--capturename-string-path-astx--boolean-)\n    - [`.find(...).replace(...)` (`void`)](#findreplace-void)\n    - [`.findImports(...)` (`Astx`)](#findimports-astx)\n    - [`.addImports(...)` (`Astx`)](#addimports-astx)\n    - [`.removeImports(...)` (`boolean`)](#removeimports-boolean)\n    - [`.replaceImport(...).with(...)` (`boolean`)](#replaceimportwith-boolean)\n    - [`.remove()` (`void`)](#remove-void)\n    - [`.matched` (`this | null`)](#matched-this--null)\n    - [`.size()` (`number`)](#size-number)\n    - [`` [name: `$${string}` | `$$${string}` | `$$$${string}`] `` (`Astx`)](#name-string--string--string-astx)\n    - [`.placeholder` (`string | undefined`)](#placeholder-string--undefined)\n    - [`.node` (`Node`)](#node-node)\n    - [`.path` (`NodePath`)](#path-nodepath)\n    - [`.code` (`string`)](#code-string)\n    - [`.stringValue` (`string`)](#stringvalue-string)\n    - [`[Symbol.iterator]` (`Iterator\u003cAstx\u003e`)](#symboliterator-iteratorastx)\n    - [`.matches` (`Match[]`)](#matches-match)\n    - [`.match` (`Match`)](#match-match)\n    - [`.paths` (`NodePath[]`)](#paths-nodepath)\n    - [`.nodes` (`Node[]`)](#nodes-node)\n    - [`.some(predicate)` (`boolean`)](#somepredicate-boolean)\n    - [`.every(predicate)` (`boolean`)](#everypredicate-boolean)\n    - [`.filter(iteratee)` (`Astx`)](#filteriteratee-astx)\n    - [`.map\u003cT\u003e(iteratee)` (`T[]`)](#maptiteratee-t)\n    - [`.at(index)` (`Astx`)](#atindex-astx)\n    - [`.withCaptures(...captures)` (`Astx`)](#withcapturescaptures-astx)\n  - [Match](#match)\n    - [`.type`](#type)\n    - [`.path`](#path)\n    - [`.node`](#node)\n    - [`.paths`](#paths)\n    - [`.nodes`](#nodes)\n    - [`.captures`](#captures)\n    - [`.pathCaptures`](#pathcaptures)\n    - [`.arrayCaptures`](#arraycaptures)\n    - [`.arrayPathCaptures`](#arraypathcaptures)\n    - [`.stringCaptures`](#stringcaptures)\n- [Transform files](#transform-files)\n  - [`exports.find` (optional)](#exportsfind-optional)\n  - [`exports.where` (optional)](#exportswhere-optional)\n  - [`exports.replace` (optional)](#exportsreplace-optional)\n  - [`exports.astx` (optional)](#exportsastx-optional)\n  - [`exports.onReport` (optional)](#exportsonreport-optional)\n  - [`exports.finish` (optional)](#exportsfinish-optional)\n- [Configuration](#configuration)\n  - [Preserving Formatting](#preserving-formatting)\n  - [Config option: `parser`](#config-option-parser)\n  - [Config option: `parserOptions`](#config-option-parseroptions)\n  - [Config option: `prettier`](#config-option-prettier)\n- [CLI](#cli)\n\n\u003c!-- tocstop --\u003e\n\n# Introduction\n\nSimple refactors can be tedious and repetitive. For example say you want to make the following change\nacross a codebase:\n\n```js\n// before:\nrmdir('old/stuff')\nrmdir('new/stuff', true)\n\n// after:\nrmdir('old/stuff')\nrmdir('new/stuff', { force: true })\n```\n\nChanging 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\nlinebreaks 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...\n\nNow there's a better option...you can refactor with confidence using `astx`!\n\n```sh\nastx \\\n  --find    'rmdir($path, $force)' \\\n  --replace 'rmdir($path, { force: $force })'\n```\n\nThis is a basic example of _`astx` [patterns](#pattern-language)_, which are just JS or TS code that can contain [placeholders](#placeholders) and other [special matching constructs](#special-constructs); `astx` looks for\ncode matching the pattern, accepting any expression in place of `$path` and `$force`. Then `astx` replaces\neach match with the replace pattern, substituting the expressions it captured for `$path` (`'new/stuff'`) and `$force` (`true`).\n\nBut this is just the beginning; `astx` patterns can be much more complex and powerful than this, and for really advanced use cases it has\nan intuitive [API](#api) you can use:\n\n```ts\nfor (const match of astx.find`rmdir($path, $force)`) {\n  const { $path, $force } = match\n  // do stuff with $path.node, $path.code, etc...\n}\n```\n\n# Usage examples\n\n## Fixing eslint errors\n\nGot a lot of `Do not access Object.prototype method 'hasOwnProperty' from target object` errors?\n\n```js\n// astx.js\nexports.find = `$a.hasOwnProperty($b)`\nexports.replace = `Object.hasOwn($a, $b)`\n```\n\n## Restructure array concatenation\n\nRecently for work I wanted to make this change:\n\n```ts\n// before\nconst pkg = OrgPackage({\n  subPackage: [\n    'services',\n    async ? 'async' : 'blocking',\n    ...namespace,\n    Names.ServiceType({ resource, async }),\n  ],\n})\n\n// after\nconst pkg = [\n  ...OrgPackage(),\n  'services',\n  async ? 'async' : 'blocking',\n  ...namespace,\n  Names.ServiceType({ resource, async }),\n]\n```\n\nThis is simple to do with [list matching](#list-matching):\n\n```js\n// astx.js\nexports.find = `OrgPackage({subPackage: [$$p]})`\nexports.replace = `[...OrgPackage(), $$p]`\n```\n\n## Convert require statements to imports\n\n```js\n// astx.js\nexports.find = `const $id = require('$source')`\nexports.replace = `import $id from '$source'`\n```\n\n## Combine unnecessary nested if statements\n\n```js\n// astx.js\nexport const find = `if ($a) { if ($b) $body }`\nexport const replace = `if ($a \u0026\u0026 $b) $body`\n```\n\n## Make code DRY\n\nIn `jscodeshift-add-imports` I had a bunch of test cases following this pattern:\n\n```js\nit(`leaves existing default imports untouched`, function () {\n  const code = `import Baz from 'baz'`\n  const root = j(code)\n  const result = addImports(root, statement`import Foo from 'baz'`)\n  expect(result).to.deep.equal({ Foo: 'Baz' })\n  expect(root.toSource()).to.equal(code)\n})\n```\n\nI wanted to make them more DRY, like this:\n\n```js\nit(`leaves existing default imports untouched`, function () {\n  testCase({\n    code: `import Baz from 'baz'`,\n    add: `import Foo from 'baz'`,\n    expectedCode: `import Baz from 'baz'`,\n    expectedReturn: { Foo: 'Baz' },\n  })\n})\n```\n\nHere was a transform for the above. (Of course, I had to run a few variations of this for\ncases where the expected code was different, etc.)\n\n```js\nexports.find = `\nconst code = $code\nconst root = j(code)\nconst result = addImports(root, statement\\`$add\\`)\nexpect(result).to.deep.equal($expectedReturn)\nexpect(root.toSource()).to.equal(code)\n`\n\nexports.replace = `\ntestCase({\n  code: $code,\n  add: \\`$add\\`,\n  expectedCode: $code,\n  expectedReturn: $expectedReturn,\n})\n`\n```\n\n# Roadmap\n\nI just finally got version 2 released as of December 2022 after tons of hard work 🎉\nRight now I'm working on the [VSCode Extension](https://github.com/codemodsquad/vscode-astx).\nAfter that I want to make a documentation website that better illustrates how to use `astx`.\n\n# VSCode Extension\n\nThe [VSCode Extension](https://github.com/codemodsquad/vscode-astx) is currently in beta, but try it out!\n\n![Screenshot](screenshot.png)\n\n# Prior art and philosophy\n\nWhile I was thinking about making this I discovered [grasp](https://www.graspjs.com/), a similar tool that inspired the `$` capture syntax.\nThere are several reasons I decided to make `astx` anyway:\n\n- Grasp uses the Acorn parser, which doesn't support TypeScript or Flow code AFAIK\n- Hasn't been updated in 4 years\n- Grasp's replace pattern syntax is clunkier, doesn't match the find pattern syntax:\n  `grasp -e 'setValue($k, $v, true)' -R 'setValueSilently({{k}}, {{v}})' file.js`\n- It has its own DSL (SQuery) that's pretty limited and has a slight learning curve\n- I wanted a `jscodeshift`-like API I could use in JS for advanced use cases that are probably awkward/impossible in Grasp\n\nSo the philosophy of `astx` is:\n\n- **Provide a simple find and replace pattern syntax that's ideal for simple cases and has minimum learning curve**\n- **Use the same search and replace pattern syntax in the javascript API for anything more complex, so that you have unlimited flexibility**\n\nPaste your code into [AST Explorer](https://astexplorer.net/) if you need to learn about the structure of the AST.\n\n# Pattern Language\n\nAstx find patterns are just JavaScript or TypeScript code that may contain _placeholder_ wildcards or other special constructs\nlike `$Or(A, B)`. Generally speaking, parts of the pattern that aren't wildcards or special constructs have to match exactly.\n\nFor example, the find pattern `foo($a)` matches any call to the function `foo` with a single argument. The argument can anything\nand is _captured_ as `$a`.\n\nReplace patterns are almost identical to find patterns, except that placeholders get replaced with whatever was _captured_ into\nthe placeholder name by the find pattern, and special find constructs like `$Or(A, B)` have no special meaning in replace patterns.\n(In the future, there may be special replace constructs that perform some kind of transformation on captured nodes.)\n\nFor example, the find pattern `foo($a)` matches `foo(1 + 2)`, then the replace pattern `foo({ value: $a })` will generate the code\n`foo({ value: 1 + 2 })`.\n\n## Placeholders\n\nGenerally speaking, an identifier starting with `$` is a _placeholder_ that functions like a wildcard. There are three types of placeholders:\n\n- `$\u003cname\u003e` matches any single node (\"node placeholder\")\n- `$$\u003cname\u003e` matches a contiguous list of nodes (\"array placeholder\")\n- `$$$\u003cname\u003e`: matches all other siblings (\"rest placeholder\")\n\nThe `\u003cname\u003e` (if given) must start with a letter or number; otherwise the identifier will\nnot be treated as a placeholder.\n\nRest placeholders (`$$$`) may not be sibilings of ordered list placeholders (`$$`).\n\nUnless a placeholder is anonymous, it will \"capture\" the matched node(s), meaning you can use the same placeholder in the\nreplacement pattern to interpolate the matched node(s) into the generated replacement. In the Node API you can also access\nthe captured AST paths/nodes via the placeholder name.\n\n### Node Placeholders (`$\u003cname\u003e`)\n\nThese placeholders match a single node. For example, the pattern `[$a, $b]` matches an array expression with two elements,\nand those elements are captured as `$a` and `$b`.\n\n### Array Placeholders (`$$\u003cname\u003e`)\n\nThese placeholders match a contiguous list of nodes. For example, the pattern `[1, $$a, 2, $$b]` matches an array expression\nwith `1` as the first element, and `2` as a succeeding element. Any elements between `1` and the first `2` is captured as `$$a`,\nand elements after the first `2` are captured as `$$b`.\n\n### Rest Placeholders (`$$$\u003cname\u003e`)\n\nThese placeholders match the rest of the siblings that weren't matched by something else. For example, the pattern `[1, $$$a, 2]`\nmatches an array expression that has elements `1` and `2` at any index. Any other elements (including additional occurrences of `1`\nand `2`) are captured as `$$$a`.\n\n### Anonymous Placeholders\n\nYou can use a placeholder without a name to match node(s) without capturing them. `$` will match any single node, `$$` will match a\ncontiguous list of nodes, and `$$$` will match all other siblings.\n\n### Backreferences\n\nIf you use the same capture placeholder more than once, subsequent positions will have to match what was captured for the first occurrence of the placeholder.\n\nFor example, the pattern `foo($a, $a, $b, $b)` will match only `foo(1, 1, {foo: 1}, {foo: 1})` in the following:\n\n```js\nfoo(1, 1, { foo: 1 }, { foo: 1 }) // match\nfoo(1, 2, { foo: 1 }, { foo: 1 }) // no match\nfoo(1, 1, { foo: 1 }, { bar: 1 }) // no match\n```\n\n**Note**: array capture placeholders (`$$a`) and rest capture placeholders (`$$$a`) don't currently support backreferencing.\n\n## Object Matching\n\nAn `ObjectExpression` (aka object literal) pattern will match any `ObjectExpression` in your code with the same properties in any order.\nIt will not match if there are missing or additional properties. For example, `{ foo: 1, bar: $bar }` will match `{ foo: 1, bar: 2 }` or `{ bar: 'hello', foo: 1 }`\nbut not `{ foo: 1 }` or `{ foo: 1, bar: 2, baz: 3 }`.\n\nYou can match additional properties by using `...$$captureName`, for example `{ foo: 1, ...$$rest }` will match `{ foo: 1 }`, `{ foo: 1, bar: 2 }`, `{ foo: 1, bar: 2, ...props }` etc.\nThe additional properties will be captured in `match.arrayCaptures`/`match.arrayPathCaptures`, and can be spread in replacement expressions. For example,\n`` astx.find`{ foo: 1, ...$$rest }`.replace`{ bar: 1, ...$$rest }` `` will transform `{ foo: 1, qux: {}, ...props }` into `{ bar: 1, qux: {}, ...props }`.\n\nA spread property that isn't of the form `/^\\$\\$[a-z0-9]+$/i` is not a capture placeholder, for example `{ ...foo }` will only match `{ ...foo }` and `{ ...$_$foo }` will only\nmatch `{ ...$$foo }` (leading `$_` is an escape for `$`).\n\nThere is currently no way to match properties in a specific order, but it could be added in the future.\n\n## List Matching\n\nIn many cases where there is a list of nodes in the AST you can match\nmultiple elements with a placeholder starting with `$$`. For example, `[$$before, 3, $$after]` will match any array expression containing an element `3`; elements before the\nfirst `3` will be captured in `$$before` and elements after the first `3` will be captured in `$$after`.\n\nThis works even with block statements. For example, `function foo() { $$before; throw new Error('test'); $$after; }` will match `function foo()` that contains a `throw new Error('test')`,\nand the statements before and after that throw statement will get captured in `$$before` and `$$after`, respectively.\n\n### Ordered and unordered List Matching\n\nIn some cases list matching will be ordered by default, and in some cases it will be unordered. For example, `ObjectExpression` property matches are unordered by default, as shown in the table below.\nUsing a `$$` placeholder or the special `$Ordered` placeholder will force ordered matching. Using a `$$$` placeholder or the special `$Unordered` placeholder will force unordered matching.\n\nIf you use a placeholder starting with `$$$`, it's treated as a \"rest\" capture, and all other elements of the\nmatch expression will be matched out of order. For example, `import {a, b, $$$rest} from 'foo'` would match\n`import {c, b, d, e, a} from 'foo'`, putting specifiers `c`, `d`, and `e`, into the `$$$rest` placeholder.\n\nRest placeholders (`$$$`) may not be sibilings of ordered list placeholders (`$$`).\n\n### Support Table\n\nSome items marked TODO probably actually work, but are untested.\n\n| Type                                                  | Supports list matching?                    | Unordered by default? | Notes                                                                      |\n| ----------------------------------------------------- | ------------------------------------------ | --------------------- | -------------------------------------------------------------------------- |\n| `ArrayExpression.elements`                            | ✅                                         |                       |                                                                            |\n| `ArrayPattern.elements`                               | ✅                                         |                       |                                                                            |\n| `BlockStatement.body`                                 | ✅                                         |                       |                                                                            |\n| `CallExpression.arguments`                            | ✅                                         |                       |                                                                            |\n| `Class(Declaration/Expression).implements`            | ✅                                         | ✅                    |                                                                            |\n| `ClassBody.body`                                      | ✅                                         | ✅                    |                                                                            |\n| `ComprehensionExpression.blocks`                      | TODO                                       |                       |                                                                            |\n| `DeclareClass.body`                                   | TODO                                       | ✅                    |                                                                            |\n| `DeclareClass.implements`                             | TODO                                       | ✅                    |                                                                            |\n| `DeclareExportDeclaration.specifiers`                 | TODO                                       | ✅                    |                                                                            |\n| `DeclareInterface.body`                               | TODO                                       |                       |                                                                            |\n| `DeclareInterface.extends`                            | TODO                                       |                       |                                                                            |\n| `DoExpression.body`                                   | TODO                                       |                       |                                                                            |\n| `ExportNamedDeclaration.specifiers`                   | ✅                                         | ✅                    |                                                                            |\n| `Function.decorators`                                 | TODO                                       |                       |                                                                            |\n| `Function.params`                                     | ✅                                         |                       |                                                                            |\n| `FunctionTypeAnnotation/TSFunctionType.params`        | ✅                                         |                       |                                                                            |\n| `GeneratorExpression.blocks`                          | TODO                                       |                       |                                                                            |\n| `ImportDeclaration.specifiers`                        | ✅                                         | ✅                    |                                                                            |\n| `(TS)InterfaceDeclaration.body`                       | TODO                                       | ✅                    |                                                                            |\n| `(TS)InterfaceDeclaration.extends`                    | TODO                                       | ✅                    |                                                                            |\n| `IntersectionTypeAnnotation/TSIntersectionType.types` | ✅                                         | ✅                    |                                                                            |\n| `JSX(Element/Fragment).children`                      | ✅                                         |                       |                                                                            |\n| `JSX(Opening)Element.attributes`                      | ✅                                         | ✅                    |                                                                            |\n| `MethodDefinition.decorators`                         | TODO                                       |                       |                                                                            |\n| `NewExpression.arguments`                             | ✅                                         |                       |                                                                            |\n| `ObjectExpression.properties`                         | ✅                                         | ✅                    |                                                                            |\n| `ObjectPattern.decorators`                            | TODO                                       |                       |                                                                            |\n| `ObjectPattern.properties`                            | ✅                                         | ✅                    |                                                                            |\n| `(ObjectTypeAnnotation/TSTypeLiteral).properties`     | ✅                                         | ✅                    | Use `$a: $` to match one property, `$$a: $` or `$$$a: $` to match multiple |\n| `Program.body`                                        | ✅                                         |                       |                                                                            |\n| `Property.decorators`                                 | TODO                                       |                       |                                                                            |\n| `SequenceExpression`                                  | ✅                                         |                       |                                                                            |\n| `SwitchCase.consequent`                               | ✅                                         |                       |                                                                            |\n| `SwitchStatement.cases`                               | TODO                                       |                       |                                                                            |\n| `TemplateLiteral.quasis/expressions`                  | ❓ not sure if I can come up with a syntax |                       |                                                                            |\n| `TryStatement.guardedHandlers`                        | TODO                                       |                       |                                                                            |\n| `TryStatement.handlers`                               | TODO                                       |                       |                                                                            |\n| `TSFunctionType.parameters`                           | ✅                                         |                       |                                                                            |\n| `TSCallSignatureDeclaration.parameters`               | TODO                                       |                       |                                                                            |\n| `TSConstructorType.parameters`                        | TODO                                       |                       |                                                                            |\n| `TSConstructSignatureDeclaration.parameters`          | TODO                                       |                       |                                                                            |\n| `TSDeclareFunction.params`                            | TODO                                       |                       |                                                                            |\n| `TSDeclareMethod.params`                              | TODO                                       |                       |                                                                            |\n| `EnumDeclaration.body/TSEnumDeclaration.members`      | TODO                                       | ✅                    |                                                                            |\n| `TSIndexSignature.parameters`                         | TODO                                       |                       |                                                                            |\n| `TSMethodSignature.parameters`                        | TODO                                       |                       |                                                                            |\n| `TSModuleBlock.body`                                  | TODO                                       |                       |                                                                            |\n| `TSTypeLiteral.members`                               | ✅                                         | ✅                    |                                                                            |\n| `TupleTypeAnnotation/TSTupleType.types`               | ✅                                         |                       |                                                                            |\n| `(TS)TypeParameterDeclaration.params`                 | ✅                                         |                       |                                                                            |\n| `(TS)TypeParameterInstantiation.params`               | ✅                                         |                       |                                                                            |\n| `UnionTypeAnnotation/TSUnionType.types`               | ✅                                         | ✅                    |                                                                            |\n| `VariableDeclaration.declarations`                    | ✅                                         |                       |                                                                            |\n| `WithStatement.body`                                  | ❌ who uses with statements...             |                       |                                                                            |\n\n## String Matching\n\nA string that's just a placeholder like `'$foo'` will match any string and capture its contents into `match.stringCaptures.$foo`.\nThe same escaping rules apply as for identifiers. This also works for template literals like `` `$foo` `` and tagged template literals like `` doSomething`$foo` ``.\n\nThis can be helpful for working with import statements. For example, see [Converting require statements to imports](#converting-require-statements-to-imports).\n\n## Extracting nodes\n\nAn empty comment (`/**/`) in a pattern will \"extract\" a node for matching.\nFor example the pattern `const x = { /**/ $key: $value }` will just\nmatch `ObjectProperty` nodes against `$key: $value`.\n\nThe parser wouldn't be able to parse `$key: $value` by itself or\nknow that you mean an `ObjectProperty`, as opposed to something different like the `x: number` in `const x: number = 1`, so using `/**/` enables you to work around this. You can use this to match any node type that isn't a valid expression or statement by itself. For example `type T = /**/ Array\u003cnumber\u003e`\nwould match `Array\u003cnumber\u003e` type annotations.\n\n`/**/` also works in replacement patterns.\n\n## Special Constructs\n\n### `$Maybe(pattern)`\n\nMatches either the given expression or no node in its place. For example `let $a = $Maybe(2)` will match `let foo = 2` and `let foo` (with no initializer), but not `let foo = 3`.\n\n### `$Or(...)`\n\nMatches nodes that match at least one of the given patterns. For example `$Or(foo($$args), {a: $value})` will match calls to `foo` and object literals with only an `a` property.\n\n### `$And(...)`\n\nMatches nodes that match all of the given patterns. This is mostly useful for narrowing down the types of nodes that can be captured into a given placeholder. For example, `let $a = $And($init, $a + $b)` will match `let` declarations where the initializer matches `$a + $b`, and capture the initializer as `$init`.\n\n### `$Maybe\u003cpattern\u003e`\n\nMatches either the given type annotation or no node in its place. For example `let $a: $Maybe\u003cnumber\u003e` will match `let foo: number` and `let foo` (with no type annotation), but not ` let foo: string``let foo: string `.\n\n### `$Or\u003c...\u003e`\n\nMatches nodes that match at least one of the given type annotations. For example `let $x: $Or\u003cnumber[], string[]\u003e` will match `let` declarations of type `number[]` or `string[]`.\n\n### `$And\u003c...\u003e`\n\nMatches nodes that match all of the given type annotations. This is mostly useful for narrowing down the types of nodes that can be captured into a given placeholder. For example, `let $a: $And\u003c$type, $elem[]\u003e` will match `let` declarations where the type annotation matches `$elem[]`, and capture the type annotation as `$type`.\n\n### `$Ordered`\n\nForces the pattern to match sibling nodes in the same order.\n\n### `$Unordered`\n\nForces the pattern to match sibling nodes in any order.\n\n# API\n\n## interface NodePath\n\n```ts\nimport { NodePath } from 'astx'\n```\n\nThis is the same `NodePath` interface as `ast-types`, with some improvements to the method type definitions.\n`astx` uses `ast-types` to traverse code, in hopes of supporting different parsers in the future.\n\n## class Astx\n\n```ts\nimport { Astx } from 'astx'\n```\n\n### `constructor(backend: Backend, paths: NodePath\u003cany\u003e[] | Match[], options?: { withCaptures?: Match[] })`\n\n`backend` is the parser/generator implementation being used.\n\n`paths` specifies the `NodePath`s or `Match`es you want `Astx` methods\nto search/operate on.\n\n### `.find(...)` (`Astx`)\n\nFinds matches for the given pattern within this instance's starting paths and returns an `Astx` instance containing the matches.\n\nIf you call `astx.find('foo($$args)')` on the initial instance passed to your transform function, it will find all calls to `foo` within the file, and return those matches in a new `Astx` instance.\n\nMethods on the returned instance will operate only on the matched paths.\n\nFor example if you do `astx.find('foo($$args)').find('$a + $b')`, the second `find` call will only search for `$a + $b` within matches to `foo($$args)`, rather than anywhere in the file.\n\nYou can call `.find` as a method or tagged template literal:\n\n- `` .find`pattern` ``\n- `.find(pattern: string | string[] | Node | Node[] | NodePath | NodePath[] | ((wrapper: Astx) =\u003e boolean), options?: FindOptions)`\n\nIf you give the pattern as a string, it must be a valid expression or statement(s). Otherwise it should be valid\nAST node(s) you already parsed or constructed.\nYou can interpolate strings, AST nodes, arrays of AST nodes, and `Astx` instances in the tagged template literal.\n\nFor example you could do `` astx.find`${t.identifier('foo')} + 3` ``.\n\nOr you could match multiple statements by doing\n\n```ts\nastx.find`\n  const $a = $b;\n  $$c;\n  const $d = $a + $e;\n`\n```\n\nThis would match (for example) the statements `const foo = 1; const bar = foo + 5;`, with any number of statements between them.\n\n### `.closest(...)` (`Astx`)\n\nLike `.find()`, but searches up the AST ancestors instead of down into descendants; finds the closest enclosing node of each input path that matches the given pattern.\n\n### `.destruct(...)` (`Astx`)\n\nLike `.find()`, but doesn't test descendants of the input path(s) against the pattern; only input paths matching the pattern will be included\nin the result.\n\n### `FindOptions`\n\nAn object with the following optional properties:\n\n#### `FindOptions.where` (`{ [captureName: string]: (path: Astx) =\u003e boolean }`)\n\nWhere conditions for node captures. For example if your find pattern is `$a()`, you could have\n`{ where: { $a: astx =\u003e /foo|bar/.test(astx.node.name) } }`, which would only match zero-argument calls\nto `foo` or `bar`.\n\n### `.find(...).replace(...)` (`void`)\n\nFinds and replaces matches for the given pattern within `root`.\n\nThere are several different ways you can call `.replace`. You can call `.find` in any way described above.\n\n- `` .find(...).replace`replacement` ``\n- `.find(...).replace(replacement: string | string | Node | Node[])`\n- `.find(...).replace(replacement: (match: Astx, parse: ParsePattern) =\u003e string)`\n- `.find(...).replace(replacement: (match: Astx, parse: ParsePattern) =\u003e Node | Node[])`\n\nIf you give the replacement as a string, it must be a valid expression or statement.\nYou can give the replacement as AST node(s) you already parsed or constructed.\nOr you can give a replacement function, which will be called with each match and must return a string or `Node | Node[]` (you can use the `parse` tagged template string function provided as the second argument to parse code into a string.\nFor example, you could uppercase the function names in all zero-argument function calls (`foo(); bar()` becomes `FOO(); BAR()`) with this:\n\n```\nastx\n  .find`$fn()`\n  .replace(({ captures: { $fn } }) =\u003e `${$fn.name.toUpperCase()}()`)\n```\n\n### `.findImports(...)` (`Astx`)\n\nA convenience version of `.find()` for finding imports that tolerates extra specifiers,\nmatches value imports of the same name if type imports were requested, etc.\n\nFor example `` .findImports`import $a from 'a'` `` would match `import A, { b, c } from 'a'`\nor `import { default as a } from 'a'`, capturing `$a`, whereas `` .find`import $a from 'a'` ``\nwould not match either of these cases.\n\nThe pattern must contain only import statements.\n\n### `.addImports(...)` (`Astx`)\n\nLike `.findImports()`, but adds any imports that were not found. For example given the\nsource code:\n\n```ts\nimport { foo, type bar as qux } from 'foo'\nimport 'g'\n```\n\nAnd the operation\n\n```ts\nconst { $bar } = astx.addImports`\n  import type { bar as $bar } from 'foo'\n  import FooDefault from 'foo'\n  import * as g from 'g'\n`\n```\n\nThe output would be\n\n```ts\nimport FooDefault, { foo, type bar as qux } from 'foo'\nimport * as g from 'g'\n```\n\nWith `$bar` capturing the identifier `qux`.\n\n### `.removeImports(...)` (`boolean`)\n\nTakes import statements in the same format as `.findImports()` but removes all given specifiers.\n\n### `.replaceImport(...).with(...)` (`boolean`)\n\nReplaces a single import specifier with another. For example given the input\n\n```ts\nimport { Match, Route, Location } from 'react-router-dom'\nimport type { History } from 'history'\n```\n\nAnd operation\n\n```ts\nastx.replaceImport`\n  import { Location } from 'react-router-dom'\n`.with`\n  import type { Location } from 'history'\n`\n```\n\nThe output would be\n\n```ts\nimport { Match, Route } from 'react-router-dom'\nimport type { History, Location } from 'history'\n```\n\nThe find and replace patterns must both contain a single import statement\nwith a single specifier.\n\n### `.remove()` (`void`)\n\nRemoves the matches from `.find()` or focused capture(s) in this `Astx` instance.\n\n### `.matched` (`this | null`)\n\nReturns this `Astx` instance if it has at least one match, otherwise returns `null`.\n\nSince `.find()`, `.closest()`, and `.destruct()` always return an `Astx` instance, even if there were no\nmatches, you can use `.find(...).matched` if you only want a defined value when there was at least\none match.\n\n### `.size()` (`number`)\n\nReturns the number of matches from the `.find()` or `.closest()` call that returned this instance.\n\n### `` [name: `$${string}` | `$$${string}` | `$$$${string}`] `` (`Astx`)\n\nGets an `Astx` instance focused on the capture(s) with the given `name`.\n\nFor example, you can do:\n\n```ts\nfor (const { $v } of astx.find`process.env.$v`) {\n  report($v.code)\n}\n```\n\n### `.placeholder` (`string | undefined`)\n\nThe name of the placeholder this instance represents. For example:\n\n```ts\nconst match = astx.find`function $fn($$params) { $$body }`\nconsole.log(match.placeholder) // undefined\nconst { $fn, $$params } = match\nconsole.log($fn.placeholder) // $fn\nconsole.log($$params.placeholder) // $$params\n```\n\n### `.node` (`Node`)\n\nReturns the first node of the first match. Throws an error if there are no matches.\n\n### `.path` (`NodePath`)\n\nReturns the first path of the first match. Throws an error if there are no matches.\n\n### `.code` (`string`)\n\nGenerates code from the first node of the first match. Throws an error if there are no matches.\n\n### `.stringValue` (`string`)\n\nReturns the string value of the first node if the focused capture is a string capture. Throws an error if there are no matches.\n\n### `[Symbol.iterator]` (`Iterator\u003cAstx\u003e`)\n\nIterates through each match, returning an `Astx` instance for each match.\n\n### `.matches` (`Match[]`)\n\nGets the matches from the `.find()` or `.closest()` call that returned this instance.\n\n### `.match` (`Match`)\n\nGets the first match from the `.find()` or `.closest()` call that returned this instance.\n\nThrows an error if there were no matches.\n\n### `.paths` (`NodePath[]`)\n\nReturns the paths that `.find()` and `.closest()` will search within.\nIf this instance was returned by `.find()` or `.closest()`, these are\nthe paths of nodes that matched the search pattern.\n\n### `.nodes` (`Node[]`)\n\nReturns the nodes that `.find()` and `.closest()` will search within.\nIf this instance was returned by `.find()` or `.closest()`, these are\nthe nodes that matched the search pattern.\n\n### `.some(predicate)` (`boolean`)\n\nReturns `false` unless `predicate` returns truthy for at least one match.\n\n`iteratee` is function that will be called with `match: Astx, index: number, parent: Astx` and returns `true` or `false`.\n\n### `.every(predicate)` (`boolean`)\n\nReturns `true` unelss `predicate` returns falsy for at least one match.\n\n`iteratee` is function that will be called with `match: Astx, index: number, parent: Astx` and returns `true` or `false`.\n\n### `.filter(iteratee)` (`Astx`)\n\nFilters the matches.\n\n`iteratee` is function that will be called with `match: Astx, index: number, parent: Astx` and returns `true` or `false`. Only matches for which `iteratee` returns `true` will be included in the result.\n\n### `.map\u003cT\u003e(iteratee)` (`T[]`)\n\nMaps the matches.\n\n`iteratee` is function that will be called with `match: Astx, index: number, parent: Astx` and returns the value to include\nin the result array.\n\n### `.at(index)` (`Astx`)\n\nSelects the match at the given `index`.\n\n### `.withCaptures(...captures)` (`Astx`)\n\nReturns an `Astx` instance that contains captures from the given `...captures` in addition to captures present in this instance.\n\nYou can pass the following kinds of arguments:\n\n- `Astx` instances - all captures from the instance will be included.\n- `Astx[placeholder]` instances - capture(s) for the given `placeholder` will be included.\n- `{ $name: Astx[placeholder] }` - capture(s) for the given `placeholder`, renamed to `$name`.\n- [`Match`](#match) objects\n\n## Match\n\n```ts\nimport { type Match } from 'astx'\n```\n\n### `.type`\n\nThe type of match: `'node'` or `'nodes'`.\n\n### `.path`\n\nThe `NodePath` of the matched node. If `type` is `'nodes'`, this will be `paths[0]`.\n\n### `.node`\n\nThe matched `Node`. If `type` is `'nodes'`, this will be `nodes[0]`.\n\n### `.paths`\n\nThe `NodePaths` of the matched nodes.\n\n### `.nodes`\n\nThe matched `Node`s.\n\n### `.captures`\n\nThe `Node`s captured from placeholders in the match pattern. For example if the pattern was `foo($bar)`, `.captures.$bar` will be the `Node` of the first argument.\n\n### `.pathCaptures`\n\nThe `NodePath`s captured from placeholders in the match pattern. For example if the pattern was `foo($bar)`, `.pathCaptures.$bar` will be the `NodePath` of the first argument.\n\n### `.arrayCaptures`\n\nThe `Node[]`s captured from array placeholders in the match pattern. For example if the pattern was `foo({ ...$bar })`, `.arrayCaptures.$bar` will be the `Node[]`s of the object properties.\n\n### `.arrayPathCaptures`\n\nThe `NodePath[]`s captured from array placeholders in the match pattern. For example if the pattern was `foo({ ...$bar })`, `.pathArrayCaptures.$bar` will be the `NodePath[]`s of the object properties.\n\n### `.stringCaptures`\n\nThe string values captured from string placeholders in the match\npattern. For example if the pattern was `import foo from '$foo'`,\n`stringCaptures.$foo` will be the import path.\n\n# Transform files\n\nLike `jscodeshift`, you can put code to perform a transform in a `.ts` or `.js` file (defaults to `astx.ts` or `astx.js` in the working directory, unless you specify a different file with the `-t` CLI option).\n\nThe transform file API is a bit different from `jscodeshift` though. You can have the following exports:\n\n## `exports.find` (optional)\n\nA code string or AST node of the pattern to find in the files being transformed.\n\n## `exports.where` (optional)\n\nWhere conditions for capture placeholders in `exports.find`.\nSee [`FindOptions.where` (`{ [captureName: string]: (path: NodePath\u003cany\u003e) =\u003e boolean }`)](#findoptionswhere--capturename-string-path-NodePathany--boolean-) for more information.\n\n## `exports.replace` (optional)\n\nA code string, AST node, or replace function to replace matches of `exports.find` with.\n\nThe function arguments are the same as described in [`.find().replace()`](#findreplace-void).\n\n## `exports.astx` (optional)\n\nA function to perform an arbitrary transform using the `Astx` API. It gets called with an object with the following properties:\n\n- `file` (`string`) - The path to the file being transformed\n- `source` (`string`) - The source code of the file being transformed\n- `astx` (`Astx`) - the `Astx` API instance\n- `t` (`AstTypes`) - `ast-types` definitions for the chosen parser\n- `expression` - tagged template literal for parsing code as an expression\n- `statement` - tagged template literal for parsing code as a statement\n- `statements` - tagged template literal for parsing code as an array of statements\n- `report` (`(message: unknown) =\u003e void`)\n- `mark` (`(...matches: Astx[]) =\u003e void`) - marks the given matches to be displayed in the matches list of vscode-astx, etc\n\nUnlike `jscodeshift`, your transform function can be async, and it doesn't have to return the transformed code,\nbut you can return a `string`. You can also return `null` to\nskip the file.\n\n## `exports.onReport` (optional)\n\nIf your call `report(x)` from an [`exports.astx` function](#exportsastx-optional), this will be called with\n`onReport({ file, report: x })`.\n\nIf you are using multiple worker threads, `onReport`\nwill be called in the parent process, so the report\nmessage must be a serializable value. This allows a\ntransform to collect reports from all workers (and then\npotentially do something with them in [`finish`](#exportsfinish-optional)).\n\nIf `onReport` returns a `Promise` it will be awaited.\n\n## `exports.finish` (optional)\n\nThis will be called after the transform has been run on\nall input files.\n\nIf you are using multiple worker threads, `finish`\nwill be called in the parent process. You can use\n[`onReport`](#exportsonreport-optional) and `finish`\ntogether to collect information from each input file\nand produce some kind of combined output at the end.\n\nIf `finish` returns a `Promise` it will be awaited.\n\n# Configuration\n\n`astx` supports configuration in the following places (via [`cosmiconfig`](https://github.com/davidtheclark/cosmiconfig)):\n\n- an `astx` property in package.json\n- an `.astxrc` file in JSON or YAML format\n- an `.astxrc.json`, `.astxrc.yaml`, `.astxrc.yml`, `.astxrc.js`, or `.astxrc.cjs` file\n- an `astx.config.js` or `astx.config.cjs` CommonJS module exporting an object\n\n### Preserving Formatting\n\nIf your codebase is formatted with prettier, I recommend trying this first:\n\n```json\n{\n  \"parser\": \"babel/auto\",\n  \"parserOptions\": {\n    \"preserveFormat\": \"generatorHack\"\n  }\n}\n```\n\n(or as CLI options)\n\n```\n--parser babel/auto --parserOptions '{\"preserveFormat\": \"generatorHack\"}'\n```\n\nIf this fails you can try `parser: 'recast/babel/auto'` or the non-`/auto` parsers.\n\nYour mileage may vary with `recast`; they just aren't able to keep it up to date\nwith new syntax features in JS and TS quickly enough, and I've seen it output invalid\nsyntax too many times.\n\nFrom now on I'm going to work on a reliable solution using `@babel/generator` or `prettier`\nto print the modified AST, with a hook to use the original source verbatim for unmodified\nnodes.\n\n### Config option: `parser`\n\nThe parser to use. Options:\n\n- `babel/auto` (default,)\n- `babel` (faster than `babel/auto`, but uses default parse options instead, you may have to configure `parserOptions`)\n- `recast/babel`\n- `recast/babel/auto`\n\n`babel/auto` automatically determines parse options from your babel config if present.\n`babel` uses fixed parse options instead, so it's faster than `babel/auto`, but you may have to configure `parserOptions`.\nThe `recast/babel(/auto)` options use [`recast`](https://github.com/benjamn/recast) to preserve formatting.\nI've seen `recast` output invalid syntax on some files, so use with caution.\n\n### Config option: `parserOptions`\n\nOptions to pass to the parser. Right now this is just the [`@babel/parser` options](https://babeljs.io/docs/en/babel-parser#options) plus\nthe following additional options:\n\n- `preserveFormat` (applies to: `babel`, `babel/auto`)\n  `preserveFormat: 'generatorHack'` uses an experimental hack\n  to preserve format of all unchanged nodes by hijacking\n  internal `@babel/generator` API.\n\n### Config option: `prettier`\n\nIf `false`, don't try to use `prettier` to reformat transformed source code.\nDefaults to `true`.\n\n# CLI\n\nAstx includes a CLI for performing transforms. The CLI will process the given files, then print out a diff of what will be\nchanged, and prompt you to confirm you want to write the changes.\n\nIt will parse with babel by default using the version installed in your project and your project's babel config, if any.\nYou can pass `--parser recast/babel` if you want to use [`recast`](https://github.com/benjamn/recast) to try to preserve\nformatting in the output, but I sometimes see syntax errors in its output.\n\nUnlike `jscodeshift`, if `prettier` is installed in your project, it will format the transformed code with `prettier`.\n\n```\nUsage:\n\nastx -f \u003ccode\u003e [\u003cfiles...\u003e] [\u003cdirectories...\u003e]\n\n  Searches for the -f pattern in the given files and directories\n  and prints out the matches in context\n\nastx -f \u003ccode\u003e -r \u003ccode\u003e [\u003cfiles...\u003e] [\u003cdirectories...\u003e]\n\n  Quick search and replace in the given files and directories\n  (make sure to quote code)\n\n  Example:\n\n    astx -f 'rmdir($path, $force)' -r 'rmdir($path, { force: $force })' src\n\nastx -t \u003ctransformFile\u003e [\u003cfiles ...\u003e] [\u003cdirectories ...\u003e]\n\n  Applies a transform file to the given files and directories\n\nastx [\u003cfiles ...\u003e] [\u003cdirectories ...\u003e]\n\n  Applies the default transform file (astx.ts or astx.js in working directory)\n  to the given files and directories\n\nOptions:\n      --help           Show help                                       [boolean]\n      --version        Show version number                             [boolean]\n  -t, --transform      path to the transform file. Can be either a local path or\n                       url. Defaults to ./astx.ts or ./astx.js if --find isn't\n                       given\n      --parser         parser to use (options: babel, babel/auto, recast/babel,\n                       recast/babel/auto)                               [string]\n      --parserOptions  options for parser                               [string]\n  -f, --find           search pattern                                   [string]\n  -r, --replace        replace pattern                                  [string]\n  -y, --yes            don't ask for confirmation before writing changes\n                                                                       [boolean]\n      --gitignore      ignore gitignored files         [boolean] [default: true]\n      --workers        number of worker threads to use                  [number]\n```\n","funding_links":[],"categories":["TypeScript","typescript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodemodsquad%2Fastx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodemodsquad%2Fastx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodemodsquad%2Fastx/lists"}