diff --git a/.credo.exs b/.credo.exs index 74864ac4..f7a2bbb3 100644 --- a/.credo.exs +++ b/.credo.exs @@ -61,7 +61,9 @@ {Credo.Check.Readability.FunctionNames}, {Credo.Check.Readability.LargeNumbers}, - {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 80}, + {Credo.Check.Readability.MaxLineLength, + priority: :low, max_length: 80, ignore_specs: true + }, {Credo.Check.Readability.ModuleAttributeNames}, {Credo.Check.Readability.ModuleDoc}, {Credo.Check.Readability.ModuleNames}, @@ -70,7 +72,7 @@ {Credo.Check.Readability.PredicateFunctionNames}, {Credo.Check.Readability.PreferImplicitTry}, {Credo.Check.Readability.RedundantBlankLines}, - {Credo.Check.Readability.Specs}, + {Credo.Check.Readability.Specs, false}, {Credo.Check.Readability.StringSigils}, {Credo.Check.Readability.TrailingBlankLine}, {Credo.Check.Readability.TrailingWhiteSpace}, diff --git a/.gitignore b/.gitignore index 77a9c54a..1a813296 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ cover /priv/build /tmp .esm-cache +.elixir_ls diff --git a/lib/elixir_script/beam.ex b/lib/elixir_script/beam.ex index 254cb803..4473f221 100644 --- a/lib/elixir_script/beam.ex +++ b/lib/elixir_script/beam.ex @@ -7,7 +7,7 @@ defmodule ElixirScript.Beam do For protocols, this will return a list of all the protocol implementations """ - @spec debug_info(atom | bitstring) :: {:ok | :error, map | binary} + @spec debug_info(atom | bitstring) :: {:ok, map} | {:ok, atom, map, list} | {:error, binary} def debug_info(module) # We get debug info from String and then replace @@ -59,7 +59,7 @@ defmodule ElixirScript.Beam do {:ok, {^module, attribute_info}} = :beam_lib.chunks(beam, [:attributes]) do if Keyword.get(attribute_info[:attributes], :protocol) do - get_protocol_implementations(module) + get_protocol_implementations(module, beam_path) else backend.debug_info(:elixir_v1, module, data, []) |> process_debug_info(beam_path) @@ -89,6 +89,8 @@ defmodule ElixirScript.Beam do Map.put(info, :last_modified, nil) end + info = Map.put(info, :beam_path, beam_path) + {:ok, info} end @@ -96,7 +98,9 @@ defmodule ElixirScript.Beam do error end - defp get_protocol_implementations(module) do + defp get_protocol_implementations(module, beam_path) do + {:ok, protocol_module_info} = process_debug_info({:ok, %{}}, beam_path) + implementations = module |> Protocol.extract_impls(:code.get_path()) |> Enum.map(fn(x) -> Module.concat([module, x]) end) @@ -109,7 +113,7 @@ defmodule ElixirScript.Beam do end end) - {:ok, module, implementations} + {:ok, module, protocol_module_info, implementations} end defp replace_definitions(original_definitions, replacement_definitions) do diff --git a/lib/elixir_script/compiler.ex b/lib/elixir_script/compiler.ex index ba0f869b..0621238e 100644 --- a/lib/elixir_script/compiler.ex +++ b/lib/elixir_script/compiler.ex @@ -31,12 +31,12 @@ defmodule ElixirScript.Compiler do Translate, FindUsedModules, FindUsedFunctions, - Output + Output, } alias ElixirScript.ModuleSystems.ES alias Kernel.ParallelCompiler - @spec compile(atom | [atom] | binary, []) :: nil + @spec compile(atom | [atom] | binary, []) :: map def compile(path, opts \\ []) def compile(path, opts) when is_binary(path) do @@ -86,7 +86,7 @@ defmodule ElixirScript.Compiler do State.stop(pid) - result + transform_output(modules, result, opts) end defp build_compiler_options(opts) do @@ -103,4 +103,25 @@ defmodule ElixirScript.Compiler do defp on_module_compile(pid, _file, module, beam) do State.put_in_memory_module(pid, module, beam) end + + defp transform_output(modules, compiled_js, opts) do + output_path = if opts.output == nil or opts.output == :stdout do + "" + else + Path.dirname(opts.output) + end + + Enum.reduce(modules, %{}, fn {module, info}, current_data -> + info = %{ + references: info.used_modules, + last_modified: info.last_modified, + beam_path: Map.get(info, :beam_path), + source: Map.get(info, :file), + js_path: Path.join(output_path, "#{module}.js"), + js_code: Keyword.get(compiled_js, module) + } + + Map.put(current_data, module, info) + end) + end end diff --git a/lib/elixir_script/manifest.ex b/lib/elixir_script/manifest.ex new file mode 100644 index 00000000..5ed1915f --- /dev/null +++ b/lib/elixir_script/manifest.ex @@ -0,0 +1,21 @@ +defmodule ElixirScript.Manifest do + @moduledoc false + + @spec read_manifest(binary) :: nil + def read_manifest(_manifest) do + + end + + @spec write_manifest(binary, map) :: :ok + def write_manifest(manifest_path, modules) do + data = Enum.reduce(modules, %{}, fn {module, info}, current_data -> + Map.put(current_data, module, Map.drop(info, [:js_code])) + end) + + data = :erlang.term_to_binary(data, [:compressed]) + File.mkdir_p!(Path.dirname(manifest_path)) + File.write!(manifest_path, data) + + :ok + end +end diff --git a/lib/elixir_script/passes/find_used_modules.ex b/lib/elixir_script/passes/find_used_modules.ex index ae75d9e6..d738a006 100644 --- a/lib/elixir_script/passes/find_used_modules.ex +++ b/lib/elixir_script/passes/find_used_modules.ex @@ -6,7 +6,7 @@ defmodule ElixirScript.FindUsedModules do @doc """ Takes a list of entry modules and finds modules they use. """ - @spec execute([atom], pid) :: nil + @spec execute([atom], pid) :: :ok def execute(modules, pid) do modules |> List.wrap @@ -26,8 +26,8 @@ defmodule ElixirScript.FindUsedModules do case result do {:ok, info} -> walk_module(module, info, pid) - {:ok, module, implementations} -> - walk_protocol(module, implementations, pid) + {:ok, module, module_info, implementations} -> + walk_protocol(module, module_info, implementations, pid) {:error, "Unknown module"} -> Logger.warn fn() -> "ElixirScript: #{inspect module} is missing or unavailable" @@ -83,7 +83,7 @@ defmodule ElixirScript.FindUsedModules do end) end - defp walk_protocol(module, implementations, pid) do + defp walk_protocol(module, module_info, implementations, pid) do impls = Enum.map(implementations, fn {impl, %{attributes: attrs}} -> protocol_impl = Keyword.fetch!(attrs, :protocol_impl) impl_for = Keyword.fetch!(protocol_impl, :for) @@ -94,7 +94,12 @@ defmodule ElixirScript.FindUsedModules do functions = Enum.map(first_implementation_functions, fn { name, _, _, _} -> name end) - ModuleState.put_module(pid, module, %{protocol: true, impls: impls, functions: functions}) + module_info = Map.merge( + module_info, + %{protocol: true, impls: impls, functions: functions} + ) + + ModuleState.put_module(pid, module, module_info) Enum.each(implementations, fn {impl, info} -> ModuleState.add_used_module(pid, module, impl) @@ -303,15 +308,14 @@ defmodule ElixirScript.FindUsedModules do end defp walk({:., _, [module, function]}, state) do - cond do - ElixirScript.Translate.Module.is_elixir_module(module) -> - ModuleState.add_used_module(state.pid, state.module, module) - if ModuleState.get_module(state.pid, module) == nil do - do_execute(module, state.pid) - end - true -> - walk(module, state) - walk(function, state) + if ElixirScript.Translate.Module.is_elixir_module(module) do + ModuleState.add_used_module(state.pid, state.module, module) + if ModuleState.get_module(state.pid, module) == nil do + do_execute(module, state.pid) + end + else + walk(module, state) + walk(function, state) end end diff --git a/lib/elixir_script/passes/output.ex b/lib/elixir_script/passes/output.ex index baaf05ba..26ccc96b 100644 --- a/lib/elixir_script/passes/output.ex +++ b/lib/elixir_script/passes/output.ex @@ -7,9 +7,9 @@ defmodule ElixirScript.Output do @doc """ Takes outputs the JavaScript code in the specified output """ - @spec execute([atom], pid, map) :: nil + @spec execute([atom], pid, map) :: [{atom, binary}] def execute(modules, pid, opts) do - modules = modules + prepared_modules = modules |> Enum.filter(fn {_, info} -> Map.has_key?(info, :js_ast) end) |> Enum.map(fn {module, info} -> {module, info.js_ast, info.used_modules} @@ -27,8 +27,7 @@ defmodule ElixirScript.Output do {module, name, path, import_path} end) - modules - |> create_modules(opts, js_modules) + create_modules(prepared_modules, opts, js_modules) end defp concat(code) do @@ -39,17 +38,6 @@ defmodule ElixirScript.Output do """ end - defp prepare_js_ast(js_ast) do - case js_ast do - modules when is_list(modules) -> - modules - |> Enum.reduce([], &(&2 ++ &1.body)) - |> Builder.program - _ -> - js_ast - end - end - defp create_modules(modules, opts, js_modules) do modules |> Task.async_stream(fn({module, [body, exports], used_modules}) -> @@ -62,7 +50,6 @@ defmodule ElixirScript.Output do js_parts |> Builder.program - |> prepare_js_ast |> Generator.generate |> concat |> output(module, Map.get(opts, :output), js_modules) @@ -84,12 +71,10 @@ defmodule ElixirScript.Output do |> String.replace(".", "$") end - defp output(code, _, nil, _) do - code - end - - defp output(code, _, :stdout, _) do + defp output(code, module, nil, _), do: {module, code} + defp output(code, module, :stdout, _) do IO.puts(code) + {module, code} end defp output(code, module, path, js_modules) do @@ -112,6 +97,8 @@ defmodule ElixirScript.Output do copy_bootstrap_js(output_dir) File.write!(file_name, code) + + {module, code} end defp copy_bootstrap_js(directory) do diff --git a/lib/mix/tasks/compile.elixir_script.ex b/lib/mix/tasks/compile.elixir_script.ex index 617a92e4..cdb05da9 100644 --- a/lib/mix/tasks/compile.elixir_script.ex +++ b/lib/mix/tasks/compile.elixir_script.ex @@ -1,7 +1,10 @@ defmodule Mix.Tasks.Compile.ElixirScript do use Mix.Task + alias ElixirScript.Manifest @recursive true + @manifest ".compile.elixir_script" + @manifest_vsn 1 @moduledoc """ Mix compiler to allow mix to compile Elixirscript source files into JavaScript @@ -41,13 +44,18 @@ defmodule Mix.Tasks.Compile.ElixirScript do defp do_compile() do {input, opts} = get_compiler_params() - ElixirScript.Compiler.compile(input, opts) + result = ElixirScript.Compiler.compile(input, opts) + + Manifest.write_manifest(manifest(), result) end def clean do :ok end + def manifests, do: [manifest()] + defp manifest, do: Path.join(Mix.Project.manifest_path(), @manifest) + @doc false def get_compiler_params() do elixirscript_config = get_elixirscript_config() diff --git a/test/beam_test.exs b/test/beam_test.exs index bc6671a3..66369a94 100644 --- a/test/beam_test.exs +++ b/test/beam_test.exs @@ -10,6 +10,6 @@ defmodule ElixirScript.Beam.Test do end test "can get ast from beam that is protocol" do - assert {:ok, Enumerable, _} = ElixirScript.Beam.debug_info(Enumerable) + assert {:ok, Enumerable, _, _} = ElixirScript.Beam.debug_info(Enumerable) end end diff --git a/test/compiler_test.exs b/test/compiler_test.exs index b4d82ec5..439975f5 100644 --- a/test/compiler_test.exs +++ b/test/compiler_test.exs @@ -3,17 +3,17 @@ defmodule ElixirScript.Compiler.Test do test "Can compile one entry module" do result = ElixirScript.Compiler.compile(Version) - assert is_binary(hd(result)) + assert result |> Map.to_list |> hd |> elem(1) |> Map.get(:js_code) |> is_binary end test "Can compile multiple entry modules" do result = ElixirScript.Compiler.compile([Atom, String, Agent]) - assert is_binary(hd(result)) + assert result |> Map.to_list |> hd |> elem(1) |> Map.get(:js_code) |> is_binary end test "Output" do result = ElixirScript.Compiler.compile(Atom, []) - assert hd(result) =~ "export default" + assert result |> Map.to_list |> hd |> elem(1) |> Map.get(:js_code) =~ "export default" end test "compile file" do diff --git a/test/manifest_test.exs b/test/manifest_test.exs new file mode 100644 index 00000000..52efd044 --- /dev/null +++ b/test/manifest_test.exs @@ -0,0 +1,12 @@ +defmodule ElixirScript.Manifest.Test do + use ExUnit.Case + alias ElixirScript.Manifest + + test "write manifest" do + result = ElixirScript.Compiler.compile(Atom) + path = Path.join([System.tmp_dir(), "write_manifest_test", ".compile.elixir_script"]) + Manifest.write_manifest(path, result) + assert File.exists?(path) + end + +end