{"id":19772866,"url":"https://github.com/ninofiliu/esgrep","last_synced_at":"2025-04-30T18:31:12.845Z","repository":{"id":37502997,"uuid":"490062694","full_name":"ninofiliu/esgrep","owner":"ninofiliu","description":"Syntactically-aware grep for JavaScript and TypeScript","archived":false,"fork":false,"pushed_at":"2023-12-29T17:02:57.000Z","size":405,"stargazers_count":12,"open_issues_count":1,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-04-29T23:00:27.741Z","etag":null,"topics":["ast","cli","grep","javascript","js","regex","ts","typescript"],"latest_commit_sha":null,"homepage":"https://npmjs.com/package/esgrep","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ninofiliu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-05-08T21:50:02.000Z","updated_at":"2023-08-06T19:50:57.000Z","dependencies_parsed_at":"2022-07-26T04:32:11.305Z","dependency_job_id":null,"html_url":"https://github.com/ninofiliu/esgrep","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ninofiliu%2Fesgrep","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ninofiliu%2Fesgrep/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ninofiliu%2Fesgrep/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ninofiliu%2Fesgrep/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ninofiliu","download_url":"https://codeload.github.com/ninofiliu/esgrep/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224219652,"owners_count":17275477,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ast","cli","grep","javascript","js","regex","ts","typescript"],"created_at":"2024-11-12T05:07:55.049Z","updated_at":"2024-11-12T05:07:56.264Z","avatar_url":"https://github.com/ninofiliu.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![example](https://raw.githubusercontent.com/ninofiliu/esgrep/main/cover.png)\n\n# ESGrep\n\nSyntactically-aware grep for JavaScript and TypeScript\n\n- [Usage as a CLI](#usage-as-a-cli)\n- [Usage as a library](#usage-as-a-library)\n- [Options](#options)\n- [ES Expressions](#es-expressions)\n- [About](#about)\n\n## Usage as a CLI\n\nInstall it with `npm install --global esgrep` or the equivalent using pnpm/yarn/etc, then use it as `esgrep [OPTION...] PATTERN [FILE...]`. If `FILE` is not precised, reads from stdin.\n\nThe CLI is basically a wrapper around [`find`](#findpattern-haystack-options) and accepts the same [options](#options) and a few more that handle help print and output format. This means that these are logically equivalent:\n\nReading from stdin:\n\n```sh\necho 'const x: number = 10' | esgrep 'const x = ES_ANY'\n```\n\n```ts\nfind(\"const x: number = 10\", \"const x = ES_ANY\");\n```\n\nReading from files:\n\n```sh\nesgrep 'fetch(ES_ANY, { method: \"POST\" })' api.js lib.ts\n```\n\n```ts\nfind('fetch(ES_ANY, { method: \"POST\" })', readFileSomehow(\"api.js\"));\nfind('fetch(ES_ANY, { method: \"POST\" })', readFileSomehow(\"lib.ts\"));\n```\n\nPassing arguments:\n\n```sh\nesgrep --statement -- '(() =\u003e {})()' file.js\n```\n\n```ts\nfind(\"(() =\u003e {})()\", readFileSomehow(\"file.js\"), { statement: true });\n```\n\n\u003e **Note**\n\u003e\n\u003e When you pass `esgrep --foo bar baz`, `esgrep` has no way to know whether `bar` is the value of `--foo` (current behavior) or if it is a positional argument just like `baz` and `--foo` is a flag option.\n\u003e\n\u003e You can tell `esgrep` to explicitly start parsing positional arguments by using `--`:\n\u003e\n\u003e ```sh\n\u003e esgrep --foo bar baz\n\u003e # Parsed options: { foo: 'bar' }\n\u003e # Parsed positional arguments: ['baz']\n\u003e esgrep --foo -- bar baz\n\u003e # Parsed options: { foo: true }\n\u003e # parsed positional arguments: ['bar', 'baz']\n\u003e ```\n\n## Usage as a library\n\nInstall it with `npm install esgrep` or the equivalent using pnpm/yarn/etc, then import it:\n\n```ts\nimport { find, findStrings } from \"esgrep\";\n// or\nconst { find, findStrings } = require(\"esgrep\");\n```\n\nFor now, the lib only targets Node but it's in the roadmap to target Deno and the Web.\n\nTypes should be included in the build so refer to them for the exact types of arguments and returned values. This doc focuses on esgrep scenari rather than spec details.\n\n### `find(pattern, haystack, options?)`\n\n`pattern` and `haystack` are `string`s that represent javascript or typescript code which will be matched based on their [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) (and not string representation).\n\n`options` is detailed in [options](#options).\n\n`find` is a [`Generator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator) that yields [estree](https://github.com/estree/estree) nodes.\n\n```ts\nimport { find } from \"esgrep\";\n\nconst pattern = \"const x = 10\";\nconst haystack = \"const x = /* a number */ 10; const y = 20;\";\nconst matches = find(pattern, haystack);\nfor (const match of matches) {\n  console.log(match);\n  // {\n  //   type: 'VariableDeclaration',\n  //   declarations: [ ... ],\n  //   kind: 'const',\n  //   range: [ 0, 28 ],\n  //   loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 28 } }\n  // }\n}\n```\n\nIf you're not comfortable with generators or don't want to use the perf and streaming capabilities they can provide, you can just spread it out into a plain array:\n\n```ts\nconsole.log([...matches]);\n// [ { type: 'VariableDeclaration', ... }]\n```\n\n### `findStrings(pattern, haystack, options?)`\n\nBasically the same as `find` but iterates over matched strings rather that matched nodes.\n\n```ts\nimport { findStrings } from \"esgrep\";\n\nconst pattern = \"const x = 10\";\nconst haystack = \"const x = /* a number */ 10; const y = 20;\";\nconsole.log([...findStrings(pattern, haystack)]);\n// [ 'const x = /* a number */ 10;' ]\n```\n\n## Options\n\n### `-h, --help` (CLI only)\n\nPrints the synopsis and lists CLI options.\n\n```console\nuser@host$ esgrep -h\n```\n\n### `-f, --format {pretty,oneline,jsonl}` (CLI only)\n\nDefines the output format of the search\n\n`pretty` (default): easy to read output\n\n```console\nuser@host$ esgrep 'const tasks = ES_ANY' src/cli/main.ts\n─────────────────────┐\nsrc/cli/main.ts:20:2 │\n   ┌─────────────────┘\n20 │   const tasks =\n21 │     paths.length === 0\n22 │       ? [{ path: \"stdin\", read: readStdin }]\n23 │       : paths.map((path) =\u003e ({ path, read: () =\u003e readFile(path) }));\n```\n\n`oneline`: streams out lines of the shape `$path:$line$column:$match_in_one_line`\n\n```console\nuser@host$ esgrep --format oneline 'const tasks = ES_ANY' src/cli/main.ts\nsrc/cli/main.ts:18:2:const tasks = paths.length === 0 ? [{ path: \"stdin\", read: readStdin }] : paths.map((path) =\u003e ({ path, read: () =\u003e readFile(path) }));\n```\n\n`jsonl`: streams out lines of the shape `{ path: string, match: Node }` where `Node` is an ESTree node\n\n```console\nuser@host$ esgrep user users.ts\n{\"path\":\"users.ts\",\"match\":{\"type\":\"Identifier\",\"name\":\"user\",\"range\":[148,152],\"loc\":{\"start\":{\"line\":4,\"column\":24},\"end\":{\"line\":4,\"column\":28}}}}\n```\n\n`count`: do not show matches, only show the number of matches per file. column-separated\n\n```console\nuser@host$ esgrep user ./src/**/*.{ts,js}\n./src/api.js:10\n./src/users.js:4\n./src/types.ts:0\n```\n\n\u003e **Note**\n\u003e\n\u003e It is by design that ESGrep doesn't exactly match Grep output options, either because some option didn't make sense anymore, was not useful, or could be achieved by a little bit of bash-fu\n\u003e\n\u003e - `grep --count ...`: `esgrep --format count ...`\n\u003e - `grep --files-without-match ...`: `esgrep --format count ... | grep ':0$' | cut -d ':' -f 1`\n\u003e - `grep --files-with-matches ...`: `esgrep --format count ... | grep -v ':0$' | cut -d ':' -f 1`\n\u003e - `grep --with-filename ...`: always on\n\u003e - `grep --no-filename ...`: always off\n\u003e - `grep --line-number ...`: always on\n\u003e - `grep --invert-match ...`: not useful\n\u003e - `grep --only-matching ...`: not useful\n\n### `-t, --ts`\n\nInclude type annotations in the comparison\n\n```ts\nimport { findStrings } from \"esgrep\";\n\nconst withTS = \"const x: number = 10\";\nconst withoutTS = \"const x = 10\";\n\nconsole.log([...findStrings(withoutTS, withTS)]);\n// [ 'const x: number = 10' ]\nconsole.log([...findStrings(withoutTS, withTS, { ts: true })]);\n// []\n```\n\n### `-r, --raw`\n\nDifferentiate between strings in single quotes, double quotes, and template literals\n\n```ts\nimport { findStrings } from \"esgrep\";\n\nconst haystack = `\nconst single = 'hello';\nconst double = \"hello\";\nconst template = \\`hello\\`\n`;\nconsole.log([...findStrings('\"hello\"', haystack)]);\n// [ \"'hello'\", '\"hello\"', '`hello`' ]\nconsole.log([...findStrings('\"hello\"', haystack, { raw: true })]);\n// [ \"'hello'\" ]\n```\n\n### `-s, --statement`\n\nIf the pattern is an expression statement, lookup the statement itself, and not the expression statement.\n\n```ts\nimport { findStrings } from \"esgrep\";\n\nconst pattern = \"10\";\nconst haystack = \"const x = 10\";\nconsole.log([...findStrings(pattern, haystack)]);\n// [ '10' ]\nconsole.log([...findStrings(pattern, haystack, { statement: true })]);\n// []\n```\n\n\u003e **Note**\n\u003e The first search matches the `10` in `const x = 10` because it looks up all _expressions_ of `10`. In plain English, it looks up all the occurrences of \"just\" the number 10. That is the most intuitive and default behavior.\n\u003e\n\u003e The second search does not match the `10` in `const x = 10` because it looks up all _statements_ of consisting of only the _expression_ `10`. In plain English, statements are anything that makes the exact same sense when adding a `;` at the end.\n\u003e\n\u003e The difference is subtle and it usually takes people not familiar with the concept a few articles to have a good grasp on _statements_ vs _expressions_. Why not start with [this one](https://2ality.com/2012/09/expressions-vs-statements.html)?\n\n## ES Expressions\n\nAdditionally to its options, patterns are allowed to be flexible thanks to ES expressions - special expressions that allow for something else than perfect matches.\n\n### `ES_ANY`\n\nMatches anything\n\n```ts\nimport { findStrings } from \"esgrep\";\n\nconsole.log([...findStrings(\"ES_ANY\", \"fn('foo', 'bar')\")]);\n// [\n//   \"fn('foo', 'bar')\", // matches the expression\n//   \"fn('foo', 'bar')\", // matches the statement (string is the same, not the node)\n//   \"fn\",\n//   \"'foo'\",\n//   \"'bar'\"\n// ];\nconsole.log([...findStrings(\"x = ES_ANY\", \"x = 10; x = 'hello'\")]);\n// [\n//   \"x = 10\",\n//   \"x = 'hello'\"\n// ];\n```\n\n### `ES_NOT`\n\nMatches anything but the first argument\n\n```ts\nimport { findStrings } from \"esgrep\";\n\nconst pattern = \"const x = ES_NOT(10)\";\nconst haystack = \"const x = 20\";\nconsole.log([...findStrings(pattern, haystack)]);\n// [ 'const x = 20' ]\n```\n\n### `ES_EVERY`\n\nMatches if all expressions passed as argument match\n\n```ts\nimport { findStrings } from \"esgrep\";\n\nconst pattern = `const x = ES_EVERY(\n  'hello',\n  \"hello\",\n  ES_ANY\n)`;\nconst haystack = \"const x = 'hello'\";\nconsole.log([...findStrings(pattern, haystack)]);\n// [ \"const x = 'hello'\" ]\n```\n\n### `ES_SOME`\n\nMatches if at least one expression passed as argument matches\n\n```ts\nimport { findStrings } from \"esgrep\";\n\nconst pattern = `const x = ES_SOME(\n  \"hello\",\n  \"goodbye\"\n)`;\nconst haystack = \"const x = 'hello'\";\nconsole.log([...findStrings(pattern, haystack)]);\n// [ \"const x = 'hello'\" ]\n```\n\n## About\n\nWant to report a bug? Don't understant the doc? Suggest an improvement? Open an issue!\n\nContributions are welcomed, the code is still small, clean, and all in TS, so it should be readable and extendable without additional guidance. For big changes, consider opening an issue before opening a PR.\n\nCoded w/ ♡ by [Nino Filiu](https://ninofiliu.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fninofiliu%2Fesgrep","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fninofiliu%2Fesgrep","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fninofiliu%2Fesgrep/lists"}