Skip to content

improvement: add rename_module/4 and mix igniter.refactor.rename_modu…#374

Merged
zachdaniel merged 4 commits intoash-project:mainfrom
exfoundry:feature/rename-module
Apr 20, 2026
Merged

improvement: add rename_module/4 and mix igniter.refactor.rename_modu…#374
zachdaniel merged 4 commits intoash-project:mainfrom
exfoundry:feature/rename-module

Conversation

@eliasforge
Copy link
Copy Markdown
Contributor

Closes #373

A few notes to make review easier:

Code style
mix format has been run. Tests follow the same naming pattern as the existing rename_function suite (lib/example.ex, lib/some_module.ex, etc.). The new tests are grouped in a describe "rename_module" block — that was a deliberate choice to keep them visually separated, but happy to flatten them into the existing list if you prefer consistency.

Why the longer @doc than rename_function
I spent a while working through the edge cases and the doc reflects that. Everything static is handled correctly: plain alias, multi-alias (alias Foo.{Bar, Other}), alias Foo.Bar, as: B, submodules, test modules, string literals, and file moves. The longer doc makes the alias behaviour explicit upfront so callers know exactly what to expect without reading the source.

Test modules are inferred and renamed together
FooTest is inferred from the old module name and renamed alongside it as a convenience — no configuration needed. Happy to make this opt-out via an option if you'd prefer.

Quick walkthrough through the code
rename_module first breaks the module down into all the forms it needs — string parts, atom lists for the AST, short form, test module variant. The logic then flows as a pipe:

  • rewrite_affected_files — walks all files that mention the module and runs two passes per file: first AST (defmodule, alias, use, call sites, short forms via expand_alias), then a string replace for literals and comments
  • move_submodule_files — moves any files whose path sits under the old module directory (e.g. lib/example/worker.exlib/new_example/worker.ex)
  • move_module_file — moves the module's own file

Contributor checklist

Leave anything that you believe does not apply unchecked.

  • I accept the AI Policy, or AI was not used in the creation of this PR.
  • Bug fixes include regression tests
  • Chores
  • Documentation changes
  • Features include unit/acceptance tests
  • Refactoring
  • Update dependencies

Copy link
Copy Markdown
Contributor

@zachdaniel zachdaniel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking mostly
Good have a few comments!

Comment thread lib/igniter/refactors/rename.ex Outdated
Comment thread lib/igniter/refactors/rename.ex Outdated
Use find_module/2 and AST-based multi-alias detection instead of
string-matching helpers. Fix an ordering bug where the AST pass ran
before the string replace (Example → NewExample → NewNewExample).
Add import/require tests.
@eliasforge
Copy link
Copy Markdown
Contributor Author

Thanks for the review! Pushed an update:

  1. find_module_file — replaced with Igniter.Project.Module.find_module/2. Should've used the existing utility — sorry for the duplication.

  2. multi_alias_in_content? (string matching) — removed. The multi-alias detection now runs on the AST via a small has_multi_alias_for? helper that zippers for {:alias, _, [{{:., _, [{:__aliases__, _, ns}, :{}]}, _, shorts}]}, which is reused both in find_affected_files and in the short-form renamer.

  3. String pre-filter in find_affected_files — yes, this is a performance filter, now explicitly: cheap String.contains? for the full module name (covers defmodule, dotted refs, string literals), with an AST fallback guarded by a cheap "<Namespace>.{" substring check so we don't parse every .ex file in the project for a multi-alias that almost never exists. False positives here are harmless — the downstream AST pass is a no-op when nothing matches.


What I already started before your comment:

  • Fixed an ordering bug where running the AST pass before the string replace caused ExampleNewExample to cascade into NewNewExample (the re-rendered content contained NewExample, which then matched Example again). Just had this in one of my projects. Rename module is not the easiest to implement...
  • Added import and require tests for rename_module. Worked before but was untested.

Tests:
........................
Finished in 0.2 seconds (0.00s async, 0.2s sync)
24 tests, 0 failures

- Replace custom find_module_file with Igniter.Project.Module.find_module/2
- Replace string-based multi_alias_in_content? with AST zipper traversal
- Fix double-rename bug (NewNewExample) by running string replace before AST pass
- Fix no-source crash by guarding move_module_file with Rewrite.source/2 check
- Fix alias Ns.{FooPage, BarPage} collapsing to alias Ns.{IndexLive, IndexLive}
  when both members move to different namespaces but share the same short name;
  expand_multi_alias_member now splits the multi-alias into separate alias statements
@eliasforge
Copy link
Copy Markdown
Contributor Author

Fixed another bug in multi-alias handling: when renaming two LiveView pages from a shared namespace to separate ones — e.g. alias MyApp.{FooLive, BarLive} after FooLive → MyApp.Foo.IndexLive and BarLive → MyApp.Bar.IndexLive — the brace form would collapse to alias MyApp.{IndexLive, IndexLive} instead of expanding to two separate alias lines.

Personally, I'm not a fan of the brace alias form — it's always been a source of friction. That said, I spent another hour specifically hunting for edge cases in the brace handling and found the one above, which is now covered. I hope this will save another developer some headache.

@zachdaniel zachdaniel merged commit 3ca42d6 into ash-project:main Apr 20, 2026
45 checks passed
@zachdaniel
Copy link
Copy Markdown
Contributor

🚀 Thank you for your contribution! 🚀

@eliasforge
Copy link
Copy Markdown
Contributor Author

I have to thank you so much for this wonderful project and the super quick responses!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rename a module across a project similar to rename_function

2 participants