ruby-lsp-factory_bot-goto 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: 88f0a2e70a0fa4e7e9beca98174dddd8f526e6aaf672d15e0e3708cc34826cdd
4
+ data.tar.gz: 8e08b6dfd8c914d6bd1b1e03c085d4a4aeb5d646f3b48b32f0e17cc76fc5b7fd
5
+ SHA512:
6
+ metadata.gz: 0e4940d123e2e059a089dc4f479faf77eaf80c011c3c7a319c7a940cad94cb4a405081a66805f75c8e42892792e6e256152887b396615d98c7aa0942b579688e
7
+ data.tar.gz: e1966518e253606a4cefde80b395e0dabab76462fcc79fcf71cb9f7f459922d0d3c3419990830429b7ebc8f1c4ead76321fa73bb2b69095435272a3d2afb29bf
data/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2025-01-XX
9
+
10
+ ### Added
11
+ - Initial release
12
+ - Go to Definition support for FactoryBot factories
13
+ - Support for factory aliases (`:aliases` parameter)
14
+ - Recursive indexing of `spec/factories/**/*.rb`
15
+ - Support for all common FactoryBot methods: `create`, `build`, `build_list`, `create_list`, `attributes_for`
16
+ - Support for both symbol and string factory names
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # Ruby LSP FactoryBot GoTo
2
+
3
+ A Ruby LSP addon that provides **"Go to Definition"** support for [FactoryBot](https://github.com/thoughtbot/factory_bot) factories.
4
+
5
+ > **Note**: This is a separate addon from [`ruby-lsp-factory_bot`](https://rubygems.org/gems/ruby-lsp-factory_bot). That gem provides code completion, while this gem provides navigation (go-to-definition). They complement each other and can be used together!
6
+
7
+ ## Features
8
+
9
+ - 🎯 **Go to Definition**: Jump from factory references to their definitions
10
+ - 🏷️ **Alias Support**: Works with factory aliases (e.g., `factory :user, aliases: [:admin]`)
11
+ - 📁 **Subdirectories**: Indexes factories in `spec/factories/**/*.rb` recursively
12
+ - ⚡ **Fast**: Uses Prism for efficient AST parsing
13
+
14
+ ### Supported FactoryBot Methods
15
+
16
+ - `create(:factory_name)`
17
+ - `build(:factory_name)`
18
+ - `build_list(:factory_name)`
19
+ - `create_list(:factory_name)`
20
+ - `attributes_for(:factory_name)`
21
+
22
+ Works with both symbol and string arguments: `create(:user)` and `create("user")`
23
+
24
+ ## Installation
25
+
26
+ Add this line to your application's Gemfile:
27
+
28
+ ```ruby
29
+ group :development do
30
+ gem 'ruby-lsp-factory_bot-goto'
31
+ end
32
+ ```
33
+
34
+ Then run:
35
+
36
+ ```bash
37
+ bundle install
38
+ ```
39
+
40
+ The addon will be automatically loaded by Ruby LSP.
41
+
42
+ ## Usage
43
+
44
+ Once installed, the addon automatically activates when you use Ruby LSP. Simply:
45
+
46
+ 1. Place your cursor on a factory name in a FactoryBot call (e.g., `:user` in `create(:user)`)
47
+ 2. Use "Go to Definition" (typically `F12` or `Cmd+Click` in VS Code)
48
+ 3. Jump directly to the factory definition!
49
+
50
+ ## Example
51
+
52
+ Given a factory definition:
53
+
54
+ ```ruby
55
+ # spec/factories/users.rb
56
+ FactoryBot.define do
57
+ factory :user do
58
+ name { "John Doe" }
59
+ email { "john@example.com" }
60
+ end
61
+ end
62
+ ```
63
+
64
+ You can now jump to it from any test:
65
+
66
+ ```ruby
67
+ # spec/models/user_spec.rb
68
+ it "creates a user" do
69
+ user = create(:user) # <- Cmd+Click on :user jumps to the factory!
70
+ end
71
+ ```
72
+
73
+ ### Factory Aliases
74
+
75
+ The addon fully supports factory aliases:
76
+
77
+ ```ruby
78
+ # spec/factories/users.rb
79
+ FactoryBot.define do
80
+ factory :user, aliases: [:admin, :member] do
81
+ name { "John Doe" }
82
+ end
83
+ end
84
+ ```
85
+
86
+ Now all three names work: `create(:user)`, `create(:admin)`, `create(:member)`
87
+
88
+ ## How It Works
89
+
90
+ The addon:
91
+
92
+ 1. Indexes all factories in `spec/factories/**/*.rb` when Ruby LSP starts
93
+ 2. Parses factory definitions using Prism to extract names and aliases
94
+ 3. Listens for FactoryBot method calls in your code
95
+ 4. Provides location information for "Go to Definition" requests
96
+
97
+ ## Requirements
98
+
99
+ - Ruby >= 3.0
100
+ - Ruby LSP >= 0.26
101
+ - Prism >= 0.19
102
+
103
+ ## Development
104
+
105
+ To test the addon locally in your Rails project:
106
+
107
+ 1. Add it to your project's Gemfile with a `path:` directive:
108
+ ```ruby
109
+ gem 'ruby-lsp-factory_bot-goto', path: '~/projects/ruby-lsp-factory_bot-goto'
110
+ ```
111
+ 2. Run `bundle install`
112
+ 3. Restart your Ruby LSP server
113
+
114
+ ## Building and Publishing
115
+
116
+ To build the gem:
117
+
118
+ ```bash
119
+ gem build ruby-lsp-factory_bot-goto.gemspec
120
+ ```
121
+
122
+ To publish to RubyGems:
123
+
124
+ ```bash
125
+ gem push ruby-lsp-factory_bot-goto-0.1.0.gem
126
+ ```
127
+
128
+ ## Contributing
129
+
130
+ Bug reports and pull requests are welcome on GitHub at https://github.com/tylercainerhodes/ruby-lsp-factory_bot-goto.
131
+
132
+ ## License
133
+
134
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
135
+
136
+ ## Credits
137
+
138
+ Built with [Ruby LSP](https://shopify.github.io/ruby-lsp/) and [Prism](https://ruby.github.io/prism/).
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby-lsp'
4
+ require 'ruby_lsp/addon'
5
+ require_relative 'factory_index'
6
+ require_relative 'definition_listener'
7
+
8
+ module RubyLsp
9
+ module FactoryBot
10
+ # Ruby LSP addon that provides "Go to Definition" for FactoryBot factory references.
11
+ # Indexes all factories in spec/factories/**/*.rb and enables jumping to factory definitions
12
+ # from calls like create(:factory_name), build(:factory_name), etc.
13
+ class Addon < ::RubyLsp::Addon
14
+ def initialize
15
+ super
16
+ @factory_index = FactoryIndex.new
17
+ end
18
+
19
+ def activate(global_state, _outgoing_queue)
20
+ workspace_root = global_state&.workspace_path || Dir.pwd
21
+ @factory_index.rebuild!(workspace_root)
22
+ end
23
+
24
+ def name
25
+ 'Ruby LSP FactoryBot'
26
+ end
27
+
28
+ def version
29
+ '0.1.0'
30
+ end
31
+
32
+ def create_definition_listener(response_builder, _uri, node_context, dispatcher)
33
+ DefinitionListener.new(response_builder, node_context, @factory_index, dispatcher)
34
+ end
35
+
36
+ def deactivate; end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_lsp/requests/support/common'
4
+ require 'language_server/protocol/interface'
5
+
6
+ module RubyLsp
7
+ module FactoryBot
8
+ class DefinitionListener
9
+ include ::RubyLsp::Requests::Support::Common
10
+
11
+ FACTORY_METHODS = %i[create build build_list create_list attributes_for].freeze
12
+
13
+ def initialize(response_builder, node_context, factory_index, dispatcher)
14
+ @response_builder = response_builder
15
+ @node_context = node_context
16
+ @factory_index = factory_index
17
+
18
+ dispatcher.register(self, :on_symbol_node_enter, :on_string_node_enter)
19
+ end
20
+
21
+ def on_symbol_node_enter(node)
22
+ handle_factory_reference(node.value)
23
+ end
24
+
25
+ def on_string_node_enter(node)
26
+ handle_factory_reference(node.content)
27
+ end
28
+
29
+ private
30
+
31
+ def handle_factory_reference(factory_name)
32
+ call = @node_context.call_node
33
+ return unless call && FACTORY_METHODS.include?(call.name)
34
+
35
+ entry = @factory_index.lookup(factory_name)
36
+ return unless entry
37
+
38
+ @response_builder << location_from_entry(entry)
39
+ end
40
+
41
+ def location_from_entry(entry)
42
+ LanguageServer::Protocol::Interface::Location.new(
43
+ uri: entry.uri,
44
+ range: range_from_location(entry.location)
45
+ )
46
+ end
47
+
48
+ def range_from_location(location)
49
+ LanguageServer::Protocol::Interface::Range.new(
50
+ start: position_from_line_column(location.start_line, location.start_column),
51
+ end: position_from_line_column(location.end_line, location.end_column)
52
+ )
53
+ end
54
+
55
+ def position_from_line_column(line, column)
56
+ LanguageServer::Protocol::Interface::Position.new(
57
+ line: line - 1,
58
+ character: column
59
+ )
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'prism'
5
+
6
+ module RubyLsp
7
+ module FactoryBot
8
+ class FactoryIndex
9
+ Entry = Struct.new(:uri, :location)
10
+
11
+ def initialize
12
+ @mutex = Mutex.new
13
+ @entries = {}
14
+ end
15
+
16
+ def rebuild!(workspace_root)
17
+ new_entries = {}
18
+ factory_pattern = File.join(workspace_root, 'spec', 'factories', '**', '*.rb')
19
+
20
+ Dir.glob(factory_pattern).each do |path|
21
+ parse_factory_file(path, new_entries)
22
+ end
23
+
24
+ @mutex.synchronize { @entries = new_entries }
25
+ end
26
+
27
+ def lookup(name)
28
+ @mutex.synchronize { @entries[name.to_s] }
29
+ end
30
+
31
+ private
32
+
33
+ def parse_factory_file(path, entries)
34
+ source = File.read(path)
35
+ ast = Prism.parse(source)
36
+ uri = URI("file://#{path}").to_s
37
+
38
+ index_factories(ast.value, uri, entries)
39
+ end
40
+
41
+ def index_factories(node, uri, entries)
42
+ return unless node.respond_to?(:child_nodes)
43
+
44
+ node.child_nodes.each do |child|
45
+ next unless child
46
+
47
+ if factory_definition?(child)
48
+ extract_factory_names(child).each do |name, location|
49
+ entries[name] = Entry.new(uri, location)
50
+ end
51
+ end
52
+
53
+ index_factories(child, uri, entries)
54
+ end
55
+ end
56
+
57
+ def factory_definition?(node)
58
+ node.is_a?(Prism::CallNode) &&
59
+ node.name == :factory &&
60
+ node.arguments&.arguments&.first.is_a?(Prism::SymbolNode)
61
+ end
62
+
63
+ def extract_factory_names(node)
64
+ args = node.arguments.arguments
65
+ primary_name = args.first
66
+ location = primary_name.location
67
+
68
+ names = [[primary_name.value.to_s, location]]
69
+
70
+ # Add aliases if present
71
+ extract_aliases(args).each do |alias_name|
72
+ names << [alias_name, location]
73
+ end
74
+
75
+ names
76
+ end
77
+
78
+ def extract_aliases(args)
79
+ keyword_hash = args.find { |arg| arg.is_a?(Prism::KeywordHashNode) }
80
+ return [] unless keyword_hash
81
+
82
+ aliases_assoc =
83
+ keyword_hash.elements.find do |el|
84
+ el.is_a?(Prism::AssocNode) &&
85
+ el.key.is_a?(Prism::SymbolNode) &&
86
+ el.key.value.to_s == 'aliases'
87
+ end
88
+
89
+ return [] unless aliases_assoc
90
+
91
+ array_value = aliases_assoc.value
92
+ return [] unless array_value.is_a?(Prism::ArrayNode)
93
+
94
+ array_value.elements.filter_map do |element|
95
+ case element
96
+ when Prism::SymbolNode then element.value.to_s
97
+ when Prism::StringNode then element.content
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'factory_bot/addon'
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-lsp-factory_bot-goto
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tyler Rhodes
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: prism
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '1.2'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '2.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '1.2'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '2.0'
32
+ - !ruby/object:Gem::Dependency
33
+ name: ruby-lsp
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: 0.20.0
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: 0.20.0
46
+ description: Provides 'Go to Definition' functionality for FactoryBot factories in
47
+ Ruby LSP. Jump from factory references like create(:user) directly to factory definitions.
48
+ email:
49
+ - tyler.rhodes@aya.yale.edu
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - CHANGELOG.md
55
+ - LICENSE.txt
56
+ - README.md
57
+ - lib/ruby_lsp/factory_bot.rb
58
+ - lib/ruby_lsp/factory_bot/addon.rb
59
+ - lib/ruby_lsp/factory_bot/definition_listener.rb
60
+ - lib/ruby_lsp/factory_bot/factory_index.rb
61
+ homepage: https://github.com/tylercainerhodes/ruby-lsp-factory_bot-goto
62
+ licenses:
63
+ - MIT
64
+ metadata:
65
+ homepage_uri: https://github.com/tylercainerhodes/ruby-lsp-factory_bot-goto
66
+ source_code_uri: https://github.com/tylercainerhodes/ruby-lsp-factory_bot-goto
67
+ changelog_uri: https://github.com/tylercainerhodes/ruby-lsp-factory_bot-goto/blob/main/CHANGELOG.md
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 3.0.0
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubygems_version: 3.7.1
83
+ specification_version: 4
84
+ summary: Ruby LSP addon for FactoryBot go-to-definition support
85
+ test_files: []