{"id":49960431,"url":"https://github.com/elixir-vibe/ex_ast","last_synced_at":"2026-05-18T02:18:41.890Z","repository":{"id":342778271,"uuid":"1175134064","full_name":"elixir-vibe/ex_ast","owner":"elixir-vibe","description":"Search, replace, and diff Elixir code by AST pattern","archived":false,"fork":false,"pushed_at":"2026-05-11T09:03:25.000Z","size":258,"stargazers_count":19,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-05-11T11:09:40.490Z","etag":null,"topics":["ast","code-replace","code-search","elixir","mix-task","pattern-matching"],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/elixir-vibe.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-03-07T09:22:33.000Z","updated_at":"2026-05-11T09:03:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/elixir-vibe/ex_ast","commit_stats":null,"previous_names":["dannote/ex_ast","elixir-vibe/ex_ast"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/elixir-vibe/ex_ast","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-vibe%2Fex_ast","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-vibe%2Fex_ast/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-vibe%2Fex_ast/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-vibe%2Fex_ast/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elixir-vibe","download_url":"https://codeload.github.com/elixir-vibe/ex_ast/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-vibe%2Fex_ast/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33162490,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-17T22:39:12.733Z","status":"online","status_checked_at":"2026-05-18T02:00:06.436Z","response_time":71,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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","code-replace","code-search","elixir","mix-task","pattern-matching"],"created_at":"2026-05-18T02:18:35.721Z","updated_at":"2026-05-18T02:18:41.884Z","avatar_url":"https://github.com/elixir-vibe.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ExAST 🔬\n\nSearch, replace, and diff Elixir code by AST pattern.\n\nPatterns are plain Elixir — variables capture, `_` is a wildcard,\nstructs match partially, pipes are normalized. No regex, no custom DSL.\n\n```bash\nmix ex_ast.search  'IO.inspect(_)'\nmix ex_ast.replace 'IO.inspect(expr, _)' 'Logger.debug(inspect(expr))' lib/\nmix ex_ast.diff lib/old.ex lib/new.ex\n```\n\n## Why\n\nRegex can't tell `IO.inspect(data)` from `IO.inspect(data, label: \"debug\")`.\nText diff doesn't know a function moved vs changed. ExAST works on the AST —\npatterns match structure, not strings.\n\n## Quick examples\n\n```elixir\n# Negative literals — flag potential bugs\nExAST.Patcher.find_all(source, \"Enum.take(_, -_)\")\n\n# Always-true comparisons\nExAST.Patcher.find_all(source, \"{a, a}\")\n\n# Compile-time config reads\nExAST.Patcher.find_all(source, \"@name Application.get_env(_, _)\")\n\n# Batch analyzer checks in one scan\nExAST.Patcher.find_many(source,\n  get_env: \"@_ Application.get_env(_, _)\",\n  dbg_call: \"dbg(expr)\"\n)\n\n# Preview rewrites before applying patches\nExAST.rewrite_plan(source, \"dbg(expr)\", \"expr\")\n#=\u003e %ExAST.Rewriter.Plan{replacements: [...], conflicts: []}\n\n# Specific atom values\nimport ExAST.Query\nfrom(\"def handle_event(event, _, _) do ... end\")\n|\u003e where(^event == :click or ^event == :keydown)\n\n# Functions with transaction but no debug output\nfrom(\"def _ do ... end\")\n|\u003e where(contains(\"Repo.transaction(_)\"))\n|\u003e where(not contains(\"IO.inspect(...)\"))\n```\n\n## Installation\n\n```elixir\ndef deps do\n  [{:ex_ast, \"~\u003e 0.12\", only: [:dev, :test], runtime: false}]\nend\n```\n\n## Documentation\n\n| Guide | Content |\n|-------|---------|\n| [Getting Started](https://hexdocs.pm/ex_ast/getting-started.html) | Install, first search, first replace |\n| [Pattern Language](https://hexdocs.pm/ex_ast/pattern-language.html) | Syntax, wildcards, captures, ellipsis, pipes, recipes |\n| [Querying](https://hexdocs.pm/ex_ast/querying.html) | Relationship filters, selectors, capture guards |\n| [Indexing and Code Intelligence](https://hexdocs.pm/ex_ast/indexing.html) | Structural terms, selector plans, comments, symbols |\n| [CLI Reference](https://hexdocs.pm/ex_ast/cli.html) | Command-line flags and usage |\n| [Diff](https://hexdocs.pm/ex_ast/diff.html) | Syntax-aware code diffing |\n| [API Reference](https://hexdocs.pm/ex_ast/api-reference.html) | Module documentation |\n\n## What you can match\n\n```elixir\n# Function calls (any arity with ...)\nEnum.map(_, _)\nLogger.info(...)\n\n# Definitions\ndef handle_call(msg, _, state) do _ end\n\n# Pipes (matches both forms)\nEnum.map(data, f)           # also matches: data |\u003e Enum.map(f)\n\n# Multi-node sequences\na = Repo.get!(_, _); Repo.delete(a)\n\n# Tuples, structs, maps\n{:ok, result}\n%User{role: :admin}\n%{name: name}\n\n# Directives and attributes\nuse GenServer\n@env Application.get_env(_, _)\n\n# Control flow\ncase _ do _ -\u003e _ end\nfn _ -\u003e _ end\n```\n\n## Code intelligence APIs\n\nExAST can expose advisory metadata for external indexes while remaining the\nsemantic verifier:\n\n```elixir\nimport ExAST.Query\n\nselector =\n  from(\"def _ do ... end\")\n  |\u003e where(contains(\"Repo.transaction(_)\"))\n\nExAST.Index.plan(selector)\n#=\u003e %ExAST.Index.Plan{required_terms: ..., requires_source?: false}\n\nExAST.Symbols.definitions(source)\nExAST.Symbols.references(source)\nExAST.Comments.extract(source)\nExAST.Comments.associated(source, range, :before)\n\nExAST.Symbols.qualified_name({Enum, :map, 2})\n#=\u003e \"Enum.map/2\"\n```\n\nSymbols keep stable string names for indexing and expose optional `mfa` tuples\nwhen a BEAM module can be safely resolved.\n\nUse these terms and facts to retrieve candidates, then verify with\n`ExAST.Selector.find_all/3` or `ExAST.Selector.match?/3`.\n\n## Limitations\n\n- Alias/import expansion is syntax-aware, not full semantic macro expansion\n- Multi-node patterns require contiguous statements\n- Replacement formatting uses `Macro.to_string/1`; pass `format: true` or run `mix format` after\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felixir-vibe%2Fex_ast","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felixir-vibe%2Fex_ast","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felixir-vibe%2Fex_ast/lists"}