ruby-lsp-rails-factory-bot 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 28d0eed4aef05099e03470f4aae508ad687ce937db3d080289e5acf44d71b4a1
4
- data.tar.gz: cfa4ac0edb00841d67a6052a90ecea3c0649420fbe7acc00dcf0d1df0aca8651
3
+ metadata.gz: 36e804a6875a0e627b7723e82840cbd502cc7dd4d352a740e9d7917379a2929a
4
+ data.tar.gz: e87ef45262a4740710ceae3229b7f681faaf002987d5a611f355e80a4e883378
5
5
  SHA512:
6
- metadata.gz: 69904e9e37c3a82cbc7f6c098d8c0c72fce15ae5e4c6a6cbf4a596d60b178be7e1577411776c191605ba0d359aef7c685dffc30ba756111f006b9b3873a44489
7
- data.tar.gz: bbaf4f6cad8a9e394b423c6c49a029fcd768543473e1165676780813e588de3d5518020bd1f0c9b0d1a11cea7d2cd2502e18ad5b0b4e8b90da6700774e639032
6
+ metadata.gz: a60ac6a177b638d4ecb930c2ccb0c0f59f1d457b421ebdcd6b64f55efe26aa179d8de68b80498b7701929c34a357410bc1e0d195835b51dc95312834648790ce
7
+ data.tar.gz: aa3ea96af8da24ef905db823967fac046a2a6900bb1f39033d8cbd20a5e284003df43669272ad48ff9eec49c5a433be534145d0855171d4423443114a9b2cc02
data/README.md CHANGED
@@ -20,6 +20,24 @@ Hover over an attribute or factory definition
20
20
 
21
21
  ![lsp-factory-bot-hover](https://github.com/user-attachments/assets/6f570288-3cf3-4d12-acf9-71c86e834cd8)
22
22
 
23
+ Receive completion suggestions as you type
24
+
25
+ ![lsp-factory-bot-completion](https://github.com/user-attachments/assets/4255a86a-8f36-4de2-8d10-8cb5a3f49e50)
26
+
27
+ ### Supports
28
+
29
+ | | Hover | Completion | Go to definition |
30
+ | ------------- |-------------| -----| ----|
31
+ | Factory name | ✅ | ⭕️ | ❌ |
32
+ | Trait | ✅ | ⭕️ | ❌ |
33
+ | Attribute | ✅ | ✅ | ❌ |
34
+
35
+ Notes:
36
+
37
+ - The extension has "understanding" of factory/trait completion items, but due to limitations on when ruby-lsp displays the completion suggestions, they aren't visible for Symbols (eg. factory/trait names) :/ though they happen to be visible for symbols in Hash/Kw notation (ie with `:` after - `key: ...`)
38
+ - Go to definition not supported yet but might come soon
39
+
40
+
23
41
  ## Development
24
42
 
25
43
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -27,23 +27,13 @@ module RubyLsp
27
27
  FactoryBot::ADDON_NAME
28
28
  end
29
29
 
30
- # def create_completion_listener(response_builder, node_context, dispatcher, uri)
31
- # path = uri.to_standardized_path
32
- # return unless path&.end_with?("_test.rb") || path&.end_with?("_spec.rb")
33
- # return unless factory_bot_call_args?(node_context)
34
-
35
- # ensure_addon_registered!
36
-
37
- # Completion.new(response_builder, node_context, dispatcher, runner_client)
38
- # end
30
+ def create_completion_listener(response_builder, node_context, dispatcher, _uri)
31
+ register_addon!
32
+ Completion.new(response_builder, node_context, dispatcher, runner_client)
33
+ end
39
34
 
40
- # TODO: need URI param to be able to filter by file name
41
35
  def create_hover_listener(response_builder, node_context, dispatcher)
42
- unless @addon_registered
43
- register_addon!
44
- return
45
- end
46
-
36
+ register_addon!
47
37
  Hover.new(response_builder, node_context, dispatcher, runner_client, @ruby_index)
48
38
  end
49
39
 
@@ -75,18 +65,6 @@ module RubyLsp
75
65
  @rails_addon.rails_runner_client
76
66
  end
77
67
 
78
- FACTORY_BOT_METHODS = %i[
79
- create
80
- build
81
- build_stubbed
82
- attributes_for
83
- ].flat_map { |attr| [attr, :"#{attr}_list", :"#{attr}_pair"] }.freeze
84
-
85
- def factory_bot_call_args?(node_context)
86
- node_context.call_node && FACTORY_BOT_METHODS.include?(node_context.call_node.name)
87
- true
88
- end
89
-
90
68
  def log(msg)
91
69
  return if !@outgoing_queue || @outgoing_queue.closed?
92
70
 
@@ -8,11 +8,7 @@ module RubyLsp
8
8
  module Rails
9
9
  module FactoryBot
10
10
  # The listener that is created when the user requests autocomplete at the relevant time.
11
- #
12
- # NOTE: autocompletion is only triggered on certain node types - almost exclusively call nodes
13
- # and constants IIRC, so you cannot currently receive autocomplete options for symbols (eg.
14
- # factory or trait names) :/
15
- class Completion
11
+ class Completion # rubocop:disable Metrics/ClassLength
16
12
  include RubyLsp::Requests::Support::Common
17
13
 
18
14
  def initialize(response_builder, node_context, dispatcher, server_client)
@@ -24,10 +20,12 @@ module RubyLsp
24
20
  end
25
21
 
26
22
  def on_call_node_enter(node)
27
- return unless FactoryBot::FACTORY_BOT_METHODS.include?(node.name) ||
28
- FactoryBot::FACTORY_BOT_METHODS.include?(@node_context.parent.name)
23
+ call_node = @node_context.call_node
24
+ return unless call_node
29
25
 
30
- process_arguments_pattern(node, node.arguments)
26
+ return unless FactoryBot::FACTORY_BOT_METHODS.include?(call_node.name)
27
+
28
+ process_arguments_pattern(node, call_node.arguments&.arguments)
31
29
  rescue StandardError => e
32
30
  $stderr.write(e, e.backtrace)
33
31
  end
@@ -40,26 +38,50 @@ module RubyLsp
40
38
  handle_factory(factory_name_node, node_string_value(factory_name_node))
41
39
 
42
40
  in [Prism::SymbolNode => factory_name_node, *, Prism::SymbolNode => trait_node]
43
- handle_trait(node_string_value(factory_name_node), node, node_string_value(trait_node))
41
+ already_used_traits = gather_already_used_traits(arguments)
42
+ handle_trait(
43
+ node_string_value(factory_name_node), node, already_used_traits, node_string_value(trait_node),
44
+ )
44
45
 
45
46
  in [Prism::SymbolNode => _factory_name_node, *, Prism::KeywordHashNode => _kw_node] |
46
47
  [Prism::SymbolNode => _factory_name_node, *, Prism::HashNode => _kw_node] |
47
48
  [Prism::SymbolNode => _factory_name_node, *, Prism::CallNode => _call_node]
48
49
 
49
- attr_name = _call_node ? _call_node.message : _kw_node.elements.last.key.value&.to_s
50
- handle_attribute(node_string_value(_factory_name_node), node, attr_name)
50
+ attr_name = node_string_value(_call_node || _kw_node.elements.last.key)
51
+ already_used_attrs = gather_already_used_attrs(_kw_node)
52
+ handle_attribute(node_string_value(_factory_name_node), node, already_used_attrs, attr_name)
51
53
  else
52
54
  nil
53
55
  end
54
56
  end
55
57
 
56
58
  def node_string_value(node)
57
- node.value.to_s
59
+ case node
60
+ when Prism::CallNode
61
+ node.name.to_s
62
+ when Prism::SymbolNode
63
+ node.value.to_s
64
+ when nil
65
+ ""
66
+ end
67
+ end
68
+
69
+ def gather_already_used_attrs(kw_node)
70
+ attrs = Set.new
71
+ return attrs unless kw_node
72
+
73
+ kw_node.elements.each do |e|
74
+ attrs.add(node_string_value(e.key))
75
+ end
76
+
77
+ attrs
58
78
  end
59
79
 
60
- def handle_attribute(factory_name, node, value = "")
80
+ def handle_attribute(factory_name, node, already_used_attrs, value = "")
61
81
  range = range_from_node(node)
62
82
  make_request(:attributes, factory_name: factory_name, name: value)&.each do |attr|
83
+ next if already_used_attrs.member?(attr[:name].to_s)
84
+
63
85
  label_details = Interface::CompletionItemLabelDetails.new(description: attr[:type])
64
86
 
65
87
  @response_builder << serialise_attribute(attr[:name], label_details, attr[:owner], range)
@@ -77,24 +99,39 @@ module RubyLsp
77
99
  )
78
100
  end
79
101
 
80
- def handle_trait(factory_name, node, value = "")
102
+ def gather_already_used_traits(arguments)
103
+ trait_names = Set.new
104
+ # skip the first one because it's factory name
105
+ 1.upto(arguments.length - 1) do |i|
106
+ arg = arguments[i]
107
+ next if arg.is_a?(Prism::IntegerNode)
108
+ break unless arg.is_a?(Prism::SymbolNode)
109
+
110
+ trait_names.add(arg.value.to_s)
111
+ end
112
+ trait_names
113
+ end
114
+
115
+ def handle_trait(factory_name, node, already_used_traits, value = "")
81
116
  make_request(:traits, factory_name: factory_name, name: value)&.each do |tr|
117
+ next if already_used_traits.member?(tr[:name].to_s)
118
+
82
119
  label_details = Interface::CompletionItemLabelDetails.new(description: tr[:owner])
83
120
  range = range_from_node(node)
84
121
  name = tr[:name]
85
122
 
86
- @response_builder << serialise_trait(name, range, label_details)
123
+ @response_builder << serialise_trait(name, range, label_details, tr[:owner])
87
124
  end
88
125
  end
89
126
 
90
- def serialise_trait(name, range, label_details)
127
+ def serialise_trait(name, range, label_details, owner)
91
128
  Interface::CompletionItem.new(
92
129
  label: name,
93
130
  filter_text: name,
94
131
  label_details: label_details,
95
132
  text_edit: Interface::TextEdit.new(range: range, new_text: name),
96
133
  kind: Constant::CompletionItemKind::PROPERTY,
97
- data: { owner_name: nil, guessed_type: tr[:owner] },
134
+ data: { owner_name: nil, guessed_type: owner },
98
135
  )
99
136
  end
100
137
 
@@ -43,8 +43,12 @@ module RubyLsp
43
43
  in [^symbol_node, *]
44
44
  handle_factory(symbol_node)
45
45
  in [Prism::SymbolNode => _factory_node, *, ^symbol_node] |
46
+ [Prism::SymbolNode => _factory_node, *, ^symbol_node, Prism::KeywordHashNode] |
47
+ [Prism::SymbolNode => _factory_node, *, ^symbol_node, Prism::HashNode] |
46
48
  [Prism::SymbolNode => _factory_node, ^symbol_node, *] |
47
- [Prism::SymbolNode => _factory_node, Integer, ^symbol_node, *]
49
+ [Prism::SymbolNode => _factory_node, Prism::IntegerNode, ^symbol_node, *] |
50
+ [Prism::SymbolNode => _factory_node, Prism::IntegerNode, *, ^symbol_node, Prism::KeywordHashNode] |
51
+ [Prism::SymbolNode => _factory_node, Prism::IntegerNode, *, ^symbol_node, Prism::HashNode]
48
52
 
49
53
  handle_trait(symbol_node, _factory_node)
50
54
 
@@ -88,6 +92,11 @@ module RubyLsp
88
92
  @response_builder.push(hint, category: :documentation)
89
93
  end
90
94
 
95
+ def trait_tooltip(trait, factory_name)
96
+ source = trait[:source]&.length&.positive? ? trait[:source] : nil
97
+ source || "#{trait[:name]} (trait of #{trait[:owner] || factory_name})"
98
+ end
99
+
91
100
  def handle_trait(symbol_node, factory_node)
92
101
  factory_name = factory_node.value.to_s
93
102
  trait_name = symbol_node.value.to_s
@@ -98,10 +107,7 @@ module RubyLsp
98
107
 
99
108
  return unless trait
100
109
 
101
- @response_builder.push(
102
- "#{trait[:name]} (trait of #{trait[:owner] || factory_name})",
103
- category: :documentation,
104
- )
110
+ @response_builder.push(trait_tooltip(trait, factory_name), category: :documentation)
105
111
  end
106
112
 
107
113
  def make_request(request_name, **params)
@@ -3,7 +3,7 @@
3
3
  module RubyLsp
4
4
  module Rails
5
5
  module FactoryBot
6
- VERSION = "0.2.0"
6
+ VERSION = "0.4.0"
7
7
  REQUIRED_RUBY_LSP_RAILS_VERSION = "~> 0.4"
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp-rails-factory-bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - johansenja
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-02-20 00:00:00.000000000 Z
11
+ date: 2025-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: factory_bot