ash-project / igniter

A code generation and project patching framework.
https://hexdocs.pm/igniter/readme.html
MIT License
177 stars 21 forks source link

`create_new_file` exs extension is not respected #150

Open dkarter opened 1 day ago

dkarter commented 1 day ago

Describe the bug

When calling

Igniter.create_new_file("lib/foo/something.exs", ~s<
defmodule Foo.Something do
end
>)

The file path unexpectedly changes to .ex

CleanShot 2024-11-12 at 11 36 36@2x

To Reproduce

defmodule Mix.Tasks.Example do
  @shortdoc "A short description of your task"
  @moduledoc """

  Longer explanation of your task

  ## Options

  * `--example-option` or `-e` - Docs for your option
  """

  use Igniter.Mix.Task

  @example "mix example --example arg"

  @impl Igniter.Mix.Task
  def info(_argv, _composing_task) do
    %Igniter.Mix.Task.Info{
      # Groups allow for overlapping arguments for tasks by the same author
      # See the generators guide for more.
      group: :houston,
      # dependencies to add
      adds_deps: [],
      # dependencies to add and call their associated installers, if they exist
      installs: [],
      # An example invocation
      example: @example,
      # a list of positional arguments, i.e `[:file]`
      positional: [],
      # Other tasks your task composes using `Igniter.compose_task`, passing in the CLI argv
      # This ensures your option schema includes options from nested tasks
      composes: [],
      # `OptionParser` schema
      schema: [],
      # Default values for the options in the `schema`
      defaults: [],
      # CLI aliases
      aliases: [],
      # A list of options in the schema that are required
      required: []
    }
  end

  @impl Igniter.Mix.Task
  def igniter(igniter) do
    Igniter.create_new_file(igniter, "lib/foo/something.exs", ~s<
      defmodule Foo.Something do
      end
    >)
  end
end

Then run mix example

Expected behavior

The file gets written to the path the user provides

** Runtime

Additional context

We are co-locating tests next to the implementation files e.g. lib/something/foo.ex lib/something/foo_test.exs and have created a generator to simplify creating business logic files and their test stubs. When we try to create the test it gets renamed to .ex which adds work to rename it back to exs.

Happy to provide a more in depth explanation of our use case if you want.

zachallaun commented 1 day ago

Thanks for the report! Tracked the bug down to the private should_move_file_to/5 inside Igniter.Project.Module, but can't keep going deeper at the moment. Should be an easy enough fix once I have a bit more time.

zachallaun commented 1 hour ago

This may be a bit more difficult to address than I thought.

When creating a file, Igniter.Project.Module.proper_location/3 is used to ultimately determine where the file should go. Extensions like Igniter.Extensions.Phoenix can "hook in" here in order to provide more application-architecture-aware information. proper_location/3 also knows about your Igniter config, which can be used to determine whether a module Foo should live at lib/foo.ex or lib/foo/foo.ex (a user preference).

Looking through the code, it seems like proper_location/3 doesn't actually know about the original path for the file at all; it uses the module name and a sort of enum to determine what "kind" of file it is: :source_folder | {:source_folder, path} | :test | :test_support. After it's determined that, it decides what the file extension should be.

This means we likely need an API change to proper_location in order to handle this. For instance:

Igniter.Project.Module.proper_location(igniter, Some.Module, :source_folder)
# perhaps changes to
Igniter.Project.Module.proper_location(igniter, Some.Module, type: :source_folder, extension: ".ex")

where the extension is inferred if it's nil or, in the case of creating a new file, uses whatever extension was originally passed.