From 2c0a609b8d441e9847393dc42f368077e0720073 Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Sun, 30 Jul 2023 18:58:30 -0400 Subject: [PATCH 1/3] fix: swap out dets for sqlite3 Also fixes a bug with go to definition. it turns out that multiple references were being stored that overlapped in range, so we'd get multiple references, which didn't match the pattern, so it returned nil. Now, sqlite easily deletes any references that overlap with the one we are inserting in that moment. --- bin/start | 2 +- lib/next_ls.ex | 64 ++++---- lib/next_ls/db.ex | 193 +++++++++++++++++++++++ lib/next_ls/db/format.ex | 18 +++ lib/next_ls/db/query.ex | 6 + lib/next_ls/db/schema.ex | 42 +++++ lib/next_ls/definition.ex | 71 +++++---- lib/next_ls/runtime/sidecar.ex | 14 +- lib/next_ls/runtime/supervisor.ex | 6 +- lib/next_ls/symbol_table.ex | 187 ---------------------- mix.exs | 1 + mix.lock | 1 + priv/monkey/_next_ls_private_compiler.ex | 4 +- test/next_ls/symbol_table_test.exs | 141 ----------------- test/next_ls_test.exs | 12 +- 15 files changed, 356 insertions(+), 406 deletions(-) create mode 100644 lib/next_ls/db.ex create mode 100644 lib/next_ls/db/format.ex create mode 100644 lib/next_ls/db/query.ex create mode 100644 lib/next_ls/db/schema.ex delete mode 100644 lib/next_ls/symbol_table.ex delete mode 100644 test/next_ls/symbol_table_test.exs diff --git a/bin/start b/bin/start index 7f4393a2..38382807 100755 --- a/bin/start +++ b/bin/start @@ -4,4 +4,4 @@ cd "$(dirname "$0")"/.. || exit 1 -elixir --sname "next-ls-$RANDOM" -S mix run --no-halt -e "Application.ensure_all_started(:next_ls)" -- "$@" +iex --sname "next-ls-$RANDOM" -S mix run --no-halt -e "Application.ensure_all_started(:next_ls)" -- "$@" diff --git a/lib/next_ls.ex b/lib/next_ls.ex index 451f79e2..ffc0f20c 100644 --- a/lib/next_ls.ex +++ b/lib/next_ls.ex @@ -2,6 +2,8 @@ defmodule NextLS do @moduledoc false use GenLSP + import NextLS.DB.Query + alias GenLSP.Enumerations.ErrorCodes alias GenLSP.Enumerations.TextDocumentSyncKind alias GenLSP.ErrorResponse @@ -33,11 +35,11 @@ defmodule NextLS do alias GenLSP.Structures.TextDocumentSyncOptions alias GenLSP.Structures.TextEdit alias GenLSP.Structures.WorkspaceFoldersChangeEvent + alias NextLS.DB alias NextLS.Definition alias NextLS.DiagnosticCache alias NextLS.Progress alias NextLS.Runtime - alias NextLS.SymbolTable def start_link(args) do {args, opts} = @@ -120,21 +122,16 @@ defmodule NextLS do def handle_request(%TextDocumentDefinition{params: %{text_document: %{uri: uri}, position: position}}, lsp) do result = - dispatch(lsp.assigns.registry, :symbol_tables, fn entries -> - for {_, %{symbol_table: symbol_table, reference_table: ref_table}} <- entries do - case Definition.fetch( - URI.parse(uri).path, - {position.line + 1, position.character + 1}, - symbol_table, - ref_table - ) do + dispatch(lsp.assigns.registry, :databases, fn entries -> + for {pid, _} <- entries do + case Definition.fetch(URI.parse(uri).path, {position.line + 1, position.character + 1}, pid) do nil -> nil [] -> nil - [{file, line, column} | _] -> + [[_pk, _mod, file, _type, _name, line, column] | _] -> %Location{ uri: "file://#{file}", range: %Range{ @@ -184,10 +181,10 @@ defmodule NextLS do end symbols = - dispatch(lsp.assigns.registry, :symbol_tables, fn entries -> - for {pid, _} <- entries, %SymbolTable.Symbol{} = symbol <- SymbolTable.symbols(pid), filter.(symbol.name) do + dispatch(lsp.assigns.registry, :databases, fn entries -> + for {pid, _} <- entries, symbol <- DB.symbols(pid), filter.(symbol.name) do name = - if symbol.type != :defstruct do + if symbol.type != "defstruct" do "#{symbol.type} #{symbol.name}" else "#{symbol.name}" @@ -201,11 +198,11 @@ defmodule NextLS do range: %Range{ start: %Position{ line: symbol.line - 1, - character: symbol.col - 1 + character: symbol.column - 1 }, end: %Position{ line: symbol.line - 1, - character: symbol.col - 1 + character: symbol.column - 1 } } } @@ -260,10 +257,6 @@ defmodule NextLS do end def handle_request(%Shutdown{}, lsp) do - dispatch(lsp.assigns.registry, :symbol_tables, fn entries -> - for {pid, _} <- entries, do: SymbolTable.close(pid) - end) - {:reply, nil, assign(lsp, exit_code: 0)} end @@ -323,6 +316,7 @@ defmodule NextLS do path: Path.join(working_dir, ".elixir-tools"), name: name, registry: lsp.assigns.registry, + logger: lsp.assigns.logger, runtime: [ task_supervisor: lsp.assigns.runtime_task_supervisor, working_dir: working_dir, @@ -463,16 +457,30 @@ defmodule NextLS do def handle_notification(%WorkspaceDidChangeWatchedFiles{params: %DidChangeWatchedFilesParams{changes: changes}}, lsp) do type = GenLSP.Enumerations.FileChangeType.deleted() - # TODO - # ✅ delete from documents - # ✅ delete all references that occur in this file - # ✅ delete all symbols from that file lsp = for %{type: ^type, uri: uri} <- changes, reduce: lsp do lsp -> - dispatch(lsp.assigns.registry, :symbol_tables, fn entries -> + dispatch(lsp.assigns.registry, :databases, fn entries -> for {pid, _} <- entries do - SymbolTable.remove(pid, uri) + file = URI.parse(uri).path + + NextLS.DB.query( + pid, + ~Q""" + DELETE FROM symbols + WHERE symbols.file = ?; + """, + [file] + ) + + NextLS.DB.query( + pid, + ~Q""" + DELETE FROM 'references' AS refs + WHERE refs.file = ?; + """, + [file] + ) end end) @@ -563,10 +571,10 @@ defmodule NextLS do end end - defp elixir_kind_to_lsp_kind(:defmodule), do: GenLSP.Enumerations.SymbolKind.module() - defp elixir_kind_to_lsp_kind(:defstruct), do: GenLSP.Enumerations.SymbolKind.struct() + defp elixir_kind_to_lsp_kind("defmodule"), do: GenLSP.Enumerations.SymbolKind.module() + defp elixir_kind_to_lsp_kind("defstruct"), do: GenLSP.Enumerations.SymbolKind.struct() - defp elixir_kind_to_lsp_kind(kind) when kind in [:def, :defp, :defmacro, :defmacrop], + defp elixir_kind_to_lsp_kind(kind) when kind in ["def", "defp", "defmacro", "defmacrop"], do: GenLSP.Enumerations.SymbolKind.function() # NOTE: this is only possible because the registry is not partitioned diff --git a/lib/next_ls/db.ex b/lib/next_ls/db.ex new file mode 100644 index 00000000..4cfe2346 --- /dev/null +++ b/lib/next_ls/db.ex @@ -0,0 +1,193 @@ +defmodule NextLS.DB do + @moduledoc false + use GenServer + + import __MODULE__.Query + + @type query :: String.t() + + def start_link(args) do + GenServer.start_link(__MODULE__, args, Keyword.take(args, [:name])) + end + + @spec query(pid(), query(), Keyword.t()) :: list() + def query(server, query, args \\ []), do: GenServer.call(server, {:query, query, args}) + + @spec symbols(pid()) :: list(map()) + def symbols(server), do: GenServer.call(server, :symbols) + + @spec insert_symbol(pid(), map()) :: :ok + def insert_symbol(server, payload), do: GenServer.cast(server, {:insert_symbol, payload}) + + @spec insert_reference(pid(), map()) :: :ok + def insert_reference(server, payload), do: GenServer.cast(server, {:insert_reference, payload}) + + def init(args) do + file = Keyword.fetch!(args, :file) + registry = Keyword.fetch!(args, :registry) + logger = Keyword.fetch!(args, :logger) + Registry.register(registry, :databases, %{}) + {:ok, conn} = :esqlite3.open(file) + + NextLS.DB.Schema.init({conn, logger}) + + {:ok, + %{ + conn: conn, + file: file, + logger: logger + }} + end + + def handle_call({:query, query, args}, _from, %{conn: conn} = s) do + rows = __query__({conn, s.logger}, query, args) + + {:reply, rows, s} + end + + def handle_call(:symbols, _from, %{conn: conn} = s) do + rows = + __query__( + {conn, s.logger}, + ~Q""" + SELECT + * + FROM + symbols; + """, + [] + ) + + symbols = + for [_pk, module, file, type, name, line, column] <- rows do + %{ + module: module, + file: file, + type: type, + name: name, + line: line, + column: column + } + end + + {:reply, symbols, s} + end + + def handle_cast({:insert_symbol, symbol}, %{conn: conn} = s) do + %{ + module: mod, + module_line: module_line, + struct: struct, + file: file, + defs: defs + } = symbol + + __query__( + {conn, s.logger}, + ~Q""" + DELETE FROM symbols + WHERE module = ?; + """, + [mod] + ) + + __query__( + {conn, s.logger}, + ~Q""" + INSERT INTO symbols (module, file, type, name, line, 'column') + VALUES (?, ?, ?, ?, ?, ?); + """, + [mod, file, "defmodule", mod, module_line, 1] + ) + + if struct do + {_, _, meta, _} = defs[:__struct__] + + __query__( + {conn, s.logger}, + ~Q""" + INSERT INTO symbols (module, file, type, name, line, 'column') + VALUES (?, ?, ?, ?, ?, ?); + """, + [mod, file, "defstruct", "%#{Macro.to_string(mod)}{}", meta[:line], 1] + ) + end + + for {name, {:v1, type, _meta, clauses}} <- defs, {meta, _, _, _} <- clauses do + __query__( + {conn, s.logger}, + ~Q""" + INSERT INTO symbols (module, file, type, name, line, 'column') + VALUES (?, ?, ?, ?, ?, ?); + """, + [mod, file, type, name, meta[:line], meta[:column] || 1] + ) + end + + {:noreply, s} + end + + def handle_cast({:insert_reference, reference}, %{conn: conn} = s) do + %{ + meta: meta, + identifier: identifier, + file: file, + type: type, + module: module + } = reference + + col = meta[:column] || 0 + + {{start_line, start_column}, {end_line, end_column}} = + {{meta[:line], col}, + {meta[:line], col + String.length(identifier |> to_string() |> String.replace("Elixir.", ""))}} + + __query__( + {conn, s.logger}, + ~Q""" + DELETE FROM 'references' AS refs + WHERE refs.file = ? + AND (? <= refs.start_line + AND refs.start_line <= ? + AND ? <= refs.start_column + AND refs.start_column <= ?) + OR (? <= refs.end_line + AND refs.end_line <= ? + AND ? <= refs.end_column + AND refs.end_column <= ?); + """, + [file, start_line, end_line, start_column, end_column, start_line, end_line, start_column, end_column] + ) + + __query__( + {conn, s.logger}, + ~Q""" + INSERT INTO 'references' (identifier, arity, file, type, module, start_line, start_column, end_line, end_column) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); + """, + [identifier, reference[:arity], file, type, module, start_line, start_column, end_line, end_column] + ) + + {:noreply, s} + end + + def __query__({conn, logger}, query, args) do + args = Enum.map(args, &cast/1) + + with {:error, _e} <- :esqlite3.q(conn, query, cast(args)) do + error = :esqlite3.error_info(conn).errmsg + NextLS.Logger.error(logger, error) + {:error, error} + end + end + + defp cast(arg) do + cond do + is_atom(arg) and Macro.classify_atom(arg) == :alias -> + Macro.to_string(arg) + + true -> + arg + end + end +end diff --git a/lib/next_ls/db/format.ex b/lib/next_ls/db/format.ex new file mode 100644 index 00000000..f8e799b9 --- /dev/null +++ b/lib/next_ls/db/format.ex @@ -0,0 +1,18 @@ +defmodule NextLS.DB.Format do + @moduledoc false + @behaviour Mix.Tasks.Format + + @impl Mix.Tasks.Format + def features(_opts), do: [sigils: [:Q], extensions: []] + + @impl Mix.Tasks.Format + def format(input, _formatter_opts, _opts \\ []) do + path = Path.join(System.tmp_dir!(), "#{System.unique_integer()}-temp.sql") + File.write!(path, input) + {result, 0} = System.cmd("pg_format", [path]) + + File.rm!(path) + + String.trim(result) <> "\n" + end +end diff --git a/lib/next_ls/db/query.ex b/lib/next_ls/db/query.ex new file mode 100644 index 00000000..b8aa0aee --- /dev/null +++ b/lib/next_ls/db/query.ex @@ -0,0 +1,6 @@ +defmodule NextLS.DB.Query do + @moduledoc false + defmacro sigil_Q({:<<>>, _, [bin]}, _mods) do + bin + end +end diff --git a/lib/next_ls/db/schema.ex b/lib/next_ls/db/schema.ex new file mode 100644 index 00000000..63ac0530 --- /dev/null +++ b/lib/next_ls/db/schema.ex @@ -0,0 +1,42 @@ +defmodule NextLS.DB.Schema do + @moduledoc false + import NextLS.DB.Query + + alias NextLS.DB + + def init(conn) do + DB.__query__( + conn, + ~Q""" + CREATE TABLE IF NOT EXISTS symbols ( + id integer PRIMARY KEY, + module text NOT NULL, + file text NOT NULL, + type text NOT NULL, + name text NOT NULL, + line integer NOT NULL, + column integer NOT NULL + ); + """, + [] + ) + + DB.__query__( + conn, + ~Q""" + CREATE TABLE IF NOT EXISTS 'references' ( + id integer PRIMARY KEY, + identifier text NOT NULL, + arity integer, + file text NOT NULL, + type text NOT NULL, + module text NOT NULL, + start_line integer NOT NULL, + start_column integer NOT NULL, + end_line integer NOT NULL, + end_column integer NOT NULL) + """, + [] + ) + end +end diff --git a/lib/next_ls/definition.ex b/lib/next_ls/definition.ex index ea663336..bdd94c12 100644 --- a/lib/next_ls/definition.ex +++ b/lib/next_ls/definition.ex @@ -1,48 +1,53 @@ defmodule NextLS.Definition do @moduledoc false - def fetch(file, {line, col}, dets_symbol_table, dets_ref_table) do - ref = - :dets.select( - dets_ref_table, - [ - {{{:"$1", {{:"$2", :"$3"}, {:"$4", :"$5"}}}, :"$6"}, - [ - {:andalso, - {:andalso, {:andalso, {:andalso, {:==, :"$1", file}, {:"=<", :"$2", line}}, {:"=<", :"$3", col}}, - {:"=<", line, :"$4"}}, {:"=<", col, :"$5"}} - ], [:"$6"]} - ] - ) + import NextLS.DB.Query - # :dets.traverse(dets_symbol_table, fn x -> {:continue, x} end) |> dbg - # :dets.traverse(dets_ref_table, fn x -> {:continue, x} end) |> dbg + alias NextLS.DB - # dbg(ref) + def fetch(file, {line, col}, db) do + [[_pk, identifier, _arity, _file, type, module, _start_l, _start_c, _end_l, _end_c]] = + DB.query( + db, + ~Q""" + SELECT + * + FROM + 'references' AS refs + WHERE + refs.file = ? + AND refs.start_line <= ? + AND ? <= refs.end_line + AND refs.start_column <= ? + AND ? <= refs.end_column; + """, + [file, line, line, col, col] + ) query = - case ref do - [%{type: :alias} = ref] -> - [ - {{:_, %{line: :"$3", name: :"$2", file: :"$5", module: :"$1", col: :"$4"}}, - [ - {:andalso, {:==, :"$1", ref.module}, {:==, :"$2", Macro.to_string(ref.module)}} - ], [{{:"$5", :"$3", :"$4"}}]} - ] + ~Q""" + SELECT + * + FROM + symbols + WHERE + symbols.module = ? + AND symbols.name = ?; + """ + + args = + case type do + "alias" -> + [module, module] - [%{type: :function} = ref] -> - [ - {{:_, %{line: :"$3", name: :"$2", file: :"$5", module: :"$1", col: :"$4"}}, - [ - {:andalso, {:==, :"$1", ref.module}, {:==, :"$2", ref.identifier}} - ], [{{:"$5", :"$3", :"$4"}}]} - ] + "function" -> + [module, identifier] _ -> nil end - if query do - :dets.select(dets_symbol_table, query) + if args do + DB.query(db, query, args) else nil end diff --git a/lib/next_ls/runtime/sidecar.ex b/lib/next_ls/runtime/sidecar.ex index 6ab4720b..18ace989 100644 --- a/lib/next_ls/runtime/sidecar.ex +++ b/lib/next_ls/runtime/sidecar.ex @@ -2,24 +2,28 @@ defmodule NextLS.Runtime.Sidecar do @moduledoc false use GenServer - alias NextLS.SymbolTable + alias NextLS.DB def start_link(args) do - GenServer.start_link(__MODULE__, Keyword.take(args, [:symbol_table]), Keyword.take(args, [:name])) + GenServer.start_link(__MODULE__, Keyword.take(args, [:symbol_table, :db]), Keyword.take(args, [:name])) end def init(args) do symbol_table = Keyword.fetch!(args, :symbol_table) - {:ok, %{symbol_table: symbol_table}} + db = Keyword.fetch!(args, :db) + + {:ok, %{symbol_table: symbol_table, db: db}} end def handle_info({:tracer, payload}, state) do - SymbolTable.put_symbols(state.symbol_table, payload) + DB.insert_symbol(state.db, payload) + {:noreply, state} end def handle_info({{:tracer, :reference}, payload}, state) do - SymbolTable.put_reference(state.symbol_table, payload) + DB.insert_reference(state.db, payload) + {:noreply, state} end end diff --git a/lib/next_ls/runtime/supervisor.ex b/lib/next_ls/runtime/supervisor.ex index e2a51025..16404e47 100644 --- a/lib/next_ls/runtime/supervisor.ex +++ b/lib/next_ls/runtime/supervisor.ex @@ -11,18 +11,20 @@ defmodule NextLS.Runtime.Supervisor do def init(init_arg) do name = init_arg[:name] registry = init_arg[:registry] + logger = init_arg[:logger] hidden_folder = init_arg[:path] File.mkdir_p!(hidden_folder) File.write!(Path.join(hidden_folder, ".gitignore"), "*\n") symbol_table_name = :"symbol-table-#{name}" + db_name = :"db-#{name}" sidecar_name = :"sidecar-#{name}" Registry.register(registry, :runtime_supervisors, %{name: name}) children = [ - {NextLS.SymbolTable, workspace: name, path: hidden_folder, registry: registry, name: symbol_table_name}, - {NextLS.Runtime.Sidecar, name: sidecar_name, symbol_table: symbol_table_name}, + {NextLS.DB, logger: logger, file: ~c"#{hidden_folder}/nextls.db", registry: registry, name: db_name}, + {NextLS.Runtime.Sidecar, name: sidecar_name, db: db_name, symbol_table: symbol_table_name}, {NextLS.Runtime, init_arg[:runtime] ++ [name: name, registry: registry, parent: sidecar_name]} ] diff --git a/lib/next_ls/symbol_table.ex b/lib/next_ls/symbol_table.ex deleted file mode 100644 index 09484d38..00000000 --- a/lib/next_ls/symbol_table.ex +++ /dev/null @@ -1,187 +0,0 @@ -defmodule NextLS.SymbolTable do - @moduledoc false - use GenServer - - defmodule Symbol do - @moduledoc false - defstruct [:file, :module, :type, :name, :line, :col] - - @type t :: %__MODULE__{ - file: String.t(), - module: module(), - type: atom(), - name: atom(), - line: integer(), - col: integer() - } - end - - @type uri :: String.t() - - def start_link(args) do - GenServer.start_link(__MODULE__, Keyword.take(args, [:path, :workspace, :registry]), Keyword.take(args, [:name])) - end - - @spec put_symbols(pid() | atom(), list(tuple())) :: :ok - def put_symbols(server, symbols), do: GenServer.cast(server, {:put_symbols, symbols}) - - @spec put_reference(pid() | atom(), map()) :: :ok - def put_reference(server, reference), do: GenServer.cast(server, {:put_reference, reference}) - - @spec remove(pid() | atom(), uri()) :: :ok - def remove(server, uri), do: GenServer.cast(server, {:remove, uri}) - - @spec symbols(pid() | atom()) :: list(struct()) - def symbols(server), do: GenServer.call(server, :symbols) - - @spec symbols(pid() | atom(), String.t()) :: list(struct()) - def symbols(server, file), do: GenServer.call(server, {:symbols, file}) - - @spec close(pid() | atom()) :: :ok | {:error, term()} - def close(server), do: GenServer.call(server, :close) - - def init(args) do - path = Keyword.fetch!(args, :path) - name = Keyword.fetch!(args, :workspace) - registry = Keyword.fetch!(args, :registry) - - {:ok, name} = - :dets.open_file(:"symbol-table-#{name}", - file: path |> Path.join("symbol_table.dets") |> String.to_charlist(), - type: :duplicate_bag - ) - - {:ok, ref_name} = - :dets.open_file(:"reference-table-#{name}", - file: path |> Path.join("reference_table.dets") |> String.to_charlist(), - type: :duplicate_bag - ) - - Registry.register(registry, :symbol_tables, %{symbol_table: name, reference_table: ref_name}) - - {:ok, %{table: name, reference_table: ref_name}} - end - - def handle_call({:symbols, file}, _, state) do - symbols = - case :dets.lookup(state.table, file) do - [{_, symbols} | _rest] -> symbols - _ -> [] - end - - {:reply, symbols, state} - end - - def handle_call(:symbols, _, state) do - symbols = - :dets.foldl( - fn {_key, symbol}, acc -> - if String.match?(to_string(symbol.name), ~r/__.*__/) do - acc - else - [symbol | acc] - end - end, - [], - state.table - ) - - {:reply, symbols, state} - end - - def handle_call(:close, _, state) do - :dets.close(state.table) - :dets.close(state.reference_table) - - {:reply, :ok, state} - end - - def handle_cast({:put_reference, reference}, state) do - %{ - meta: meta, - identifier: identifier, - file: file - } = reference - - col = meta[:column] || 0 - - range = - {{meta[:line], col}, - {meta[:line], col + String.length(identifier |> to_string() |> String.replace("Elixir.", ""))}} - - :dets.insert(state.reference_table, { - {file, range}, - reference - }) - - {:noreply, state} - end - - def handle_cast({:put_symbols, symbols}, state) do - %{ - module: mod, - module_line: module_line, - struct: struct, - file: file, - defs: defs - } = symbols - - :dets.delete(state.table, mod) - - :dets.insert( - state.table, - {mod, - %Symbol{ - module: mod, - file: file, - type: :defmodule, - name: Macro.to_string(mod), - line: module_line, - col: 1 - }} - ) - - if struct do - {_, _, meta, _} = defs[:__struct__] - - :dets.insert( - state.table, - {mod, - %Symbol{ - module: mod, - file: file, - type: :defstruct, - name: "%#{Macro.to_string(mod)}{}", - line: meta[:line], - col: 1 - }} - ) - end - - for {name, {:v1, type, _meta, clauses}} <- defs, {meta, _, _, _} <- clauses do - :dets.insert( - state.table, - {mod, - %Symbol{ - module: mod, - file: file, - type: type, - name: name, - line: meta[:line], - col: meta[:column] || 1 - }} - ) - end - - {:noreply, state} - end - - def handle_cast({:remove, uri}, %{table: symbol_table, reference_table: reference_table} = state) do - file = URI.parse(uri).path - - :dets.select_delete(symbol_table, [{{:_, %{file: :"$1"}}, [], [{:==, :"$1", file}]}]) - :dets.select_delete(reference_table, [{{{:"$1", :_}, :_}, [], [{:==, :"$1", file}]}]) - - {:noreply, state} - end -end diff --git a/mix.exs b/mix.exs index dd457995..6437fa91 100644 --- a/mix.exs +++ b/mix.exs @@ -35,6 +35,7 @@ defmodule NextLS.MixProject do defp deps do [ {:gen_lsp, "~> 0.5"}, + {:esqlite, "~> 0.8.6"}, {:styler, "~> 0.8", only: :dev}, {:ex_doc, ">= 0.0.0", only: :dev}, {:dialyxir, ">= 0.0.0", only: [:dev, :test], runtime: false} diff --git a/mix.lock b/mix.lock index 4c33c383..8ef09834 100644 --- a/mix.lock +++ b/mix.lock @@ -2,6 +2,7 @@ "dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "00b2a4bcd6aa8db9dcb0b38c1225b7277dca9bc370b6438715667071a304696f"}, "earmark_parser": {:hex, :earmark_parser, "1.4.32", "fa739a0ecfa34493de19426681b23f6814573faee95dfd4b4aafe15a7b5b32c6", [:mix], [], "hexpm", "b8b0dd77d60373e77a3d7e8afa598f325e49e8663a51bcc2b88ef41838cca755"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "esqlite": {:hex, :esqlite, "0.8.6", "7852d506eb12bd519c1084f03c467f364f8ddff69227154e5e6df9cc6346fbbf", [:rebar3], [], "hexpm", "607e45f4da42601d8f530979417f57a4cd629ab49085891849302057e68ea188"}, "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, "gen_lsp": {:hex, :gen_lsp, "0.5.0", "463d25c2b81f64b95667e1e6fa9bf2a4ed00896f5e9abe2965bc50edeebae747", [:mix], [{:jason, "~> 1.3", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.5 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:schematic, "~> 0.2.1", [hex: :schematic, repo: "hexpm", optional: false]}, {:typed_struct, "~> 0.3.0", [hex: :typed_struct, repo: "hexpm", optional: false]}], "hexpm", "6d40e2315bd2206cb0e40e93c5d58b28ab15787214fea1066f6561df783ef7c9"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, diff --git a/priv/monkey/_next_ls_private_compiler.ex b/priv/monkey/_next_ls_private_compiler.ex index e50ebbbd..2a1cdfbb 100644 --- a/priv/monkey/_next_ls_private_compiler.ex +++ b/priv/monkey/_next_ls_private_compiler.ex @@ -25,9 +25,7 @@ defmodule NextLSPrivate.Tracer do :ok end - def trace({type, meta, module, func, arity}, env) - when type in [:remote_function, :remote_macro, :imported_macro] and - module not in [:elixir_def, :elixir_utils, Kernel, Enum] do + def trace({type, meta, module, func, arity}, env) when type in [:remote_function, :remote_macro, :imported_macro] do parent = parent_pid() if type == :remote_macro && meta[:closing][:line] != meta[:line] do diff --git a/test/next_ls/symbol_table_test.exs b/test/next_ls/symbol_table_test.exs deleted file mode 100644 index 0a47ca27..00000000 --- a/test/next_ls/symbol_table_test.exs +++ /dev/null @@ -1,141 +0,0 @@ -defmodule NextLS.SymbolTableTest do - use ExUnit.Case, async: true - - alias NextLS.SymbolTable - - @moduletag :tmp_dir - - setup %{tmp_dir: dir} = cxt do - File.mkdir_p!(dir) - - start_supervised!({Registry, [keys: :duplicate, name: Registry.SymbolTableTest.Registry]}) - # this fails with `{:error, incompatible_arguments}` on CI a lot, and I have no idea why - pid = - try_start_supervised( - {SymbolTable, [path: dir, workspace: cxt.test, registry: Registry.SymbolTableTest.Registry]}, - 10 - ) - - Process.link(pid) - [pid: pid, dir: dir] - end - - defp try_start_supervised(spec, 0) do - start_supervised!(spec) - end - - defp try_start_supervised(spec, num) do - start_supervised!(spec) - rescue - _ -> - Process.sleep(250) - try_start_supervised(spec, num - 1) - end - - test "creates a dets table", %{dir: dir, pid: pid} = cxt do - assert File.exists?(Path.join([dir, "symbol_table.dets"])) - assert File.exists?(Path.join([dir, "reference_table.dets"])) - assert :sys.get_state(pid).table == :"symbol-table-#{cxt.test}" - end - - test "builds the symbol table", %{pid: pid} do - symbols = symbols() - - SymbolTable.put_symbols(pid, symbols) - - assert [ - %SymbolTable.Symbol{ - module: NextLS, - file: "/Users/alice/next_ls/lib/next_ls.ex", - type: :def, - name: :start_link, - line: 45, - col: 1 - }, - %SymbolTable.Symbol{ - module: NextLS, - file: "/Users/alice/next_ls/lib/next_ls.ex", - type: :def, - name: :start_link, - line: 44, - col: 1 - }, - %SymbolTable.Symbol{ - module: NextLS, - file: "/Users/alice/next_ls/lib/next_ls.ex", - type: :defmodule, - name: "NextLS", - line: 1, - col: 1 - } - ] == SymbolTable.symbols(pid) - end - - defp symbols do - %{ - file: "/Users/alice/next_ls/lib/next_ls.ex", - module: NextLS, - module_line: 1, - struct: nil, - defs: [ - __foo__: {:v1, :def, [line: 900], [{[line: 900], [], [], nil}]}, - start_link: - {:v1, :def, [line: 44], - [ - {[line: 44], [{:args, [version: 0, line: 44, column: 18], nil}], [], - {:__block__, [], - [ - {:=, - [ - end_of_expression: [newlines: 2, line: 52, column: 9], - line: 45, - column: 18 - ], - [ - {{:args, [version: 1, line: 45, column: 6], nil}, {:opts, [version: 2, line: 45, column: 12], nil}}, - {{:., [line: 46, column: 14], [Keyword, :split]}, - [closing: [line: 52, column: 8], line: 46, column: 15], - [ - {:args, [version: 0, line: 46, column: 21], nil}, - [:cache, :task_supervisor, :dynamic_supervisor, :extensions, :extension_registry] - ]} - ]}, - {{:., [line: 54, column: 11], [GenLSP, :start_link]}, - [closing: [line: 54, column: 45], line: 54, column: 12], - [ - NextLS, - {:args, [version: 1, line: 54, column: 35], nil}, - {:opts, [version: 2, line: 54, column: 41], nil} - ]} - ]}}, - {[line: 45], [{:args, [version: 0, line: 45, column: 18], nil}], [], - {:__block__, [], - [ - {:=, - [ - end_of_expression: [newlines: 2, line: 52, column: 9], - line: 45, - column: 18 - ], - [ - {{:args, [version: 1, line: 45, column: 6], nil}, {:opts, [version: 2, line: 45, column: 12], nil}}, - {{:., [line: 46, column: 14], [Keyword, :split]}, - [closing: [line: 52, column: 8], line: 46, column: 15], - [ - {:args, [version: 0, line: 46, column: 21], nil}, - [:cache, :task_supervisor, :dynamic_supervisor, :extensions, :extension_registry] - ]} - ]}, - {{:., [line: 54, column: 11], [GenLSP, :start_link]}, - [closing: [line: 54, column: 45], line: 54, column: 12], - [ - NextLS, - {:args, [version: 1, line: 54, column: 35], nil}, - {:opts, [version: 2, line: 54, column: 41], nil} - ]} - ]}} - ]} - ] - } - end -end diff --git a/test/next_ls_test.exs b/test/next_ls_test.exs index bc597ffd..f0a56c54 100644 --- a/test/next_ls_test.exs +++ b/test/next_ls_test.exs @@ -407,15 +407,15 @@ defmodule NextLSTest do "location" => %{ "range" => %{ "start" => %{ - "line" => 3, + "line" => 4, "character" => 0 }, "end" => %{ - "line" => 3, + "line" => 4, "character" => 0 } }, - "uri" => "file://#{cwd}/my_proj/lib/bar.ex" + "uri" => "file://#{cwd}/my_proj/lib/code_action.ex" }, "name" => "def foo" }, @@ -424,15 +424,15 @@ defmodule NextLSTest do "location" => %{ "range" => %{ "start" => %{ - "line" => 4, + "line" => 3, "character" => 0 }, "end" => %{ - "line" => 4, + "line" => 3, "character" => 0 } }, - "uri" => "file://#{cwd}/my_proj/lib/code_action.ex" + "uri" => "file://#{cwd}/my_proj/lib/bar.ex" }, "name" => "def foo" } From 0f7e8cf100e712f1a76234ae007e81b17c69fcc3 Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Sun, 30 Jul 2023 19:09:07 -0400 Subject: [PATCH 2/3] fixup! fix: swap out dets for sqlite3 --- lib/next_ls/db.ex | 2 +- lib/next_ls/db/format.ex | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/next_ls/db.ex b/lib/next_ls/db.ex index 4cfe2346..14beeffd 100644 --- a/lib/next_ls/db.ex +++ b/lib/next_ls/db.ex @@ -10,7 +10,7 @@ defmodule NextLS.DB do GenServer.start_link(__MODULE__, args, Keyword.take(args, [:name])) end - @spec query(pid(), query(), Keyword.t()) :: list() + @spec query(pid(), query(), list()) :: list() def query(server, query, args \\ []), do: GenServer.call(server, {:query, query, args}) @spec symbols(pid()) :: list(map()) diff --git a/lib/next_ls/db/format.ex b/lib/next_ls/db/format.ex index f8e799b9..aa1ff94d 100644 --- a/lib/next_ls/db/format.ex +++ b/lib/next_ls/db/format.ex @@ -1,18 +1,18 @@ defmodule NextLS.DB.Format do @moduledoc false - @behaviour Mix.Tasks.Format + # @behaviour Mix.Tasks.Format - @impl Mix.Tasks.Format - def features(_opts), do: [sigils: [:Q], extensions: []] + # @impl Mix.Tasks.Format + # def features(_opts), do: [sigils: [:Q], extensions: []] - @impl Mix.Tasks.Format - def format(input, _formatter_opts, _opts \\ []) do - path = Path.join(System.tmp_dir!(), "#{System.unique_integer()}-temp.sql") - File.write!(path, input) - {result, 0} = System.cmd("pg_format", [path]) + # @impl Mix.Tasks.Format + # def format(input, _formatter_opts, _opts \\ []) do + # path = Path.join(System.tmp_dir!(), "#{System.unique_integer()}-temp.sql") + # File.write!(path, input) + # {result, 0} = System.cmd("pg_format", [path]) - File.rm!(path) + # File.rm!(path) - String.trim(result) <> "\n" - end + # String.trim(result) <> "\n" + # end end From 9317cff7bf20d1e7fc35e6894be60488a5c11da9 Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Sun, 30 Jul 2023 19:22:46 -0400 Subject: [PATCH 3/3] fixup! fix: swap out dets for sqlite3 --- lib/next_ls/db.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/next_ls/db.ex b/lib/next_ls/db.ex index 14beeffd..564598c0 100644 --- a/lib/next_ls/db.ex +++ b/lib/next_ls/db.ex @@ -183,7 +183,7 @@ defmodule NextLS.DB do defp cast(arg) do cond do - is_atom(arg) and Macro.classify_atom(arg) == :alias -> + is_atom(arg) and String.starts_with?(to_string(arg), "Elixir.") -> Macro.to_string(arg) true ->