Skip to content

Module paths containing "Live" segments result in duplicated directory segments in the generated file path #329

@matthewsinclair

Description

@matthewsinclair

Code of Conduct

  • I agree to follow this project's Code of Conduct

AI Policy

  • I agree to follow this project's AI Policy, or I agree that AI was not used while creating this issue.

Versions

elixir: "~> 1.18"

{:igniter, "~> 0.6", only: [:dev, :test]}

{:phoenix, "~> 1.8.0", override: true}
{:phoenix_html, "~> 4.2", override: true}
{:phoenix_live_reload, "~> 1.6", only: :dev}
{:phoenix_live_view, "~> 1.1.3", override: true}

Operating system

macOS Sequoia 15.6.1

Current Behavior

I have found what I think is a bug with the way Igniter (or perhaps Igniter+Phoenix?) generates file paths. When the module name has "Live" in the namespace, the output seems to have a duplicate "live/live" in the filename.

# Define a module that contains "Live" in its namespace
# This should create: lib/my_app/live/dashboard/test_live.ex
# But Igniter creates: lib/my_app/live/live/dashboard/test_live.ex

I've included what I hope is a minimal test to demonstrate what I am seeing (below).

Perhaps I am missing something fundamental about the way that Igniter and Phoenix LiveView co-exist or interact? If so, it would be helpful to understand that.

Thanks,
M@

Reproduction

defmodule Mix.Tasks.Rv.Gen.IgniterPathBugTest do
  @moduledoc """
  Demonstrates an Igniter bug where module paths containing "Live" segments
  result in duplicated directory segments in the generated file path.

  This appears to be related to how Igniter interprets Phoenix module conventions
  when combined with the .igniter.exs configuration setting:
    module_location: :outside_matching_folder
  """

  use ExUnit.Case
  import ExUnit.CaptureIO

  @tag :igniter_bug
  test "demonstrates live/live path duplication bug in Igniter" do
    # Create a minimal Igniter instance
    igniter = Igniter.new()

    # Define a module that contains "Live" in its namespace
    # This should create: lib/my_app/live/dashboard/test_live.ex
    # But Igniter creates: lib/my_app/live/live/dashboard/test_live.ex
    module_name = MyApp.Live.Dashboard.TestLive

    # Simple module content
    module_content = """
    @moduledoc "Test module to demonstrate Igniter path bug"
    def hello, do: :world
    """

    # capture_io(fn ->
      # Create the module using Igniter
      igniter = Igniter.Project.Module.create_module(igniter, module_name, module_content)

      # Find the created module to get its path
      case Igniter.Project.Module.find_module(igniter, module_name) do
        {:ok, {_igniter, source, _zipper}} ->
          actual_path = Rewrite.Source.get(source, :path)

          # Expected path based on module name
          expected_path = "lib/my_app/live/dashboard/test_live.ex"

          # This assertion will FAIL due to the bug
          # Actual path will be: "lib/my_app/live/live/dashboard/test_live.ex"
          IO.puts("\nExpected path: #{expected_path}")
          IO.puts("Actual path:   #{actual_path}")

          # This demonstrates the bug - there's a duplicate "live" in the path
          if actual_path =~ ~r/live\/live/ do
            IO.puts("BUG CONFIRMED: Path contains duplicate 'live' directory!")
          end

          # The test assertion that demonstrates the bug
          refute actual_path =~ ~r/live\/live/,
                 "Path should not contain duplicate 'live' directories"

        _ ->
          flunk("Could not find created module")
      end
    # end)
  end

  @tag :igniter_bug
  test "module without Live in namespace works correctly" do
    igniter = Igniter.new()

    # Module without "Live" in the namespace works fine
    module_name = MyApp.Web.Dashboard.TestController

    module_content = """
    @moduledoc "Test module without Live in namespace"
    def hello, do: :world
    """

    # capture_io(fn ->
      igniter = Igniter.Project.Module.create_module(igniter, module_name, module_content)

      case Igniter.Project.Module.find_module(igniter, module_name) do
        {:ok, {_igniter, source, _zipper}} ->
          actual_path = Rewrite.Source.get(source, :path)
          expected_path = "lib/my_app/web/dashboard/test_controller.ex"

          IO.puts("\nExpected path: #{expected_path}")
          IO.puts("Actual path:   #{actual_path}")

          # This should pass - no duplication when "Live" is not in the module name
          refute actual_path =~ ~r/web\/web/,
                 "Path should not contain duplicate 'web' directories"

          assert actual_path == expected_path,
                 "Path should match expected for non-Live modules"

        _ ->
          flunk("Could not find created module")
      end
    # end)
  end
end

Expected Behaviour

See test.

AI

I went back and forth on this. I use AI tools extensively on a daily basis, so it's impossible to say "yes" to "I agree that AI was not used while creating this issue". Of course I did, I use it everywhere. Did an AI generate this issue on its own? No. Did I create the issue myself and the test and the issue here in GitHub, yes. Did AI help me with that process, yes on any number of levels from simple spell checking through to sophisticated code completion. If that violates the AI policy, then I'm not sure how to help with this project.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions