Inserting a use statement with Sourceror in a mix task

Problem

Am writing a mix task that adds a use ModuleName to the top of the generated liveview, after the use MyAppWeb, :live_view line.

Using Igniter & Sourceror to perform the insertion.

defp enhance_index_live(igniter, context, schema, table, fields) do
    schema_module = Module.concat([app_module, String.to_atom(context), String.to_atom(schema)])
    index_path = "lib/#{app_name}_web/live/#{schema_underscore}_live/index.ex"
    # Other variables...

    igniter
    |> Igniter.include_existing_file(index_path)
    |> then(fn igniter ->
      case Igniter.Project.Module.find_and_update_module(igniter, index_module, fn zipper ->
             with {:ok, zipper} <- add_live_resource_use(zipper, schema_module) do
               {:ok, zipper}
             end
           end) do
        {:ok, igniter} ->
          igniter
      end
    end)
  end

In the add_live_resource function,

def add_live_resource_use(zipper, schema_module) do
    use_statement = "use LiveTable.LiveResource, schema: #{inspect(schema_module)}"
    {:ok, ast} = Code.string_to_quoted(use_statement)
    
   case find_liveview_use_statement(zipper) do
      {:ok, liveview_use_zipper} ->
        updated_zipper =
            liveview_use_zipper 
         |> Sourceror.Zipper.insert_right(ast)
         |> Sourceror.Zipper.top()
        {:ok, updated_zipper}

      :error ->
        {:ok, Sourceror.Zipper.insert_child(zipper, ast)}
    end
  end

  def find_liveview_use_statement(zipper) do
    zipper
    |> Sourceror.Zipper.find(:next, fn z ->
      case Sourceror.Zipper.node(z) do
          {:use, _, _} ->
            true

        _ ->
          false
      end
    end)
    |> case do
      nil -> :error
      zipper -> {:ok, zipper}
    end
  end

But its unable to pattern match on the use :live_view line - and always returns errors.

The following arguments were given to Sourceror.Zipper.node/1:
        # 1
        {:__block__, [trailing_comments: [], leading_comments: []]...

I think I’m missing something in the Raw AST vs Zipper Struct conversion.

How to do this properly?

1 Like
      Project.Module.find_and_update_module!(igniter, module, fn zipper -> 
         with {:ok, zipper} <- 
                Common.within(zipper, fn zipper -> 
                  with {:ok, zipper} <- Function.move_to_def(zipper, :router, 0), 
                       {:ok, zipper} <- Common.move_to_do_block(zipper) do 
                    line = "use Routex.Router" 
                    {:ok, Common.add_code(zipper, line, placement: :before)}      
                  end 
                end),

here’s an example I did in routex, maybe it helps. I think you don’t even need move calls. :thinking: not sure, it’s been awhile. :sweat_smile:

2 Likes

Above code had 2 issues-

  1. Improper pattern matching to find the liveview use statement
  2. Returning AST at the incorrect level after inserting the use LiveTable

These were resolved by using the Sourceror.Zipper.search_pattern/2 & Sourceror.Zipper.up/2, as seen in the following code snippet-

defp add_live_resource_use(zipper, schema_module) do
    use_statement = "use LiveTable.LiveResource, schema: #{inspect(schema_module)}"
    {:ok, ast} = Code.string_to_quoted(use_statement)

    case find_liveview_use_statement(zipper) do
      {:ok, liveview_use_zipper} ->
        updated_zipper =
          liveview_use_zipper
          |> Sourceror.Zipper.insert_right(ast)
          |> Sourceror.Zipper.up()

        {:ok, updated_zipper}

      :error ->
        {:ok, Sourceror.Zipper.insert_child(zipper, ast)}
    end
  end

  defp find_liveview_use_statement(zipper) do
    case Sourceror.Zipper.search_pattern(zipper, "use __, :live_view") do
      nil -> :error
      z -> {:ok, z}
    end
  end

This fixed the issue. If you want to go through the entire code, here’s the link Git Link
This was used to implement Livetable’s mix live_table.gen.live task.

2 Likes