ruby-lsp-rails-factory-bot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 41ed188af5e2b4b289442274d6538f7105ce3d0e93dd7340923b7f752c7b042b
4
+ data.tar.gz: a8b258fb2f0c05d17f614d3614515da65b29c04c95c17d7a39c2cd13bc1df261
5
+ SHA512:
6
+ metadata.gz: dbcb7349c449d4d33076e65c04060ca179c150a204d9d6ce93b274bdd5f25357c98a32263a98092943f7845086d8117266394af00f086db21750bf4d83963f92
7
+ data.tar.gz: a2053e5de42267b86d927e7e0aa3fd4ab99475d9d82843086d4d91b1bb08dfa3efccf1a28aa79fbfd17990ee8d5bb330f99418590a561e878003b728f8ba39be
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+
4
+ Style/StringLiterals:
5
+ EnforcedStyle: double_quotes
6
+
7
+ Style/StringLiteralsInInterpolation:
8
+ EnforcedStyle: double_quotes
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Joseph Johansen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # ruby-lsp-rails-factory-bot
2
+
3
+ A ruby-lsp addon to integrate with [Factory bot](https://github.com/thoughtbot/factory_bot). Currently supports hover and completion
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ $ bundle add ruby-lsp-rails-factory-bot
10
+
11
+ If bundler is not being used to manage dependencies, install the gem by executing:
12
+
13
+ $ gem install ruby-lsp-rails-factory-bot
14
+
15
+ Note that this currenlty uses a fork of ruby-lsp-rails (to extend its server to be able to provide factory information).
16
+
17
+ ## Usage
18
+
19
+ Hover over an attribute or factory definition
20
+
21
+ ![lsp-factory-bot-hover](https://github.com/user-attachments/assets/6f570288-3cf3-4d12-acf9-71c86e834cd8)
22
+
23
+ ## Development
24
+
25
+ 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.
26
+
27
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
28
+
29
+ ## Contributing
30
+
31
+ Bug reports and pull requests are welcome on GitHub at https://github.com/johansenja/ruby-lsp-factory-bot.
32
+
33
+ ## License
34
+
35
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rubocop/rake_task"
5
+
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: :rubocop
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby_lsp/addon"
4
+ require "ruby_lsp_rails/runner_client"
5
+
6
+ require_relative "completion"
7
+ require_relative "hover"
8
+ require_relative "server_extension"
9
+
10
+ module RubyLsp
11
+ module Rails
12
+ module FactoryBot
13
+ class Addon < ::RubyLsp::Addon
14
+ def activate(global_state, *)
15
+ @ruby_index = global_state.index
16
+ end
17
+
18
+ def deactivate(*); end
19
+
20
+ def name
21
+ "ruby-lsp-rails-factory-bot"
22
+ end
23
+
24
+ def create_completion_listener(response_builder, node_context, dispatcher, uri)
25
+ path = uri.to_standardized_path
26
+ return unless path&.end_with?("_test.rb") || path&.end_with?("_spec.rb")
27
+ return unless factory_bot_call_args?(node_context)
28
+
29
+ Completion.new(response_builder, node_context, dispatcher, RubyLsp::Rails::RunnerClient.instance)
30
+ end
31
+
32
+ def create_hover_listener(response_builder, node_context, dispatcher)
33
+ # TODO: need URI param
34
+ Hover.new(response_builder, node_context, dispatcher, RubyLsp::Rails::RunnerClient.instance, @ruby_index)
35
+ end
36
+
37
+ def workspace_did_change_watched_files(changes)
38
+ return unless changes.any? do |change|
39
+ change[:uri].match?(/(?:spec|test).+factor.+\.rb/)
40
+ end
41
+
42
+ RubyLsp::Rails::RunnerClient.instance.trigger_reload
43
+ end
44
+
45
+ private
46
+
47
+ FACTORY_BOT_METHODS = %i[
48
+ create
49
+ build
50
+ build_stubbed
51
+ attributes_for
52
+ ].flat_map { |attr| [attr, :"#{attr}_list", :"#{attr}_pair"] }.freeze
53
+
54
+ def factory_bot_call_args?(node_context)
55
+ node_context.call_node && FACTORY_BOT_METHODS.include?(node_context.call_node.name)
56
+ true
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby_lsp/internal"
4
+
5
+ module RubyLsp
6
+ module Rails
7
+ module FactoryBot
8
+ class Completion
9
+ include RubyLsp::Requests::Support::Common
10
+
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
+ def initialize(response_builder, node_context, dispatcher, server_client)
20
+ @response_builder = response_builder
21
+ @node_context = node_context
22
+ @server_client = server_client
23
+
24
+ dispatcher.register self, :on_call_node_enter
25
+ end
26
+
27
+ def on_call_node_enter(node)
28
+ return unless FACTORY_BOT_METHODS.include?(node.name) ||
29
+ FACTORY_BOT_METHODS.include?(@node_context.parent.name)
30
+
31
+ case node.arguments
32
+ in [Prism::SymbolNode => factory_name_node]
33
+ handle_factory(factory_name_node, factory_name_node.value.to_s)
34
+
35
+ in [Prism::SymbolNode => factory_name_node, *, Prism::SymbolNode => trait_node]
36
+ handle_trait(factory_name_node.value.to_s, node, trait_node.value.to_s)
37
+
38
+ in [Prism::SymbolNode => factory_name_node, *, Prism::CallNode => call_node]
39
+ handle_attribute(factory_name_node.value.to_s, node, call_node.message)
40
+
41
+ in [Prism::SymbolNode => factory_name_node, *, Prism::KeywordHashNode => kw_node]
42
+ handle_attribute(
43
+ factory_name_node.value.to_s, node, kw_node.elements.last.key.value&.to_s
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
+ )
49
+ else
50
+ $stderr.write node.arguments
51
+ nil
52
+ end
53
+ rescue => e
54
+ $stderr.write(e, e.backtrace)
55
+ end
56
+
57
+ private
58
+
59
+ def handle_attribute(factory_name, node, value = "")
60
+ @server_client.get_attributes(factory_name: factory_name, name: value)&.each do |attr|
61
+ $stderr.write "attribute", attr
62
+ label_details = Interface::CompletionItemLabelDetails.new(
63
+ description: attr[:type],
64
+ )
65
+ range = range_from_node(node)
66
+
67
+ @response_builder << Interface::CompletionItem.new(
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
+ )
78
+ end
79
+ end
80
+
81
+ def handle_trait(factory_name, node, value = "")
82
+ @server_client.get_traits(factory_name: factory_name, name: value)&.each do |tr|
83
+ $stderr.write "trait", tr
84
+ label_details = Interface::CompletionItemLabelDetails.new(
85
+ description: tr[:owner],
86
+ )
87
+ range = range_from_node(node)
88
+
89
+ name = tr[:name]
90
+
91
+ @response_builder << 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: {
98
+ owner_name: nil,
99
+ guessed_type: tr[:owner] # the type of the owner
100
+ },
101
+ )
102
+ end
103
+ end
104
+
105
+ def handle_factory(node, name)
106
+ @server_client.get_factories(name: name)&.each do |fact|
107
+ $stderr.write "factory", fact
108
+ @response_builder << Interface::CompletionItem.new(
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
+ )
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby_lsp/internal"
4
+
5
+ module RubyLsp
6
+ module Rails
7
+ module FactoryBot
8
+ class Hover
9
+ include RubyLsp::Requests::Support::Common
10
+
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
+ def initialize(response_builder, node_context, dispatcher, server_client, ruby_index)
20
+ @response_builder = response_builder
21
+ @node_context = node_context
22
+ @server_client = server_client
23
+ @ruby_index = ruby_index
24
+
25
+ dispatcher.register self, :on_symbol_node_enter
26
+ end
27
+
28
+ def on_symbol_node_enter(symbol_node)
29
+ parent = @node_context.parent
30
+ call_node = @node_context.call_node
31
+
32
+ return unless call_node
33
+
34
+ # "parent" isn't strictly speaking the immediate parent as in the AST - the
35
+ # element it refers to is a bit opinionated... in this case, it is always the call node,
36
+ # 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)
38
+ return
39
+ end
40
+
41
+ case call_node.arguments.arguments
42
+ in [^symbol_node, *]
43
+ handle_factory(symbol_node)
44
+ in [Prism::SymbolNode => factory_node, *, ^symbol_node]
45
+ handle_trait(symbol_node, factory_node)
46
+ in [Prism::SymbolNode => factory_node, ^symbol_node, *]
47
+ handle_trait(symbol_node, factory_node)
48
+ in [Prism::SymbolNode => factory_node, Integer, ^symbol_node, *]
49
+ handle_trait(symbol_node, factory_node)
50
+ in [Prism::SymbolNode => factory_node, *, Prism::KeywordHashNode => kw_hash] if kw_hash.elements.any? { |e| e.key == symbol_node }
51
+ handle_attribute(symbol_node, factory_node)
52
+ in [Prism::SymbolNode => factory_node, *, Prism::HashNode => kw_hash] if kw_hash.elements.any? { |e| e.key == symbol_node }
53
+ handle_attribute(symbol_node, factory_node)
54
+ else
55
+ $stderr.write "nope fact_or_trait", call_node.arguments.arguments
56
+ nil
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def handle_attribute(symbol_node, factory_node)
63
+ name = symbol_node.value.to_s
64
+ attribute = @server_client.get_attributes(
65
+ factory_name: factory_node.value.to_s, name: name
66
+ )&.find { |attr| attr[:name] == name }
67
+
68
+ return unless attribute
69
+
70
+ @response_builder.push(
71
+ "#{attribute[:name]} (#{attribute[:type]})",
72
+ category: :documentation
73
+ )
74
+ end
75
+
76
+ def handle_factory(symbol_node)
77
+ name = symbol_node.value.to_s
78
+
79
+ factory = @server_client.get_factories(name: name)&.find { |f| f[:name] == name }
80
+
81
+ return unless factory
82
+
83
+ index_entry = @ruby_index.first_unqualified_const(factory[:name])
84
+
85
+ if index_entry
86
+ @response_builder.push(
87
+ markdown_from_index_entries(
88
+ factory[:model_class],
89
+ index_entry
90
+ ),
91
+ category: :documentation
92
+ )
93
+ else
94
+ @response_builder.push(
95
+ "#{factory[:name]} (#{factory[:model_class]})",
96
+ category: :documentation
97
+ )
98
+ end
99
+ end
100
+
101
+ def handle_trait(symbol_node, factory_node)
102
+ factory_name = factory_node.value.to_s
103
+ trait_name = symbol_node.value.to_s
104
+
105
+ trait = @server_client.get_traits(
106
+ factory_name: factory_name, name: trait_name
107
+ )&.find { |tr| tr[:name] == trait_name }
108
+
109
+ return unless trait
110
+
111
+ @response_builder.push(
112
+ "#{trait[:name]} (trait of #{trait[:owner] || factory_name})",
113
+ category: :documentation
114
+ )
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,131 @@
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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLsp
4
+ module Rails
5
+ module FactoryBot
6
+ VERSION = "0.1.0".freeze
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module RubyLsp
2
+ module Rails
3
+ module FactoryBot
4
+ VERSION: String
5
+ end
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-lsp-rails-factory-bot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - johansenja
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-09-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: factory_bot
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 6.4.6
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 6.4.6
27
+ - !ruby/object:Gem::Dependency
28
+ name: ruby-lsp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.17.17
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.17.17
41
+ - !ruby/object:Gem::Dependency
42
+ name: ruby-lsp-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: A ruby-lsp-rails extension for factorybot, providing factory, trait and
56
+ attribute completion, and more
57
+ email:
58
+ - 43235608+johansenja@users.noreply.github.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".rubocop.yml"
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - lib/ruby_lsp/rails/factory_bot/addon.rb
68
+ - lib/ruby_lsp/rails/factory_bot/completion.rb
69
+ - lib/ruby_lsp/rails/factory_bot/hover.rb
70
+ - lib/ruby_lsp/rails/factory_bot/server_extension.rb
71
+ - lib/ruby_lsp_rails_factory_bot.rb
72
+ - sig/ruby_lsp/rails/factory_bot.rbs
73
+ homepage: https://github.com/johansenja/ruby-lsp-factory-bot
74
+ licenses:
75
+ - MIT
76
+ metadata:
77
+ allowed_push_host: https://rubygems.org
78
+ homepage_uri: https://github.com/johansenja/ruby-lsp-factory-bot
79
+ source_code_uri: https://github.com/johansenja/ruby-lsp-rails-factory-bot
80
+ changelog_uri: https://github.com/johansenja/ruby-lsp-rails-factory-bot
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: 3.0.0
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubygems_version: 3.3.26
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: A ruby-lsp-rails extension for factorybot
100
+ test_files: []