Skip to content

Commit

Permalink
Some fixes after testing in VSCode
Browse files Browse the repository at this point in the history
  • Loading branch information
ChallaHalla committed Dec 12, 2024
1 parent 320ac93 commit 7fb06e9
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 23 deletions.
58 changes: 51 additions & 7 deletions lib/ruby_lsp/ruby_lsp_rails/completion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,42 +29,86 @@ def initialize(client, response_builder, node_context, dispatcher, uri)
sig { params(node: Prism::CallNode).void }
def on_call_node_enter(node)
if node.name == :where
handle_active_record_where_completions(node)
handle_active_record_where_completions(call_node: node)
elsif @node_context.call_node && @node_context.call_node&.name == :where
handle_active_record_where_completions(call_node: T.must(@node_context.call_node), current_node: node)
end
end

private

sig { params(node: Prism::CallNode).void }
def handle_active_record_where_completions(node)
receiver = node.receiver
sig { params(call_node: Prism::CallNode, current_node: T.nilable(Prism::CallNode)).void }
def handle_active_record_where_completions(call_node:, current_node: nil)
receiver = call_node.receiver
return if receiver.nil?
return unless receiver.is_a?(Prism::ConstantReadNode)

resolved_class = @client.model(receiver.name.to_s)
return if resolved_class.nil?

arguments = node.arguments&.arguments
arguments = call_node.arguments&.arguments
existing_args = T.let({}, T::Hash[String, T::Boolean])
if arguments.present?

if arguments&.is_a?(Array)
return if current_node && current_node_is_argument_value?(current_node: current_node, arguments: arguments)

existing_args = get_call_node_arguments(arguments: arguments)
end

resolved_class[:columns].each do |column|
next if current_node && !column[0].start_with?(current_node.name.to_s)
next if existing_args[column[0]]

if current_node
location = current_node.location
range = Interface::Range.new(
start: Interface::Position.new(
line: location.start_line - 1,
character: location.start_column,
),
end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
)
else
location = call_node.location
# unclear how to calculate location in this scenario
range = Interface::Range.new(
start: Interface::Position.new(
line: location.start_line - 1,
character: 0,
),
end: Interface::Position.new(line: location.end_line - 1, character: 0),
)

end

@response_builder << Interface::CompletionItem.new(
label: column[0],
filter_text: column[0],
label_details: Interface::CompletionItemLabelDetails.new(
description: "Filter #{receiver.name} records by #{column[0]}",
),
text_edit: Interface::TextEdit.new(range: 0, new_text: "#{column[0]}: "),
text_edit: Interface::TextEdit.new(range: range, new_text: "#{column[0]}: "),
kind: Constant::CompletionItemKind::FIELD,
)
end
end

sig { params(current_node: Prism::Node, arguments: T::Array[Prism::Node]).returns(T::Boolean) }
def current_node_is_argument_value?(current_node:, arguments:)
arguments.any? do |argument|
next unless argument.is_a?(Prism::KeywordHashNode)

argument.elements.any? do |e|
next unless e.is_a?(Prism::AssocNode)

value = e.value
if value.is_a?(Prism::CallNode)
value == current_node
end
end
end
end

sig { params(arguments: T::Array[Prism::Node]).returns(T::Hash[String, T::Boolean]) }
def get_call_node_arguments(arguments:)
existing_args = {}
Expand Down
3 changes: 3 additions & 0 deletions test/dummy/app/models/country.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# typed: false
# frozen_string_literal: true

class Country < ApplicationRecord
User.where
User.where
end
41 changes: 25 additions & 16 deletions test/ruby_lsp_rails/completion_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
module RubyLsp
module Rails
class CompletionTest < ActiveSupport::TestCase
test "recognizes Active Record .where call on an Active Record model when cursor is on (" do
response = generate_completions_for_source(<<~RUBY, { line: 1, character: 10 })
# This test uses node as the call node with name where
test "recognizes Active Record .where call on an Active Record model using (" do
response = generate_completions_for_source(<<~RUBY, { line: 1, character: 11 })
# typed: false
User.where(
RUBY
Expand All @@ -33,7 +34,7 @@ class CompletionTest < ActiveSupport::TestCase
test "Does not suggest column if it already exists within .where as an arg and parantheses are not closed" do
response = generate_completions_for_source(<<~RUBY, { line: 1, character: 28 })
# typed: false
User.where(id:, first_name:,#{" "}
User.where(id:, first_name:,
RUBY

columns = ["last_name", "age", "created_at", "updated_at", "country_id", "active"]
Expand All @@ -45,27 +46,35 @@ class CompletionTest < ActiveSupport::TestCase
end
end

test "Does not suggest column if it already exists within .where as an arg and parantheses are closed" do
response = generate_completions_for_source(<<~RUBY, { line: 1, character: 28 })
test "Does not provide suggestions when typing argument values" do
response = generate_completions_for_source(<<~RUBY, { line: 1, character: 14 })
# typed: false
User.where(id:, first_name:, )
User.where(id:
RUBY

columns = ["last_name", "age", "created_at", "updated_at", "country_id", "active"]
assert_equal(columns.size, response.size)

columns.each_with_index do |column, i|
assert_equal(column, response[i].label)
assert_equal(column, response[i].filter_text)
end
assert_equal(0, response.size)
end

test "Does not provide suggestions when typing argument values" do
response = generate_completions_for_source(<<~RUBY, { line: 1, character: 14 })
test "Provides suggestions when typing column name partially" do
response = generate_completions_for_source(<<~RUBY, { line: 1, character: 17 })
# typed: false
User.where(id:#{" "}
User.where(first_
RUBY

assert_equal(1, response.size)
assert_equal("first_name", response[0].label)
assert_equal("first_name", response[0].filter_text)
assert_equal(11, response[0].text_edit.range.start.character)
assert_equal(1, response[0].text_edit.range.start.line)
assert_equal(17, response[0].text_edit.range.end.character)
assert_equal(1, response[0].text_edit.range.end.line)
end

test "Does not provide suggestion when typing argument value" do
response = generate_completions_for_source(<<~RUBY, { line: 1, character: 16 })
# typed: false
User.where(id: f
RUBY
assert_equal(0, response.size)
end

Expand Down

0 comments on commit 7fb06e9

Please sign in to comment.