--- url: /reference/cli/new.md --- # `ast-grep new` Create new ast-grep project or items like rules/tests. Also see the step by step [guide](/guide/scan-project.html). ## Usage ```shell ast-grep new [COMMAND] [OPTIONS] [NAME] ``` ## Commands ### `project` Create an new project by scaffolding. By default, this command will create a root config file `sgconfig.yml`, a rule folder `rules`, a test case folder `rule-tests` and a utility rule folder `utils`. You can customize the folder names during the creation. ### `rule` Create a new rule. This command will create a new rule in one of the `rule_dirs`. You need to provide `name` and `language` either by interactive input or via command line arguments. ast-grep will ask you which `rule_dir` to use if multiple ones are configured in the `sgconfig.yml`. If `-y, --yes` flag is true, ast-grep will choose the first `rule_dir` to create the new rule. ### `test` Create a new test case. This command will create a new test in one of the `test_dirs`. You need to provide `name` either by interactive input or via command line arguments. ast-grep will ask you which `test_dir` to use if multiple ones are configured in the `sgconfig.yml`. If `-y, --yes` flag is true, ast-grep will choose the first `test_dir` to create the new test. ### `util` Create a new global utility rule. This command will create a new global utility rule in one of the `utils` folders. You need to provide `name` and `language` either by interactive input or via command line arguments. ast-grep will ask you which `util_dir` to use if multiple ones are configured in the `sgconfig.yml`. If `-y, --yes` flag is true, ast-grep will choose the first `util_dir` to create the new item. ### `help` Print this message or the help of the given subcommand(s) ## Arguments `[NAME]` The id of the item to create ## Options ### `-l, --lang ` The language of the item to create. This option is only available when creating rule and util. ### `-y, --yes` Accept all default options without interactive input during creation. You need to provide all required arguments via command line if this flag is true. Please see the command description for the what arguments are required. ### `-c, --config ` Path to ast-grep root config, default is sgconfig.yml ### `-h, --help` Print help (see a summary with '-h') --- --- url: /reference/cli/run.md --- # `ast-grep run` Run one time search or rewrite in command line. This is the default command when you run the CLI, so `ast-grep -p 'foo()'` is equivalent to `ast-grep run -p 'foo()'`. ## Usage ```shell ast-grep run [OPTIONS] --pattern [PATHS]... ``` ## Arguments `[PATHS]...` The paths to search. You can provide multiple paths separated by spaces [default: `.`] ## Run Specific Options ### `-p, --pattern ` AST pattern to match ### `-r, --rewrite ` String to replace the matched AST node ### `-l, --lang ` The language of the pattern. For full language list, visit https://ast-grep.github.io/reference/languages.html ### `--debug-query[=]` Print query pattern's tree-sitter AST. Requires lang be set explicitly. Possible values: - **pattern**: Print the query parsed in Pattern format - **ast**: Print the query in tree-sitter AST format, only named nodes are shown - **cst**: Print the query in tree-sitter CST format, both named and unnamed nodes are shown - **sexp**: Print the query in S-expression format ### `--selector ` AST kind to extract sub-part of pattern to match. selector defines the sub-syntax node kind that is the actual matcher of the pattern. See https://ast-grep.github.io/guide/rule-config/atomic-rule.html#pattern-object. ### `--strictness ` The strictness of the pattern. More strict algorithm will match less code. See [match algorithm deep dive](/advanced/match-algorithm.html) for more details. Possible values: - **cst**: Match exact all node - **smart**: Match all node except source trivial nodes - **ast**: Match only ast nodes - **relaxed**: Match ast node except comments - **signature**: Match ast node except comments, without text [default: smart] ## Input Options ### `--no-ignore ` Do not respect hidden file system or ignore files (.gitignore, .ignore, etc.). You can suppress multiple ignore files by passing `no-ignore` multiple times. Possible values: - **hidden**: Search hidden files and directories. By default, hidden files and directories are skipped - **dot**: Don't respect .ignore files. This does *not* affect whether ast-grep will ignore files and directories whose names begin with a dot. For that, use --no-ignore hidden - **exclude**: Don't respect ignore files that are manually configured for the repository such as git's '.git/info/exclude' - **global**: Don't respect ignore files that come from "global" sources such as git's `core.excludesFile` configuration option (which defaults to `$HOME/.config/git/ignore`) - **parent**: Don't respect ignore files (.gitignore, .ignore, etc.) in parent directories - **vcs**: Don't respect version control ignore files (.gitignore, etc.). This implies --no-ignore parent for VCS files. Note that .ignore files will continue to be respected ### `--stdin` Enable search code from StdIn. Use this if you need to take code stream from standard input. ### `--globs ` Include or exclude file paths. Include or exclude files and directories for searching that match the given glob. This always overrides any other ignore logic. Multiple glob flags may be used. Globbing rules match .gitignore globs. Precede a glob with a `!` to exclude it. If multiple globs match a file or directory, the glob given later in the command line takes precedence. ### `--follow` Follow symbolic links. This flag instructs ast-grep to follow symbolic links while traversing directories. This behavior is disabled by default. Note that ast-grep will check for symbolic link loops and report errors if it finds one. ast-grep will also report errors for broken links. ## Output Options ### `-i, --interactive` Start interactive edit session. You can confirm the code change and apply it to files selectively, or you can open text editor to tweak the matched code. Note that code rewrite only happens inside a session. ### `-j, --threads ` Set the approximate number of threads to use. This flag sets the approximate number of threads to use. A value of 0 (which is the default) causes ast-grep to choose the thread count using heuristics. [default: 0] ### `-U, --update-all` Apply all rewrite without confirmation if true ### `--json[=

Hello World!

``` Running this ast-grep command will extract the matching CSS style code out of the HTML file! ```sh ast-grep run -p 'color: $COLOR' ``` ast-grep outputs this beautiful CLI report. ```shell test.html 2│ h1 { color: red; } ``` ast-grep works well even if just providing the pattern without specifying the pattern language! ### **Using `ast-grep scan`**: find JavaScript in HTML with rule files You can also use ast-grep's [rule file](https://ast-grep.github.io/guide/rule-config.html) to search injected languages. For example, we can warn the use of `alert` in JavaScript, even if it is inside the HTML file. ```yml id: no-alert language: JavaScript severity: warning rule: pattern: alert($MSG) message: Prefer use appropriate custom UI instead of obtrusive alert call. ``` The rule above will detect usage of `alert` in JavaScript. Running the rule via `ast-grep scan`. ```sh ast-grep scan --rule no-alert.yml ``` The command leverages built-in behaviors in ast-grep to handle language injection seamlessly. It will produce the following warning message for the HTML file above. ```sh warning[no-alert]: Prefer use appropriate custom UI instead of obtrusive alert call. ┌─ test.html:8:3 │ 8 │ alert('hello world!') │ ^^^^^^^^^^^^^^^^^^^^^ ``` ## How language injections work? ast-grep employs a multi-step process to handle language injections effectively. Here's a detailed breakdown of the workflow: 1. **File Discovery**: The CLI first discovers files on the disk via the venerable [ignore](https://crates.io/crates/ignore) crate, the same library under [ripgrep](https://github.com/BurntSushi/ripgrep)'s hood. 2. **Language Inference**: ast-grep infers the language of each discovered file based on file extensions. 3. **Injection Extraction**: For documents that contain code written in multiple languages (e.g., HTML with embedded JS), ast-grep extracts the injected language sub-regions. _At the moment, ast-grep handles HTML/JS/CSS natively_. 4. **Code Matching**: ast-grep matches the specified patterns or rules against these regions. Pattern code will be interpreted according to the injected language (e.g. JS/CSS), instead of the parent document language (e.g. HTML). ## Customize Language Injection: styled-components in JavaScript You can customize language injection via the `sgconfig.yml` [configuration file](https://ast-grep.github.io/reference/sgconfig.html). This allows you to specify how ast-grep handles multi-language documents based on your specific needs, without modifying ast-grep's built-in behaviors. Let's see an example of searching CSS code in JavaScript. [styled-components](https://styled-components.com/) is a library for styling React applications using [CSS-in-JS](https://bootcamp.uxdesign.cc/css-in-js-libraries-for-styling-react-components-a-comprehensive-comparison-56600605a5a1). It allows you to write CSS directly within your JavaScript via [tagged template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals), creating styled elements as React components. The example will configure ast-grep to detect styled-components' CSS. ### Injection Configuration You can add the `languageInjections` section in the project configuration file `sgconfig.yml`. ```yaml languageInjections: - hostLanguage: js rule: pattern: styled.$TAG`$CONTENT` injected: css ``` Let's break the configuration down. 1. `hostLanguage`: Specifies the main language of the document. In this example, it is set to `js` (JavaScript). 2. `rule`: Defines the ast-grep rule to identify the injected language region within the host language. * `pattern`: The pattern matches styled components syntax where `styled` is followed by a tag (e.g., `button`, `div`) and a template literal containing CSS. * the rule should have a meta variable `$CONTENT` to specify the subregion of injected language. In this case, it is the content inside the template string. 3. `injected`: Specifies the injected language within the identified regions. In this case, it is `css`. ### Example Match Consider a JSX file using styled components: ```js import styled from 'styled-components'; const Button = styled.button` background: red; color: white; padding: 10px 20px; border-radius: 3px; ` export default function App() { return } ``` With the above `languageInjections` configuration, ast-grep will: 1. Identify the `styled.button` block as a CSS region. 2. Extract the CSS code inside the template literal. 3. Apply any CSS-specific pattern searches within this extracted region. You can search the CSS inside JavaScript in the project configuration folder using this command: ```sh ast-grep -p 'background: $COLOR' -C 2 ``` It will produce the match result: ```shell styled.js 2│ 3│const Button = styled.button` 4│ background: red; 5│ color: white; 6│ padding: 10px 20px; ``` ## Using Custom Language with Injection Finally, let's look at an example of searching for GraphQL within JavaScript files. This demonstrates ast-grep's flexibility in handling custom language injections. ### Define graphql custom language in `sgconfig.yml`. First, we need to register graphql as a custom language in ast-grep. See [custom language reference](https://ast-grep.github.io/advanced/custom-language.html) for more details. ```yaml customLanguages: graphql: libraryPath: graphql.so # the graphql tree-sitter parser dynamic library extensions: [graphql] # graphql file extension expandoChar: $ # see reference above for explanation ``` ### Define graphql injection in `sgconfig.yml`. Next, we need to customize what region should be parsed as graphql string in JavaScript. This is similar to styled-components example above. ```yaml languageInjections: - hostLanguage: js rule: pattern: graphql`$CONTENT` injected: graphql ``` ### Search GraphQL in JavaScript Suppose we have this JavaScript file from [Relay](https://relay.dev/), a GraphQL client framework. ```js import React from "react" import { graphql } from "react-relay" const artistsQuery = graphql` query ArtistQuery($artistID: String!) { artist(id: $artistID) { name ...ArtistDescription_artist } } ` ``` We can search the GraphQL fragment via this `--inline-rules` scan. ```sh ast-grep scan --inline-rules="{id: test, language: graphql, rule: {kind: fragment_spread}}" ``` Output ```sh help[test]: ┌─ relay.js:8:7 │ 8 │ ...ArtistDescription_artist │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``` ## More Possibility to be Unlocked... By following these steps, you can effectively use ast-grep to search and analyze code across multiple languages within the same document, enhancing your ability to manage and understand complex codebases. This feature extends to various frameworks like [Vue](https://vuejs.org/) and [Svelte](https://svelte.dev/), enables searching for [SQL in React server actions](https://x.com/peer_rich/status/1717609270475194466), and supports new patterns like [Vue-Vine](https://x.com/hd_nvim/status/1815300932793663658). Hope you enjoy the feature! Happy ast-grepping! --- --- url: /guide/test-rule.md --- # Test Your Rule Though it is easy to write a simple rule to match some code in ast-grep, writing a robust and comprehensive rule to cover codebase in production is still a pretty challenging work. To alleviate this pain, ast-grep provides a builtin tool to help you test your rule. You can provide a list of `valid` cases and `invalid` cases to test against your rule. ## Basic Concepts Ideally, a perfect rule will approve all valid code and report issues only for all invalid code. Testing a rule should also cover two categories of code accordingly. If you are familiar with [detection theory](https://en.wikipedia.org/wiki/Detection_theory), you should recognize that testing rule will involve the four scenarios tabulated below. |Code Validity \ Rule Report | No Report | Has Report | |----------------------------|-----------|------------| | Valid | Validated | Noisy | | Invalid | Missing | Reported | * If ast-grep reports error for invalid code, it is a correct **reported** match. * If ast-grep reports error for valid code, it is called **noisy** match. * If ast-grep reports nothing for invalid code, we have a **missing** match. * If ast-grep reports nothing for valid code, it is called **validated** match. We will see these four case status in ast-grep's test output. ## Test Setup Let's write a test for the rule we wrote in the [previous section](/guide/rule-config.html#rule-file). To write a test, we first need to specify a rule test directory in `sgconfig.yml`. This directory will be used to store all test cases for rules. Suppose we have the `sgconfig.yml` as below. ```yaml{4,5} ruleDirs: - rules # testConfigs contains a list of test directories for rules. testConfigs: - testDir: rule-tests ``` The configuration file should be located at a directory that looks like this. ```bash{3,5} my-awesome-rules/ |- rules/ | |- no-await-in-loop.yml # rule file |- rule-tests/ | |- no-await-in-loop-test.yml # test file |- sgconfig.yml ``` `rules` folder contains all rule files, while `rule-tests` folder contains all test cases for rules. In the example, `no-await-in-loop.yml` contains the rule configuration we wrote before. Below are all relevant files used in this example. ::: code-group ```yaml [no-await-in-loop.yml]{1} id: no-await-in-loop message: Don't use await inside of loops severity: warning language: TypeScript rule: all: - inside: any: - kind: for_in_statement - kind: while_statement stopBy: end - pattern: await $_ ``` ```yaml [no-await-in-loop-test.yml]{1} id: no-await-in-loop valid: - for (let a of b) { console.log(a) } # .... more valid test cases invalid: - async function foo() { for (var bar of baz) await bar; } # .... more invalid test cases ``` ```yaml [sgconfig.yml]{4,5} ruleDirs: - rules # testConfigs contains a list of test directories for rules. testConfigs: - testDir: rule-tests ``` ::: We will delve into `no-await-in-loop-test.yml` in next section. ## Test Case Configuration Test configuration file is very straightforward. It contains a list of `valid` and `invalid` cases with an `id` field to specify which rule will be tested against. `valid` is a list of source code that we **do not** expect the rule to report any issue. `invalid` is a list of source code that we **do** expect the rule to report some issues. ```yaml id: no-await-in-loop valid: - for (let a of b) { console.log(a) } # .... more valid test cases invalid: - async function foo() { for (var bar of baz) await bar; } # .... more invalid test cases ``` After writing the test configuration file, you can run `ast-grep test` in the root folder to test your rule. We will discuss the `skip-snapshot-tests` option later. ```bash $ ast-grep test --skip-snapshot-tests Running 1 tests PASS no-await-in-loop ......................... test result: ok. 1 passed; 0 failed; ``` ast-grep will report the passed rule and failed rule. The dots behind test case id represent passed cases. If we swap the test case and make them failed, we will get the following output. ```bash Running 1 tests FAIL no-await-in-loop ...........N............M ----------- Failure Details ----------- [Noisy] Expect no-await-in-loop to report no issue, but some issues found in: async function foo() { for (var bar of baz) await bar; } [Missing] Expect rule no-await-in-loop to report issues, but none found in: for (let a of b) { console.log(a) } Error: test failed. 0 passed; 1 failed; ``` The output shows that we have two failed cases. One is a **noisy** match, which means ast-grep reports error for valid code. The other is a **missing** match, which means ast-grep reports nothing for invalid code. In the test summary, we can see the cases are marked with `N` and `M` respectively. In failure details, we can see the detailed code snippet for each case. Besides testing code validity, we can further test rule's output like error's message and span. This is what snapshot test will cover. ## Snapshot Test Let's rerun `ast-grep test` without `--skip-snapshot-tests` option. This time we will get test failure that invalid code error does not have a matching snapshot. Previously we use the `skip-snapshot-tests` option to suppress snapshot test, which is useful when you are still working on your rule. But after the rule is polished, we can create snapshot to capture the desired output of the rule. The `--update-all` or `-U` will generate a snapshot directory for us. ```bash my-awesome-rules/ |- rules/ | |- no-await-in-loop.yml # test file |- rule-tests/ | |- no-await-in-loop-test.yml # rule file | |- __snapshots__/ # snapshots folder | | |- no-await-in-loop-snapshot.yml # generated snapshot file! |- sgconfig.yml ``` The generated `__snapshots__` folder will store all the error output and later test run will match against them. After the snapshot is generated, we can run `ast-grep test` again, without any option this time, and pass all the test cases! Furthermore, when we change the rule or update the test case, we can use interactive mode to update the snapshot. Running this command ```bash ast-grep test --interactive ``` ast-grep will spawn an interactive session to ask you select desired snapshot updates. Example interactive session will look like this. Note the snapshot diff is highlighted in red/green color. ```diff [Wrong] no-await-in-loop snapshot is different from baseline. Diff: labels: - source: await bar style: Primary - start: 2 + start: 28 end: 37 - source: do { await bar; } while (baz); style: Secondary For Code: async function foo() { do { await bar; } while (baz); } Accept new snapshot? (Yes[y], No[n], Accept All[a], Quit[q]) ``` Pressing the `y` key will accept the new snapshot and update the snapshot file. --- --- url: /links/roadmap.md --- # TODO: ## Core - [x] Add replace - [x] Add find_all - [x] Add metavar char customization - [x] Add per-language customization - [x] Add support for vec/sequence matcher - [x] View node in context - [x] implement iterative DFS mode - [ ] Investigate perf heuristic (e.g. match fixed-string) - [x] Group matching rules based on root pattern kind id - [ ] Remove unwrap usage and implement error handling ## Metavariable Matcher - [x] Regex - [x] Pattern - [x] Kind - [ ] Use CoW to optimize MetaVarEnv ## Operators/Combinators - [x] every / all - [x] either / any - [x] inside - [x] has - [x] follows - [x] precedes ## CLI - [x] match against files in directory recursively - [x] interactive mode - [x] as dry run mode (listing all rewrite) - [x] inplace edit mode - [x] no-color mode - [x] JSON output - [ ] execute remote rules ## Config - [x] support YAML config rule - [x] Add support for severity - [x] Add support for error message - [x] Add support for error labels - [x] Add support for fix ## Binding - [ ] NAPI binding - [x] WASM binding - [ ] Python binding ## Playground - [x] build a playground based on WASM binding - [x] build YAML config for WASM playground - [x] URL sharing - [x] add fix/rewrite ## LSP - [x] Add LSP command - [ ] implement LSP incremental - [ ] add code action ## Builtin Ruleset - [ ] Migrate some ESLint rule (or RSLint rule) --- --- url: /reference/yaml/transformation.md --- # Transformation Object A transformation object is used to manipulate meta variables. It is a dictionary with the following structure: * a **key** that specifies which string operation will be applied to the meta variable, and * a **value** that is another object with the details of how to perform the operation. Different string operation keys expect different object values. ## `replace` Use a regular expression to replace the text in a meta-variable with a new text. `replace` transformation expects an object value with the following properties: ### `replace` * type: `String` * required: true A Rust regular expression to match the text to be replaced. ### `by` * type: `String` * required: true A string to replace the matched text. ### `source` * type: `String` * required: true A meta-variable name to be replaced. _The meta-variable name must be prefixed with `$`._ **Example**: ```yaml transform: NEW_VAR: replace: replace: regex by: replacement source: $VAR ``` :::tip Pro tip You can use regular expression capture groups in the `replace` field and refer to them in the `by` field. See [replace guide](/guide/rewrite-code.html#rewrite-with-regex-capture-groups) ::: ## `substring` Create a new string by cutting off leading and trailing characters. `substring` transformation expects an object value with the following properties: ### `startChar` * type: `Integer` * required: false The starting character index of the new string, **inclusively**.
If omitted, the new string starts from the beginning of the source string.
The index can be negative, in which case the index is counted from the end of the string. ### `endChar` * type: `Integer` * required: false The ending character index of the new string, **exclusively**.
If omitted, the new string ends at the end of the source string.
The index can be negative, in which case the index is counted from the end of the string. ### `source` * type: `String` * required: true A meta-variable name to be truncated. _The meta-variable name must be prefixed with `$`._ **Example**: ```yaml transform: NEW_VAR: substring: startChar: 1 endChar: -1 source: $VAR ``` :::tip Pro Tip `substring` works like [Python's string slicing](https://www.digitalocean.com/community/tutorials/python-slice-string). They both have inclusive start, exclusive end and support negative index. `substring`'s index is based on unicode character count, instead of byte. ::: ## `convert` Change the string case of a meta-variable, such as from `camelCase` to `underscore_case`. This transformation is inspired by TypeScript's [intrinsic string manipulation type](https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html#intrinsic-string-manipulation-types). Ideally, the source string should be an identifier in the rule language. `convert` transformation expects an object value with the following properties: ### `toCase` * type: `StringCase` * required: true The target case to convert to. Some string cases will first split the source string into words, then convert each word's case, and finally join the words back together. You can fine-tune the behavior of these separator-sensitive string cases by the `separatedBy` option. ast-grep supports the following cases: #### `StringCase` |Name|Example input|Example output|Separator sensitive?| |---|---:|---:|--:| |`lowerCase`| astGrep| astgrep| No| |`upperCase`| astGrep| ASTGREP| No| |`capitalize`| astGrep| AstGrep| No| |`camelCase`| ast_grep| astGrep| Yes| |`snakeCase`| astGrep| ast_grep| Yes| |`kebabCase`| astGrep| ast-grep| Yes| |`pascalCase`| astGrep| AstGrep| Yes| ### `separatedBy` * type: `Array` * required: false * default: all separators A list of separators to be used to separate words in the source string. ast-grep supports the following separators: #### `Separator` |Name|Separator character |Example input|Example output| |---|:---:|:---:|:---:| |`Dash`|`-`| ast-grep| [ast, grep]| |`Dot`|`.`| ast.grep| [ast, grep]| |`Space`|` `| ast grep| [ast, grep]| |`Slash`|`/`| ast/grep| [ast, grep]| |`Underscore`|`_`| ast_grep| [ast, grep]| |`CaseChange`|Described below| astGrep| [ast, grep]| `CaseChange` separator is a special separator that splits the string when two consecutive characters' case changed. More specifically, it splits the string in the following two scenarios. * At the position between a lowercase letter and an uppercase letter, e.g. `astGrep` -> `[ast, Grep]` * Before an uppercase letter that is not the first character and is followed by a lowercase letter, e.g. `ASTGrep` -> `[AST, Grep]` More examples are shown below. You can also inspect [the equivalent regular expression examples](https://regexr.com/7prq5) to see how `CaseChange` works in action ``` RegExp -> [Reg, Exp] XMLHttpRequest -> [XML, Http, Request] regExp -> [reg, Exp] writeHTML -> [write, HTML] ``` ### `source` * type: `String` * required: true A meta-variable name to convert. _The meta-variable name must be prefixed with `$`._ **Example**: ```yaml transform: NEW_VAR: convert: toCase: kebabCase separatedBy: [underscore] source: $VAR ``` Suppose we have a string `ast_Grep` as the input `$VAR`, The example above will convert the string as following: * split the string by `_` into `[ast, Grep]` * convert the words to lowercase words `[ast, grep]` * join the words by `-` into the target string `ast-grep` Thank [Aarni Koskela](https://github.com/akx) for proposing and implementing the first version of this feature! ## `rewrite` `rewrite` is an experimental transformation that allows you to selectively transform a meta variable by `rewriter` rules. Instead of rewriting the single target node which matches the rule, `rewrite` can rewrite a subset of AST captured by a meta-variable. Currently, it is an experimental feature. Please see the [issue](https://github.com/ast-grep/ast-grep/issues/723) `rewrite` transformation expects an object value with the following properties: ### `source` * type: `String` * required: true The meta-variable name to be rewritten. _The meta-variable can be single meta-variable, prefixed with `$`, or multiple prefixed with `$$$$`._ ast-grep will find matched descendants nodes of the source meta-variable for single meta-variable and apply the rewriter rules to them. For multiple meta-variables, ast-grep will find matched descendants nodes of each node in the meta-variable list. ### `rewriters` * type: `Array` * required: true A list of rewriter rules to apply to the source meta-variable. The rewrite rules work like ast-grep's fix mode. `rewriters` can only refer to the rules specified in [`rewriters`](/reference/yaml/rewriter.html) [section](/reference/yaml.html#rewriters). ast-grep will find nodes in the meta-variable's AST that match the rewriter rules, and rewrite them to the `fix` string/object in the matched rule. `rewriter` rules will not have overlapping matches. Nodes on the higher level of AST, or closer to the root node, will be matched first. For one single node, `rewriters` are matched in order, and only the first match will be applied. Subsequent rules will be ignored. ### `joinBy` * type: `String` * required: false By default, the rewritten nodes will be put back to the original syntax tree. If you want to aggregate the rewrite in other fashion, you can specify a string to join the rewritten nodes. For example, you can join generated statements using new line. **Example**: ```yaml transform: NEW_VAR: rewrite: source: $VAR rewriters: [rule1, rule2] joinBy: "\n" ``` Thank [Eitan Mosenkis](https://github.com/emosenkis) for proposing this idea! --- --- url: /catalog/tsx/index.md --- # TSX This page curates a list of example ast-grep rules to check and to rewrite TypeScript with JSX syntax. :::danger TSX and TypeScript are different. TSX differs from TypeScript because it is an extension of the latter that supports JSX elements. They need distinct parsers because of [conflicting syntax](https://www.typescriptlang.org/docs/handbook/jsx.html#the-as-operator). In order to reduce rule duplication, you can use the [`languageGlobs`](/reference/sgconfig.html#languageglobs) option to force ast-grep to use parse `.ts` files as TSX. ::: --- --- url: /catalog/typescript/index.md --- # 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](https://github.com/ast-grep/eslint/) recreated with ast-grep. :::danger 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](https://www.typescriptlang.org/docs/handbook/jsx.html#the-as-operator). However, you can use the [`languageGlobs`](/reference/sgconfig.html#languageglobs) option to force ast-grep to use parse `.ts` files as TSX. ::: --- --- url: /catalog/java/no-unused-vars.md --- ## No Unused Vars in Java * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmEiLCJxdWVyeSI6ImlmKHRydWUpeyQkJEJPRFl9IiwicmV3cml0ZSI6IiRDOiBMaXN0WyRUXSA9IHJlbGF0aW9uc2hpcCgkJCRBLCB1c2VsaXN0PVRydWUsICQkJEIpIiwic3RyaWN0bmVzcyI6InNtYXJ0Iiwic2VsZWN0b3IiOiIiLCJjb25maWciOiJpZDogbm8tdW51c2VkLXZhcnNcbnJ1bGU6XG4gICAga2luZDogbG9jYWxfdmFyaWFibGVfZGVjbGFyYXRpb25cbiAgICBhbGw6XG4gICAgICAgIC0gaGFzOlxuICAgICAgICAgICAgaGFzOlxuICAgICAgICAgICAgICAgIGtpbmQ6IGlkZW50aWZpZXJcbiAgICAgICAgICAgICAgICBwYXR0ZXJuOiAkSURFTlRcbiAgICAgICAgLSBub3Q6XG4gICAgICAgICAgICBwcmVjZWRlczpcbiAgICAgICAgICAgICAgICBzdG9wQnk6IGVuZFxuICAgICAgICAgICAgICAgIGhhczpcbiAgICAgICAgICAgICAgICAgICAgc3RvcEJ5OiBlbmRcbiAgICAgICAgICAgICAgICAgICAgYW55OlxuICAgICAgICAgICAgICAgICAgICAgICAgLSB7IGtpbmQ6IGlkZW50aWZpZXIsIHBhdHRlcm46ICRJREVOVCB9XG4gICAgICAgICAgICAgICAgICAgICAgICAtIHsgaGFzOiB7a2luZDogaWRlbnRpZmllciwgcGF0dGVybjogJElERU5ULCBzdG9wQnk6IGVuZH19XG5maXg6ICcnXG4iLCJzb3VyY2UiOiJTdHJpbmcgdW51c2VkID0gXCJ1bnVzZWRcIjtcbk1hcDxTdHJpbmcsIFN0cmluZz4gZGVjbGFyZWRCdXROb3RJbnN0YW50aWF0ZWQ7XG5cblN0cmluZyB1c2VkMSA9IFwidXNlZFwiO1xuaW50IHVzZWQyID0gMztcbmJvb2xlYW4gdXNlZDMgPSBmYWxzZTtcbmludCB1c2VkNCA9IDQ7XG5TdHJpbmcgdXNlZDUgPSBcIjVcIjtcblxuXG5cbnVzZWQxO1xuU3lzdGVtLm91dC5wcmludGxuKHVzZWQyKTtcbmlmKHVzZWQzKXtcbiAgICBTeXN0ZW0ub3V0LnByaW50bG4oXCJzb21lIHZhcnMgYXJlIHVudXNlZFwiKTtcbiAgICBNYXA8U3RyaW5nLCBTdHJpbmc+IHVudXNlZE1hcCA9IG5ldyBIYXNoTWFwPD4oKSB7e1xuICAgICAgICBwdXQodXNlZDUsIFwidXNlZDVcIik7XG4gICAgfX07XG5cbiAgICAvLyBFdmVuIHRob3VnaCB3ZSBkb24ndCByZWFsbHkgZG8gYW55dGhpbmcgd2l0aCB0aGlzIG1hcCwgc2VwYXJhdGluZyB0aGUgZGVjbGFyYXRpb24gYW5kIGluc3RhbnRpYXRpb24gbWFrZXMgaXQgY291bnQgYXMgYmVpbmcgdXNlZFxuICAgIGRlY2xhcmVkQnV0Tm90SW5zdGFudGlhdGVkID0gbmV3IEhhc2hNYXA8PigpO1xuXG4gICAgcmV0dXJuIHVzZWQ0O1xufSJ9) ### Description Identifying unused variables is a common task in code refactoring. You should rely on a Java linter or IDE for this task rather than writing a custom rule in ast-grep, but for educational purposes, this rule demonstrates how to find unused variables in Java. This approach makes some simplifying assumptions. We only consider local variable declarations and ignore the other many ways variables can be declared: Method Parameters, Fields, Class Variables, Constructor Parameters, Loop Variables, Exception Handler Parameters, Lambda Parameters, Annotation Parameters, Enum Constants, and Record Components. Now you may see why it is recommended to use a rule from an established linter or IDE rather than writing your own. ### YAML ```yaml id: no-unused-vars rule: kind: local_variable_declaration all: - has: has: kind: identifier pattern: $IDENT - not: precedes: stopBy: end has: stopBy: end any: - { kind: identifier, pattern: $IDENT } - { has: {kind: identifier, pattern: $IDENT, stopBy: end}} fix: '' ``` First, we identify the local variable declaration and capture the pattern of the identifier inside of it. Then we use `not` and `precedes` to only match the local variable declaration if the identifier we captured does not appear later in the code. It is important to note that we use `all` here to force the ordering of the `has` rule to be before the `not` rule. This guarantees that the meta-variable `$IDENT` is captured by looking inside of the local variable declaration. Additionally, when looking ahead in the code, we can't just look for the identifier directly, but for any node that may contain the identifier. ### Example ```java String unused = "unused"; // [!code --] String used = "used"; System.out.println(used); ``` --- --- url: /catalog/html/extract-i18n-key.md --- ## Extract i18n Keys * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6Imh0bWwiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoicmVsYXhlZCIsInNlbGVjdG9yIjoiIiwiY29uZmlnIjoicnVsZTpcbiAga2luZDogdGV4dFxuICBwYXR0ZXJuOiAkVFxuICBub3Q6XG4gICAgcmVnZXg6ICdcXHtcXHsuKlxcfVxcfSdcbmZpeDogXCJ7eyAkKCckVCcpIH19XCIiLCJzb3VyY2UiOiI8dGVtcGxhdGU+XG4gIDxzcGFuPkhlbGxvPC9zcGFuPlxuICA8c3Bhbj57eyB0ZXh0IH19PC9zcGFuPlxuPC90ZW1wbGF0ZT4ifQ==) ### Description It is tedious to manually find and replace all the text in the template with i18n keys. This rule helps to extract static text into i18n keys. Dynamic text, e.g. mustache syntax, will be skipped. In practice, you may want to map the extracted text to a key in a dictionary file. While this rule only demonstrates the extraction part, further mapping process can be done via a script reading the output of ast-grep's [`--json`](/guide/tools/json.html) mode, or using [`@ast-grep/napi`](/guide/api-usage/js-api.html). ### YAML ```yaml id: extract-i18n-key language: html rule: kind: text pattern: $T # skip dynamic text in mustache syntax not: { regex: '\{\{.*\}\}' } fix: "{{ $('$T') }}" ``` ### Example ```html {2} ``` ### Diff ```html ``` ### Contributed by Inspired by [Vue.js RFC](https://github.com/vuejs/rfcs/discussions/705#discussion-7255672) --- --- url: /catalog/go/match-function-call.md --- ## Match Function Call in Golang * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImdvIiwicXVlcnkiOiJhd2FpdCAkQSIsInJld3JpdGUiOiJ0cnkge1xuICAgIGF3YWl0ICRBXG59IGNhdGNoKGUpIHtcbiAgICAvLyB0b2RvXG59IiwiY29uZmlnIjoicnVsZTpcbiAgcGF0dGVybjpcbiAgICBjb250ZXh0OiAnZnVuYyB0KCkgeyBmbXQuUHJpbnRsbigkJCRBKSB9J1xuICAgIHNlbGVjdG9yOiBjYWxsX2V4cHJlc3Npb25cbiIsInNvdXJjZSI6ImZ1bmMgbWFpbigpIHtcbiAgICBmbXQuUHJpbnRsbihcIk9LXCIpXG59In0=) ### Description One of the common questions of ast-grep is to match function calls in Golang. A plain pattern like `fmt.Println($A)` will not work. This is because Golang syntax also allows type conversions, e.g. `int(3.14)`, that look like function calls. Tree-sitter, ast-grep's parser, will prefer parsing `func_call(arg)` as a type conversion instead of a call expression. To avoid this ambiguity, ast-grep lets us write a [contextual pattern](/guide/rule-config/atomic-rule.html#pattern), which is a pattern inside a larger code snippet. We can use `context` to write a pattern like this: `func t() { fmt.Println($A) }`. Then, we can use the selector `call_expression` to match only function calls. Please also read the [deep dive](/advanced/pattern-parse.html) on [ambiguous pattern](/advanced/pattern-parse.html#ambiguous-pattern-code). ### YAML ```yaml id: match-function-call language: go rule: pattern: context: 'func t() { fmt.Println($A) }' selector: call_expression ``` ### Example ```go{2} func main() { fmt.Println("OK") } ``` ### Contributed by Inspired by [QuantumGhost](https://github.com/QuantumGhost) from [ast-grep/ast-grep#646](https://github.com/ast-grep/ast-grep/issues/646) --- --- url: /catalog/go/find-func-declaration-with-prefix.md --- ## Find function declarations with names of certain pattern * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImdvIiwicXVlcnkiOiJyJ15bQS1aYS16MC05Xy1dKyciLCJyZXdyaXRlIjoiIiwiY29uZmlnIjoiaWQ6IHRlc3QtZnVuY3Rpb25zXG5sYW5ndWFnZTogZ29cbnJ1bGU6XG4gIGtpbmQ6IGZ1bmN0aW9uX2RlY2xhcmF0aW9uXG4gIGhhczpcbiAgICBmaWVsZDogbmFtZVxuICAgIHJlZ2V4OiBUZXN0LipcbiIsInNvdXJjZSI6InBhY2thZ2UgYWJzXG5pbXBvcnQgXCJ0ZXN0aW5nXCJcbmZ1bmMgVGVzdEFicyh0ICp0ZXN0aW5nLlQpIHtcbiAgICBnb3QgOj0gQWJzKC0xKVxuICAgIGlmIGdvdCAhPSAxIHtcbiAgICAgICAgdC5FcnJvcmYoXCJBYnMoLTEpID0gJWQ7IHdhbnQgMVwiLCBnb3QpXG4gICAgfVxufVxuIn0=) ### Description ast-grep can find function declarations by their names. But not all names can be matched by a meta variable pattern. For instance, you cannot use a meta variable pattern to find function declarations whose names start with a specific prefix, e.g. `TestAbs` with the prefix `Test`. Attempting `Test$_` will fail because it is not a valid syntax. Instead, you can use a [YAML rule](/reference/rule.html) to use the [`regex`](/guide/rule-config/atomic-rule.html#regex) atomic rule. ### YAML ```yaml id: test-functions language: go rule: kind: function_declaration has: field: name regex: Test.* ``` ### Example ```go{3-8} package abs import "testing" func TestAbs(t *testing.T) { got := Abs(-1) if got != 1 { t.Errorf("Abs(-1) = %d; want 1", got) } } ``` ### Contributed by [kevinkjt2000](https://twitter.com/kevinkjt2000) on [Discord](https://discord.com/invite/4YZjf6htSQ). --- --- url: /catalog/cpp/find-struct-inheritance.md --- ## Find Struct Inheritance * [Playground Link](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoiY3BwIiwicXVlcnkiOiJzdHJ1Y3QgJFNPTUVUSElORzogICRJTkhFUklUU19GUk9NIHsgJCQkQk9EWTsgfSIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoic21hcnQiLCJzZWxlY3RvciI6IiIsImNvbmZpZyI6IiIsInNvdXJjZSI6InN0cnVjdCBGb286IEJhciB7fTtcblxuc3RydWN0IEJhcjogQmF6IHtcbiAgaW50IGEsIGI7XG59In0=) ### Description ast-grep's pattern is AST based. A code snippet like `struct $SOMETHING: $INHERITS` will not work because it does not have a correct AST structure. The correct pattern should spell out the full syntax like `struct $SOMETHING: $INHERITS { $$$BODY; }`. Compare the ast structure below to see the difference, especially the `ERROR` node. You can also use the playground's pattern panel to debug. :::code-group ```shell [Wrong Pattern] ERROR $SOMETHING base_class_clause $INHERITS ``` ```shell [Correct Pattern] struct_specifier $SOMETHING base_class_clause $INHERITS field_declaration_list field_declaration $$$BODY ``` ::: If it is not possible to write a full pattern, [YAML rule](/guide/rule-config.html) is a better choice. ### Pattern ```shell ast-grep --lang cpp --pattern ' struct $SOMETHING: $INHERITS { $$$BODY; }' ``` ### Example ```cpp {1-3} struct Bar: Baz { int a, b; } ``` ### Contributed by Inspired by this [tweet](https://x.com/techno_bog/status/1885421768384331871) --- --- url: /catalog/cpp/fix-format-vuln.md --- ## Fix Format String Vulnerability * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImNwcCIsInF1ZXJ5IjoiIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJzbWFydCIsInNlbGVjdG9yIjoiIiwiY29uZmlnIjoiaWQ6IGZpeC1mb3JtYXQtc2VjdXJpdHktZXJyb3Jcbmxhbmd1YWdlOiBDcHBcbnJ1bGU6XG4gIHBhdHRlcm46ICRQUklOVEYoJFMsICRWQVIpXG5jb25zdHJhaW50czpcbiAgUFJJTlRGOiAjIGEgZm9ybWF0IHN0cmluZyBmdW5jdGlvblxuICAgIHsgcmVnZXg6IFwiXnNwcmludGZ8ZnByaW50ZiRcIiB9XG4gIFZBUjogIyBub3QgYSBsaXRlcmFsIHN0cmluZ1xuICAgIG5vdDpcbiAgICAgIGFueTpcbiAgICAgIC0geyBraW5kOiBzdHJpbmdfbGl0ZXJhbCB9XG4gICAgICAtIHsga2luZDogY29uY2F0ZW5hdGVkX3N0cmluZyB9XG5maXg6ICRQUklOVEYoJFMsIFwiJXNcIiwgJFZBUilcbiIsInNvdXJjZSI6Ii8vIEVycm9yXG5mcHJpbnRmKHN0ZGVyciwgb3V0KTtcbnNwcmludGYoJmJ1ZmZlclsyXSwgb2JqLT5UZXh0KTtcbnNwcmludGYoYnVmMSwgVGV4dF9TdHJpbmcoVFhUX1dBSVRJTkdfRk9SX0NPTk5FQ1RJT05TKSk7XG4vLyBPS1xuZnByaW50ZihzdGRlcnIsIFwiJXNcIiwgb3V0KTtcbnNwcmludGYoJmJ1ZmZlclsyXSwgXCIlc1wiLCBvYmotPlRleHQpO1xuc3ByaW50ZihidWYxLCBcIiVzXCIsIFRleHRfU3RyaW5nKFRYVF9XQUlUSU5HX0ZPUl9DT05ORUNUSU9OUykpOyJ9) ### Description The [Format String exploit](https://owasp.org/www-community/attacks/Format_string_attack) occurs when the submitted data of an input string is evaluated as a command by the application. For example, using `sprintf(s, var)` can lead to format string vulnerabilities if `var` contains user-controlled data. This can be exploited to execute arbitrary code. By explicitly specifying the format string as `"%s"`, you ensure that `var` is treated as a string, mitigating this risk. ### YAML ```yaml id: fix-format-security-error language: Cpp rule: pattern: $PRINTF($S, $VAR) constraints: PRINTF: # a format string function { regex: "^sprintf|fprintf$" } VAR: # not a literal string not: any: - { kind: string_literal } - { kind: concatenated_string } fix: $PRINTF($S, "%s", $VAR) ``` ### Example ```cpp {2-4} // Error fprintf(stderr, out); sprintf(&buffer[2], obj->Text); sprintf(buf1, Text_String(TXT_WAITING_FOR_CONNECTIONS)); // OK fprintf(stderr, "%s", out); sprintf(&buffer[2], "%s", obj->Text); sprintf(buf1, "%s", Text_String(TXT_WAITING_FOR_CONNECTIONS)); ``` ### Diff ```js // Error fprintf(stderr, out); // [!code --] fprintf(stderr, "%s", out); // [!code ++] sprintf(&buffer[2], obj->Text); // [!code --] sprintf(&buffer[2], "%s", obj->Text); // [!code ++] sprintf(buf1, Text_String(TXT_WAITING_FOR_CONNECTIONS)); // [!code --] sprintf(buf1, "%s", Text_String(TXT_WAITING_FOR_CONNECTIONS)); // [!code ++] // OK fprintf(stderr, "%s", out); sprintf(&buffer[2], "%s", obj->Text); sprintf(buf1, "%s", Text_String(TXT_WAITING_FOR_CONNECTIONS)); ``` ### Contributed by [xiaoxiangmoe](https://github.com/xiaoxiangmoe) --- --- url: /catalog/c/yoda-condition.md --- ## Rewrite Check to Yoda Condition * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImMiLCJxdWVyeSI6IiRDOiAkVCA9IHJlbGF0aW9uc2hpcCgkJCRBLCB1c2VsaXN0PVRydWUsICQkJEIpIiwicmV3cml0ZSI6IiRDOiBMaXN0WyRUXSA9IHJlbGF0aW9uc2hpcCgkJCRBLCB1c2VsaXN0PVRydWUsICQkJEIpIiwiY29uZmlnIjoiaWQ6IG1heS10aGUtZm9yY2UtYmUtd2l0aC15b3Vcbmxhbmd1YWdlOiBjXG5ydWxlOlxuICBwYXR0ZXJuOiAkQSA9PSAkQiBcbiAgaW5zaWRlOlxuICAgIGtpbmQ6IHBhcmVudGhlc2l6ZWRfZXhwcmVzc2lvblxuICAgIGluc2lkZToge2tpbmQ6IGlmX3N0YXRlbWVudH1cbmNvbnN0cmFpbnRzOlxuICBCOiB7IGtpbmQ6IG51bWJlcl9saXRlcmFsIH1cbmZpeDogJEIgPT0gJEEiLCJzb3VyY2UiOiJpZiAobXlOdW1iZXIgPT0gNDIpIHsgLyogLi4uICovfVxuaWYgKG5vdE1hdGNoID09IGFub3RoZXIpIHt9XG5pZiAobm90TWF0Y2gpIHt9In0=) ### Description In programming jargon, a [Yoda condition](https://en.wikipedia.org/wiki/Yoda_conditions) is a style that places the constant portion of the expression on the left side of the conditional statement. It is used to prevent assignment errors that may occur in languages like C. ### YAML ```yaml id: may-the-force-be-with-you language: c rule: pattern: $A == $B # Find equality comparison inside: # inside an if_statement kind: parenthesized_expression inside: {kind: if_statement} constraints: # with the constraint that B: { kind: number_literal } # right side is a number fix: $B == $A ``` The rule targets an equality comparison, denoted by the [pattern](/guide/pattern-syntax.html) `$A == $B`. This comparison must occur [inside](/reference/rule.html#inside) an `if_statement`. Additionally, there’s a [constraint](/reference/yaml.html#constraints) that the right side of the comparison, `$B`, must be a number_literal like `42`. ### Example ```c {1} if (myNumber == 42) { /* ... */} if (notMatch == another) { /* ... */} if (notMatch) { /* ... */} ``` ### Diff ```c if (myNumber == 42) { /* ... */} // [!code --] if (42 == myNumber) { /* ... */} // [!code ++] if (notMatch == another) { /* ... */} if (notMatch) { /* ... */} ``` ### Contributed by Inspired by this [thread](https://x.com/cocoa1han/status/1763020689303581141) --- --- url: /catalog/c/rewrite-method-to-function-call.md --- ## Rewrite Method to Function Call * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImMiLCJxdWVyeSI6IiRDT1VOVCA9ICRcbiIsInJld3JpdGUiOiIiLCJjb25maWciOiJpZDogbWV0aG9kX3JlY2VpdmVyXG5ydWxlOlxuICBwYXR0ZXJuOiAkUi4kTUVUSE9EKCQkJEFSR1MpXG50cmFuc2Zvcm06XG4gIE1BWUJFX0NPTU1BOlxuICAgIHJlcGxhY2U6XG4gICAgICBzb3VyY2U6ICQkJEFSR1NcbiAgICAgIHJlcGxhY2U6ICdeLisnXG4gICAgICBieTogJywgJ1xuZml4OlxuICAkTUVUSE9EKCYkUiRNQVlCRV9DT01NQSQkJEFSR1MpXG4iLCJzb3VyY2UiOiJ2b2lkIHRlc3RfZnVuYygpIHtcbiAgICBzb21lX3N0cnVjdC0+ZmllbGQubWV0aG9kKCk7XG4gICAgc29tZV9zdHJ1Y3QtPmZpZWxkLm90aGVyX21ldGhvZCgxLCAyLCAzKTtcbn0ifQ==) ### Description In C, there is no built-in support for object-oriented programming, but some programmers use structs and function pointers to simulate classes and methods. However, this style can have some drawbacks, such as: * extra memory allocation and deallocation for the struct and the function pointer. * indirection overhead when calling the function pointer. A possible alternative is to use a plain function call with the struct pointer as the first argument. ### YAML ```yaml id: method_receiver language: c rule: pattern: $R.$METHOD($$$ARGS) transform: MAYBE_COMMA: replace: source: $$$ARGS replace: '^.+' by: ', ' fix: $METHOD(&$R$MAYBE_COMMA$$$ARGS) ``` ### Example ```c {2-3} void test_func() { some_struct->field.method(); some_struct->field.other_method(1, 2, 3); } ``` ### Diff ```c void test_func() { some_struct->field.method(); // [!code --] method(&some_struct->field); // [!code ++] some_struct->field.other_method(1, 2, 3); // [!code --] other_method(&some_struct->field, 1, 2, 3); // [!code ++] } ``` ### Contributed by [Surma](https://twitter.com/DasSurma), adapted from the [original tweet](https://twitter.com/DasSurma/status/1706086320051794217) --- --- url: /catalog/kotlin/ensure-clean-architecture.md --- ## Ensure Clean Architecture * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImtvdGxpbiIsInF1ZXJ5IjoiIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJyZWxheGVkIiwic2VsZWN0b3IiOiIiLCJjb25maWciOiJpZDogaW1wb3J0LWRlcGVuZGVuY3ktdmlvbGF0aW9uXG5tZXNzYWdlOiBJbXBvcnQgRGVwZW5kZW5jeSBWaW9sYXRpb24gXG5ub3RlczogRW5zdXJlcyB0aGF0IGltcG9ydHMgY29tcGx5IHdpdGggYXJjaGl0ZWN0dXJhbCBydWxlcy4gXG5zZXZlcml0eTogZXJyb3JcbnJ1bGU6XG4gIHBhdHRlcm46IGltcG9ydCAkUEFUSFxuY29uc3RyYWludHM6XG4gIFBBVEg6XG4gICAgYW55OlxuICAgIC0gcmVnZXg6IGNvbVxcLmV4YW1wbGUoXFwuXFx3KykqXFwuZGF0YVxuICAgIC0gcmVnZXg6IGNvbVxcLmV4YW1wbGUoXFwuXFx3KykqXFwucHJlc2VudGF0aW9uXG5maWxlczpcbi0gY29tL2V4YW1wbGUvZG9tYWluLyoqLyoua3QiLCJzb3VyY2UiOiJpbXBvcnQgYW5kcm9pZHgubGlmZWN5Y2xlLlZpZXdNb2RlbFxuaW1wb3J0IGFuZHJvaWR4LmxpZmVjeWNsZS5WaWV3TW9kZWxTY29wZVxuaW1wb3J0IGNvbS5leGFtcGxlLmN1c3RvbWxpbnRleGFtcGxlLmRhdGEubW9kZWxzLlVzZXJEdG9cbmltcG9ydCBjb20uZXhhbXBsZS5jdXN0b21saW50ZXhhbXBsZS5kb21haW4udXNlY2FzZXMuR2V0VXNlclVzZUNhc2VcbmltcG9ydCBjb20uZXhhbXBsZS5jdXN0b21saW50ZXhhbXBsZS5wcmVzZW50YXRpb24uc3RhdGVzLk1haW5TdGF0ZVxuaW1wb3J0IGRhZ2dlci5oaWx0LmFuZHJvaWQubGlmZWN5Y2xlLkhpbHRWaWV3TW9kZWwifQ==) ### Description This ast-grep rule ensures that the **domain** package in a [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) project does not import classes from the **data** or **presentation** packages. It enforces the separation of concerns by preventing the domain layer from depending on other layers, maintaining the integrity of the architecture. For example, the rule will trigger an error if an import statement like `import com.example.data.SomeClass` or `import com.example.presentation.AnotherClass` is found within the domain package. The rule uses the [`files`](/reference/yaml.html#files) field to apply only to the domain package. ### YAML ```yaml id: import-dependency-violation message: Import Dependency Violation notes: Ensures that imports comply with architectural rules. severity: error rule: pattern: import $PATH # capture the import statement constraints: PATH: # find specific package imports any: - regex: com\.example(\.\w+)*\.data - regex: com\.example(\.\w+)*\.presentation files: # apply only to domain package - com/example/domain/**/*.kt ``` ### Example ```kotlin {3,5} import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelScope import com.example.customlintexample.data.models.UserDto import com.example.customlintexample.domain.usecases.GetUserUseCase import com.example.customlintexample.presentation.states.MainState import dagger.hilt.android.lifecycle.HiltViewModel ``` ### Contributed by Inspired by the post [Custom Lint Task Configuration in Gradle with Kotlin DSL](https://www.sngular.com/insights/320/custom-lint-task-configuration-in-gradle-with-kotlin-dsl) --- --- url: /catalog/tsx/avoid-jsx-short-circuit.md --- ## Avoid `&&` short circuit in JSX * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InRzeCIsInF1ZXJ5IjoiY29uc29sZS5sb2coJE1BVENIKSIsInJld3JpdGUiOiJsb2dnZXIubG9nKCRNQVRDSCkiLCJjb25maWciOiJpZDogZG8td2hhdC1icm9vb29vb2tseW4tc2FpZFxubGFuZ3VhZ2U6IFRzeFxuc2V2ZXJpdHk6IGVycm9yXG5ydWxlOlxuICBraW5kOiBqc3hfZXhwcmVzc2lvblxuICBoYXM6XG4gICAgcGF0dGVybjogJEEgJiYgJEJcbiAgbm90OlxuICAgIGluc2lkZTpcbiAgICAgIGtpbmQ6IGpzeF9hdHRyaWJ1dGVcbmZpeDogXCJ7JEEgPyAkQiA6IG51bGx9XCIiLCJzb3VyY2UiOiI8ZGl2PntcbiAgbnVtICYmIDxkaXYvPlxufTwvZGl2PiJ9) ### Description In [React](https://react.dev/learn/conditional-rendering), you can conditionally render JSX using JavaScript syntax like `if` statements, `&&`, and `? :` operators. However, you should almost never put numbers on the left side of `&&`. This is because React will render the number `0`, instead of the JSX element on the right side. A concrete example will be conditionally rendering a list when the list is not empty. This rule will find and fix any short-circuit rendering in JSX and rewrite it to a ternary operator. ### YAML ```yaml id: do-what-brooooooklyn-said language: Tsx rule: kind: jsx_expression has: pattern: $A && $B not: inside: kind: jsx_attribute fix: "{$A ? $B : null}" ``` ### Example ```tsx {1}
{ list.length && list.map(i =>

) }

``` ### Diff ```tsx
{ list.length && list.map(i =>

) }

// [!code --]
{ list.length ? list.map(i =>

) : null }

// [!code ++] ``` ### Contributed by [Herrington Darkholme](https://twitter.com/hd_nvim), inspired by [@Brooooook_lyn](https://twitter.com/Brooooook_lyn/status/1666637274757595141) --- --- url: /catalog/rust/rewrite-indoc-macro.md --- ## Rewrite `indoc!` macro * [Playground Link](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoicnVzdCIsInF1ZXJ5IjoiaW5kb2MhIHsgciNcIiQkJEFcIiMgfSIsInJld3JpdGUiOiJgJCQkQWAiLCJzdHJpY3RuZXNzIjoicmVsYXhlZCIsInNlbGVjdG9yIjoiIiwiY29uZmlnIjoicnVsZTogXG4gYW55OlxuIC0gcGF0dGVybjogJFYgPT09ICRTRU5TRVRJVkVXT1JEXG4gLSBwYXR0ZXJuOiAkU0VOU0VUSVZFV09SRCA9PT0gJFZcbmNvbnN0cmFpbnRzOlxuICBTRU5TRVRJVkVXT1JEOlxuICAgIHJlZ2V4OiBwYXNzd29yZCIsInNvdXJjZSI6ImZuIG1haW4oKSB7XG4gICAgaW5kb2MhIHtyI1wiXG4gICAgICAgIC5mb28ge1xuICAgICAgICAgICAgb3JkZXI6IDE7XG4gICAgICAgIH1cbiAgICBcIiN9O1xufSJ9) ### Description This example, created from [a Tweet](https://x.com/zack_overflow/status/1885065128590401551), shows a refactoring operation being performed on Rust source code. The changes involve removing `indoc!` macro declarations while preserving the CSS-like content within them. Previously, the same refactor is implemented by a _unreadable monster regex_ in vim syntax. :::details Click to see the original regex (neovim, btw) ```vimscript :%s/\v(indoc!|)(| )([|\{)r#"(([^#]+|\n+)+)"#/`\4` ``` I have to confess that I don't understand this regex even if I use neovim, btw. Let Claude break it down piece by piece: - `:%s/` - Vim substitution command for all lines - `\v` - Very magic mode in vim for simpler regex syntax - `(indoc!|)` - First capture group: matches either "indoc!" or nothing - `(| )` - Second capture group: matches either empty string or a space - `([|\{)` - Third capture group: matches either `[` or `{` - `r#"` - Matches literal `r#"` (Rust raw string delimiter) - `(([^#]+|\n+)+)` - Fourth capture group (nested): - `[^#]+` - One or more non-# characters - `|\n+` - OR one or more newlines - Outer `()+` makes this repeat one or more times - `"#` - Matches the closing raw string delimiter - \`\4\` - Replaces with the fourth capture group wrapped in backticks This regex is designed to find Rust raw string literals (possibly wrapped in `indoc!` macro), capture their content, and replace the entire match with just the content wrapped in backticks. It's more precise than my previous explanation and matches the pattern you're showing. ::: ### Pattern ```shell ast-grep --pattern 'indoc! { r#"$$$A"# }' --rewrite '`$$$A`' sgtest.rs ``` ### Example ```rs {2-6} fn main() { indoc! {r#" .foo { order: 1; } "#}; } ``` ### Diff ```rs fn main() { indoc! {r#" // [!code --] `.foo { // [!code ++] order: 1; } "#}; // [!code --] `; // [!code ++] } ``` ### Contributed by [Zack in SF](https://x.com/zack_overflow) --- --- url: /catalog/rust/get-digit-count-in-usize.md --- ## Get number of digits in a `usize` * [Playground Link](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoicnVzdCIsInF1ZXJ5IjoiJE5VTS50b19zdHJpbmcoKS5jaGFycygpLmNvdW50KCkiLCJyZXdyaXRlIjoiJE5VTS5jaGVja2VkX2lsb2cxMCgpLnVud3JhcF9vcigwKSArIDEiLCJjb25maWciOiIjIFlBTUwgUnVsZSBpcyBtb3JlIHBvd2VyZnVsIVxuIyBodHRwczovL2FzdC1ncmVwLmdpdGh1Yi5pby9ndWlkZS9ydWxlLWNvbmZpZy5odG1sI3J1bGVcbnJ1bGU6XG4gIGFueTpcbiAgICAtIHBhdHRlcm46IGNvbnNvbGUubG9nKCRBKVxuICAgIC0gcGF0dGVybjogY29uc29sZS5kZWJ1ZygkQSlcbmZpeDpcbiAgbG9nZ2VyLmxvZygkQSkiLCJzb3VyY2UiOiJsZXQgd2lkdGggPSAobGluZXMgKyBudW0pLnRvX3N0cmluZygpLmNoYXJzKCkuY291bnQoKTsifQ==) ### Description Getting the number of digits in a usize number can be useful for various purposes, such as counting the column width of line numbers in a text editor or formatting the output of a number with commas or spaces. A common but inefficient way of getting the number of digits in a `usize` number is to use `num.to_string().chars().count()`. This method converts the number to a string, iterates over its characters, and counts them. However, this method involves allocating a new string, which can be costly in terms of memory and time. A better alternative is to use [`checked_ilog10`](https://doc.rust-lang.org/std/primitive.usize.html#method.checked_ilog10). ```rs num.checked_ilog10().unwrap_or(0) + 1 ``` The snippet above computes the integer logarithm base 10 of the number and adds one. This snippet does not allocate any memory and is faster than the string conversion approach. The [efficient](https://doc.rust-lang.org/src/core/num/int_log10.rs.html) `checked_ilog10` function returns an `Option` that is `Some(log)` if the number is positive and `None` if the number is zero. The `unwrap_or(0)` function returns the value inside the option or `0` if the option is `None`. ### Pattern ```shell ast-grep -p '$NUM.to_string().chars().count()' \ -r '$NUM.checked_ilog10().unwrap_or(0) + 1' \ -l rs ``` ### Example ```rs {1} let width = (lines + num).to_string().chars().count(); ``` ### Diff ```rs let width = (lines + num).to_string().chars().count(); // [!code --] let width = (lines + num).checked_ilog10().unwrap_or(0) + 1; // [!code ++] ``` ### Contributed by [Herrington Darkholme](https://twitter.com/hd_nvim), inspired by [dogfooding ast-grep](https://github.com/ast-grep/ast-grep/issues/550) --- --- url: /catalog/rust/boshen-footgun.md --- ## Beware of char offset when iterate over a string * [Playground Link](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoicnVzdCIsInF1ZXJ5IjoiJEEuY2hhcnMoKS5lbnVtZXJhdGUoKSIsInJld3JpdGUiOiIkQS5jaGFyX2luZGljZXMoKSIsImNvbmZpZyI6IiIsInNvdXJjZSI6ImZvciAoaSwgY2hhcikgaW4gc291cmNlLmNoYXJzKCkuZW51bWVyYXRlKCkge1xuICAgIHByaW50bG4hKFwiQm9zaGVuIGlzIGFuZ3J5IDopXCIpO1xufSJ9) ### Description It's a common pitfall in Rust that counting _character offset_ is not the same as counting _byte offset_ when iterating through a string. Rust string is represented by utf-8 byte array, which is a variable-length encoding scheme. `chars().enumerate()` will yield the character offset, while [`char_indices()`](https://doc.rust-lang.org/std/primitive.str.html#method.char_indices) will yield the byte offset. ```rs let yes = "y̆es"; let mut char_indices = yes.char_indices(); assert_eq!(Some((0, 'y')), char_indices.next()); // not (0, 'y̆') assert_eq!(Some((1, '\u{0306}')), char_indices.next()); // note the 3 here - the last character took up two bytes assert_eq!(Some((3, 'e')), char_indices.next()); assert_eq!(Some((4, 's')), char_indices.next()); ``` Depending on your use case, you may want to use `char_indices()` instead of `chars().enumerate()`. ### Pattern ```shell ast-grep -p '$A.chars().enumerate()' \ -r '$A.char_indices()' \ -l rs ``` ### Example ```rs {1} for (i, char) in source.chars().enumerate() { println!("Boshen is angry :)"); } ``` ### Diff ```rs for (i, char) in source.chars().enumerate() { // [!code --] for (i, char) in source.char_indices() { // [!code ++] println!("Boshen is angry :)"); } ``` ### Contributed by Inspired by [Boshen's Tweet](https://x.com/boshen_c/status/1719033308682870891) ![Boshen's footgun](https://pbs.twimg.com/media/F9s7mJHaYAEndnY?format=jpg&name=medium) --- --- url: /catalog/rust/avoid-duplicated-exports.md --- ## Avoid Duplicated Exports * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InJ1c3QiLCJxdWVyeSI6IiIsImNvbmZpZyI6InJ1bGU6XG4gIGFsbDpcbiAgICAgLSBwYXR0ZXJuOiBwdWIgdXNlICRCOjokQztcbiAgICAgLSBpbnNpZGU6XG4gICAgICAgIGtpbmQ6IHNvdXJjZV9maWxlXG4gICAgICAgIGhhczpcbiAgICAgICAgICBwYXR0ZXJuOiBwdWIgbW9kICRBO1xuICAgICAtIGhhczpcbiAgICAgICAgcGF0dGVybjogJEFcbiAgICAgICAgc3RvcEJ5OiBlbmQiLCJzb3VyY2UiOiJwdWIgbW9kIGZvbztcbnB1YiB1c2UgZm9vOjpGb287XG5wdWIgdXNlIGZvbzo6QTo6QjtcblxuXG5wdWIgdXNlIGFhYTo6QTtcbnB1YiB1c2Ugd29vOjpXb287In0=) ### Description Generally, we don't encourage the use of re-exports. However, sometimes, to keep the interface exposed by a lib crate tidy, we use re-exports to shorten the path to specific items. When doing so, a pitfall is to export a single item under two different names. Consider: ```rs pub mod foo; pub use foo::Foo; ``` The issue with this code, is that `Foo` is now exposed under two different paths: `Foo`, `foo::Foo`. This unnecessarily increases the surface of your API. It can also cause issues on the client side. For example, it makes the usage of auto-complete in the IDE more involved. Instead, ensure you export only once with `pub`. ### YAML ```yaml id: avoid-duplicate-export language: rust rule: all: - pattern: pub use $B::$C; - inside: kind: source_file has: pattern: pub mod $A; - has: pattern: $A stopBy: end ``` ### Example ```rs {2,3} pub mod foo; pub use foo::Foo; pub use foo::A::B; pub use aaa::A; pub use woo::Woo; ``` ### Contributed by Julius Lungys([voidpumpkin](https://github.com/voidpumpkin)) --- --- url: /catalog/ruby/prefer-symbol-over-proc.md --- ## Prefer Symbol over Proc * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InJ1YnkiLCJxdWVyeSI6IiRMSVNULnNlbGVjdCB7IHwkVnwgJFYuJE1FVEhPRCB9IiwicmV3cml0ZSI6IiRMSVNULnNlbGVjdCgmOiRNRVRIT0QpIiwiY29uZmlnIjoiaWQ6IHByZWZlci1zeW1ib2wtb3Zlci1wcm9jXG5ydWxlOlxuICBwYXR0ZXJuOiAkTElTVC4kSVRFUiB7IHwkVnwgJFYuJE1FVEhPRCB9XG5sYW5ndWFnZTogUnVieVxuY29uc3RyYWludHM6XG4gIElURVI6XG4gICAgcmVnZXg6ICdtYXB8c2VsZWN0fGVhY2gnXG5maXg6ICckTElTVC4kSVRFUigmOiRNRVRIT0QpJ1xuIiwic291cmNlIjoiWzEsIDIsIDNdLnNlbGVjdCB7IHx2fCB2LmV2ZW4/IH1cbigxLi4xMDApLmVhY2ggeyB8aXwgaS50b19zIH1cbm5vdF9saXN0Lm5vX21hdGNoIHsgfHZ8IHYuZXZlbj8gfVxuIn0=) ### Description Ruby has a more concise symbol shorthand `&:` to invoke methods. This rule simplifies `proc` to `symbol`. This example is inspired by this [dev.to article](https://dev.to/baweaver/future-of-ruby-ast-tooling-9i1). ### YAML ```yaml id: prefer-symbol-over-proc language: ruby rule: pattern: $LIST.$ITER { |$V| $V.$METHOD } constraints: ITER: regex: 'map|select|each' fix: '$LIST.$ITER(&:$METHOD)' ``` ### Example ```rb {1,2} [1, 2, 3].select { |v| v.even? } (1..100).each { |i| i.to_s } not_list.no_match { |v| v.even? } ``` ### Diff ```rb [1, 2, 3].select { |v| v.even? } # [!code --] [1, 2, 3].select(&:even?) # [!code ++] (1..100).each { |i| i.to_s } # [!code --] (1..100).each(&:to_s) # [!code ++] not_list.no_match { |v| v.even? } ``` ### Contributed by [Herrington Darkholme](https://twitter.com/hd_nvim) --- --- url: /catalog/ruby/migrate-action-filter.md --- ## Migrate action_filter in Ruby on Rails * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InJ1YnkiLCJxdWVyeSI6ImNvbnNvbGUubG9nKCRNQVRDSCkiLCJyZXdyaXRlIjoibG9nZ2VyLmxvZygkTUFUQ0gpIiwiY29uZmlnIjoiIyBhc3QtZ3JlcCBZQU1MIFJ1bGUgaXMgcG93ZXJmdWwgZm9yIGxpbnRpbmchXG4jIGh0dHBzOi8vYXN0LWdyZXAuZ2l0aHViLmlvL2d1aWRlL3J1bGUtY29uZmlnLmh0bWwjcnVsZVxucnVsZTpcbiAgYW55OlxuICAgIC0gcGF0dGVybjogYmVmb3JlX2ZpbHRlciAkJCRBQ1RJT05cbiAgICAtIHBhdHRlcm46IGFyb3VuZF9maWx0ZXIgJCQkQUNUSU9OXG4gICAgLSBwYXR0ZXJuOiBhZnRlcl9maWx0ZXIgJCQkQUNUSU9OXG4gIGhhczpcbiAgICBwYXR0ZXJuOiAkRklMVEVSXG4gICAgZmllbGQ6IG1ldGhvZFxuZml4OiBcbiAgJE5FV19BQ1RJT04gJCQkQUNUSU9OXG50cmFuc2Zvcm06XG4gIE5FV19BQ1RJT046XG4gICAgcmVwbGFjZTpcbiAgICAgIHNvdXJjZTogJEZJTFRFUlxuICAgICAgcmVwbGFjZTogX2ZpbHRlclxuICAgICAgYnk6IF9hY3Rpb24iLCJzb3VyY2UiOiJjbGFzcyBUb2Rvc0NvbnRyb2xsZXIgPCBBcHBsaWNhdGlvbkNvbnRyb2xsZXJcbiAgYmVmb3JlX2ZpbHRlciA6YXV0aGVudGljYXRlXG4gIGFyb3VuZF9maWx0ZXIgOndyYXBfaW5fdHJhbnNhY3Rpb24sIG9ubHk6IDpzaG93XG4gIGFmdGVyX2ZpbHRlciBkbyB8Y29udHJvbGxlcnxcbiAgICBmbGFzaFs6ZXJyb3JdID0gXCJZb3UgbXVzdCBiZSBsb2dnZWQgaW5cIlxuICBlbmRcblxuICBkZWYgaW5kZXhcbiAgICBAdG9kb3MgPSBUb2RvLmFsbFxuICBlbmRcbmVuZFxuIn0=) ### Description This rule is used to migrate `{before,after,around}_filter` to `{before,after,around}_action` in Ruby on Rails controllers. These are methods that run before, after or around an action is executed, and they can be used to check permissions, set variables, redirect requests, log events, etc. However, these methods are [deprecated](https://stackoverflow.com/questions/16519828/rails-4-before-filter-vs-before-action) in Rails 5.0 and will be removed in Rails 5.1. `{before,after,around}_action` are the new syntax for the same functionality. This rule will replace all occurrences of `{before,after,around}_filter` with `{before,after,around}_action` in the controller code. ### YAML ```yaml id: migration-action-filter language: ruby rule: any: - pattern: before_filter $$$ACTION - pattern: around_filter $$$ACTION - pattern: after_filter $$$ACTION has: pattern: $FILTER field: method fix: $NEW_ACTION $$$ACTION transform: NEW_ACTION: replace: source: $FILTER replace: _filter by: _action ``` ### Example ```rb {2-4} class TodosController < ApplicationController before_filter :authenticate around_filter :wrap_in_transaction, only: :show after_filter do |controller| flash[:error] = "You must be logged in" end def index @todos = Todo.all end end ``` ### Diff ```rb class TodosController < ApplicationController before_action :authenticate # [!code --] before_filter :authenticate # [!code ++] around_action :wrap_in_transaction, only: :show # [!code --] around_filter :wrap_in_transaction, only: :show # [!code ++] after_action do |controller| # [!code --] flash[:error] = "You must be logged in" # [!code --] end # [!code --] after_filter do |controller| # [!code ++] flash[:error] = "You must be logged in" # [!code ++] end # [!code ++] def index @todos = Todo.all end end ``` ### Contributed by [Herrington Darkholme](https://twitter.com/hd_nvim), inspired by [Future of Ruby - AST Tooling](https://dev.to/baweaver/future-of-ruby-ast-tooling-9i1). --- --- url: /catalog/python/use-walrus-operator-in-if.md --- ## Use Walrus Operator in `if` statement * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InB5dGhvbiIsInF1ZXJ5IjoiZm4gbWFpbigpIHsgXG4gICAgJCQkO1xuICAgIGlmKCRBKXskJCRCfSBcbiAgICBpZigkQSl7JCQkQ30gXG4gICAgJCQkRlxufSIsInJld3JpdGUiOiJmbiBtYWluKCkgeyAkJCRFOyBpZigkQSl7JCQkQiAkJCRDfSAkJCRGfSIsImNvbmZpZyI6ImlkOiB1c2Utd2FscnVzLW9wZXJhdG9yXG5ydWxlOlxuICBmb2xsb3dzOlxuICAgIHBhdHRlcm46XG4gICAgICBjb250ZXh0OiAkVkFSID0gJCQkRVhQUlxuICAgICAgc2VsZWN0b3I6IGV4cHJlc3Npb25fc3RhdGVtZW50XG4gIHBhdHRlcm46IFwiaWYgJFZBUjogJCQkQlwiXG5maXg6IHwtXG4gIGlmICRWQVIgOj0gJCQkRVhQUjpcbiAgICAkJCRCXG4tLS1cbmlkOiByZW1vdmUtZGVjbGFyYXRpb25cbnJ1bGU6XG4gIHBhdHRlcm46XG4gICAgY29udGV4dDogJFZBUiA9ICQkJEVYUFJcbiAgICBzZWxlY3RvcjogZXhwcmVzc2lvbl9zdGF0ZW1lbnRcbiAgcHJlY2VkZXM6XG4gICAgcGF0dGVybjogXCJpZiAkVkFSOiAkJCRCXCJcbmZpeDogJyciLCJzb3VyY2UiOiJhID0gZm9vKClcblxuaWYgYTpcbiAgICBkb19iYXIoKSJ9) ### Description The walrus operator (`:=`) introduced in Python 3.8 allows you to assign values to variables as part of an expression. This rule aims to simplify code by using the walrus operator in `if` statements. This first part of the rule identifies cases where a variable is assigned a value and then immediately used in an `if` statement to control flow. ```yaml id: use-walrus-operator language: python rule: pattern: "if $VAR: $$$B" follows: pattern: context: $VAR = $$$EXPR selector: expression_statement fix: |- if $VAR := $$$EXPR: $$$B ``` The `pattern` clause finds an `if` statement that checks the truthiness of `$VAR`. If this pattern `follows` an expression statement where `$VAR` is assigned `$$$EXPR`, the `fix` clause changes the `if` statements to use the walrus operator. The second part of the rule: ```yaml id: remove-declaration rule: pattern: context: $VAR = $$$EXPR selector: expression_statement precedes: pattern: "if $VAR: $$$B" fix: '' ``` This rule removes the standalone variable assignment when it directly precedes an `if` statement that uses the walrus operator. Since the assignment is now part of the `if` statement, the separate declaration is no longer needed. By applying these rules, you can refactor your Python code to be more concise and readable, taking advantage of the walrus operator's ability to combine an assignment with an expression. ### YAML ```yaml id: use-walrus-operator language: python rule: follows: pattern: context: $VAR = $$$EXPR selector: expression_statement pattern: "if $VAR: $$$B" fix: |- if $VAR := $$$EXPR: $$$B --- id: remove-declaration language: python rule: pattern: context: $VAR = $$$EXPR selector: expression_statement precedes: pattern: "if $VAR: $$$B" fix: '' ``` ### Example ```python a = foo() if a: do_bar() ``` ### Diff ```python a = foo() # [!code --] if a: # [!code --] if a := foo(): # [!code ++] do_bar() ``` ### Contributed by Inspired by reddit user [/u/jackerhack](https://www.reddit.com/r/rust/comments/13eg738/comment/kagdklw/?) --- --- url: /catalog/python/refactor-pytest-fixtures.md --- ## Refactor pytest fixtures * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InB5dGhvbiIsInF1ZXJ5IjoiZGVmIGZvbygkWCk6XG4gICRTIiwicmV3cml0ZSI6ImxvZ2dlci5sb2coJE1BVENIKSIsImNvbmZpZyI6ImlkOiBweXRlc3QtdHlwZS1oaW50LWZpeHR1cmVcbmxhbmd1YWdlOiBQeXRob25cbnV0aWxzOlxuICBpcy1maXh0dXJlLWZ1bmN0aW9uOlxuICAgIGtpbmQ6IGZ1bmN0aW9uX2RlZmluaXRpb25cbiAgICBmb2xsb3dzOlxuICAgICAga2luZDogZGVjb3JhdG9yXG4gICAgICBoYXM6XG4gICAgICAgIGtpbmQ6IGlkZW50aWZpZXJcbiAgICAgICAgcmVnZXg6IF5maXh0dXJlJFxuICAgICAgICBzdG9wQnk6IGVuZFxuICBpcy10ZXN0LWZ1bmN0aW9uOlxuICAgIGtpbmQ6IGZ1bmN0aW9uX2RlZmluaXRpb25cbiAgICBoYXM6XG4gICAgICBmaWVsZDogbmFtZVxuICAgICAgcmVnZXg6IF50ZXN0X1xuICBpcy1weXRlc3QtY29udGV4dDpcbiAgICAjIFB5dGVzdCBjb250ZXh0IGlzIGEgbm9kZSBpbnNpZGUgYSBweXRlc3RcbiAgICAjIHRlc3QvZml4dHVyZVxuICAgIGluc2lkZTpcbiAgICAgIHN0b3BCeTogZW5kXG4gICAgICBhbnk6XG4gICAgICAgIC0gbWF0Y2hlczogaXMtZml4dHVyZS1mdW5jdGlvblxuICAgICAgICAtIG1hdGNoZXM6IGlzLXRlc3QtZnVuY3Rpb25cbiAgaXMtZml4dHVyZS1hcmc6XG4gICAgIyBGaXh0dXJlIGFyZ3VtZW50cyBhcmUgaWRlbnRpZmllcnMgaW5zaWRlIHRoZSBcbiAgICAjIHBhcmFtZXRlcnMgb2YgYSB0ZXN0L2ZpeHR1cmUgZnVuY3Rpb25cbiAgICBhbGw6XG4gICAgICAtIGtpbmQ6IGlkZW50aWZpZXJcbiAgICAgIC0gbWF0Y2hlczogaXMtcHl0ZXN0LWNvbnRleHRcbiAgICAgIC0gaW5zaWRlOlxuICAgICAgICAgIGtpbmQ6IHBhcmFtZXRlcnNcbnJ1bGU6XG4gIG1hdGNoZXM6IGlzLWZpeHR1cmUtYXJnXG4gIHJlZ2V4OiBeZm9vJFxuZml4OiAnZm9vOiBpbnQnXG4iLCJzb3VyY2UiOiJmcm9tIGNvbGxlY3Rpb25zLmFiYyBpbXBvcnQgSXRlcmFibGVcbmZyb20gdHlwaW5nIGltcG9ydCBBbnlcblxuaW1wb3J0IHB5dGVzdFxuZnJvbSBweXRlc3QgaW1wb3J0IGZpeHR1cmVcblxuQHB5dGVzdC5maXh0dXJlKHNjb3BlPVwic2Vzc2lvblwiKVxuZGVmIGZvbygpIC0+IEl0ZXJhYmxlW2ludF06XG4gICAgeWllbGQgNVxuXG5AZml4dHVyZVxuZGVmIGJhcihmb28pIC0+IHN0cjpcbiAgICByZXR1cm4gc3RyKGZvbylcblxuZGVmIHJlZ3VsYXJfZnVuY3Rpb24oZm9vKSAtPiBOb25lOlxuICAgICMgVGhpcyBmdW5jdGlvbiBkb2Vzbid0IHVzZSB0aGUgJ2ZvbycgZml4dHVyZVxuICAgIHByaW50KGZvbylcblxuZGVmIHRlc3RfMShmb28sIGJhcik6XG4gICAgcHJpbnQoZm9vLCBiYXIpXG5cbmRlZiB0ZXN0XzIoYmFyKTpcbiAgICAuLi4ifQ==) ### Description One of the most commonly used testing framework in Python is [pytest](https://docs.pytest.org/en/8.2.x/). Among other things, it allows the use of [fixtures](https://docs.pytest.org/en/6.2.x/fixture.html). Fixtures are defined as functions that can be required in test code, or in other fixtures, as an argument. This means that all functions arguments with a given name in a pytest context (test function or fixture) are essentially the same entity. However, not every editor's LSP is able to keep track of this, making refactoring challenging. Using ast-grep, we can define some rules to match fixture definition and usage without catching similarly named entities in a non-test context. First, we define utils to select pytest test/fixture functions. ```yaml utils: is-fixture-function: kind: function_definition follows: kind: decorator has: kind: identifier regex: ^fixture$ stopBy: end is-test-function: kind: function_definition has: field: name regex: ^test_ ``` Pytest fixtures are declared with a decorator `@pytest.fixture`. We match the `function_definition` node that directly follows a `decorator` node. That decorator node must have a `fixture` identifier somewhere. This accounts for different location of the `fixture` node depending on the type of imports and whether the decorator is used as is or called with parameters. Pytest functions are fairly straghtforward to detect, as they always start with `test_` by convention. The next utils builds onto those two to incrementally: - Find if a node is inside a pytest context (test/fixture) - Find if a node is an argument in such a context ```yaml utils: is-pytest-context: # Pytest context is a node inside a pytest # test/fixture inside: stopBy: end any: - matches: is-fixture-function - matches: is-test-function is-fixture-arg: # Fixture arguments are identifiers inside the # parameters of a test/fixture function all: - kind: identifier - inside: kind: parameters - matches: is-pytest-context ``` Once those utils are declared, you can perform various refactoring on a specific fixture. The following rule adds a type-hint to a fixture. ```yaml rule: matches: is-fixture-arg regex: ^foo$ fix: 'foo: int' ``` This one renames a fixture and all its references. ```yaml rule: kind: identifier matches: is-fixture-context regex: ^foo$ fix: 'five' ``` ### Example #### Renaming Fixtures ```python {2,6,7,12,13} @pytest.fixture def foo() -> int: return 5 @pytest.fixture(scope="function") def some_fixture(foo: int) -> str: return str(foo) def regular_function(foo) -> None: ... def test_code(foo: int) -> None: assert foo == 5 ``` #### Diff ```python {2,6,7,12} @pytest.fixture def foo() -> int: # [!code --] def five() -> int: # [!code ++] return 5 @pytest.fixture(scope="function") def some_fixture(foo: int) -> str: # [!code --] def some_fixture(five: int) -> str: # [!code ++] return str(foo) def regular_function(foo) -> None: ... def test_code(foo: int) -> None: # [!code --] def test_code(five: int) -> None: # [!code ++] assert foo == 5 # [!code --] assert five == 5 # [!code ++] ``` #### Type Hinting Fixtures ```python {6,12} @pytest.fixture def foo() -> int: return 5 @pytest.fixture(scope="function") def some_fixture(foo) -> str: return str(foo) def regular_function(foo) -> None: ... def test_code(foo) -> None: assert foo == 5 ``` #### Diff ```python {2,6,7,12} @pytest.fixture def foo() -> int: return 5 @pytest.fixture(scope="function") def some_fixture(foo) -> str: # [!code --] def some_fixture(foo: int) -> str: # [!code ++] return str(foo) def regular_function(foo) -> None: ... def test_code(foo) -> None: # [!code --] def test_code(foo: int) -> None: # [!code ++] assert foo == 5 ``` --- --- url: /catalog/python/prefer-generator-expressions.md --- ## Prefer Generator Expressions * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InB5dGhvbiIsInF1ZXJ5IjoiWyQkJEFdIiwicmV3cml0ZSI6IiRBPy4oKSIsImNvbmZpZyI6InJ1bGU6XG4gIHBhdHRlcm46ICRGVU5DKCRMSVNUKVxuY29uc3RyYWludHM6XG4gIExJU1Q6IHsga2luZDogbGlzdF9jb21wcmVoZW5zaW9uIH1cbiAgRlVOQzpcbiAgICBhbnk6XG4gICAgICAtIHBhdHRlcm46IGFueVxuICAgICAgLSBwYXR0ZXJuOiBhbGxcbiAgICAgIC0gcGF0dGVybjogc3VtXG4gICAgICAjIC4uLlxudHJhbnNmb3JtOlxuICBJTk5FUjpcbiAgICBzdWJzdHJpbmc6IHtzb3VyY2U6ICRMSVNULCBzdGFydENoYXI6IDEsIGVuZENoYXI6IC0xIH1cbmZpeDogJEZVTkMoJElOTkVSKSIsInNvdXJjZSI6ImFsbChbeCBmb3IgeCBpbiB5XSlcblt4IGZvciB4IGluIHldIn0=) ### Description List comprehensions like `[x for x in range(10)]` are a concise way to create lists in Python. However, we can achieve better memory efficiency by using generator expressions like `(x for x in range(10))` instead. List comprehensions create the entire list in memory, while generator expressions generate each element one at a time. We can make the change by replacing the square brackets with parentheses. ### YAML ```yaml id: prefer-generator-expressions language: python rule: pattern: $LIST kind: list_comprehension transform: INNER: substring: {source: $LIST, startChar: 1, endChar: -1 } fix: ($INNER) ``` This rule converts every list comprehension to a generator expression. However, **not every list comprehension can be replaced with a generator expression.** If the list is used multiple times, is modified, is sliced, or is indexed, a generator is not a suitable replacement. Some common functions like `any`, `all`, and `sum` take an `iterable` as an argument. A generator function counts as an `iterable`, so it is safe to change a list comprehension to a generator expression in this context. ```yaml id: prefer-generator-expressions language: python rule: pattern: $FUNC($LIST) constraints: LIST: { kind: list_comprehension } FUNC: any: - pattern: any - pattern: all - pattern: sum # ... transform: INNER: substring: {source: $LIST, startChar: 1, endChar: -1 } fix: $FUNC($INNER) ``` ### Example ```python any([x for x in range(10)]) ``` ### Diff ```python any([x for x in range(10)]) # [!code --] any(x for x in range(10)) # [!code ++] ``` ### Contributed by [Steven Love](https://github.com/StevenLove) --- --- url: /catalog/python/optional-to-none-union.md --- ## Rewrite `Optional[Type]` to `Type | None` * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InB5dGhvbiIsInF1ZXJ5IjoiIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJzaWduYXR1cmUiLCJzZWxlY3RvciI6IiIsImNvbmZpZyI6InJ1bGU6XG4gIHBhdHRlcm46IFxuICAgIGNvbnRleHQ6ICdhOiBPcHRpb25hbFskVF0nXG4gICAgc2VsZWN0b3I6IGdlbmVyaWNfdHlwZVxuZml4OiAkVCB8IE5vbmUiLCJzb3VyY2UiOiJkZWYgYShhcmc6IE9wdGlvbmFsW0ludF0pOiBwYXNzIn0=) ### Description [PEP 604](https://peps.python.org/pep-0604/) recommends that `Type | None` is preferred over `Optional[Type]` for Python 3.10+. This rule performs such rewriting. Note `Optional[$T]` alone is interpreted as subscripting expression instead of generic type, we need to use [pattern object](/guide/rule-config/atomic-rule.html#pattern-object) to disambiguate it with more context code. ### YAML ```yaml id: optional-to-none-union language: python rule: pattern: context: 'a: Optional[$T]' selector: generic_type fix: $T | None ``` ### Example ```py {1} def a(arg: Optional[Int]): pass ``` ### Diff ```py def a(arg: Optional[Int]): pass # [!code --] def a(arg: Int | None): pass # [!code ++] ``` ### Contributed by [Bede Carroll](https://github.com/ast-grep/ast-grep/discussions/1492) --- --- url: /catalog/python/migrate-openai-sdk.md --- ## Migrate OpenAI SDK * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InB5dGhvbiIsInF1ZXJ5IjoiZGVmICRGVU5DKCQkJEFSR1MpOiAkJCRCT0RZIiwicmV3cml0ZSI6IiIsImNvbmZpZyI6InJ1bGU6XG4gIHBhdHRlcm46IGltcG9ydCBvcGVuYWlcbmZpeDogZnJvbSBvcGVuYWkgaW1wb3J0IENsaWVudFxuLS0tXG5ydWxlOlxuICBwYXR0ZXJuOiBvcGVuYWkuYXBpX2tleSA9ICRLRVlcbmZpeDogY2xpZW50ID0gQ2xpZW50KCRLRVkpXG4tLS1cbnJ1bGU6XG4gIHBhdHRlcm46IG9wZW5haS5Db21wbGV0aW9uLmNyZWF0ZSgkJCRBUkdTKVxuZml4OiB8LVxuICBjbGllbnQuY29tcGxldGlvbnMuY3JlYXRlKFxuICAgICQkJEFSR1NcbiAgKSIsInNvdXJjZSI6ImltcG9ydCBvc1xuaW1wb3J0IG9wZW5haVxuZnJvbSBmbGFzayBpbXBvcnQgRmxhc2ssIGpzb25pZnlcblxuYXBwID0gRmxhc2soX19uYW1lX18pXG5vcGVuYWkuYXBpX2tleSA9IG9zLmdldGVudihcIk9QRU5BSV9BUElfS0VZXCIpXG5cblxuQGFwcC5yb3V0ZShcIi9jaGF0XCIsIG1ldGhvZHM9KFwiUE9TVFwiKSlcbmRlZiBpbmRleCgpOlxuICAgIGFuaW1hbCA9IHJlcXVlc3QuZm9ybVtcImFuaW1hbFwiXVxuICAgIHJlc3BvbnNlID0gb3BlbmFpLkNvbXBsZXRpb24uY3JlYXRlKFxuICAgICAgICBtb2RlbD1cInRleHQtZGF2aW5jaS0wMDNcIixcbiAgICAgICAgcHJvbXB0PWdlbmVyYXRlX3Byb21wdChhbmltYWwpLFxuICAgICAgICB0ZW1wZXJhdHVyZT0wLjYsXG4gICAgKVxuICAgIHJldHVybiBqc29uaWZ5KHJlc3BvbnNlLmNob2ljZXMpIn0=) ### Description OpenAI has introduced some breaking changes in their API, such as using `Client` to initialize the service and renaming the `Completion` method to `completions` . This example shows how to use ast-grep to automatically update your code to the new API. API migration requires multiple related rules to work together. The example shows how to write [multiple rules](/reference/playground.html#test-multiple-rules) in a [single YAML](/guide/rewrite-code.html#using-fix-in-yaml-rule) file. The rules and patterns in the example are simple and self-explanatory, so we will not explain them further. ### YAML ```yaml id: import-openai language: python rule: pattern: import openai fix: from openai import Client --- id: rewrite-client language: python rule: pattern: openai.api_key = $KEY fix: client = Client($KEY) --- id: rewrite-chat-completion language: python rule: pattern: openai.Completion.create($$$ARGS) fix: |- client.completions.create( $$$ARGS ) ``` ### Example ```python {2,6,11-15} import os import openai from flask import Flask, jsonify app = Flask(__name__) openai.api_key = os.getenv("OPENAI_API_KEY") @app.route("/chat", methods=("POST")) def index(): animal = request.form["animal"] response = openai.Completion.create( model="text-davinci-003", prompt=generate_prompt(animal), temperature=0.6, ) return jsonify(response.choices) ``` ### Diff ```python import os import openai # [!code --] from openai import Client # [!code ++] from flask import Flask, jsonify app = Flask(__name__) openai.api_key = os.getenv("OPENAI_API_KEY") # [!code --] client = Client(os.getenv("OPENAI_API_KEY")) # [!code ++] @app.route("/chat", methods=("POST")) def index(): animal = request.form["animal"] response = openai.Completion.create( # [!code --] response = client.completions.create( # [!code ++] model="text-davinci-003", prompt=generate_prompt(animal), temperature=0.6, ) return jsonify(response.choices) ``` ### Contributed by [Herrington Darkholme](https://twitter.com/hd_nvim), inspired by [Morgante](https://twitter.com/morgantepell/status/1721668781246750952) from [grit.io](https://www.grit.io/) --- --- url: /catalog/tsx/redundant-usestate-type.md --- ## Unnecessary `useState` Type * [Playground Link](/playground.html#eyJtb2RlIjoiUGF0Y2giLCJsYW5nIjoidHlwZXNjcmlwdCIsInF1ZXJ5IjoidXNlU3RhdGU8c3RyaW5nPigkQSkiLCJyZXdyaXRlIjoidXNlU3RhdGUoJEEpIiwiY29uZmlnIjoiIyBZQU1MIFJ1bGUgaXMgbW9yZSBwb3dlcmZ1bCFcbiMgaHR0cHM6Ly9hc3QtZ3JlcC5naXRodWIuaW8vZ3VpZGUvcnVsZS1jb25maWcuaHRtbCNydWxlXG5ydWxlOlxuICBhbnk6XG4gICAgLSBwYXR0ZXJuOiBjb25zb2xlLmxvZygkQSlcbiAgICAtIHBhdHRlcm46IGNvbnNvbGUuZGVidWcoJEEpXG5maXg6XG4gIGxvZ2dlci5sb2coJEEpIiwic291cmNlIjoiZnVuY3Rpb24gQ29tcG9uZW50KCkge1xuICBjb25zdCBbbmFtZSwgc2V0TmFtZV0gPSB1c2VTdGF0ZTxzdHJpbmc+KCdSZWFjdCcpXG59In0=) ### Description React's [`useState`](https://react.dev/reference/react/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(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 ::: code-group ```bash [number] ast-grep -p 'useState($A)' -r 'useState($A)' -l tsx ``` ```bash [string] ast-grep -p 'useState($A)' -r 'useState($A)' ``` ```bash [boolean] ast-grep -p 'useState($A)' -r 'useState($A)' ``` ::: ### Example ```ts {2} function Component() { const [name, setName] = useState('React') } ``` ### Diff ```ts function Component() { const [name, setName] = useState('React') // [!code --] const [name, setName] = useState('React') // [!code ++] } ``` ### Contributed by [Herrington Darkholme](https://twitter.com/hd_nvim) --- --- url: /catalog/tsx/avoid-nested-links.md --- ## Avoid nested links * [Playground Link](https://ast-grep.github.io/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InRzeCIsInF1ZXJ5IjoiaWYgKCRBKSB7ICQkJEIgfSIsInJld3JpdGUiOiJpZiAoISgkQSkpIHtcbiAgICByZXR1cm47XG59XG4kJCRCIiwic3RyaWN0bmVzcyI6InNtYXJ0Iiwic2VsZWN0b3IiOiIiLCJjb25maWciOiJpZDogbm8tbmVzdGVkLWxpbmtzXG5sYW5ndWFnZTogdHN4XG5zZXZlcml0eTogZXJyb3JcbnJ1bGU6XG4gIHBhdHRlcm46IDxhICQkJD4kJCRBPC9hPlxuICBoYXM6XG4gICAgcGF0dGVybjogPGEgJCQkPiQkJDwvYT5cbiAgICBzdG9wQnk6IGVuZCIsInNvdXJjZSI6ImZ1bmN0aW9uIENvbXBvbmVudCgpIHtcbiAgcmV0dXJuIDxhIGhyZWY9Jy9kZXN0aW5hdGlvbic+XG4gICAgPGEgaHJlZj0nL2Fub3RoZXJkZXN0aW5hdGlvbic+TmVzdGVkIGxpbmshPC9hPlxuICA8L2E+O1xufVxuZnVuY3Rpb24gT2theUNvbXBvbmVudCgpIHtcbiAgcmV0dXJuIDxhIGhyZWY9Jy9kZXN0aW5hdGlvbic+XG4gICAgSSBhbSBqdXN0IGEgbGluay5cbiAgPC9hPjtcbn0ifQ==) ### Description React will produce a warning message if you nest a link element inside of another link element. This rule will catch this mistake! ### YAML ```yaml id: no-nested-links language: tsx severity: error rule: pattern: $$$A has: pattern: $$$ stopBy: end ``` ### Example ```tsx {1-5} function Component() { return Nested link! ; } function OkayComponent() { return I am just a link. ; } ``` ### Contributed by [Tom MacWright](https://macwright.com/) --- --- url: /catalog/yaml/find-key-value.md --- ## Find key/value and Show Message * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InlhbWwiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoic21hcnQiLCJzZWxlY3RvciI6IiIsImNvbmZpZyI6ImlkOiBkZXRlY3QtaG9zdC1wb3J0XG5tZXNzYWdlOiBZb3UgYXJlIHVzaW5nICRIT1NUIG9uIFBvcnQgJFBPUlQsIHBsZWFzZSBjaGFuZ2UgaXQgdG8gODAwMFxuc2V2ZXJpdHk6IGVycm9yXG5ydWxlOlxuICBhbnk6XG4gIC0gcGF0dGVybjogfFxuICAgICBwb3J0OiAkUE9SVFxuICAtIHBhdHRlcm46IHxcbiAgICAgaG9zdDogJEhPU1QiLCJzb3VyY2UiOiJkYjpcbiAgIHVzZXJuYW1lOiByb290XG4gICBwYXNzd29yZDogcm9vdFxuXG5zZXJ2ZXI6XG4gIGhvc3Q6IDEyNy4wLjAuMVxuICBwb3J0OiA4MDAxIn0=) ### Description This YAML rule helps detecting specific host and port configurations in your code. For example, it checks if the port is set to something other than 8000 or if a particular host is used. It provides an error message prompting you to update the configuration. ### YAML ```yaml id: detect-host-port message: You are using $HOST on Port $PORT, please change it to 8000 severity: error rule: any: - pattern: | port: $PORT - pattern: | host: $HOST ``` ### Example ```yaml {5,6} db: username: root password: root server: host: 127.0.0.1 port: 8001 ``` ### Contributed by [rohitcoder](https://twitter.com/rohitcoder) on [Discord](https://discord.com/invite/4YZjf6htSQ). --- --- url: /catalog/typescript/switch-from-should-to-expect.md --- ## Switch Chai from `should` style to `expect` * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InJ1c3QiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoicmVsYXhlZCIsInNlbGVjdG9yIjoiIiwiY29uZmlnIjoiaWQ6IHNob3VsZF90b19leHBlY3RfaW5zdGFuY2VvZlxubGFuZ3VhZ2U6IFR5cGVTY3JpcHRcbnJ1bGU6XG4gIGFueTpcbiAgLSBwYXR0ZXJuOiAkTkFNRS5zaG91bGQuYmUuYW4uaW5zdGFuY2VvZigkVFlQRSlcbiAgLSBwYXR0ZXJuOiAkTkFNRS5zaG91bGQuYmUuYW4uaW5zdGFuY2VPZigkVFlQRSlcbmZpeDogfC1cbiAgZXhwZWN0KCROQU1FKS5pbnN0YW5jZU9mKCRUWVBFKVxuLS0tXG5pZDogc2hvdWxkX3RvX2V4cGVjdF9nZW5lcmljU2hvdWxkQmVcbmxhbmd1YWdlOiBUeXBlU2NyaXB0XG5ydWxlOlxuICBwYXR0ZXJuOiAkTkFNRS5zaG91bGQuYmUuJFBST1BcbmZpeDogfC1cbiAgZXhwZWN0KCROQU1FKS50by5iZS4kUFJPUFxuIiwic291cmNlIjoiaXQoJ3Nob3VsZCBwcm9kdWNlIGFuIGluc3RhbmNlIG9mIGNob2tpZGFyLkZTV2F0Y2hlcicsICgpID0+IHtcbiAgd2F0Y2hlci5zaG91bGQuYmUuYW4uaW5zdGFuY2VvZihjaG9raWRhci5GU1dhdGNoZXIpO1xufSk7XG5pdCgnc2hvdWxkIGV4cG9zZSBwdWJsaWMgQVBJIG1ldGhvZHMnLCAoKSA9PiB7XG4gIHdhdGNoZXIub24uc2hvdWxkLmJlLmEoJ2Z1bmN0aW9uJyk7XG4gIHdhdGNoZXIuZW1pdC5zaG91bGQuYmUuYSgnZnVuY3Rpb24nKTtcbiAgd2F0Y2hlci5hZGQuc2hvdWxkLmJlLmEoJ2Z1bmN0aW9uJyk7XG4gIHdhdGNoZXIuY2xvc2Uuc2hvdWxkLmJlLmEoJ2Z1bmN0aW9uJyk7XG4gIHdhdGNoZXIuZ2V0V2F0Y2hlZC5zaG91bGQuYmUuYSgnZnVuY3Rpb24nKTtcbn0pOyJ9) ### Description [Chai](https://www.chaijs.com) is a BDD / TDD assertion library for JavaScript. It comes with [two styles](https://www.chaijs.com/) of assertions: `should` and `expect`. The `expect` interface provides a function as a starting point for chaining your language assertions and works with `undefined` and `null` values. The `should` style allows for the same chainable assertions as the expect interface, however it extends each object with a should property to start your chain and [does not work](https://www.chaijs.com/guide/styles/#should-extras) with `undefined` and `null` values. This rule migrates Chai `should` style assertions to `expect` style assertions. Note this is an example rule and a excerpt from [the original rules](https://github.com/43081j/codemods/blob/cddfe101e7f759e4da08b7e2f7bfe892c20f6f48/codemods/chai-should-to-expect.yml). ### YAML ```yaml id: should_to_expect_instanceof language: TypeScript rule: any: - pattern: $NAME.should.be.an.instanceof($TYPE) - pattern: $NAME.should.be.an.instanceOf($TYPE) fix: |- expect($NAME).instanceOf($TYPE) --- id: should_to_expect_genericShouldBe language: TypeScript rule: pattern: $NAME.should.be.$PROP fix: |- expect($NAME).to.be.$PROP ``` ### Example ```js {2,5-9} it('should produce an instance of chokidar.FSWatcher', () => { watcher.should.be.an.instanceof(chokidar.FSWatcher); }); it('should expose public API methods', () => { watcher.on.should.be.a('function'); watcher.emit.should.be.a('function'); watcher.add.should.be.a('function'); watcher.close.should.be.a('function'); watcher.getWatched.should.be.a('function'); }); ``` ### Diff ```js it('should produce an instance of chokidar.FSWatcher', () => { watcher.should.be.an.instanceof(chokidar.FSWatcher); // [!code --] expect(watcher).instanceOf(chokidar.FSWatcher); // [!code ++] }); it('should expose public API methods', () => { watcher.on.should.be.a('function'); // [!code --] watcher.emit.should.be.a('function'); // [!code --] watcher.add.should.be.a('function'); // [!code --] watcher.close.should.be.a('function'); // [!code --] watcher.getWatched.should.be.a('function'); // [!code --] expect(watcher.on).to.be.a('function'); // [!code ++] expect(watcher.emit).to.be.a('function'); // [!code ++] expect(watcher.add).to.be.a('function'); // [!code ++] expect(watcher.close).to.be.a('function'); // [!code ++] expect(watcher.getWatched).to.be.a('function'); // [!code ++] }); ``` ### Contributed by [James](https://bsky.app/profile/43081j.com), by [this post](https://bsky.app/profile/43081j.com/post/3lgimzfxza22i) ### Exercise Exercise left to the reader: can you write a rule to implement [this migration to `node:assert`](https://github.com/paulmillr/chokidar/pull/1409/files)? --- --- url: /catalog/typescript/no-console-except-catch.md --- ## No `console` except in `catch` block * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6ImlmICRBLmhhc19mZWF0dXJlP1xuICAgICQkJEJcbmVsc2UgXG4gICAgJCQkQyBcbmVuZCAiLCJyZXdyaXRlIjoiJCQkQiIsImNvbmZpZyI6InJ1bGU6XG4gIGFueTpcbiAgICAtIHBhdHRlcm46IGNvbnNvbGUuZXJyb3IoJCQkKVxuICAgICAgbm90OlxuICAgICAgICBpbnNpZGU6XG4gICAgICAgICAga2luZDogY2F0Y2hfY2xhdXNlXG4gICAgICAgICAgc3RvcEJ5OiBlbmRcbiAgICAtIHBhdHRlcm46IGNvbnNvbGUuJE1FVEhPRCgkJCQpXG5jb25zdHJhaW50czpcbiAgTUVUSE9EOlxuICAgIHJlZ2V4OiAnbG9nfGRlYnVnfHdhcm4nXG5maXg6ICcnIiwic291cmNlIjoiY29uc29sZS5kZWJ1ZygnJylcbnRyeSB7XG4gICAgY29uc29sZS5sb2coJ2hlbGxvJylcbn0gY2F0Y2ggKGUpIHtcbiAgICBjb25zb2xlLmVycm9yKGUpXG59In0=) ### 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 {1,3} console.debug('') try { console.log('hello') } catch (e) { console.error(e) // OK } ``` ### Diff ```ts console.debug('') // [!code --] try { console.log('hello') // [!code --] } catch (e) { console.error(e) // OK } ``` ### Contributed by Inspired by [Jerry Mouse](https://github.com/WWK563388548) --- --- url: /catalog/typescript/no-await-in-promise-all.md --- ## No `await` in `Promise.all` array * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6ImNvbnNvbGUubG9nKCRNQVRDSCkiLCJyZXdyaXRlIjoibG9nZ2VyLmxvZygkTUFUQ0gpIiwiY29uZmlnIjoiaWQ6IG5vLWF3YWl0LWluLXByb21pc2UtYWxsXG5zZXZlcml0eTogZXJyb3Jcbmxhbmd1YWdlOiBKYXZhU2NyaXB0XG5tZXNzYWdlOiBObyBhd2FpdCBpbiBQcm9taXNlLmFsbFxucnVsZTpcbiAgcGF0dGVybjogYXdhaXQgJEFcbiAgaW5zaWRlOlxuICAgIHBhdHRlcm46IFByb21pc2UuYWxsKCRfKVxuICAgIHN0b3BCeTpcbiAgICAgIG5vdDogeyBhbnk6IFt7a2luZDogYXJyYXl9LCB7a2luZDogYXJndW1lbnRzfV0gfVxuZml4OiAkQSIsInNvdXJjZSI6ImNvbnN0IFtmb28sIGJhcl0gPSBhd2FpdCBQcm9taXNlLmFsbChbXG4gIGF3YWl0IGdldEZvbygpLFxuICBnZXRCYXIoKSxcbiAgKGFzeW5jICgpID0+IHsgYXdhaXQgZ2V0QmF6KCl9KSgpLFxuXSkifQ==) ### 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 {2} const [foo, bar] = await Promise.all([ await getFoo(), getBar(), (async () => { await getBaz()})(), ]) ``` ### Diff ```ts const [foo, bar] = await Promise.all([ await getFoo(), // [!code --] getFoo(), // [!code ++] getBar(), (async () => { await getBaz()})(), ]) ``` ### Contributed by Inspired by [Alvar Lagerlöf](https://twitter.com/alvarlagerlof) --- --- url: /catalog/typescript/migrate-xstate-v5.md --- ## Migrate XState to v5 from v4 * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6ImlmICgkQSkgeyAkJCRCIH0iLCJyZXdyaXRlIjoiaWYgKCEoJEEpKSB7XG4gICAgcmV0dXJuO1xufVxuJCQkQiIsImNvbmZpZyI6InV0aWxzOlxuICBGUk9NX1hTVEFURTogeyBraW5kOiBpbXBvcnRfc3RhdGVtZW50LCBoYXM6IHsga2luZDogc3RyaW5nLCByZWdleDogeHN0YXRlIH0gfVxuICBYU1RBVEVfRVhQT1JUOlxuICAgIGtpbmQ6IGlkZW50aWZpZXJcbiAgICBpbnNpZGU6IHsgaGFzOiB7IG1hdGNoZXM6IEZST01fWFNUQVRFIH0sIHN0b3BCeTogZW5kIH1cbnJ1bGU6IHsgcmVnZXg6IF5NYWNoaW5lfGludGVycHJldCQsIHBhdHRlcm46ICRJTVBPUlQsIG1hdGNoZXM6IFhTVEFURV9FWFBPUlQgfVxudHJhbnNmb3JtOlxuICBTVEVQMTogXG4gICAgcmVwbGFjZToge2J5OiBjcmVhdGUkMSwgcmVwbGFjZTogKE1hY2hpbmUpLCBzb3VyY2U6ICRJTVBPUlQgfVxuICBGSU5BTDpcbiAgICByZXBsYWNlOiB7IGJ5OiBjcmVhdGVBY3RvciwgcmVwbGFjZTogaW50ZXJwcmV0LCBzb3VyY2U6ICRTVEVQMSB9XG5maXg6ICRGSU5BTFxuLS0tIFxucnVsZTogeyBwYXR0ZXJuOiAkTUFDSElORS53aXRoQ29uZmlnIH1cbmZpeDogJE1BQ0hJTkUucHJvdmlkZVxuLS0tXG5ydWxlOlxuICBraW5kOiBwcm9wZXJ0eV9pZGVudGlmaWVyXG4gIHJlZ2V4OiBec2VydmljZXMkXG4gIGluc2lkZTogeyBwYXR0ZXJuOiAgJE0ud2l0aENvbmZpZygkJCRBUkdTKSwgc3RvcEJ5OiBlbmQgfVxuZml4OiBhY3RvcnMiLCJzb3VyY2UiOiJpbXBvcnQgeyBNYWNoaW5lLCBpbnRlcnByZXQgfSBmcm9tICd4c3RhdGUnO1xuXG5jb25zdCBtYWNoaW5lID0gTWFjaGluZSh7IC8qLi4uKi99KTtcblxuY29uc3Qgc3BlY2lmaWNNYWNoaW5lID0gbWFjaGluZS53aXRoQ29uZmlnKHtcbiAgYWN0aW9uczogeyAvKiAuLi4gKi8gfSxcbiAgZ3VhcmRzOiB7IC8qIC4uLiAqLyB9LFxuICBzZXJ2aWNlczogeyAvKiAuLi4gKi8gfSxcbn0pO1xuXG5jb25zdCBhY3RvciA9IGludGVycHJldChzcGVjaWZpY01hY2hpbmUsIHtcbi8qIGFjdG9yIG9wdGlvbnMgKi9cbn0pOyJ9) ### Description [XState](https://xstate.js.org/) 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`](https://stately.ai/docs/migration#use-createmachine-not-machine), [`createActor`](https://stately.ai/docs/migration#use-createactor-not-interpret), and [`machine.provide`](https://stately.ai/docs/migration#use-machineprovide-not-machinewithconfig). The example shows how ast-grep can use various features like [utility rule](/guide/rule-config/utility-rule.html), [transformation](/reference/yaml/transformation.html) and [multiple rule in single file](/reference/playground.html#test-multiple-rules) to automate the migration. Each rule has a clear and descriptive `id` field that explains its purpose. For more information, you can use [Codemod AI](https://app.codemod.com/studio?ai_thread_id=new) 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 {1,3,5,8,11} 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'; // [!code --] import { createMachine, createActor } from 'xstate'; // [!code ++] const machine = Machine({ /*...*/}); // [!code --] const machine = createMachine({ /*...*/}); // [!code ++] const specificMachine = machine.withConfig({ // [!code --] const specificMachine = machine.provide({ // [!code ++] actions: { /* ... */ }, guards: { /* ... */ }, services: { /* ... */ }, // [!code --] actors: { /* ... */ }, // [!code ++] }); const actor = interpret(specificMachine, { // [!code --] const actor = createActor(specificMachine, { // [!code ++] /* actor options */ }); ``` ### Contributed by Inspired by [XState's blog](https://stately.ai/blog/2023-12-01-xstate-v5). --- --- url: /catalog/typescript/find-import-usage.md --- ## Find Import Usage * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InR5cGVzY3JpcHQiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoicmVsYXhlZCIsInNlbGVjdG9yIjoiIiwiY29uZmlnIjoicnVsZTpcbiAgIyB0aGUgdXNhZ2VcbiAga2luZDogaWRlbnRpZmllclxuICBwYXR0ZXJuOiAkTU9EXG4gICMgaXRzIHJlbGF0aW9uc2hpcCB0byB0aGUgcm9vdFxuICBpbnNpZGU6XG4gICAgc3RvcEJ5OiBlbmRcbiAgICBraW5kOiBwcm9ncmFtXG4gICAgIyBhbmQgYmFjayBkb3duIHRvIHRoZSBpbXBvcnQgc3RhdGVtZW50XG4gICAgaGFzOlxuICAgICAga2luZDogaW1wb3J0X3N0YXRlbWVudFxuICAgICAgIyBhbmQgZGVlcGVyIGludG8gdGhlIGltcG9ydCBzdGF0ZW1lbnQgbG9va2luZyBmb3IgdGhlIG1hdGNoaW5nIGlkZW50aWZpZXJcbiAgICAgIGhhczpcbiAgICAgICAgc3RvcEJ5OiBlbmRcbiAgICAgICAga2luZDogaW1wb3J0X3NwZWNpZmllclxuICAgICAgICBwYXR0ZXJuOiAkTU9EICMgc2FtZSBwYXR0ZXJuIGFzIHRoZSB1c2FnZSBpcyBlbmZvcmNlZCBoZXJlIiwic291cmNlIjoiaW1wb3J0IHsgTW9uZ29DbGllbnQgfSBmcm9tICdtb25nb2RiJztcbmNvbnN0IHVybCA9ICdtb25nb2RiOi8vbG9jYWxob3N0OjI3MDE3JztcbmFzeW5jIGZ1bmN0aW9uIHJ1bigpIHtcbiAgY29uc3QgY2xpZW50ID0gbmV3IE1vbmdvQ2xpZW50KHVybCk7XG59XG4ifQ==) ### Description It is common to find the usage of an imported module in a codebase. This rule helps you to find the usage of an imported module in your codebase. The idea of this rule can be broken into several parts: * Find the use of an identifier `$MOD` * To find the import, we first need to find the root file of which `$MOD` is `inside` * The `program` file `has` an `import` statement * The `import` statement `has` the identifier `$MOD` ### YAML ```yaml id: find-import-usage language: typescript rule: kind: identifier # ast-grep requires a kind pattern: $MOD # the identifier to find inside: # find the root stopBy: end kind: program has: # and has the import statement kind: import_statement has: # look for the matching identifier stopBy: end kind: import_specifier pattern: $MOD # same pattern as the usage is enforced here ``` ### Example ```ts {4} import { MongoClient } from 'mongodb'; const url = 'mongodb://localhost:27017'; async function run() { const client = new MongoClient(url); } ``` ### Contributed by [Steven Love](https://github.com/StevenLove) --- --- url: /catalog/typescript/find-import-file-without-extension.md --- ## Find Import File without Extension * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6ImNvbnNvbGUubG9nKCRNQVRDSCkiLCJyZXdyaXRlIjoibG9nZ2VyLmxvZygkTUFUQ0gpIiwiY29uZmlnIjoibGFuZ3VhZ2U6IFwianNcIlxucnVsZTpcbiAgcmVnZXg6IFwiL1teLl0rW14vXSRcIiAgXG4gIGtpbmQ6IHN0cmluZ19mcmFnbWVudFxuICBhbnk6XG4gICAgLSBpbnNpZGU6XG4gICAgICAgIHN0b3BCeTogZW5kXG4gICAgICAgIGtpbmQ6IGltcG9ydF9zdGF0ZW1lbnRcbiAgICAtIGluc2lkZTpcbiAgICAgICAgc3RvcEJ5OiBlbmRcbiAgICAgICAga2luZDogY2FsbF9leHByZXNzaW9uXG4gICAgICAgIGhhczpcbiAgICAgICAgICBmaWVsZDogZnVuY3Rpb25cbiAgICAgICAgICByZWdleDogXCJeaW1wb3J0JFwiXG4iLCJzb3VyY2UiOiJpbXBvcnQgYSwge2IsIGMsIGR9IGZyb20gXCIuL2ZpbGVcIjtcbmltcG9ydCBlIGZyb20gXCIuL290aGVyX2ZpbGUuanNcIjtcbmltcG9ydCBcIi4vZm9sZGVyL1wiO1xuaW1wb3J0IHt4fSBmcm9tIFwicGFja2FnZVwiO1xuaW1wb3J0IHt5fSBmcm9tIFwicGFja2FnZS93aXRoL3BhdGhcIjtcblxuaW1wb3J0KFwiLi9keW5hbWljMVwiKTtcbmltcG9ydChcIi4vZHluYW1pYzIuanNcIik7XG5cbm15X2Z1bmMoXCIuL3VucmVsYXRlZF9wYXRoX3N0cmluZ1wiKVxuXG4ifQ==) ### 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](https://stackoverflow.com/questions/66375075/node-14-ecmascript-modules-import-modules-without-file-extensions). 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 {1,5,7} 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](https://twitter.com/DasSurma) in [this tweet](https://x.com/DasSurma/status/1706213303331029277). --- --- url: /catalog/tsx/unnecessary-react-hook.md --- ## Avoid Unnecessary React Hook * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6IiIsInJld3JpdGUiOiIiLCJzdHJpY3RuZXNzIjoic21hcnQiLCJzZWxlY3RvciI6IiIsImNvbmZpZyI6InV0aWxzOlxuICBob29rX2NhbGw6XG4gICAgaGFzOlxuICAgICAga2luZDogY2FsbF9leHByZXNzaW9uXG4gICAgICByZWdleDogXnVzZVxuICAgICAgc3RvcEJ5OiBlbmRcbnJ1bGU6XG4gIGFueTpcbiAgLSBwYXR0ZXJuOiBmdW5jdGlvbiAkRlVOQygkJCQpIHsgJCQkIH1cbiAgLSBwYXR0ZXJuOiBsZXQgJEZVTkMgPSAoJCQkKSA9PiAkJCQgXG4gIC0gcGF0dGVybjogY29uc3QgJEZVTkMgPSAoJCQkKSA9PiAkJCRcbiAgaGFzOlxuICAgIHBhdHRlcm46ICRCT0RZXG4gICAga2luZDogc3RhdGVtZW50X2Jsb2NrXG4gICAgc3RvcEJ5OiBlbmQgXG5jb25zdHJhaW50czpcbiAgRlVOQzoge3JlZ2V4OiBedXNlIH1cbiAgQk9EWTogeyBub3Q6IHsgbWF0Y2hlczogaG9va19jYWxsIH0gfSBcbiIsInNvdXJjZSI6ImZ1bmN0aW9uIHVzZUlBbU5vdEhvb2tBY3R1YWxseShhcmdzKSB7XG4gICAgY29uc29sZS5sb2coJ0NhbGxlZCBpbiBSZWFjdCBidXQgSSBkb250IG5lZWQgdG8gYmUgYSBob29rJylcbiAgICByZXR1cm4gYXJncy5sZW5ndGhcbn1cbmNvbnN0IHVzZUlBbU5vdEhvb2tUb28gPSAoLi4uYXJncykgPT4ge1xuICAgIGNvbnNvbGUubG9nKCdDYWxsZWQgaW4gUmVhY3QgYnV0IEkgZG9udCBuZWVkIHRvIGJlIGEgaG9vaycpXG4gICAgcmV0dXJuIGFyZ3MubGVuZ3RoXG59XG5cbmZ1bmN0aW9uIHVzZUhvb2soKSB7XG4gICAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICAgIGNvbnNvbGUubG9nKCdSZWFsIGhvb2snKSAgIFxuICAgIH0pXG59In0=) ### Description React hook is a powerful feature in React that allows you to use state and other React features in a functional component. However, you should avoid using hooks when you don't need them. If the code does not contain using any other React hooks, it can be rewritten to a plain function. This can help to separate your application logic from the React-specific UI logic. ### YAML ```yaml id: unnecessary-react-hook language: Tsx utils: hook_call: has: kind: call_expression regex: ^use stopBy: end rule: any: - pattern: function $FUNC($$$) { $$$ } - pattern: let $FUNC = ($$$) => $$$ - pattern: const $FUNC = ($$$) => $$$ has: pattern: $BODY kind: statement_block stopBy: end constraints: FUNC: {regex: ^use } BODY: { not: { matches: hook_call } } ``` ### Example ```tsx {1-8} function useIAmNotHookActually(args) { console.log('Called in React but I dont need to be a hook') return args.length } const useIAmNotHookToo = (...args) => { console.log('Called in React but I dont need to be a hook') return args.length } function useTrueHook() { useEffect(() => { console.log('Real hook') }) } ``` ### Contributed by [Herrington Darkholme](https://twitter.com/hd_nvim) --- --- url: /catalog/tsx/rewrite-mobx-component.md --- ## Rewrite MobX Component Style * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6ImphdmFzY3JpcHQiLCJxdWVyeSI6ImNvbnNvbGUubG9nKCRNQVRDSCkiLCJyZXdyaXRlIjoibG9nZ2VyLmxvZygkTUFUQ0gpIiwiY29uZmlnIjoicnVsZTpcbiAgcGF0dGVybjogZXhwb3J0IGNvbnN0ICRDT01QID0gb2JzZXJ2ZXIoJEZVTkMpXG5maXg6IHwtXG4gIGNvbnN0IEJhc2UkQ09NUCA9ICRGVU5DXG4gIGV4cG9ydCBjb25zdCAkQ09NUCA9IG9ic2VydmVyKEJhc2UkQ09NUCkiLCJzb3VyY2UiOiJleHBvcnQgY29uc3QgRXhhbXBsZSA9IG9ic2VydmVyKCgpID0+IHtcbiAgcmV0dXJuIDxkaXY+SGVsbG8gV29ybGQ8L2Rpdj5cbn0pIn0=) ### Description React and MobX are libraries that help us build user interfaces with JavaScript. [React hooks](https://react.dev/reference/react) allow us to use state and lifecycle methods in functional components. But we need follow some hook rules, or React may break. [MobX](https://mobx.js.org/react-integration.html) has an `observer` function that makes a component update when data changes. When we use the `observer` function like this: ```JavaScript export const Example = observer(() => {…}) ``` ESLint, the tool that checks hooks, thinks that `Example` is not a React component, but just a regular function. So it does not check the hooks inside it, and we may miss some wrong usages. To fix this, we need to change our component style to this: ```JavaScript const BaseExample = () => {…} const Example = observer(BaseExample) ``` Now ESLint can see that `BaseExample` is a React component, and it can check the hooks inside it. ### YAML ```yaml id: rewrite-mobx-component language: typescript rule: pattern: export const $COMP = observer($FUNC) fix: |- const Base$COMP = $FUNC export const $COMP = observer(Base$COMP) ``` ### Example ```js {1-3} export const Example = observer(() => { return
Hello World
}) ``` ### Diff ```js export const Example = observer(() => { // [!code --] return
Hello World
// [!code --] }) // [!code --] const BaseExample = () => { // [!code ++] return
Hello World
// [!code ++] } // [!code ++] export const Example = observer(BaseExample) // [!code ++] ``` ### Contributed by [Bryan Lee](https://twitter.com/meetliby/status/1698601672568901723) --- --- url: /catalog/tsx/reverse-react-compiler.md --- ## Reverse React Compiler™ * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InRzeCIsInF1ZXJ5IjoiIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJyZWxheGVkIiwic2VsZWN0b3IiOiIiLCJjb25maWciOiJpZDogcmV3cml0ZS1jYWNoZSBcbmxhbmd1YWdlOiB0c3hcbnJ1bGU6XG4gIGFueTpcbiAgLSBwYXR0ZXJuOiB1c2VDYWxsYmFjaygkRk4sICQkJClcbiAgLSBwYXR0ZXJuOiBtZW1vKCRGTiwgJCQkKVxuZml4OiAkRk5cblxuLS0tXG5cbmlkOiByZXdyaXRlLXVzZS1tZW1vXG5sYW5ndWFnZTogdHN4XG5ydWxlOiB7IHBhdHRlcm46ICd1c2VNZW1vKCRGTiwgJCQkKScgfVxuZml4OiAoJEZOKSgpIiwic291cmNlIjoiY29uc3QgQ29tcG9uZW50ID0gKCkgPT4ge1xuICBjb25zdCBbY291bnQsIHNldENvdW50XSA9IHVzZVN0YXRlKDApXG4gIGNvbnN0IGluY3JlbWVudCA9IHVzZUNhbGxiYWNrKCgpID0+IHtcbiAgICBzZXRDb3VudCgocHJldkNvdW50KSA9PiBwcmV2Q291bnQgKyAxKVxuICB9LCBbXSlcbiAgY29uc3QgZXhwZW5zaXZlQ2FsY3VsYXRpb24gPSB1c2VNZW1vKCgpID0+IHtcbiAgICAvLyBtb2NrIEV4cGVuc2l2ZSBjYWxjdWxhdGlvblxuICAgIHJldHVybiBjb3VudCAqIDJcbiAgfSwgW2NvdW50XSlcblxuICByZXR1cm4gKFxuICAgIDw+XG4gICAgICA8cD5FeHBlbnNpdmUgUmVzdWx0OiB7ZXhwZW5zaXZlQ2FsY3VsYXRpb259PC9wPlxuICAgICAgPGJ1dHRvbiBvbkNsaWNrPXtpbmNyZW1lbnR9Pntjb3VudH08L2J1dHRvbj5cbiAgICA8Lz5cbiAgKVxufSJ9) ### Description React Compiler is a build-time only tool that automatically optimizes your React app, working with plain JavaScript and understanding the Rules of React without requiring a rewrite. It optimizes apps by automatically memoizing code, similar to `useMemo`, `useCallback`, and `React.memo`, reducing unnecessary recomputation due to incorrect or forgotten memoization. Reverse React Compiler™ is a [parody tweet](https://x.com/aidenybai/status/1881397529369034997) that works in the opposite direction. It takes React code and removes memoization, guaranteed to make your code slower. ([not](https://x.com/kentcdodds/status/1881404373646880997) [necessarily](https://dev.to/prathamisonline/are-you-over-using-usememo-and-usecallback-hooks-in-react-5lp)) It is originally written in Babel and this is an [ast-grep version](https://x.com/hd_nvim/status/1881402678493970620) of it. :::details The Original Babel Implementation For comparison purposes only. Note the original code [does not correctly rewrite](https://x.com/hd_nvim/status/1881404893136896415) `useMemo`. ```js const ReverseReactCompiler = ({ types: t }) => ({ visitor: { CallExpression(path) { const callee = path.node.callee; if ( t.isIdentifier(callee, { name: "useMemo" }) || t.isIdentifier(callee, { name: "useCallback" }) || t.isIdentifier(callee, { name: "memo" }) ) { path.replaceWith(args[0]); } }, }, }); ``` ::: ### YAML ```yaml id: rewrite-cache language: tsx rule: any: - pattern: useCallback($FN, $$$) - pattern: memo($FN, $$$) fix: $FN --- id: rewrite-use-memo language: tsx rule: { pattern: 'useMemo($FN, $$$)' } fix: ($FN)() # need IIFE to wrap memo function ``` ### Example ```tsx {3-5,6-9} const Component = () => { const [count, setCount] = useState(0) const increment = useCallback(() => { setCount((prevCount) => prevCount + 1) }, []) const expensiveCalculation = useMemo(() => { // mock Expensive calculation return count * 2 }, [count]) return ( <>

Expensive Result: {expensiveCalculation}

) } ``` ### Diff ```tsx const Component = () => { const [count, setCount] = useState(0) const increment = useCallback(() => { // [!code --] setCount((prevCount) => prevCount + 1) // [!code --] }, []) // [!code --] const increment = () => { // [!code ++] setCount((prevCount) => prevCount + 1) // [!code ++] } // [!code ++] const expensiveCalculation = useMemo(() => { // [!code --] // mock Expensive calculation // [!code --] return count * 2 // [!code --] }, [count]) // [!code --] const expensiveCalculation = (() => { // [!code ++] // mock Expensive calculation // [!code ++] return count * 2 // [!code ++] })() // [!code ++] return ( <>

Expensive Result: {expensiveCalculation}

) } ``` ### Contributed by Inspired by [Aiden Bai](https://twitter.com/aidenybai) --- --- url: /catalog/tsx/rename-svg-attribute.md --- ## Rename SVG Attribute * [Playground Link](/playground.html#eyJtb2RlIjoiQ29uZmlnIiwibGFuZyI6InRzeCIsInF1ZXJ5IjoiIiwicmV3cml0ZSI6IiIsInN0cmljdG5lc3MiOiJyZWxheGVkIiwic2VsZWN0b3IiOiIiLCJjb25maWciOiJpZDogcmV3cml0ZS1zdmctYXR0cmlidXRlXG5sYW5ndWFnZTogdHN4XG5ydWxlOlxuICBwYXR0ZXJuOiAkUFJPUFxuICByZWdleDogKFthLXpdKyktKFthLXpdKVxuICBraW5kOiBwcm9wZXJ0eV9pZGVudGlmaWVyXG4gIGluc2lkZTpcbiAgICBraW5kOiBqc3hfYXR0cmlidXRlXG50cmFuc2Zvcm06XG4gIE5FV19QUk9QOlxuICAgIGNvbnZlcnQ6XG4gICAgICBzb3VyY2U6ICRQUk9QXG4gICAgICB0b0Nhc2U6IGNhbWVsQ2FzZVxuZml4OiAkTkVXX1BST1AiLCJzb3VyY2UiOiJjb25zdCBlbGVtZW50ID0gKFxuICA8c3ZnIHdpZHRoPVwiMTAwXCIgaGVpZ2h0PVwiMTAwXCIgdmlld0JveD1cIjAgMCAxMDAgMTAwXCI+XG4gICAgPHBhdGggZD1cIk0xMCAyMCBMMzAgNDBcIiBzdHJva2UtbGluZWNhcD1cInJvdW5kXCIgZmlsbC1vcGFjaXR5PVwiMC41XCIgLz5cbiAgPC9zdmc+XG4pIn0=) ### Description [SVG](https://en.wikipedia.org/wiki/SVG)(Scalable Vector Graphics)s' hyphenated names are not compatible with JSX syntax in React. JSX requires [camelCase naming](https://react.dev/learn/writing-markup-with-jsx#3-camelcase-salls-most-of-the-things) for attributes. For example, an SVG attribute like `stroke-linecap` needs to be renamed to `strokeLinecap` to work correctly in React. ### YAML ```yaml id: rewrite-svg-attribute language: tsx rule: pattern: $PROP # capture in metavar regex: ([a-z]+)-([a-z]) # hyphenated name kind: property_identifier inside: kind: jsx_attribute # in JSX attribute transform: NEW_PROP: # new property name convert: # use ast-grep's convert source: $PROP toCase: camelCase # to camelCase naming fix: $NEW_PROP ``` ### Example ```tsx {3} const element = ( ) ``` ### Diff ```ts const element = ( // [!code --] // [!code ++] ) ``` ### Contributed by Inspired by [SVG Renamer](https://admondtamang.medium.com/introducing-svg-renamer-your-solution-for-react-svg-attributes-26503382d5a8) --- --- url: /guide/introduction.md --- # What is ast-grep? ## Introduction ast-grep is a new AST based tool to manage your code, at massive scale. Using ast-grep can be as simple as running a single command in your terminal: ```bash ast-grep --pattern 'var code = $PAT' --rewrite 'let code = $PAT' --lang js ``` The command above will replace `var` statement with `let` for all JavaScript files. --- ast-grep is a versatile tool for searching, linting and rewriting code in various languages. * **Search**: As a _command line tool_ in your terminal, `ast-grep` can precisely search code _based on AST_, running through ten thousand files in sub seconds. * **Lint**: You can use ast-grep as a linter. Thanks to the flexible rule system, adding a new customized rule is intuitive and straightforward, with _pretty error reporting_ out of box. * **Rewrite**: ast-grep provide API to traverse and manipulate syntax tree. Besides, you can also use operators to compose complex matching from simple patterns. > Think ast-grep as an hybrid of [grep](https://www.gnu.org/software/grep/manual/grep.html), [eslint](https://eslint.org/) and [codemod](https://github.com/facebookincubator/fastmod). Wanna try it out? Check out the [quick start guide](/guide/quick-start)! Or see some [examples](/catalog/) to get a sense of what ast-grep can do. We also have a [playground](/playground.html) for you to try out ast-grep online! ## Supported Languages ast-grep supports a wide range of programming languages. Here is a list of notable programming languages it supports. |Language Domain|Supported Languages| |:--------------|------------------:| |System Programming| `C`, `Cpp`, `Rust`| |Server Side Programming| `Go`, `Java`, `Python`, `C-sharp`| |Web Development| `JS(X)`, `TS(X)`, `HTML`, `CSS`| |Mobile App Development| `Kotlin`, `Swift`| |Configuration | `Json`, `YAML`| |Scripting, Protocols, etc.| `Lua`, `Thrift`| Thanks to [tree-sitter](https://tree-sitter.github.io/tree-sitter/), a popular parser generator library, ast-grep manages to support [many languages](/reference/languages) out of the box! ## Motivation Using text-based tool for searching code is fast but imprecise. We usually prefer to parse the code into [abstract syntax tree](https://www.wikiwand.com/en/Abstract_syntax_tree) for precise matches. However, developing with AST is tedious and frustrating. Consider this "hello-world" level task: matching `console.log` in JavaScript using Babel. We will need to write code like below. ```javascript path.parentPath.isMemberExpression() && path.parentPath.get('object').isIdentifier({ name: 'console' }) && path.parentPath.get('property').isIdentifier({ name: 'log' }) ``` This snippet deserves a detailed explanation for beginners. Even for experienced developers, authoring this snippet also requires a lot of looking up references. The pain is not language specific. The [quotation](https://portswigger.net/daily-swig/semgrep-static-code-analysis-tool-helps-eliminate-entire-classes-of-vulnerabilities) from Jobert Abma, co-founder of HackerOne, manifests the universal pain across many languages. > The internal AST query interfaces those tools offer are often poorly documented and difficult to write, understand, and maintain. ---- ast-grep solves the problem by providing a simple core mechanism: using code to search code with the same pattern. Consider it as same as `grep` but based on AST instead of text. In comparison to Babel, we can complete this hello-world task in ast-grep trivially ```bash ast-grep -p "console.log" ``` See [playground](/playground.html) in action! Upon the simple pattern code, we can build a series of operators to compose complex matching rules for various scenarios. Though we use JavaScript in our introduction, ast-grep is not language specific. It is a _polyglot_ tool backed by the renowned library [tree-sitter](https://tree-sitter.github.io/). The idea of ast-grep can be applied to many other languages! ## Features There are a lot of other tools that looks like ast-grep, notable predecessors including [Semgrep](https://semgrep.dev/), [comby](https://comby.dev/), [shisho](https://github.com/flatt-security/shisho), [gogocode](https://github.com/thx/gogocode), and new comers like [gritQL](https://about.grit.io/) What makes ast-grep stands out is: ### Performance It is written in Rust, a native language and utilize multiple cores. (It can even beat ag when searching simple pattern). ast-grep can handle tens of thousands files in seconds. ### Progressiveness You can start from creating a one-liner to rewrite code at command line with minimal investment. Later if you see some code smell recurrently appear in your projects, you can write a linting rule in YAML with a few patterns combined. Finally if you are a library author or framework designer, ast-grep provide programmatic interface to rewrite or transpile code efficiently. ### Pragmatism ast-grep comes with batteries included. Interactive code modification is available. Linter and language server work out of box when you install the command line tool. ast-grep is also shipped with test framework for rule authors. ## Check out Discord and StackOverflow Still got questions? Join our [Discord](https://discord.gg/4YZjf6htSQ) and discuss with other users! You can also ask questions under the [ast-grep](https://stackoverflow.com/questions/tagged/ast-grep) tag on [StackOverflow](https://stackoverflow.com/questions/ask). --- --- url: /catalog/yaml/index.md --- # YAML This page curates a list of example ast-grep rules to check and to rewrite YAML code.