feat: add hex intelligence#570
Conversation
| document = Document.Container.context_document(params, nil) | ||
| projects = ActiveProjects.projects() | ||
| project = Project.project_for_document(projects, document) | ||
|
|
||
| document = Document.Container.context_document(params, nil) | ||
|
|
There was a problem hiding this comment.
not related, but a duplicated call (document is set on line 20 already)
|
@dbernheisel - if this is accepted, will we still need hex-cmp ? ps hex-cmp has been very handy thanks for this great tool! |
|
Nope! It's more complete within Expert; supports custom repos, displays the installed version of the dep, and has access to the projects actual hex to know these things rather than reinventing what mix/hex do. |
katafrakt
left a comment
There was a problem hiding this comment.
I tested it and it works really well! Just left some comments, mostly about minor things.
| end | ||
|
|
||
| defp project_files(%Project{} = project) do | ||
| key = {__MODULE__, :project_files, Project.name(project)} |
There was a problem hiding this comment.
I think it's better to use project.root_uri for cache keys, similar to #575
| end | ||
|
|
||
| def dep_version(app) when is_atom(app) do | ||
| case Application.spec(app, :vsn) do |
There was a problem hiding this comment.
This only returns dependencies that are actually loaded in the engine node, which means it does not return anything for deps with runtime: false or only: :dev (because IIRC engine node is run in test env). I actually noticed that while testing, before reading the code.
I wonder if it would make sense to use something like Mix.Dep.Lock.read, either as an alternative or as fallback.
Here's a dev-only dep (no remote version info):
and here's one with [:dev, :test]:
There was a problem hiding this comment.
good catch. I switched over to using the lock file instead, and falling back onto the Application.spec
| def project_scope(nil), do: :__local__ | ||
| def project_scope(%Project{} = project), do: Project.name(project) |
There was a problem hiding this comment.
These should be private perhaps?
And also wondering if project.root_uri would not be better choice for a cache key here.
There was a problem hiding this comment.
yes this is a better choice. I changed it to use project.root_uri
| test "returns a list (possibly empty)" do | ||
| assert is_list(Deps.project_files()) | ||
| end |
There was a problem hiding this comment.
I'm not sure this test is actually useful
There was a problem hiding this comment.
you're right. I deleted it.
| test "returns a string or nil (doesn't crash when Mix context is partial)" do | ||
| refute Deps.project_file(:app) | ||
| refute Deps.project_file(:umbrella) | ||
| end |
There was a problem hiding this comment.
The assertions and the test description here don't match. I think the biggest value is checking that it does not crash, so something like:
| test "returns a string or nil (doesn't crash when Mix context is partial)" do | |
| refute Deps.project_file(:app) | |
| refute Deps.project_file(:umbrella) | |
| end | |
| test "doesn't crash when Mix context is partial" do | |
| Deps.project_file(:app) | |
| Deps.project_file(:umbrella) | |
| end |
(not sure how others feel about test cases without assertions though)
|
One additional thing I noticed is that the changes here don't play nice with Looks like the code for |
This was found in a #570 , which added DETS file inside the cache directory. Then when `engine clean` was run, it failed because it could only handle directories. The failure looked like this: ``` ❯ ~/.local/bin/expert_darwin_arm64 engine clean --force Kernel pid terminated (application_controller) ("{application_start_failure,xp_expert,{bad_return,{{'Elixir.XPExpert.Application',start,[normal,[]]},{'EXIT',{#{reason => enotdir,path => <<\"/Users/pawel.sw/Library/Caches/expert/hex.dets\">>,'__struct__' => 'Elixir.File.Error','__exception__' => true,action => <<\"list directory\">>},[{'Elixir.File','ls!',1,[{file,\"lib/file.ex\"},{line,2064}]},{'Elixir.XPExpert.Engine','-get_engine_dirs/0-fun-1-',3,[{file,\"lib/expert/engine.ex\"},{line,103}]},{'Elixir.Enum','-reduce/3-lists^foldl/2-0-',3,[{file,\"lib/enum.ex\"},{line,2520}]},{'Elixir.XPExpert.Engine',get_engine_dirs,0,[{file,\"lib/expert/engine.ex\"},{line,102}]},{'Elixir.XPExpert.Engine',clean_engines,1,[{file,\"lib/expert/engine.ex\"},{line,80}]},{'Elixir.XPExpert.Application',start,2,[{file,\"lib/expert/application.ex\"},{line,26}]},{application_master,start_it_old,4,[{file,\"application_master.erl\"},{line,299}]}]}}}}}") Crash dump is being written to: erl_crash.dump...done ``` This PR makes the `clean` command skip any files in the cache directory.
|
Could you go over this code again manually, there seems to be a lot of pointless wrapping and unwrapping of return values, unnecessary intermediate variables, and some basic things. I imagine the design can be improved and minimized with a closer inspection. |
I did before I opened it and a couple times after, and I didn't see anything obvious. There are some "safe" rescues and catches that are annoying but they're at RPC boundaries that seem reasonable. Feel free to modify it as you wish. |
Adds hex package intelligence to
mix.exs— package name completion, version completion, dep option suggestions, and hover documentation. Works across the public hex.pm registry and custom self-hosted repos like Oban with no configuration beyond what Mix already knows.This is fairly self-contained with a few integrated points, so because of that, I felt a large PR would be ok for completeness. Happy to refactor/reorganize modules as you see fit.
Generally, this is porting hex-cmp
What it does
{:phoeand getphoenix,phoenix_live_view, etc. with download counts and descriptions. Custom repo packages (likeoban_pro) appear alongside hexpm results with their repo labeled.{:phoenix, "~> 1.and get filtered version candidates sorted newest-first. Retired versions are annotated with the retirement reason. Works for custom repos without needingrepo:typed first.{:phoenix, "~> 1.7",and getonly,runtime,override,repo, etc.How it works
depsfunction tuple using Sourceror's error-recovering parser, with a regex line fallback for cases where both parsers fail (unclosed strings). This assumes the conventional "deps" function within the project file (eg,mix.exs), and will not offer intelligence otherwise.:hex_coredep for public packages and the repo protocol (:hex_repo) for custom repos. Custom repo responses are enriched with tarball metadata if they're already cached.Testing
mix.exs