ruby-lsp-rails-factory-bot 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +20 -0
- data/Rakefile +3 -1
- data/lib/ruby_lsp/rails/factory_bot/addon.rb +50 -13
- data/lib/ruby_lsp/rails/factory_bot/addon_name.rb +9 -0
- data/lib/ruby_lsp/rails/factory_bot/completion.rb +81 -80
- data/lib/ruby_lsp/rails/factory_bot/hover.rb +45 -47
- data/lib/ruby_lsp/rails/factory_bot/server_addon/attribute_handler.rb +69 -0
- data/lib/ruby_lsp/rails/factory_bot/server_addon/base_handler.rb +35 -0
- data/lib/ruby_lsp/rails/factory_bot/server_addon/factory_handler.rb +37 -0
- data/lib/ruby_lsp/rails/factory_bot/server_addon/trait_handler.rb +51 -0
- data/lib/ruby_lsp/rails/factory_bot/server_addon.rb +45 -0
- data/lib/ruby_lsp/rails/factory_bot.rb +14 -0
- data/lib/ruby_lsp_rails_factory_bot.rb +2 -1
- metadata +28 -8
- data/lib/ruby_lsp/rails/factory_bot/server_extension.rb +0 -131
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28d0eed4aef05099e03470f4aae508ad687ce937db3d080289e5acf44d71b4a1
|
4
|
+
data.tar.gz: cfa4ac0edb00841d67a6052a90ecea3c0649420fbe7acc00dcf0d1df0aca8651
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69904e9e37c3a82cbc7f6c098d8c0c72fce15ae5e4c6a6cbf4a596d60b178be7e1577411776c191605ba0d359aef7c685dffc30ba756111f006b9b3873a44489
|
7
|
+
data.tar.gz: bbaf4f6cad8a9e394b423c6c49a029fcd768543473e1165676780813e588de3d5518020bd1f0c9b0d1a11cea7d2cd2502e18ad5b0b4e8b90da6700774e639032
|
data/.rubocop.yml
CHANGED
@@ -1,8 +1,28 @@
|
|
1
1
|
AllCops:
|
2
2
|
TargetRubyVersion: 3.0
|
3
|
+
NewCops: enable
|
4
|
+
Exclude:
|
5
|
+
- '**/*.gemspec'
|
6
|
+
- '**/vendor/bundle/**/*'
|
7
|
+
|
8
|
+
Metrics/BlockLength:
|
9
|
+
Exclude:
|
10
|
+
- 'spec/**/*_spec.rb'
|
11
|
+
|
12
|
+
Style/RedundantReturn:
|
13
|
+
AllowMultipleReturnValues: true
|
3
14
|
|
4
15
|
Style/StringLiterals:
|
5
16
|
EnforcedStyle: double_quotes
|
6
17
|
|
7
18
|
Style/StringLiteralsInInterpolation:
|
8
19
|
EnforcedStyle: double_quotes
|
20
|
+
|
21
|
+
Style/TrailingCommaInHashLiteral:
|
22
|
+
EnforcedStyleForMultiline: consistent_comma
|
23
|
+
|
24
|
+
Style/TrailingCommaInArrayLiteral:
|
25
|
+
EnforcedStyleForMultiline: consistent_comma
|
26
|
+
|
27
|
+
Style/TrailingCommaInArguments:
|
28
|
+
EnforcedStyleForMultiline: consistent_comma
|
data/Rakefile
CHANGED
@@ -1,37 +1,50 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "ruby_lsp/addon"
|
4
|
-
require "ruby_lsp_rails/
|
4
|
+
require "ruby_lsp/ruby_lsp_rails/addon"
|
5
5
|
|
6
6
|
require_relative "completion"
|
7
7
|
require_relative "hover"
|
8
|
-
require_relative "
|
8
|
+
require_relative "addon_name"
|
9
|
+
require_relative "../factory_bot"
|
10
|
+
require_relative "../../../ruby_lsp_rails_factory_bot"
|
9
11
|
|
10
12
|
module RubyLsp
|
11
13
|
module Rails
|
12
14
|
module FactoryBot
|
15
|
+
# The addon to be registered with ruby-lsp. See https://shopify.github.io/ruby-lsp/add-ons.html
|
13
16
|
class Addon < ::RubyLsp::Addon
|
14
|
-
def activate(global_state,
|
17
|
+
def activate(global_state, outgoing_queue)
|
15
18
|
@ruby_index = global_state.index
|
19
|
+
|
20
|
+
@outgoing_queue = outgoing_queue
|
21
|
+
log "Activating #{name} add-on v#{VERSION}"
|
16
22
|
end
|
17
23
|
|
18
24
|
def deactivate(*); end
|
19
25
|
|
20
26
|
def name
|
21
|
-
|
27
|
+
FactoryBot::ADDON_NAME
|
22
28
|
end
|
23
29
|
|
24
|
-
def create_completion_listener(response_builder, node_context, dispatcher, uri)
|
25
|
-
|
26
|
-
|
27
|
-
|
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)
|
28
34
|
|
29
|
-
|
30
|
-
|
35
|
+
# ensure_addon_registered!
|
36
|
+
|
37
|
+
# Completion.new(response_builder, node_context, dispatcher, runner_client)
|
38
|
+
# end
|
31
39
|
|
40
|
+
# TODO: need URI param to be able to filter by file name
|
32
41
|
def create_hover_listener(response_builder, node_context, dispatcher)
|
33
|
-
|
34
|
-
|
42
|
+
unless @addon_registered
|
43
|
+
register_addon!
|
44
|
+
return
|
45
|
+
end
|
46
|
+
|
47
|
+
Hover.new(response_builder, node_context, dispatcher, runner_client, @ruby_index)
|
35
48
|
end
|
36
49
|
|
37
50
|
def workspace_did_change_watched_files(changes)
|
@@ -39,11 +52,29 @@ module RubyLsp
|
|
39
52
|
change[:uri].match?(/(?:spec|test).+factor.+\.rb/)
|
40
53
|
end
|
41
54
|
|
42
|
-
|
55
|
+
runner_client.trigger_reload
|
43
56
|
end
|
44
57
|
|
45
58
|
private
|
46
59
|
|
60
|
+
# the addon must be registered as a rails server addon once the server has booted
|
61
|
+
def register_addon!
|
62
|
+
@addon_registered ||= # rubocop:disable Naming/MemoizedInstanceVariableName
|
63
|
+
begin
|
64
|
+
addon_path = File.expand_path("server_addon.rb", __dir__)
|
65
|
+
runner_client.register_server_addon(addon_path)
|
66
|
+
true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def runner_client
|
71
|
+
@rails_addon ||= ::RubyLsp::Addon.get(
|
72
|
+
"Ruby LSP Rails",
|
73
|
+
::RubyLsp::Rails::FactoryBot::REQUIRED_RUBY_LSP_RAILS_VERSION,
|
74
|
+
)
|
75
|
+
@rails_addon.rails_runner_client
|
76
|
+
end
|
77
|
+
|
47
78
|
FACTORY_BOT_METHODS = %i[
|
48
79
|
create
|
49
80
|
build
|
@@ -55,6 +86,12 @@ module RubyLsp
|
|
55
86
|
node_context.call_node && FACTORY_BOT_METHODS.include?(node_context.call_node.name)
|
56
87
|
true
|
57
88
|
end
|
89
|
+
|
90
|
+
def log(msg)
|
91
|
+
return if !@outgoing_queue || @outgoing_queue.closed?
|
92
|
+
|
93
|
+
@outgoing_queue << Notification.window_log_message(msg)
|
94
|
+
end
|
58
95
|
end
|
59
96
|
end
|
60
97
|
end
|
@@ -2,20 +2,19 @@
|
|
2
2
|
|
3
3
|
require "ruby_lsp/internal"
|
4
4
|
|
5
|
+
require_relative "addon_name"
|
6
|
+
|
5
7
|
module RubyLsp
|
6
8
|
module Rails
|
7
9
|
module FactoryBot
|
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) :/
|
8
15
|
class Completion
|
9
16
|
include RubyLsp::Requests::Support::Common
|
10
17
|
|
11
|
-
# TODO: Avoid duplication with other class
|
12
|
-
FACTORY_BOT_METHODS = %i[
|
13
|
-
create
|
14
|
-
build
|
15
|
-
build_stubbed
|
16
|
-
attributes_for
|
17
|
-
].flat_map { |attr| [attr, :"#{attr}_list", :"#{attr}_pair"] }.freeze
|
18
|
-
|
19
18
|
def initialize(response_builder, node_context, dispatcher, server_client)
|
20
19
|
@response_builder = response_builder
|
21
20
|
@node_context = node_context
|
@@ -25,103 +24,105 @@ module RubyLsp
|
|
25
24
|
end
|
26
25
|
|
27
26
|
def on_call_node_enter(node)
|
28
|
-
return unless FACTORY_BOT_METHODS.include?(node.name) ||
|
29
|
-
FACTORY_BOT_METHODS.include?(@node_context.parent.name)
|
27
|
+
return unless FactoryBot::FACTORY_BOT_METHODS.include?(node.name) ||
|
28
|
+
FactoryBot::FACTORY_BOT_METHODS.include?(@node_context.parent.name)
|
29
|
+
|
30
|
+
process_arguments_pattern(node, node.arguments)
|
31
|
+
rescue StandardError => e
|
32
|
+
$stderr.write(e, e.backtrace)
|
33
|
+
end
|
30
34
|
|
31
|
-
|
35
|
+
private
|
36
|
+
|
37
|
+
def process_arguments_pattern(node, arguments) # rubocop:disable Metrics/MethodLength
|
38
|
+
case arguments
|
32
39
|
in [Prism::SymbolNode => factory_name_node]
|
33
|
-
handle_factory(factory_name_node, factory_name_node
|
40
|
+
handle_factory(factory_name_node, node_string_value(factory_name_node))
|
34
41
|
|
35
42
|
in [Prism::SymbolNode => factory_name_node, *, Prism::SymbolNode => trait_node]
|
36
|
-
handle_trait(factory_name_node
|
37
|
-
|
38
|
-
in [Prism::SymbolNode =>
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
)
|
45
|
-
in [Prism::SymbolNode => factory_name_node, *, Prism::HashNode => hash_node]
|
46
|
-
handle_attribute(
|
47
|
-
factory_name_node.value.to_s, node, hash_node.elements.last.key.value&.to_s
|
48
|
-
)
|
43
|
+
handle_trait(node_string_value(factory_name_node), node, node_string_value(trait_node))
|
44
|
+
|
45
|
+
in [Prism::SymbolNode => _factory_name_node, *, Prism::KeywordHashNode => _kw_node] |
|
46
|
+
[Prism::SymbolNode => _factory_name_node, *, Prism::HashNode => _kw_node] |
|
47
|
+
[Prism::SymbolNode => _factory_name_node, *, Prism::CallNode => _call_node]
|
48
|
+
|
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)
|
49
51
|
else
|
50
|
-
$stderr.write node.arguments
|
51
52
|
nil
|
52
53
|
end
|
53
|
-
rescue => e
|
54
|
-
$stderr.write(e, e.backtrace)
|
55
54
|
end
|
56
55
|
|
57
|
-
|
56
|
+
def node_string_value(node)
|
57
|
+
node.value.to_s
|
58
|
+
end
|
58
59
|
|
59
60
|
def handle_attribute(factory_name, node, value = "")
|
60
|
-
|
61
|
-
|
62
|
-
label_details = Interface::CompletionItemLabelDetails.new(
|
63
|
-
description: attr[:type],
|
64
|
-
)
|
65
|
-
range = range_from_node(node)
|
61
|
+
range = range_from_node(node)
|
62
|
+
make_request(:attributes, factory_name: factory_name, name: value)&.each do |attr|
|
63
|
+
label_details = Interface::CompletionItemLabelDetails.new(description: attr[:type])
|
66
64
|
|
67
|
-
@response_builder <<
|
68
|
-
label: attr[:name],
|
69
|
-
filter_text: attr[:name],
|
70
|
-
label_details: label_details,
|
71
|
-
text_edit: Interface::TextEdit.new(range: range, new_text: attr[:name]),
|
72
|
-
kind: Constant::CompletionItemKind::PROPERTY,
|
73
|
-
data: {
|
74
|
-
owner_name: attr[:owner],
|
75
|
-
guessed_type: attr[:owner], # the type of the owner, not the attribute
|
76
|
-
},
|
77
|
-
)
|
65
|
+
@response_builder << serialise_attribute(attr[:name], label_details, attr[:owner], range)
|
78
66
|
end
|
79
67
|
end
|
80
68
|
|
69
|
+
def serialise_attribute(name, label_details, owner, range)
|
70
|
+
Interface::CompletionItem.new(
|
71
|
+
label: name,
|
72
|
+
filter_text: name,
|
73
|
+
label_details: label_details,
|
74
|
+
text_edit: Interface::TextEdit.new(range: range, new_text: name),
|
75
|
+
kind: Constant::CompletionItemKind::PROPERTY,
|
76
|
+
data: { owner_name: owner, guessed_type: owner }, # the type of the owner, not the attribute
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
81
80
|
def handle_trait(factory_name, node, value = "")
|
82
|
-
|
83
|
-
|
84
|
-
label_details = Interface::CompletionItemLabelDetails.new(
|
85
|
-
description: tr[:owner],
|
86
|
-
)
|
81
|
+
make_request(:traits, factory_name: factory_name, name: value)&.each do |tr|
|
82
|
+
label_details = Interface::CompletionItemLabelDetails.new(description: tr[:owner])
|
87
83
|
range = range_from_node(node)
|
88
|
-
|
89
84
|
name = tr[:name]
|
90
85
|
|
91
|
-
@response_builder <<
|
92
|
-
label: name,
|
93
|
-
filter_text: name,
|
94
|
-
label_details: label_details,
|
95
|
-
text_edit: Interface::TextEdit.new(range: range, new_text: name),
|
96
|
-
kind: Constant::CompletionItemKind::PROPERTY,
|
97
|
-
data: {
|
98
|
-
owner_name: nil,
|
99
|
-
guessed_type: tr[:owner] # the type of the owner
|
100
|
-
},
|
101
|
-
)
|
86
|
+
@response_builder << serialise_trait(name, range, label_details)
|
102
87
|
end
|
103
88
|
end
|
104
89
|
|
90
|
+
def serialise_trait(name, range, label_details)
|
91
|
+
Interface::CompletionItem.new(
|
92
|
+
label: name,
|
93
|
+
filter_text: name,
|
94
|
+
label_details: label_details,
|
95
|
+
text_edit: Interface::TextEdit.new(range: range, new_text: name),
|
96
|
+
kind: Constant::CompletionItemKind::PROPERTY,
|
97
|
+
data: { owner_name: nil, guessed_type: tr[:owner] },
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
105
101
|
def handle_factory(node, name)
|
106
|
-
|
107
|
-
|
108
|
-
@response_builder <<
|
109
|
-
label: fact[:name],
|
110
|
-
filter_text: fact[:name],
|
111
|
-
label_details: Interface::CompletionItemLabelDetails.new(
|
112
|
-
description: fact[:model_class]
|
113
|
-
),
|
114
|
-
text_edit: Interface::TextEdit.new(
|
115
|
-
range: range_from_node(node),
|
116
|
-
new_text: fact[:name],
|
117
|
-
),
|
118
|
-
kind: Constant::CompletionItemKind::CLASS,
|
119
|
-
data: {
|
120
|
-
guessed_type: fact[:model_class]
|
121
|
-
}
|
122
|
-
)
|
102
|
+
range = range_from_node(node)
|
103
|
+
make_request(:factories, name: name)&.each do |fact|
|
104
|
+
@response_builder << serialise_factory(fact[:name], fact[:model_class], range)
|
123
105
|
end
|
124
106
|
end
|
107
|
+
|
108
|
+
def make_request(request_name, **params)
|
109
|
+
@server_client.delegate_request(
|
110
|
+
server_addon_name: FactoryBot::ADDON_NAME,
|
111
|
+
request_name: request_name.to_s,
|
112
|
+
**params,
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
def serialise_factory(name, model_class, range)
|
117
|
+
Interface::CompletionItem.new(
|
118
|
+
label: name,
|
119
|
+
filter_text: name,
|
120
|
+
label_details: Interface::CompletionItemLabelDetails.new(description: model_class),
|
121
|
+
text_edit: Interface::TextEdit.new(range: range, new_text: name),
|
122
|
+
kind: Constant::CompletionItemKind::CLASS,
|
123
|
+
data: { guessed_type: model_class },
|
124
|
+
)
|
125
|
+
end
|
125
126
|
end
|
126
127
|
end
|
127
128
|
end
|
@@ -2,20 +2,15 @@
|
|
2
2
|
|
3
3
|
require "ruby_lsp/internal"
|
4
4
|
|
5
|
+
require_relative "addon_name"
|
6
|
+
|
5
7
|
module RubyLsp
|
6
8
|
module Rails
|
7
9
|
module FactoryBot
|
10
|
+
# The listener that is created during relevant hover actions
|
8
11
|
class Hover
|
9
12
|
include RubyLsp::Requests::Support::Common
|
10
13
|
|
11
|
-
# TODO: Avoid duplication with other class
|
12
|
-
FACTORY_BOT_METHODS = %i[
|
13
|
-
create
|
14
|
-
build
|
15
|
-
build_stubbed
|
16
|
-
attributes_for
|
17
|
-
].flat_map { |attr| [attr, :"#{attr}_list", :"#{attr}_pair"] }.freeze
|
18
|
-
|
19
14
|
def initialize(response_builder, node_context, dispatcher, server_client, ruby_index)
|
20
15
|
@response_builder = response_builder
|
21
16
|
@node_context = node_context
|
@@ -34,83 +29,86 @@ module RubyLsp
|
|
34
29
|
# "parent" isn't strictly speaking the immediate parent as in the AST - the
|
35
30
|
# element it refers to is a bit opinionated... in this case, it is always the call node,
|
36
31
|
# whether the symbol is an argument in the call_node args, or a symbol in a kw hash :/
|
37
|
-
unless parent.is_a?(Prism::CallNode) || FACTORY_BOT_METHODS.include?(call_node.message.to_sym)
|
32
|
+
unless parent.is_a?(Prism::CallNode) || FactoryBot::FACTORY_BOT_METHODS.include?(call_node.message.to_sym)
|
38
33
|
return
|
39
34
|
end
|
40
35
|
|
41
|
-
|
36
|
+
process_arguments_pattern(symbol_node, call_node.arguments.arguments)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def process_arguments_pattern(symbol_node, arguments) # rubocop:disable Metrics/MethodLength
|
42
|
+
case arguments
|
42
43
|
in [^symbol_node, *]
|
43
44
|
handle_factory(symbol_node)
|
44
|
-
in [Prism::SymbolNode =>
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
in [Prism::SymbolNode =>
|
51
|
-
|
52
|
-
|
53
|
-
handle_attribute(symbol_node,
|
45
|
+
in [Prism::SymbolNode => _factory_node, *, ^symbol_node] |
|
46
|
+
[Prism::SymbolNode => _factory_node, ^symbol_node, *] |
|
47
|
+
[Prism::SymbolNode => _factory_node, Integer, ^symbol_node, *]
|
48
|
+
|
49
|
+
handle_trait(symbol_node, _factory_node)
|
50
|
+
|
51
|
+
in [Prism::SymbolNode => _factory_node, *, Prism::KeywordHashNode => _kw_hash] |
|
52
|
+
[Prism::SymbolNode => _factory_node, *, Prism::HashNode => _kw_hash]
|
53
|
+
|
54
|
+
handle_attribute(symbol_node, _factory_node) if _kw_hash.elements.any? { |e| e.key == symbol_node }
|
54
55
|
else
|
55
|
-
$stderr.write "nope fact_or_trait", call_node.arguments.arguments
|
56
56
|
nil
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
-
private
|
61
|
-
|
62
60
|
def handle_attribute(symbol_node, factory_node)
|
63
61
|
name = symbol_node.value.to_s
|
64
|
-
attribute =
|
65
|
-
|
62
|
+
attribute = make_request(
|
63
|
+
:attributes,
|
64
|
+
factory_name: factory_node.value.to_s, name: name,
|
66
65
|
)&.find { |attr| attr[:name] == name }
|
67
66
|
|
68
67
|
return unless attribute
|
69
68
|
|
70
69
|
@response_builder.push(
|
71
70
|
"#{attribute[:name]} (#{attribute[:type]})",
|
72
|
-
category: :documentation
|
71
|
+
category: :documentation,
|
73
72
|
)
|
74
73
|
end
|
75
74
|
|
76
75
|
def handle_factory(symbol_node)
|
77
76
|
name = symbol_node.value.to_s
|
78
|
-
|
79
|
-
factory = @server_client.get_factories(name: name)&.find { |f| f[:name] == name }
|
80
|
-
|
77
|
+
factory = make_request(:factories, name: name)&.find { |f| f[:name] == name }
|
81
78
|
return unless factory
|
82
79
|
|
83
80
|
index_entry = @ruby_index.first_unqualified_const(factory[:name])
|
84
81
|
|
85
|
-
if index_entry
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
)
|
93
|
-
else
|
94
|
-
@response_builder.push(
|
95
|
-
"#{factory[:name]} (#{factory[:model_class]})",
|
96
|
-
category: :documentation
|
97
|
-
)
|
98
|
-
end
|
82
|
+
hint = if index_entry
|
83
|
+
markdown_from_index_entries(factory[:model_class], index_entry)
|
84
|
+
else
|
85
|
+
"#{factory[:name]} (#{factory[:model_class]})"
|
86
|
+
end
|
87
|
+
|
88
|
+
@response_builder.push(hint, category: :documentation)
|
99
89
|
end
|
100
90
|
|
101
91
|
def handle_trait(symbol_node, factory_node)
|
102
92
|
factory_name = factory_node.value.to_s
|
103
93
|
trait_name = symbol_node.value.to_s
|
104
94
|
|
105
|
-
trait =
|
106
|
-
|
107
|
-
|
95
|
+
trait = make_request(:traits, factory_name: factory_name, name: trait_name)&.find do |tr|
|
96
|
+
tr[:name] == trait_name
|
97
|
+
end
|
108
98
|
|
109
99
|
return unless trait
|
110
100
|
|
111
101
|
@response_builder.push(
|
112
102
|
"#{trait[:name]} (trait of #{trait[:owner] || factory_name})",
|
113
|
-
category: :documentation
|
103
|
+
category: :documentation,
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
def make_request(request_name, **params)
|
108
|
+
@server_client.delegate_request(
|
109
|
+
server_addon_name: FactoryBot::ADDON_NAME,
|
110
|
+
request_name: request_name.to_s,
|
111
|
+
**params,
|
114
112
|
)
|
115
113
|
end
|
116
114
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base_handler"
|
4
|
+
|
5
|
+
module RubyLsp
|
6
|
+
module Rails
|
7
|
+
module FactoryBot
|
8
|
+
class ServerAddon < RubyLsp::Rails::ServerAddon
|
9
|
+
# Handler for fetching and serialising traits
|
10
|
+
class AttributeHandler < BaseHandler
|
11
|
+
private
|
12
|
+
|
13
|
+
def fetch(params)
|
14
|
+
factory = ::FactoryBot.factories[params[:factory_name]]
|
15
|
+
name = params[:name]
|
16
|
+
|
17
|
+
attributes = factory.definition.declarations.select do |attr|
|
18
|
+
name.nil? || name.empty? ? true : attr.name.to_s.include?(name)
|
19
|
+
end
|
20
|
+
model_class = factory.send :class_name
|
21
|
+
|
22
|
+
return attributes, model_class
|
23
|
+
rescue KeyError
|
24
|
+
# FactoryBot throws a KeyError if the factory isn't found, so nothing to do here
|
25
|
+
end
|
26
|
+
|
27
|
+
def serialise(attributes, model_class)
|
28
|
+
attributes.map do |attribute|
|
29
|
+
source_location = block_for(attribute)&.source_location
|
30
|
+
{
|
31
|
+
name: attribute.name,
|
32
|
+
owner: model_class.name,
|
33
|
+
type: guess_attribute_type(attribute, model_class),
|
34
|
+
source_location: source_location,
|
35
|
+
source: block_source(attribute),
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def guess_attribute_type(attribute, model_class)
|
41
|
+
if model_class.respond_to? :attribute_types
|
42
|
+
type = model_class.attribute_types[attribute.name.to_s]
|
43
|
+
return nil if type.nil?
|
44
|
+
|
45
|
+
return ACTIVE_MODEL_TYPE_TO_RUBY_TYPE[type.class] || type.type
|
46
|
+
end
|
47
|
+
|
48
|
+
return unless model_class.respond_to? :reflections
|
49
|
+
|
50
|
+
association = model_class.reflections[attribute.name.to_s]
|
51
|
+
|
52
|
+
association&.klass
|
53
|
+
end
|
54
|
+
|
55
|
+
ACTIVE_MODEL_TYPE_TO_RUBY_TYPE = {
|
56
|
+
ActiveModel::Type::String => "String",
|
57
|
+
ActiveModel::Type::ImmutableString => "String",
|
58
|
+
ActiveModel::Type::Float => "Float",
|
59
|
+
ActiveModel::Type::Integer => "Integer",
|
60
|
+
ActiveModel::Type::Boolean => "boolean",
|
61
|
+
ActiveModel::Type::Date => "Date",
|
62
|
+
ActiveModel::Type::Time => "Time",
|
63
|
+
ActiveModel::Type::DateTime => "DateTime",
|
64
|
+
}.freeze
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLsp
|
4
|
+
module Rails
|
5
|
+
module FactoryBot
|
6
|
+
class ServerAddon < RubyLsp::Rails::ServerAddon
|
7
|
+
# Common handler functionality
|
8
|
+
class BaseHandler
|
9
|
+
def execute(params)
|
10
|
+
collection, *rest = fetch(params)
|
11
|
+
|
12
|
+
serialise(collection, *rest) if collection
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def fetch(_params) = throw("Not implemented")
|
18
|
+
|
19
|
+
def serialise(_collection, *) = throw("Not implemented")
|
20
|
+
|
21
|
+
# helper - might be best to live elsewhere?
|
22
|
+
def block_for(attr)
|
23
|
+
attr.instance_variable_get :@block
|
24
|
+
end
|
25
|
+
|
26
|
+
# helper - might be best to live elsewhere?
|
27
|
+
def block_source(attr)
|
28
|
+
blk = block_for(attr)
|
29
|
+
blk.source if blk.respond_to? :source
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base_handler"
|
4
|
+
|
5
|
+
module RubyLsp
|
6
|
+
module Rails
|
7
|
+
module FactoryBot
|
8
|
+
class ServerAddon < RubyLsp::Rails::ServerAddon
|
9
|
+
# Handler for fetching and serialising factories
|
10
|
+
class FactoryHandler < BaseHandler
|
11
|
+
private
|
12
|
+
|
13
|
+
def fetch(params)
|
14
|
+
name = params[:name]
|
15
|
+
factories = ::FactoryBot.factories.select do |f|
|
16
|
+
name.nil? || name.empty? ? true : f.name.to_s.include?(params[:name])
|
17
|
+
end
|
18
|
+
return factories, nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def serialise(factories, *)
|
22
|
+
factories.map do |fact|
|
23
|
+
model_class = fact.send :class_name
|
24
|
+
|
25
|
+
case model_class
|
26
|
+
when String, Symbol
|
27
|
+
model_class = model_class.to_s.camelize
|
28
|
+
end
|
29
|
+
|
30
|
+
{ name: fact.name, model_class: model_class }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base_handler"
|
4
|
+
|
5
|
+
module RubyLsp
|
6
|
+
module Rails
|
7
|
+
module FactoryBot
|
8
|
+
class ServerAddon < RubyLsp::Rails::ServerAddon
|
9
|
+
# Handler for fetching and serialising traits
|
10
|
+
class TraitHandler < BaseHandler
|
11
|
+
private
|
12
|
+
|
13
|
+
def fetch(params)
|
14
|
+
factory = ::FactoryBot.factories[params[:factory_name]]
|
15
|
+
|
16
|
+
trait_name_partial = params[:name]
|
17
|
+
defined_traits = defined_traits(factory, trait_name_partial)
|
18
|
+
internal_traits = internal_traits(factory.send(:class_name), trait_name_partial)
|
19
|
+
|
20
|
+
traits = defined_traits.concat(internal_traits)
|
21
|
+
return traits, nil
|
22
|
+
rescue KeyError
|
23
|
+
# FactoryBot throws a KeyError if the factory isn't found, so nothing to do here
|
24
|
+
end
|
25
|
+
|
26
|
+
def defined_traits(factory, trait_name_partial)
|
27
|
+
factory.defined_traits.select { |tr| tr.name.to_s.include? trait_name_partial }
|
28
|
+
end
|
29
|
+
|
30
|
+
def internal_traits(factory_class_name, trait_name_partial)
|
31
|
+
::FactoryBot::Internal.traits.select do |tr|
|
32
|
+
(tr.klass == factory_class_name || !tr.klass) && tr.name.to_s.include?(trait_name_partial)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def serialise(traits, *)
|
37
|
+
traits.map do |tr|
|
38
|
+
source_location = block_for(tr)&.source_location
|
39
|
+
{
|
40
|
+
name: tr.name,
|
41
|
+
source_location: source_location,
|
42
|
+
source: block_source(tr),
|
43
|
+
owner: tr.klass,
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "addon_name"
|
4
|
+
require_relative "server_addon/factory_handler"
|
5
|
+
require_relative "server_addon/trait_handler"
|
6
|
+
require_relative "server_addon/attribute_handler"
|
7
|
+
|
8
|
+
module RubyLsp
|
9
|
+
module Rails
|
10
|
+
module FactoryBot
|
11
|
+
# The addon for the ruby-lsp-rails server runtime
|
12
|
+
class ServerAddon < RubyLsp::Rails::ServerAddon
|
13
|
+
def initialize(stdout, stderr, capabilities)
|
14
|
+
super
|
15
|
+
|
16
|
+
# TODO: move to before_start hook
|
17
|
+
with_progress "ruby-lsp-rails-factory-bot-1", "initialisation" do
|
18
|
+
require "factory_bot"
|
19
|
+
::FactoryBot.find_definitions
|
20
|
+
::FactoryBot.factories.each(&:compile)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def name = FactoryBot::ADDON_NAME
|
25
|
+
|
26
|
+
def execute(request, params) # rubocop:disable Metrics/MethodLength
|
27
|
+
case request.to_sym
|
28
|
+
when :factories
|
29
|
+
collection = FactoryHandler.new.execute(params)
|
30
|
+
when :traits
|
31
|
+
collection = TraitHandler.new.execute(params)
|
32
|
+
when :attributes
|
33
|
+
collection = AttributeHandler.new.execute(params)
|
34
|
+
else
|
35
|
+
return send_error_response("#{request} no supported")
|
36
|
+
end
|
37
|
+
|
38
|
+
send_result(collection || [])
|
39
|
+
rescue => e # rubocop:disable Style/RescueStandardError
|
40
|
+
send_error_response("An error occurred while fetching #{request} - #{e}")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyLsp
|
4
|
+
module Rails
|
5
|
+
module FactoryBot
|
6
|
+
FACTORY_BOT_METHODS = %i[
|
7
|
+
create
|
8
|
+
build
|
9
|
+
build_stubbed
|
10
|
+
attributes_for
|
11
|
+
].flat_map { |attr| [attr, :"#{attr}_list", :"#{attr}_pair"] }.freeze
|
12
|
+
end
|
13
|
+
end
|
14
|
+
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.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- johansenja
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-02-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: factory_bot
|
@@ -30,22 +30,36 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
33
|
+
version: '0.23'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
40
|
+
version: '0.23'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: ruby-lsp-rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.4'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
44
58
|
requirements:
|
45
59
|
- - ">="
|
46
60
|
- !ruby/object:Gem::Version
|
47
61
|
version: '0'
|
48
|
-
type: :
|
62
|
+
type: :development
|
49
63
|
prerelease: false
|
50
64
|
version_requirements: !ruby/object:Gem::Requirement
|
51
65
|
requirements:
|
@@ -64,18 +78,24 @@ files:
|
|
64
78
|
- LICENSE.txt
|
65
79
|
- README.md
|
66
80
|
- Rakefile
|
81
|
+
- lib/ruby_lsp/rails/factory_bot.rb
|
67
82
|
- lib/ruby_lsp/rails/factory_bot/addon.rb
|
83
|
+
- lib/ruby_lsp/rails/factory_bot/addon_name.rb
|
68
84
|
- lib/ruby_lsp/rails/factory_bot/completion.rb
|
69
85
|
- lib/ruby_lsp/rails/factory_bot/hover.rb
|
70
|
-
- lib/ruby_lsp/rails/factory_bot/
|
86
|
+
- lib/ruby_lsp/rails/factory_bot/server_addon.rb
|
87
|
+
- lib/ruby_lsp/rails/factory_bot/server_addon/attribute_handler.rb
|
88
|
+
- lib/ruby_lsp/rails/factory_bot/server_addon/base_handler.rb
|
89
|
+
- lib/ruby_lsp/rails/factory_bot/server_addon/factory_handler.rb
|
90
|
+
- lib/ruby_lsp/rails/factory_bot/server_addon/trait_handler.rb
|
71
91
|
- lib/ruby_lsp_rails_factory_bot.rb
|
72
92
|
- sig/ruby_lsp/rails/factory_bot.rbs
|
73
|
-
homepage: https://github.com/johansenja/ruby-lsp-factory-bot
|
93
|
+
homepage: https://github.com/johansenja/ruby-lsp-rails-factory-bot
|
74
94
|
licenses:
|
75
95
|
- MIT
|
76
96
|
metadata:
|
77
97
|
allowed_push_host: https://rubygems.org
|
78
|
-
homepage_uri: https://github.com/johansenja/ruby-lsp-factory-bot
|
98
|
+
homepage_uri: https://github.com/johansenja/ruby-lsp-rails-factory-bot
|
79
99
|
source_code_uri: https://github.com/johansenja/ruby-lsp-rails-factory-bot
|
80
100
|
changelog_uri: https://github.com/johansenja/ruby-lsp-rails-factory-bot
|
81
101
|
post_install_message:
|
@@ -1,131 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "ruby_lsp_rails/server/extension"
|
4
|
-
|
5
|
-
module RubyLsp
|
6
|
-
module Rails
|
7
|
-
module FactoryBot
|
8
|
-
class ServerExtension < RubyLsp::Rails::Server::Extension
|
9
|
-
|
10
|
-
before_start do
|
11
|
-
require "factory_bot"
|
12
|
-
::FactoryBot.find_definitions
|
13
|
-
::FactoryBot.factories.each(&:compile)
|
14
|
-
end
|
15
|
-
|
16
|
-
after_reload do
|
17
|
-
::FactoryBot.find_definitions
|
18
|
-
::FactoryBot.factories.each(&:compile)
|
19
|
-
end
|
20
|
-
|
21
|
-
command :factories do |params|
|
22
|
-
name = params[:name]
|
23
|
-
factories = ::FactoryBot.factories.select do |f|
|
24
|
-
name.nil? || name.empty? ? true : f.name.to_s.include?(params[:name])
|
25
|
-
end
|
26
|
-
|
27
|
-
factories.map do |fact|
|
28
|
-
model_class = fact.send :class_name
|
29
|
-
case model_class
|
30
|
-
when String, Symbol
|
31
|
-
model_class = model_class.to_s.camelize
|
32
|
-
end
|
33
|
-
{
|
34
|
-
name: fact.name,
|
35
|
-
model_class: model_class,
|
36
|
-
}
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
command :traits do |params|
|
41
|
-
factory = ::FactoryBot.factories[params[:factory_name]]
|
42
|
-
|
43
|
-
trait_name_partial = params[:name]
|
44
|
-
traits = factory.defined_traits.select { |tr| tr.name.to_s.include? trait_name_partial }.concat(
|
45
|
-
::FactoryBot::Internal.traits.select { |tr|
|
46
|
-
(tr.klass == factory.send(:class_name) || !tr.klass) && tr.name.to_s.include?(trait_name_partial)
|
47
|
-
}
|
48
|
-
)
|
49
|
-
|
50
|
-
traits.map do |tr|
|
51
|
-
source_location = ServerExtension.block_for(tr)&.source_location
|
52
|
-
{
|
53
|
-
name: tr.name,
|
54
|
-
source_location: source_location,
|
55
|
-
source: ServerExtension.block_source(tr),
|
56
|
-
owner: tr.klass,
|
57
|
-
}
|
58
|
-
end
|
59
|
-
rescue KeyError
|
60
|
-
end
|
61
|
-
|
62
|
-
command :attributes do |params|
|
63
|
-
factory = ::FactoryBot.factories[params[:factory_name]]
|
64
|
-
name = params[:name]
|
65
|
-
|
66
|
-
attributes = factory.definition.declarations.select { |attr|
|
67
|
-
name.nil? || name.empty? ? true : attr.name.to_s.include?(name)
|
68
|
-
}
|
69
|
-
|
70
|
-
model_class = factory.send :class_name
|
71
|
-
|
72
|
-
attributes.map do |attribute|
|
73
|
-
source_location = ServerExtension.block_for(attribute)&.source_location
|
74
|
-
{
|
75
|
-
name: attribute.name,
|
76
|
-
owner: model_class.name,
|
77
|
-
type: ServerExtension.guess_attribute_type(attribute, model_class),
|
78
|
-
source_location: source_location,
|
79
|
-
source: ServerExtension.block_source(attribute),
|
80
|
-
}
|
81
|
-
end
|
82
|
-
rescue KeyError
|
83
|
-
end
|
84
|
-
|
85
|
-
class << self
|
86
|
-
def block_for(attr)
|
87
|
-
attr.instance_variable_get :@block
|
88
|
-
end
|
89
|
-
|
90
|
-
def block_source(attr)
|
91
|
-
blk = block_for(attr)
|
92
|
-
|
93
|
-
blk.source if blk.respond_to? :source
|
94
|
-
end
|
95
|
-
|
96
|
-
def guess_attribute_type(attribute, model_class)
|
97
|
-
if model_class.respond_to? :attribute_types
|
98
|
-
type = model_class.attribute_types[attribute.name.to_s]
|
99
|
-
|
100
|
-
return case type
|
101
|
-
when nil then nil
|
102
|
-
when ActiveModel::Type::String, ActiveModel::Type::ImmutableString
|
103
|
-
"String"
|
104
|
-
when ActiveModel::Type::Float
|
105
|
-
"Float"
|
106
|
-
when ActiveModel::Type::Integer
|
107
|
-
"Integer"
|
108
|
-
when ActiveModel::Type::Boolean
|
109
|
-
"boolean"
|
110
|
-
when ActiveModel::Type::Date
|
111
|
-
"Date"
|
112
|
-
when ActiveModel::Type::Time
|
113
|
-
"Time"
|
114
|
-
when ActiveModel::Type::DateTime
|
115
|
-
"DateTime"
|
116
|
-
else
|
117
|
-
type.type
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
return unless model_class.respond_to? :reflections
|
122
|
-
|
123
|
-
association = model_class.reflections[attribute.name.to_s]
|
124
|
-
|
125
|
-
association&.klass
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|